mirror of
https://github.com/nuxsmin/sysPass.git
synced 2026-03-06 16:36:59 +01:00
test(IT): Test account file use cases (WIP)
Signed-off-by: Rubén D <nuxsmin@syspass.org>
This commit is contained in:
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user