Release 2.46 (#5757)
This commit is contained in:
@@ -327,7 +327,8 @@ abstract class APIControllerBaseTestCase extends AbstractControllerBaseTestCase
|
||||
return [
|
||||
'id' => 'int',
|
||||
'name' => 'string',
|
||||
'color' => 'string',
|
||||
'color' => '@string',
|
||||
'color-safe' => 'string',
|
||||
'visible' => 'bool',
|
||||
];
|
||||
|
||||
@@ -358,7 +359,8 @@ abstract class APIControllerBaseTestCase extends AbstractControllerBaseTestCase
|
||||
'enabled' => 'bool',
|
||||
'apiToken' => 'bool',
|
||||
'systemAccount' => 'bool',
|
||||
'color' => 'string',
|
||||
'color' => '@string',
|
||||
'color-safe' => 'string',
|
||||
'avatar' => '@string',
|
||||
'alias' => '@string',
|
||||
'accountNumber' => '@string',
|
||||
@@ -378,7 +380,8 @@ abstract class APIControllerBaseTestCase extends AbstractControllerBaseTestCase
|
||||
'enabled' => 'bool',
|
||||
'apiToken' => 'bool',
|
||||
'systemAccount' => 'bool',
|
||||
'color' => 'string',
|
||||
'color' => '@string',
|
||||
'color-safe' => 'string',
|
||||
'avatar' => '@string',
|
||||
'alias' => '@string',
|
||||
'accountNumber' => '@string',
|
||||
@@ -402,7 +405,8 @@ abstract class APIControllerBaseTestCase extends AbstractControllerBaseTestCase
|
||||
return [
|
||||
'id' => 'int',
|
||||
'name' => 'string',
|
||||
'color' => 'string',
|
||||
'color' => '@string',
|
||||
'color-safe' => 'string',
|
||||
];
|
||||
|
||||
// explicitly requested team
|
||||
@@ -410,7 +414,8 @@ abstract class APIControllerBaseTestCase extends AbstractControllerBaseTestCase
|
||||
return [
|
||||
'id' => 'int',
|
||||
'name' => 'string',
|
||||
'color' => 'string',
|
||||
'color' => '@string',
|
||||
'color-safe' => 'string',
|
||||
'members' => ['result' => 'array', 'type' => 'TeamMember'],
|
||||
// TODO more info in entity than in collection
|
||||
'customers' => ['result' => 'array', 'type' => '@Customer'],
|
||||
@@ -439,7 +444,8 @@ abstract class APIControllerBaseTestCase extends AbstractControllerBaseTestCase
|
||||
'name' => 'string',
|
||||
'visible' => 'bool',
|
||||
'billable' => 'bool',
|
||||
'color' => 'string',
|
||||
'color' => '@string',
|
||||
'color-safe' => 'string',
|
||||
'number' => '@string',
|
||||
'comment' => '@string',
|
||||
'currency' => 'string',
|
||||
@@ -460,7 +466,8 @@ abstract class APIControllerBaseTestCase extends AbstractControllerBaseTestCase
|
||||
'name' => 'string',
|
||||
'visible' => 'boolean',
|
||||
'billable' => 'bool',
|
||||
'color' => 'string',
|
||||
'color' => '@string',
|
||||
'color-safe' => 'string',
|
||||
'number' => '@string',
|
||||
'comment' => '@string',
|
||||
'currency' => 'string',
|
||||
@@ -482,7 +489,8 @@ abstract class APIControllerBaseTestCase extends AbstractControllerBaseTestCase
|
||||
'name' => 'string',
|
||||
'visible' => 'bool',
|
||||
'billable' => 'bool',
|
||||
'color' => 'string',
|
||||
'color' => '@string',
|
||||
'color-safe' => 'string',
|
||||
'number' => '@string',
|
||||
'comment' => '@string',
|
||||
'currency' => 'string',
|
||||
@@ -519,7 +527,8 @@ abstract class APIControllerBaseTestCase extends AbstractControllerBaseTestCase
|
||||
'name' => 'string',
|
||||
'visible' => 'bool',
|
||||
'billable' => 'bool',
|
||||
'color' => 'string',
|
||||
'color' => '@string',
|
||||
'color-safe' => 'string',
|
||||
'customer' => 'int',
|
||||
'number' => '@string',
|
||||
'orderNumber' => '@string',
|
||||
@@ -538,7 +547,8 @@ abstract class APIControllerBaseTestCase extends AbstractControllerBaseTestCase
|
||||
'name' => 'string',
|
||||
'visible' => 'bool',
|
||||
'billable' => 'bool',
|
||||
'color' => 'string',
|
||||
'color' => '@string',
|
||||
'color-safe' => 'string',
|
||||
'customer' => ['result' => 'object', 'type' => 'Customer'],
|
||||
'number' => '@string',
|
||||
'orderNumber' => '@string',
|
||||
@@ -557,7 +567,8 @@ abstract class APIControllerBaseTestCase extends AbstractControllerBaseTestCase
|
||||
'name' => 'string',
|
||||
'visible' => 'bool',
|
||||
'billable' => 'bool',
|
||||
'color' => 'string',
|
||||
'color' => '@string',
|
||||
'color-safe' => 'string',
|
||||
'customer' => 'int',
|
||||
'number' => '@string',
|
||||
'orderNumber' => '@string',
|
||||
@@ -578,7 +589,8 @@ abstract class APIControllerBaseTestCase extends AbstractControllerBaseTestCase
|
||||
'name' => 'string',
|
||||
'visible' => 'bool',
|
||||
'billable' => 'bool',
|
||||
'color' => 'string',
|
||||
'color' => '@string',
|
||||
'color-safe' => 'string',
|
||||
'customer' => 'int',
|
||||
'number' => '@string',
|
||||
'orderNumber' => '@string',
|
||||
@@ -605,7 +617,8 @@ abstract class APIControllerBaseTestCase extends AbstractControllerBaseTestCase
|
||||
'billable' => 'bool',
|
||||
'project' => '@int',
|
||||
'number' => '@string',
|
||||
'color' => 'string',
|
||||
'color' => '@string',
|
||||
'color-safe' => 'string',
|
||||
'metaFields' => ['result' => 'array', 'type' => 'ProjectMeta'], // since 2.45
|
||||
'comment' => '@string',
|
||||
];
|
||||
@@ -618,7 +631,8 @@ abstract class APIControllerBaseTestCase extends AbstractControllerBaseTestCase
|
||||
'billable' => 'bool',
|
||||
'project' => ['result' => 'object', 'type' => '@ProjectExpanded'],
|
||||
'number' => '@string',
|
||||
'color' => 'string',
|
||||
'color' => '@string',
|
||||
'color-safe' => 'string',
|
||||
'metaFields' => ['result' => 'array', 'type' => 'ProjectMeta'], // since 2.45
|
||||
'comment' => '@string',
|
||||
];
|
||||
@@ -632,7 +646,8 @@ abstract class APIControllerBaseTestCase extends AbstractControllerBaseTestCase
|
||||
'billable' => 'bool',
|
||||
'project' => '@int',
|
||||
'number' => '@string',
|
||||
'color' => 'string',
|
||||
'color' => '@string',
|
||||
'color-safe' => 'string',
|
||||
'metaFields' => ['result' => 'array', 'type' => 'ProjectMeta'],
|
||||
'comment' => '@string',
|
||||
'parentTitle' => '@string',
|
||||
@@ -648,7 +663,8 @@ abstract class APIControllerBaseTestCase extends AbstractControllerBaseTestCase
|
||||
'billable' => 'bool',
|
||||
'project' => '@int',
|
||||
'number' => '@string',
|
||||
'color' => 'string',
|
||||
'color' => '@string',
|
||||
'color-safe' => 'string',
|
||||
'metaFields' => ['result' => 'array', 'type' => 'ProjectMeta'],
|
||||
'comment' => '@string',
|
||||
'parentTitle' => '@string',
|
||||
|
||||
@@ -244,7 +244,6 @@ class ActivityControllerTest extends APIControllerBaseTestCase
|
||||
self::assertIsArray($result);
|
||||
self::assertApiResponseTypeStructure('ActivityEntity', $result);
|
||||
|
||||
self::assertCount(14, array_keys($result));
|
||||
self::assertNull($result['parentTitle']);
|
||||
self::assertNotEmpty($result['id']);
|
||||
self::assertIsArray($result['teams']);
|
||||
@@ -258,7 +257,8 @@ class ActivityControllerTest extends APIControllerBaseTestCase
|
||||
self::assertNull($result['budgetType']);
|
||||
self::assertNull($result['number']);
|
||||
self::assertEquals('Test comment', $result['comment']);
|
||||
self::assertEquals('#5319e7', $result['color']);
|
||||
self::assertNull($result['color']);
|
||||
self::assertEquals('#5319e7', $result['color-safe']);
|
||||
self::assertTrue($result['visible']);
|
||||
self::assertTrue($result['billable']);
|
||||
}
|
||||
@@ -297,7 +297,7 @@ class ActivityControllerTest extends APIControllerBaseTestCase
|
||||
self::assertEquals('Test', $result['parentTitle']);
|
||||
self::assertNotEmpty($result['id']);
|
||||
self::assertIsArray($result['teams']);
|
||||
self::assertEquals([['id' => 1, 'name' => 'Test team', 'color' => '#03A9F4']], $result['teams']);
|
||||
self::assertEquals([['id' => 1, 'name' => 'Test team', 'color' => null, 'color-safe' => '#03A9F4']], $result['teams']);
|
||||
self::assertIsArray($result['metaFields']);
|
||||
self::assertEquals([], $result['metaFields']);
|
||||
self::assertEquals('foo', $result['name']);
|
||||
|
||||
@@ -229,7 +229,6 @@ class CustomerControllerTest extends APIControllerBaseTestCase
|
||||
self::assertIsArray($result);
|
||||
self::assertApiResponseTypeStructure('CustomerEntity', $result);
|
||||
|
||||
self::assertCount(30, array_keys($result));
|
||||
self::assertNotEmpty($result['id']);
|
||||
self::assertIsArray($result['teams']);
|
||||
self::assertCount(1, $result['teams']);
|
||||
@@ -262,7 +261,8 @@ class CustomerControllerTest extends APIControllerBaseTestCase
|
||||
self::assertNull($result['homepage']);
|
||||
self::assertEquals('Europe/Berlin', $result['timezone']);
|
||||
self::assertNull($result['buyerReference']);
|
||||
self::assertEquals('#5319e7', $result['color']);
|
||||
self::assertNull($result['color']);
|
||||
self::assertEquals('#5319e7', $result['color-safe']);
|
||||
self::assertTrue($result['visible']);
|
||||
self::assertTrue($result['billable']);
|
||||
}
|
||||
@@ -316,7 +316,7 @@ class CustomerControllerTest extends APIControllerBaseTestCase
|
||||
self::assertApiResponseTypeStructure('CustomerEntity', $result);
|
||||
self::assertNotEmpty($result['id']);
|
||||
self::assertIsArray($result['teams']);
|
||||
self::assertEquals([['id' => 1, 'name' => 'Test team', 'color' => '#03A9F4']], $result['teams']);
|
||||
self::assertEquals([['id' => 1, 'name' => 'Test team', 'color' => null, 'color-safe' => '#03A9F4']], $result['teams']);
|
||||
self::assertIsArray($result['metaFields']);
|
||||
self::assertEquals([], $result['metaFields']);
|
||||
self::assertEquals('foo', $result['name']);
|
||||
|
||||
@@ -311,7 +311,6 @@ class ProjectControllerTest extends APIControllerBaseTestCase
|
||||
self::assertIsArray($result);
|
||||
self::assertApiResponseTypeStructure('ProjectEntity', $result);
|
||||
|
||||
self::assertCount(19, array_keys($result));
|
||||
self::assertEquals('first one', $result['parentTitle']);
|
||||
self::assertEquals($project->getId(), $result['id']);
|
||||
self::assertIsArray($result['teams']);
|
||||
@@ -329,7 +328,8 @@ class ProjectControllerTest extends APIControllerBaseTestCase
|
||||
self::assertNull($result['orderNumber']);
|
||||
self::assertNull($result['number']);
|
||||
self::assertNull($result['comment']);
|
||||
self::assertEquals('#2ECC40', $result['color']);
|
||||
self::assertNull($result['color']);
|
||||
self::assertEquals('#2ECC40', $result['color-safe']);
|
||||
self::assertTrue($result['globalActivities']);
|
||||
self::assertTrue($result['billable']);
|
||||
self::assertTrue($result['visible']);
|
||||
@@ -374,7 +374,7 @@ class ProjectControllerTest extends APIControllerBaseTestCase
|
||||
self::assertEquals('Test', $result['parentTitle']);
|
||||
self::assertNotEmpty($result['id']);
|
||||
self::assertIsArray($result['teams']);
|
||||
self::assertEquals([['id' => 1, 'name' => 'Test team', 'color' => '#03A9F4']], $result['teams']);
|
||||
self::assertEquals([['id' => 1, 'name' => 'Test team', 'color' => null, 'color-safe' => '#03A9F4']], $result['teams']);
|
||||
self::assertIsArray($result['metaFields']);
|
||||
self::assertEquals([], $result['metaFields']);
|
||||
self::assertEquals('foo', $result['name']);
|
||||
|
||||
128
tests/Export/Base/DefaultRendererTest.php
Normal file
128
tests/Export/Base/DefaultRendererTest.php
Normal file
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Tests\Export\Base;
|
||||
|
||||
use App\Export\Base\CsvRenderer;
|
||||
use App\Export\Base\HtmlRenderer;
|
||||
use App\Export\Base\PDFRenderer;
|
||||
use App\Export\Base\XlsxRenderer;
|
||||
use App\Export\ServiceExport;
|
||||
use App\Repository\ExportTemplateRepository;
|
||||
use App\Tests\Export\Renderer\AbstractRendererTestCase;
|
||||
use App\Tests\Mocks\Export\CsvRendererFactoryMock;
|
||||
use App\Tests\Mocks\Export\HtmlRendererFactoryMock;
|
||||
use App\Tests\Mocks\Export\PdfRendererFactoryMock;
|
||||
use App\Tests\Mocks\Export\XlsxRendererFactoryMock;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
#[CoversClass(ServiceExport::class)]
|
||||
#[CoversClass(CsvRenderer::class)]
|
||||
#[CoversClass(XlsxRenderer::class)]
|
||||
#[CoversClass(PDFRenderer::class)]
|
||||
#[CoversClass(HtmlRenderer::class)]
|
||||
#[Group('integration')]
|
||||
class DefaultRendererTest extends AbstractRendererTestCase
|
||||
{
|
||||
private function createServiceExport(): ServiceExport
|
||||
{
|
||||
$repository = $this->createMock(ExportTemplateRepository::class);
|
||||
$repository->expects($this->once())->method('findAll')->willReturn([]);
|
||||
$logger = $this->createMock(LoggerInterface::class);
|
||||
|
||||
return new ServiceExport(
|
||||
$this->createMock(EventDispatcherInterface::class),
|
||||
(new HtmlRendererFactoryMock($this))->create(),
|
||||
(new PdfRendererFactoryMock($this))->create(),
|
||||
(new CsvRendererFactoryMock($this))->create(),
|
||||
(new XlsxRendererFactoryMock($this))->create(),
|
||||
$repository,
|
||||
$logger,
|
||||
);
|
||||
}
|
||||
|
||||
public function testRenderDefaultTemplates(): void
|
||||
{
|
||||
$sut = $this->createServiceExport();
|
||||
|
||||
$renderer = $sut->getRenderer();
|
||||
self::assertCount(4, $renderer);
|
||||
self::assertInstanceOf(CsvRenderer::class, $renderer[0]);
|
||||
self::assertInstanceOf(XlsxRenderer::class, $renderer[1]);
|
||||
self::assertInstanceOf(PDFRenderer::class, $renderer[2]);
|
||||
self::assertInstanceOf(HtmlRenderer::class, $renderer[3]);
|
||||
|
||||
// make sure that the default templates do NOT violate the Twig SecurityPolicy
|
||||
$response = $this->render($renderer[0]);
|
||||
self::assertEquals('text/csv', $response->headers->get('Content-Type'));
|
||||
self::assertStringContainsString('attachment; filename', $response->headers->get('Content-Disposition') ?? '');
|
||||
|
||||
$response = $this->render($renderer[1]);
|
||||
self::assertEquals('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', $response->headers->get('Content-Type') ?? '');
|
||||
self::assertStringContainsString('attachment; filename', $response->headers->get('Content-Disposition') ?? '');
|
||||
|
||||
$response = $this->render($renderer[2]);
|
||||
self::assertEquals('application/pdf', $response->headers->get('Content-Type') ?? '');
|
||||
self::assertStringContainsString('attachment; filename', $response->headers->get('Content-Disposition') ?? '');
|
||||
|
||||
$response = $this->render($renderer[3]);
|
||||
self::assertEquals('text/html', $response->headers->get('Content-Type') ?? '');
|
||||
// HTML is attached to the body and twig is a mock in this setupo, so we just receive an empty string
|
||||
}
|
||||
|
||||
public function testRenderCustomTemplates(): void
|
||||
{
|
||||
$searchDir = __DIR__ . '/../../../var/templates';
|
||||
if (!is_dir($searchDir)) {
|
||||
$this->expectNotToPerformAssertions();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$finder = new Finder();
|
||||
$finder
|
||||
->in($searchDir)
|
||||
->name('*.twig')
|
||||
->path('export-tpl/')
|
||||
->files()
|
||||
;
|
||||
|
||||
$files = [];
|
||||
$dirs = [];
|
||||
foreach ($finder->getIterator() as $filename => $splFile) {
|
||||
$files[] = $splFile->getRealPath();
|
||||
$dir = \dirname($splFile->getRealPath());
|
||||
$dirs[$dir] = $dir;
|
||||
}
|
||||
$dirs = array_keys($dirs);
|
||||
|
||||
if (\count($dirs) === 0) {
|
||||
$this->expectNotToPerformAssertions();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$sut = $this->createServiceExport();
|
||||
foreach ($dirs as $dir) {
|
||||
$sut->addDirectory($dir);
|
||||
}
|
||||
|
||||
$renderers = $sut->getRenderer();
|
||||
self::assertCount(4 + \count($files), $renderers);
|
||||
foreach ($renderers as $renderer) {
|
||||
$response = $this->render($renderer);
|
||||
self::assertInstanceOf(Response::class, $response);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -129,8 +129,8 @@ abstract class AbstractRendererTestCase extends KernelTestCase
|
||||
|
||||
$entries = [$timesheet, $timesheet2, $timesheet3, $timesheet4, $timesheet5, $timesheet6];
|
||||
|
||||
$currentUser = $this->createMock(User::class);
|
||||
$currentUser->expects($this->any())->method('isExportDecimal')->willReturn($exportDecimal);
|
||||
$currentUser = new User();
|
||||
$currentUser->setPreferenceValue('export_decimal', $exportDecimal);
|
||||
|
||||
$query = new TimesheetQuery();
|
||||
$query->setActivities([$activity]);
|
||||
|
||||
@@ -14,10 +14,12 @@ use App\Invoice\InvoiceItemHydrator;
|
||||
use App\Invoice\InvoiceModel;
|
||||
use App\Invoice\InvoiceModelHydrator;
|
||||
use App\Model\InvoiceDocument;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
#[CoversClass(DebugRenderer::class)]
|
||||
class DebugRendererTest extends TestCase
|
||||
{
|
||||
use RendererTestTrait;
|
||||
|
||||
@@ -18,6 +18,7 @@ use App\Tests\Mocks\FileHelperFactory;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Twig\Environment;
|
||||
@@ -118,12 +119,26 @@ class PdfRendererTest extends KernelTestCase
|
||||
|
||||
$dirs = [
|
||||
__DIR__ . '/../../../templates/invoice/renderer/',
|
||||
//__DIR__ . '/../../../var/invoices/',
|
||||
//__DIR__ . '/../../../var/invoices_customer/',
|
||||
//__DIR__ . '/../../../var/invoices_old/',
|
||||
];
|
||||
|
||||
$files = [];
|
||||
|
||||
$additionalTemplatesDir = __DIR__ . '/../../../var/templates';
|
||||
if (is_dir($additionalTemplatesDir)) {
|
||||
$finder = new Finder();
|
||||
$finder
|
||||
->in($additionalTemplatesDir)
|
||||
->name('*.pdf.twig')
|
||||
->path('invoice-tpl/')
|
||||
->files();
|
||||
|
||||
foreach ($finder->getIterator() as $splFile) {
|
||||
$filename = $splFile->getRealPath();
|
||||
$files[] = $filename;
|
||||
$loader->addPath(\dirname($filename) . '/', 'invoice');
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($dirs as $dir) {
|
||||
if (!is_dir($dir)) {
|
||||
continue;
|
||||
@@ -145,7 +160,6 @@ class PdfRendererTest extends KernelTestCase
|
||||
$response = $sut->render($document, $model);
|
||||
self::assertEquals('application/pdf', $response->headers->get('Content-Type'));
|
||||
self::assertStringContainsString('attachment; filename', $response->headers->get('Content-Disposition'));
|
||||
self::assertNotEmpty($response->getContent());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,6 @@ use App\Model\InvoiceDocument;
|
||||
use App\Repository\InvoiceRepository;
|
||||
use App\Repository\Query\InvoiceQuery;
|
||||
use App\Tests\Mocks\InvoiceModelFactoryFactory;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
|
||||
trait RendererTestTrait
|
||||
{
|
||||
@@ -85,6 +84,10 @@ trait RendererTestTrait
|
||||
|
||||
protected function getInvoiceModel(): InvoiceModel
|
||||
{
|
||||
$activityId = new \ReflectionProperty(Activity::class, 'id');
|
||||
$projectId = new \ReflectionProperty(Project::class, 'id');
|
||||
$userId = new \ReflectionProperty(User::class, 'id');
|
||||
|
||||
$user = new User();
|
||||
$user->setUserIdentifier('one-user');
|
||||
$user->setTitle('user title');
|
||||
@@ -107,59 +110,54 @@ trait RendererTestTrait
|
||||
|
||||
$pMeta = new ProjectMeta();
|
||||
$pMeta->setName('foo-project')->setValue('bar-project')->setIsVisible(true);
|
||||
$project = $this->createMock(Project::class);
|
||||
$project->method('getId')->willReturn(0);
|
||||
$project->method('getName')->willReturn('project name');
|
||||
$project->method('getCustomer')->willReturn($customer);
|
||||
$project->method('getMetaFields')->willReturn(new ArrayCollection([$pMeta]));
|
||||
$project->method('getVisibleMetaFields')->willReturn([$pMeta]);
|
||||
$project = new Project();
|
||||
$projectId->setValue($project, 0);
|
||||
$project->setName('project name');
|
||||
$project->setCustomer($customer);
|
||||
$project->setMetaField($pMeta);
|
||||
|
||||
$aMeta = new ActivityMeta();
|
||||
$aMeta->setName('foo-activity');
|
||||
$aMeta->setValue('bar-activity');
|
||||
$aMeta->setIsVisible(true);
|
||||
$activity = $this->createMock(Activity::class);
|
||||
$activity->method('getId')->willReturn(0);
|
||||
$activity->method('getName')->willReturn('activity description');
|
||||
$activity->method('getProject')->willReturn($project);
|
||||
$activity->method('getMetaFields')->willReturn(new ArrayCollection([$aMeta]));
|
||||
$activity->method('getVisibleMetaFields')->willReturn([$aMeta]);
|
||||
$activity = new Activity();
|
||||
$activityId->setValue($activity, 0);
|
||||
$activity->setName('activity description');
|
||||
$activity->setProject($project);
|
||||
$activity->setMetaField($aMeta);
|
||||
|
||||
$pMeta2 = new ProjectMeta();
|
||||
$pMeta2->setName('foo-project')->setValue('bar-project2')->setIsVisible(true);
|
||||
$project2 = $this->createMock(Project::class);
|
||||
$project2->method('getId')->willReturn(1);
|
||||
$project2->method('getName')->willReturn('project 2 name');
|
||||
$project2->method('getCustomer')->willReturn($customer);
|
||||
$project2->method('getMetaFields')->willReturn(new ArrayCollection([$pMeta2]));
|
||||
$project2->method('getVisibleMetaFields')->willReturn([$pMeta2]);
|
||||
$project2 = new Project();
|
||||
$projectId->setValue($project2, 1);
|
||||
$project2->setName('project 2 name');
|
||||
$project2->setCustomer($customer);
|
||||
$project2->setMetaField($pMeta2);
|
||||
|
||||
$aMeta2 = new ActivityMeta();
|
||||
$aMeta2->setName('foo-activity');
|
||||
$aMeta2->setValue('bar-activity2');
|
||||
$aMeta2->setIsVisible(true);
|
||||
$activity2 = $this->createMock(Activity::class);
|
||||
$activity2->method('getId')->willReturn(1);
|
||||
$activity2->method('getName')->willReturn('activity 1 description');
|
||||
$activity2->method('getProject')->willReturn($project2);
|
||||
$activity2->method('getMetaFields')->willReturn(new ArrayCollection([$aMeta2]));
|
||||
$activity2->method('getVisibleMetaFields')->willReturn([$aMeta2]);
|
||||
$activity2 = new Activity();
|
||||
$activityId->setValue($activity2, 1);
|
||||
$activity2->setName('activity 1 description');
|
||||
$activity2->setProject($project2);
|
||||
$activity2->setMetaField($aMeta2);
|
||||
|
||||
$pref1 = new UserPreference('foo', 'bar');
|
||||
$pref2 = new UserPreference('mad', 123.45);
|
||||
$userMethods = ['getId', 'getPreferenceValue', 'getVisiblePreferences', 'getUsername', 'getUserIdentifier'];
|
||||
$user1 = $this->getMockBuilder(User::class)->onlyMethods($userMethods)->disableOriginalConstructor()->getMock();
|
||||
$user1->method('getId')->willReturn(1);
|
||||
$user1->method('getPreferenceValue')->willReturn('50');
|
||||
$user1->method('getUsername')->willReturn('foo-bar');
|
||||
$user1->method('getUserIdentifier')->willReturn('foo-bar');
|
||||
$user1->method('getVisiblePreferences')->willReturn([$pref1, $pref2]);
|
||||
$user1 = new User();
|
||||
$user1->setUserIdentifier('foo-bar');
|
||||
$userId->setValue($user1, 1);
|
||||
//$user1->method('getPreferenceValue')->willReturn('50');
|
||||
$user1->addPreference($pref1);
|
||||
$user1->addPreference($pref2);
|
||||
|
||||
$user2 = $this->createMock(User::class);
|
||||
$user2->method('getId')->willReturn(2);
|
||||
$user2->method('getUsername')->willReturn('hello-world');
|
||||
$user2->method('getUserIdentifier')->willReturn('hello-world');
|
||||
$user2->method('getVisiblePreferences')->willReturn([$pref1, $pref2]);
|
||||
$user2 = new User();
|
||||
$userId->setValue($user2, 2);
|
||||
$user2->setUserIdentifier('hello-world');
|
||||
$user2->addPreference($pref1);
|
||||
$user2->addPreference($pref2);
|
||||
|
||||
$timesheet = new Timesheet();
|
||||
$timesheet->setDuration(3600);
|
||||
@@ -302,12 +300,14 @@ trait RendererTestTrait
|
||||
|
||||
$pref1 = new UserPreference('foo', 'bar');
|
||||
$pref2 = new UserPreference('mad', 123.45);
|
||||
$user1 = $this->createMock(User::class);
|
||||
$user1->method('getId')->willReturn(1);
|
||||
$user1->method('getPreferenceValue')->willReturn('50');
|
||||
$user1->method('getUsername')->willReturn('foo-bar');
|
||||
$user1->method('getUserIdentifier')->willReturn('foo-bar');
|
||||
$user1->method('getVisiblePreferences')->willReturn([$pref1, $pref2]);
|
||||
|
||||
$userId = new \ReflectionProperty(User::class, 'id');
|
||||
$user1 = new User();
|
||||
$user1->setUserIdentifier('foo-bar');
|
||||
$user1->addPreference($pref1);
|
||||
$user1->addPreference($pref2);
|
||||
$userId->setValue($user1, 1);
|
||||
//$user1->method('getPreferenceValue')->willReturn('50');
|
||||
|
||||
$timesheet = new Timesheet();
|
||||
$timesheet->setDuration(3600);
|
||||
|
||||
91
tests/Twig/SecurityPolicy/AbstractPolicyTestCase.php
Normal file
91
tests/Twig/SecurityPolicy/AbstractPolicyTestCase.php
Normal file
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Tests\Twig\SecurityPolicy;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\Pdf\PdfContext;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Bridge\Twig\AppVariable;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\ServerBag;
|
||||
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
||||
use Symfony\Component\String\UnicodeString;
|
||||
use Twig\Sandbox\SecurityNotAllowedMethodError;
|
||||
use Twig\Sandbox\SecurityPolicyInterface;
|
||||
|
||||
abstract class AbstractPolicyTestCase extends TestCase
|
||||
{
|
||||
abstract protected function createPolicy(): SecurityPolicyInterface;
|
||||
|
||||
public function testCheckSecurity(): void
|
||||
{
|
||||
$sut = $this->createPolicy();
|
||||
$sut->checkSecurity([], [], []);
|
||||
$this->expectNotToPerformAssertions();
|
||||
}
|
||||
|
||||
public function testCheckPropertyAllowed(): void
|
||||
{
|
||||
$sut = $this->createPolicy();
|
||||
$sut->checkPropertyAllowed(new \stdClass(), 'foo');
|
||||
$this->expectNotToPerformAssertions();
|
||||
}
|
||||
|
||||
#[DataProvider('getCheckMethodAllowedData')]
|
||||
public function testCheckMethodAllowed(object $obj, string $method, ?string $expectedExceptionMessage = null): void
|
||||
{
|
||||
$sut = $this->createPolicy();
|
||||
|
||||
if ($expectedExceptionMessage !== null) {
|
||||
$this->expectException(SecurityNotAllowedMethodError::class);
|
||||
$this->expectExceptionMessage($expectedExceptionMessage);
|
||||
}
|
||||
|
||||
$sut->checkMethodAllowed($obj, $method);
|
||||
|
||||
if ($expectedExceptionMessage === null) {
|
||||
$this->expectNotToPerformAssertions();
|
||||
}
|
||||
}
|
||||
|
||||
public static function getCheckMethodAllowedData(): array
|
||||
{
|
||||
return [
|
||||
[new ServerBag(), 'get', 'Tried to access server environment'],
|
||||
[self::createStub(SessionInterface::class), 'getId', 'Tried to access session'],
|
||||
[new \stdClass(), 'foo', 'Tried to access non-read method'],
|
||||
[new \stdClass(), 'setFoo', 'Tried to access non-read method'],
|
||||
[new \stdClass(), 'getFoo'],
|
||||
[new \stdClass(), 'hasFoo'],
|
||||
[new \stdClass(), 'isFoo'],
|
||||
[new UnicodeString(), '__toString'],
|
||||
// Request
|
||||
[new Request(), 'get', null],
|
||||
[new Request(), 'isXmlHttpRequest', 'Tried to call setter() of app variable'],
|
||||
[new Request(), 'hasSession', 'Tried to call setter() of app variable'],
|
||||
// PdfContext
|
||||
[new PdfContext(), 'setOption'],
|
||||
[new PdfContext(), 'getOption', 'Tried to access forbidden method on PdfContext'],
|
||||
// AppVariable
|
||||
[new AppVariable(), 'getRequest'],
|
||||
[new AppVariable(), 'getUser'],
|
||||
[new AppVariable(), 'getLocale'],
|
||||
[new AppVariable(), 'getCharset', 'Tried to access forbidden app variable method'],
|
||||
// User
|
||||
[new User(), 'getUsername'],
|
||||
[new User(), 'getPassword', 'Tried to access user secrets'],
|
||||
[new User(), 'getTotpSecret', 'Tried to access user secrets'],
|
||||
[new User(), 'getPlainPassword', 'Tried to access user secrets'],
|
||||
[new User(), 'getConfirmationToken', 'Tried to access user secrets'],
|
||||
[new User(), 'getTotpAuthenticationConfiguration', 'Tried to access user secrets'],
|
||||
];
|
||||
}
|
||||
}
|
||||
66
tests/Twig/SecurityPolicy/ChainPolicyTest.php
Normal file
66
tests/Twig/SecurityPolicy/ChainPolicyTest.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Tests\Twig\SecurityPolicy;
|
||||
|
||||
use App\Twig\SecurityPolicy\ChainPolicy;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Twig\Sandbox\SecurityPolicyInterface;
|
||||
|
||||
#[CoversClass(ChainPolicy::class)]
|
||||
class ChainPolicyTest extends TestCase
|
||||
{
|
||||
public function testCheckSecurity(): void
|
||||
{
|
||||
$policy1 = $this->createMock(SecurityPolicyInterface::class);
|
||||
$policy1->expects(self::once())->method('checkSecurity')->with(['tag'], ['filter'], ['function']);
|
||||
|
||||
$policy2 = $this->createMock(SecurityPolicyInterface::class);
|
||||
$policy2->expects(self::once())->method('checkSecurity')->with(['tag'], ['filter'], ['function']);
|
||||
|
||||
$sut = new ChainPolicy();
|
||||
$sut->addPolicy($policy1);
|
||||
$sut->addPolicy($policy2);
|
||||
|
||||
$sut->checkSecurity(['tag'], ['filter'], ['function']);
|
||||
}
|
||||
|
||||
public function testCheckMethodAllowed(): void
|
||||
{
|
||||
$obj = new \stdClass();
|
||||
$policy1 = $this->createMock(SecurityPolicyInterface::class);
|
||||
$policy1->expects(self::once())->method('checkMethodAllowed')->with($obj, 'method');
|
||||
|
||||
$policy2 = $this->createMock(SecurityPolicyInterface::class);
|
||||
$policy2->expects(self::once())->method('checkMethodAllowed')->with($obj, 'method');
|
||||
|
||||
$sut = new ChainPolicy();
|
||||
$sut->addPolicy($policy1);
|
||||
$sut->addPolicy($policy2);
|
||||
|
||||
$sut->checkMethodAllowed($obj, 'method');
|
||||
}
|
||||
|
||||
public function testCheckPropertyAllowed(): void
|
||||
{
|
||||
$obj = new \stdClass();
|
||||
$policy1 = $this->createMock(SecurityPolicyInterface::class);
|
||||
$policy1->expects(self::once())->method('checkPropertyAllowed')->with($obj, 'property');
|
||||
|
||||
$policy2 = $this->createMock(SecurityPolicyInterface::class);
|
||||
$policy2->expects(self::once())->method('checkPropertyAllowed')->with($obj, 'property');
|
||||
|
||||
$sut = new ChainPolicy();
|
||||
$sut->addPolicy($policy1);
|
||||
$sut->addPolicy($policy2);
|
||||
|
||||
$sut->checkPropertyAllowed($obj, 'property');
|
||||
}
|
||||
}
|
||||
23
tests/Twig/SecurityPolicy/DefaultPolicyTest.php
Normal file
23
tests/Twig/SecurityPolicy/DefaultPolicyTest.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Tests\Twig\SecurityPolicy;
|
||||
|
||||
use App\Twig\SecurityPolicy\DefaultPolicy;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use Twig\Sandbox\SecurityPolicyInterface;
|
||||
|
||||
#[CoversClass(DefaultPolicy::class)]
|
||||
class DefaultPolicyTest extends AbstractPolicyTestCase
|
||||
{
|
||||
protected function createPolicy(): SecurityPolicyInterface
|
||||
{
|
||||
return new DefaultPolicy();
|
||||
}
|
||||
}
|
||||
23
tests/Twig/SecurityPolicy/ExportPolicyTest.php
Normal file
23
tests/Twig/SecurityPolicy/ExportPolicyTest.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Tests\Twig\SecurityPolicy;
|
||||
|
||||
use App\Twig\SecurityPolicy\ExportPolicy;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use Twig\Sandbox\SecurityPolicyInterface;
|
||||
|
||||
#[CoversClass(ExportPolicy::class)]
|
||||
class ExportPolicyTest extends AbstractPolicyTestCase
|
||||
{
|
||||
protected function createPolicy(): SecurityPolicyInterface
|
||||
{
|
||||
return new ExportPolicy();
|
||||
}
|
||||
}
|
||||
90
tests/Twig/SecurityPolicy/InvoicePolicyTest.php
Normal file
90
tests/Twig/SecurityPolicy/InvoicePolicyTest.php
Normal file
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Tests\Twig\SecurityPolicy;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\Pdf\PdfContext;
|
||||
use App\Twig\SecurityPolicy\InvoicePolicy;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Bridge\Twig\AppVariable;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\ServerBag;
|
||||
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
||||
use Symfony\Component\String\UnicodeString;
|
||||
use Twig\Sandbox\SecurityNotAllowedMethodError;
|
||||
use Twig\Sandbox\SecurityPolicyInterface;
|
||||
|
||||
#[CoversClass(InvoicePolicy::class)]
|
||||
class InvoicePolicyTest extends TestCase
|
||||
{
|
||||
protected function createPolicy(): SecurityPolicyInterface
|
||||
{
|
||||
return new InvoicePolicy();
|
||||
}
|
||||
|
||||
public function testCheckSecurity(): void
|
||||
{
|
||||
$sut = $this->createPolicy();
|
||||
$sut->checkSecurity([], [], []);
|
||||
$this->expectNotToPerformAssertions();
|
||||
}
|
||||
|
||||
#[DataProvider('getCheckMethodAllowedData')]
|
||||
public function testCheckMethodAllowed(object $obj, string $method, ?string $expectedExceptionMessage = null): void
|
||||
{
|
||||
$sut = $this->createPolicy();
|
||||
|
||||
if ($expectedExceptionMessage !== null) {
|
||||
$this->expectException(SecurityNotAllowedMethodError::class);
|
||||
$this->expectExceptionMessage($expectedExceptionMessage);
|
||||
}
|
||||
|
||||
$sut->checkMethodAllowed($obj, $method);
|
||||
|
||||
if ($expectedExceptionMessage === null) {
|
||||
$this->expectNotToPerformAssertions();
|
||||
}
|
||||
}
|
||||
|
||||
public static function getCheckMethodAllowedData(): array
|
||||
{
|
||||
return [
|
||||
[new ServerBag(), 'get', 'Tried to access server environment'],
|
||||
[self::createStub(SessionInterface::class), 'getId', 'Tried to access session'],
|
||||
[new \stdClass(), 'foo', 'Tried to access non-read method'],
|
||||
[new \stdClass(), 'setFoo', 'Tried to access non-read method'],
|
||||
[new \stdClass(), 'getFoo'],
|
||||
[new \stdClass(), 'hasFoo'],
|
||||
[new \stdClass(), 'isFoo'],
|
||||
[new UnicodeString(), '__toString'],
|
||||
// Request
|
||||
[new Request(), 'get', null],
|
||||
[new Request(), 'isXmlHttpRequest', 'Tried to call setter() of app variable'],
|
||||
[new Request(), 'hasSession', 'Tried to call setter() of app variable'],
|
||||
// PdfContext
|
||||
[new PdfContext(), 'setOption'],
|
||||
[new PdfContext(), 'getOption', 'Tried to access forbidden method on PdfContext'],
|
||||
// AppVariable
|
||||
[new AppVariable(), 'getRequest'],
|
||||
[new AppVariable(), 'getUser'],
|
||||
[new AppVariable(), 'getLocale'],
|
||||
[new AppVariable(), 'getCharset', 'Tried to access forbidden app variable method'],
|
||||
// User
|
||||
[new User(), 'getUsername'],
|
||||
[new User(), 'getPassword', 'Tried to access user secrets'],
|
||||
[new User(), 'getTotpSecret', 'Tried to access user secrets'],
|
||||
[new User(), 'getPlainPassword', 'Tried to access user secrets'],
|
||||
[new User(), 'getConfirmationToken', 'Tried to access user secrets'],
|
||||
[new User(), 'getTotpAuthenticationConfiguration', 'Tried to access user secrets'],
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user