test(IT): Test account file use cases (WIP)

Signed-off-by: Rubén D <nuxsmin@syspass.org>
This commit is contained in:
Rubén D
2024-08-21 14:32:51 +02:00
parent 0dc169b785
commit f602c7916e
7 changed files with 139 additions and 74 deletions

View File

@@ -32,6 +32,8 @@ use SP\Core\Events\EventMessage;
use SP\Domain\Account\Models\File;
use SP\Domain\Account\Ports\AccountFileService;
use SP\Domain\Account\Ports\AccountService;
use SP\Domain\Auth\Services\AuthException;
use SP\Domain\Core\Exceptions\SessionTimeout;
use SP\Domain\Core\Exceptions\SPException;
use SP\Domain\File\Ports\FileHandlerInterface;
use SP\Infrastructure\File\FileException;
@@ -40,6 +42,10 @@ use SP\Modules\Web\Controllers\ControllerBase;
use SP\Modules\Web\Controllers\Traits\JsonTrait;
use SP\Mvc\Controller\WebControllerHelper;
use function SP\__;
use function SP\__u;
use function SP\processException;
/**
* Class UploadController
*
@@ -49,21 +55,19 @@ final class UploadController extends ControllerBase
{
use JsonTrait;
private AccountFileService $accountFileService;
private AccountService $accountService;
/**
* @throws AuthException
* @throws SessionTimeout
*/
public function __construct(
Application $application,
WebControllerHelper $webControllerHelper,
AccountFileService $accountFileService,
AccountService $accountService
Application $application,
WebControllerHelper $webControllerHelper,
private readonly AccountFileService $accountFileService,
private readonly AccountService $accountService
) {
parent::__construct($application, $webControllerHelper);
$this->checkLoggedIn();
$this->accountFileService = $accountFileService;
$this->accountService = $accountService;
}
/**
@@ -73,6 +77,7 @@ final class UploadController extends ControllerBase
*
* @return bool
* @throws JsonException
* @throws SPException
*/
public function uploadAction(int $accountId): bool
{
@@ -90,57 +95,57 @@ final class UploadController extends ControllerBase
}
try {
$fileHandler = new FileHandler($file['tmp_name']);
$fileName = htmlspecialchars($file['name'] ?? '', ENT_QUOTES);
$fileData = new File();
$fileData->setAccountId($accountId);
$fileData->setName(htmlspecialchars($file['name'], ENT_QUOTES));
$fileData->setSize($file['size']);
$fileData->setType($file['type']);
$fileData->setExtension(mb_strtoupper(pathinfo($fileData->getName(), PATHINFO_EXTENSION)));
if ($fileData->getName() === '') {
throw new SPException(
__u('Invalid file'),
SPException::ERROR,
sprintf(__u('File: %s'), $fileData->getName())
);
if (empty($fileName)) {
throw SPException::error(__u('Invalid file'), sprintf(__u('File: %s'), $fileName));
}
$fileHandler->checkFileExists();
$fileData->setType($this->checkAllowedMimeType($fileData, $fileHandler));
$allowedSize = $this->configData->getFilesAllowedSize();
if ($fileData->getSize() > ($allowedSize * 1000)) {
throw new SPException(
if ($file['size'] > ($allowedSize * 1000)) {
throw SPException::error(
__u('File size exceeded'),
SPException::ERROR,
sprintf(__u('Maximum size: %d KB'), $fileData->getRoundSize())
sprintf(__u('Maximum size: %f KB'), round($allowedSize / 1000, 2))
);
}
$fileData->setContent($fileHandler->readToString());
$fileHandler = new FileHandler($file['tmp_name']);
$fileData = [
'accountId' => $accountId,
'name' => $fileName,
'size' => $file['size'],
'type' => $this->checkAllowedMimeType($file['type'], $fileHandler),
'extension' => mb_strtoupper(pathinfo($fileName, PATHINFO_EXTENSION)),
'content' => $fileHandler->readToString()
];
} catch (FileException $e) {
throw new SPException(__u('Internal error while reading the file'));
$this->eventDispatcher->notify('exception', new Event($e));
throw SPException::error(__u('Internal error while reading the file'));
}
$this->accountFileService->create($fileData);
$account = $this->accountService->getByIdEnriched($accountId)->getAccountVData();
$this->accountFileService->create(new File($fileData));
$this->eventDispatcher->notify(
'upload.accountFile',
new Event(
$this,
EventMessage::factory()
->addDescription(__u('File saved'))
->addDetail(__u('File'), $fileData->getName())
->addDetail(__u('Account'), $account->getName())
->addDetail(__u('Client'), $account->getClientName())
->addDetail(__u('Type'), $fileData->getType())
->addDetail(__u('Size'), $fileData->getRoundSize() . 'KB')
static function () use ($accountId, $fileData): EventMessage {
$account = $this->accountService->getByIdEnriched($accountId);
return EventMessage::factory()
->addDescription(__u('File saved'))
->addDetail(__u('File'), $fileData['name'])
->addDetail(__u('Account'), $account->getName())
->addDetail(__u('Client'), $account->getClientName())
->addDetail(__u('Type'), $fileData['type'])
->addDetail(
__u('Size'),
sprintf('%f KB', round($fileData['size'] / 1000))
);
}
)
);
@@ -150,7 +155,7 @@ final class UploadController extends ControllerBase
$this->eventDispatcher->notify('exception', new Event($e));
return $this->returnJsonResponse(1, $e->getMessage(), [$e->getHint()]);
return $this->returnJsonResponse(1, $e->getMessage(), $e->getHint());
} catch (Exception $e) {
processException($e);
@@ -161,27 +166,23 @@ final class UploadController extends ControllerBase
}
/**
* @param \SP\Domain\Account\Models\File $fileData
* @param string $type
* @param FileHandlerInterface $fileHandler
*
* @return string
* @throws SPException
* @throws FileException
* @throws SPException
*/
private function checkAllowedMimeType(File $fileData, FileHandlerInterface $fileHandler): string
private function checkAllowedMimeType(string $type, FileHandlerInterface $fileHandler): string
{
if (in_array($fileData->getType(), $this->configData->getFilesAllowedMime(), true)) {
return $fileData->getType();
if (in_array($type, $this->configData->getFilesAllowedMime(), true)) {
return $type;
}
if (in_array($fileHandler->getFileType(), $this->configData->getFilesAllowedMime(), true)) {
return $fileHandler->getFileType();
}
throw new SPException(
__u('File type not allowed'),
SPException::ERROR,
sprintf(__('MIME type: %s'), $fileData->getType())
);
throw SPException::error(__u('File type not allowed'), sprintf(__('MIME type: %s'), $type));
}
}

View File

@@ -44,19 +44,19 @@ trait JsonTrait
*
* @param int $status Status code
* @param string $description Untranslated description string
* @param array|null $messages Untranslated massages array of strings
* @param array|string|null $messages Untranslated massages array of strings
*
* @return bool
* @throws SPException
*/
protected function returnJsonResponse(int $status, string $description, ?array $messages = null): bool
protected function returnJsonResponse(int $status, string $description, array|string|null $messages = null): bool
{
$jsonResponse = new JsonMessage();
$jsonResponse->setStatus($status);
$jsonResponse->setDescription($description);
if (null !== $messages) {
$jsonResponse->setMessages($messages);
$jsonResponse->setMessages((array)$messages);
}
return JsonResponse::factory($this->router->response())->send($jsonResponse);

View File

@@ -26,6 +26,7 @@ declare(strict_types=1);
namespace SP\Core\Events;
use Closure;
use SP\Domain\Core\Exceptions\InvalidClassException;
/**
@@ -33,9 +34,13 @@ use SP\Domain\Core\Exceptions\InvalidClassException;
*/
readonly class Event
{
/**
* @param object $source The emmiter of the event
* @param EventMessage|Closure|null $eventMessage An {@link EventMessage} or a {@link Closure} that returns an {@link EventMessage}
*/
public function __construct(
private object $source,
private ?EventMessage $eventMessage = null
private object $source,
private EventMessage|Closure|null $eventMessage = null
) {
}
@@ -60,6 +65,10 @@ readonly class Event
public function getEventMessage(): ?EventMessage
{
if ($this->eventMessage instanceof Closure) {
return $this->eventMessage->call($this);
}
return $this->eventMessage;
}
}

View File

@@ -91,13 +91,4 @@ class File extends Model implements ItemWithIdAndNameModel
{
return $this->size;
}
public function getRoundSize(): float
{
if (null === $this->size) {
return 0.0;
}
return round($this->size / 1000, 2);
}
}

View File

@@ -80,7 +80,7 @@ final class JsonMessage implements JsonSerializable
public function setMessages(array $messages): JsonMessage
{
$this->messages = array_map('__', $messages);
$this->messages = array_map('SP\__', $messages);
return $this;
}

View File

@@ -182,7 +182,6 @@ abstract class IntegrationTestCase extends TestCase
$configData->method('isMaintenance')->willReturn(false);
$configData->method('getDbName')->willReturn(self::$faker->colorName());
$configData->method('getPasswordSalt')->willReturn($this->passwordSalt);
$configData->method('isFilesEnabled')->willReturn(true);
return $configData;
}
@@ -244,8 +243,13 @@ abstract class IntegrationTestCase extends TestCase
$this->databaseMapperResolvers[$className] = $queryResult;
}
protected function buildRequest(string $method, string $uri, array $paramsGet = [], array $paramsPost = []): Request
{
protected function buildRequest(
string $method,
string $uri,
array $paramsGet = [],
array $paramsPost = [],
array $files = []
): Request {
$server = array_merge(
$_SERVER,
[
@@ -262,7 +266,7 @@ abstract class IntegrationTestCase extends TestCase
array_merge($_POST, $paramsPost),
$_COOKIE,
$server,
$_FILES,
array_merge($_FILES, $files),
null
);
}

View File

@@ -29,9 +29,11 @@ namespace SP\Tests\Modules\Web\Controllers\AccountFile;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\MockObject\Exception;
use PHPUnit\Framework\MockObject\Stub;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use SP\Domain\Account\Models\File;
use SP\Domain\Config\Ports\ConfigDataInterface;
use SP\Domain\Core\Exceptions\InvalidClassException;
use SP\Infrastructure\Database\QueryData;
use SP\Infrastructure\Database\QueryResult;
@@ -224,4 +226,62 @@ class AccountFileTest extends IntegrationTestCase
$this->runApp($container);
}
/**
* @return void
* @throws ContainerExceptionInterface
* @throws Exception
* @throws FileException
* @throws InvalidClassException
* @throws NotFoundExceptionInterface
*/
#[Test]
public function upload()
{
$definitions = $this->getModuleDefinitions();
$definitions[OutputHandlerInterface::class] = $this->setupOutputHandler(function (string $output): void {
$crawler = new Crawler($output);
$filter = $crawler->filterXPath('//table/tbody//tr[string-length(@data-item-id) > 0]')
->extract(['class']);
assert(!empty($output));
assert(count($filter) === 2);
$this->assertTrue(true);
});
$file = sprintf('%s.txt', self::$faker->filePath());
file_put_contents($file, self::$faker->text());
$files = [
'inFile' => [
'name' => self::$faker->name(),
'tmp_name' => $file,
'size' => filesize($file),
'type' => 'text/plain'
]
];
$container = $this->buildContainer(
$definitions,
$this->buildRequest('post', 'index.php', ['r' => 'accountFile/upload/100'], [], $files)
);
$this->runApp($container);
$this->expectOutputString(
'{"status":0,"description":"File saved","data":[],"messages":[]}'
);
}
protected function getConfigData(): ConfigDataInterface|Stub
{
$configData = parent::getConfigData();
$configData->method('isFilesEnabled')->willReturn(true);
$configData->method('getFilesAllowedMime')->willReturn(['text/plain']);
$configData->method('getFilesAllowedSize')->willReturn(1000);
return $configData;
}
}