Release 2.53 (#5878)

This commit is contained in:
Kevin Papst
2026-04-10 18:09:27 +02:00
committed by GitHub
parent fe4185ae45
commit 999d820d4c
79 changed files with 1046 additions and 430 deletions

View File

@@ -43,6 +43,17 @@ abstract class APIControllerBaseTestCase extends AbstractControllerBaseTestCase
};
}
protected function getAuthenticatedUserId(string $role = User::ROLE_USER): int
{
return match ($role) {
User::ROLE_SUPER_ADMIN => 6,
User::ROLE_ADMIN => 5,
User::ROLE_TEAMLEAD => 4,
User::ROLE_USER => 2,
default => throw new \Exception(\sprintf('Unknown role "%s"', $role)),
};
}
protected function createUrl(string $url): string
{
return '/' . ltrim($url, '/');
@@ -215,7 +226,7 @@ abstract class APIControllerBaseTestCase extends AbstractControllerBaseTestCase
/**
* @param Response $response
* @param array<int, string>|array<string, mixed> $failedFields
* @param array<int, string>|array<string, string> $failedFields
* @param bool $extraFields test for the error "This form should not contain extra fields"
* @param array<int, string>|array<string, mixed> $globalError
*/
@@ -793,6 +804,7 @@ abstract class APIControllerBaseTestCase extends AbstractControllerBaseTestCase
self::assertIsString($subResult);
} else {
self::assertIsArray($subResult);
self::assertIsString($value['type']);
if ($value['type'][0] === '@') {
if (empty($result[$key])) {
@@ -807,6 +819,7 @@ abstract class APIControllerBaseTestCase extends AbstractControllerBaseTestCase
break;
case 'object':
self::assertIsString($value['type']);
if ($value['type'][0] === '@') {
if (empty($result[$key])) {
break;

View File

@@ -220,11 +220,24 @@ class ActivityControllerTest extends APIControllerBaseTestCase
self::assertIsArray($result);
self::assertNotEmpty($result);
self::assertEquals(5, \count($result));
self::assertIsArray($result[0]);
self::assertApiResponseTypeStructure('ActivityCollection', $result[0]);
self::assertEquals($imports[0]->getId(), $result[4]['project']);
self::assertEquals($imports[1]->getId(), $result[3]['project']);
self::assertIsArray($result[2]);
self::assertApiResponseTypeStructure('ActivityCollection', $result[2]);
self::assertArrayHasKey('project', $result[2]);
self::assertEquals($imports[1]->getId(), $result[2]['project']);
self::assertIsArray($result[3]);
self::assertApiResponseTypeStructure('ActivityCollection', $result[3]);
self::assertArrayHasKey('project', $result[3]);
self::assertEquals($imports[1]->getId(), $result[3]['project']);
self::assertIsArray($result[4]);
self::assertApiResponseTypeStructure('ActivityCollection', $result[4]);
self::assertArrayHasKey('project', $result[4]);
self::assertEquals($imports[0]->getId(), $result[4]['project']);
}
public function testGetEntityIsSecure(): void

View File

@@ -32,6 +32,7 @@ class ApiDocControllerTest extends AbstractControllerBaseTestCase
self::assertStringContainsString('const config = {"basePath":"/api/doc","router":"memory","logo":"/touch-icon-192x192.png","hideInternal":true};', $content);
$results = preg_match('/docs\.apiDescriptionDocument\ \=\ (.*)\.spec;/', $content, $matches);
self::assertNotFalse($results);
self::assertArrayHasKey(1, $matches);
$swaggerJson = json_decode($matches[1], true);
self::assertIsArray($swaggerJson);
self::assertArrayHasKey('spec', $swaggerJson);

View File

@@ -171,6 +171,7 @@ class TeamControllerTest extends APIControllerBaseTestCase
self::assertIsArray($result);
self::assertApiResponseTypeStructure('TeamEntity', $result);
self::assertNotEmpty($result['id']);
self::assertIsArray($result['members']);
self::assertCount(3, $result['members']);
self::assertIsNumeric($updateId);
@@ -182,18 +183,23 @@ class TeamControllerTest extends APIControllerBaseTestCase
self::assertIsArray($result['members']);
self::assertCount(3, $result['members']);
self::assertIsArray($result['members'][0]);
self::assertTrue($result['members'][0]['teamlead']);
self::assertIsArray($result['members'][0]['user']);
self::assertEquals(2, $result['members'][0]['user']['id']);
self::assertEquals('john_user', $result['members'][0]['user']['username']);
self::assertIsArray($result['members'][1]);
self::assertFalse($result['members'][1]['teamlead']);
self::assertIsArray($result['members'][1]['user']);
self::assertEquals(1, $result['members'][1]['user']['id']);
self::assertEquals('clara_customer', $result['members'][1]['user']['username']);
self::assertIsArray($result['members'][2]);
self::assertTrue($result['members'][2]['teamlead']);
self::assertIsArray($result['members'][2]['user']);
self::assertEquals(4, $result['members'][2]['user']['id']);
self::assertEquals('tony_teamlead', $result['members'][2]['user']['username']);
self::assertTrue($result['members'][0]['teamlead']);
self::assertEquals(2, $result['members'][0]['user']['id']);
self::assertEquals('john_user', $result['members'][0]['user']['username']);
}
public function testPatchActionWithValidationErrors(): void
@@ -380,6 +386,7 @@ class TeamControllerTest extends APIControllerBaseTestCase
$result = json_decode($client->getResponse()->getContent(), true);
self::assertIsArray($result);
self::assertIsNumeric($result['id']);
self::assertIsArray($result['customers']);
self::assertCount(0, $result['customers']);
$this->request($client, '/api/teams/' . $result['id'] . '/customers/1', 'POST');
@@ -422,6 +429,7 @@ class TeamControllerTest extends APIControllerBaseTestCase
$result = json_decode($client->getResponse()->getContent(), true);
self::assertIsArray($result);
self::assertIsNumeric($result['id']);
self::assertIsArray($result['customers']);
self::assertCount(1, $result['customers']);
// cannot add existing customer
@@ -445,6 +453,7 @@ class TeamControllerTest extends APIControllerBaseTestCase
$result = json_decode($client->getResponse()->getContent(), true);
self::assertIsArray($result);
self::assertIsNumeric($result['id']);
self::assertIsArray($result['customers']);
self::assertCount(0, $result['customers']);
// add customer
@@ -453,6 +462,7 @@ class TeamControllerTest extends APIControllerBaseTestCase
$result = json_decode($client->getResponse()->getContent(), true);
self::assertIsArray($result);
self::assertIsNumeric($result['id']);
self::assertIsArray($result['customers']);
self::assertCount(1, $result['customers']);
$this->request($client, '/api/teams/' . $result['id'] . '/customers/1', 'DELETE');
@@ -515,6 +525,7 @@ class TeamControllerTest extends APIControllerBaseTestCase
$result = json_decode($client->getResponse()->getContent(), true);
self::assertIsArray($result);
self::assertIsNumeric($result['id']);
self::assertIsArray($result['projects']);
self::assertCount(0, $result['projects']);
$this->request($client, '/api/teams/' . $result['id'] . '/projects/1', 'POST');
self::assertTrue($client->getResponse()->isSuccessful());
@@ -558,6 +569,7 @@ class TeamControllerTest extends APIControllerBaseTestCase
$result = json_decode($client->getResponse()->getContent(), true);
self::assertIsArray($result);
self::assertIsNumeric($result['id']);
self::assertIsArray($result['projects']);
self::assertCount(1, $result['projects']);
// cannot add existing project
@@ -581,6 +593,7 @@ class TeamControllerTest extends APIControllerBaseTestCase
$result = json_decode($client->getResponse()->getContent(), true);
self::assertIsArray($result);
self::assertIsNumeric($result['id']);
self::assertIsArray($result['projects']);
self::assertCount(0, $result['projects']);
// add project
@@ -590,6 +603,7 @@ class TeamControllerTest extends APIControllerBaseTestCase
self::assertIsArray($result);
self::assertIsArray($result['projects']);
self::assertCount(1, $result['projects']);
self::assertIsInt($result['id']);
$this->request($client, '/api/teams/' . $result['id'] . '/projects/1', 'DELETE');
self::assertTrue($client->getResponse()->isSuccessful());
@@ -649,6 +663,7 @@ class TeamControllerTest extends APIControllerBaseTestCase
$result = json_decode($client->getResponse()->getContent(), true);
self::assertIsArray($result);
self::assertIsNumeric($result['id']);
self::assertIsArray($result['activities']);
self::assertCount(0, $result['activities']);
$this->request($client, '/api/teams/' . $result['id'] . '/activities/1', 'POST');
self::assertTrue($client->getResponse()->isSuccessful());
@@ -690,6 +705,7 @@ class TeamControllerTest extends APIControllerBaseTestCase
$result = json_decode($client->getResponse()->getContent(), true);
self::assertIsArray($result);
self::assertIsNumeric($result['id']);
self::assertIsArray($result['activities']);
self::assertCount(1, $result['activities']);
// cannot add existing activity
@@ -714,6 +730,7 @@ class TeamControllerTest extends APIControllerBaseTestCase
self::assertIsArray($result);
self::assertIsArray($result['activities']);
self::assertCount(0, $result['activities']);
self::assertIsInt($result['id']);
// add activity
$this->request($client, '/api/teams/' . $result['id'] . '/activities/1', 'POST');
@@ -721,6 +738,7 @@ class TeamControllerTest extends APIControllerBaseTestCase
$result = json_decode($client->getResponse()->getContent(), true);
self::assertIsArray($result);
self::assertIsNumeric($result['id']);
self::assertIsArray($result['activities']);
self::assertCount(1, $result['activities']);
$this->request($client, '/api/teams/' . $result['id'] . '/activities/1', 'DELETE');

View File

@@ -35,13 +35,21 @@ class TimesheetControllerTest extends APIControllerBaseTestCase
/**
* @return Timesheet[]
*/
protected function importFixtureForUser(string $role, int $amount = 10): array
protected function importFixtureForUser(User|string $user, int $amount = 10): array
{
$fixture = new TimesheetFixtures($this->getUserByRole($role), $amount);
if (\is_string($user)) {
$role = $user;
$user = $this->getUserByRole($role);
}
$start = DateTimeFactory::createByUser($user)->createDateTime('first day of this month');
$start = $start->setTime(0, 0, 1);
$fixture = new TimesheetFixtures($user, $amount);
$fixture->setFixedRate(true);
$fixture->setHourlyRate(true);
$fixture->setAllowEmptyDescriptions(false);
$fixture->setStartDate((new \DateTime('first day of this month'))->setTime(0, 0, 1));
$fixture->setStartDate($start);
return $this->importFixture($fixture);
}
@@ -212,18 +220,25 @@ class TimesheetControllerTest extends APIControllerBaseTestCase
public function testGetCollectionWithQuery(): void
{
$modifiedAfter = new \DateTime('-1 hour');
$begin = new \DateTime('first day of this month');
$begin->setTime(0, 0, 0);
$end = new \DateTime('last day of this month');
$end->setTime(23, 59, 59);
$role = User::ROLE_USER;
$client = $this->getClientForAuthenticatedUser($role);
$user = $this->getUserByRole($role);
$factory = DateTimeFactory::createByUser($user);
$begin = $factory->createDateTime('first day of this month');
$begin = $begin->setTime(0, 0, 1);
$end = $factory->createDateTime('last day of this month');
$end = $end->setTime(23, 59, 59);
$modifiedAfter = $factory->createDateTime('-20 hour');
$query = [
'customers' => ['1'],
'projects' => ['1'],
'activities' => ['1'],
'page' => 2,
'size' => 4,
'page' => '2',
'size' => '4',
'order' => 'DESC',
'orderBy' => 'rate',
'active' => 0,
@@ -233,8 +248,7 @@ class TimesheetControllerTest extends APIControllerBaseTestCase
'exported' => 0,
];
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
$this->importFixtureForUser(User::ROLE_USER, 22);
$this->importFixtureForUser($user, 22);
$this->assertAccessIsGranted($client, '/api/timesheets', 'GET', $query);
$content = $client->getResponse()->getContent();
self::assertIsString($content);
@@ -250,11 +264,6 @@ class TimesheetControllerTest extends APIControllerBaseTestCase
public function testGetCollectionWithQueryFailsWith404OnOutOfRangedPage(): void
{
$begin = new \DateTime('first day of this month');
$begin->setTime(0, 0, 0);
$end = new \DateTime('last day of this month');
$end->setTime(23, 59, 59);
$query = [
'page' => 19,
'size' => 50,
@@ -268,10 +277,16 @@ class TimesheetControllerTest extends APIControllerBaseTestCase
public function testGetCollectionWithSingleParamsQuery(): void
{
$begin = new \DateTime('first day of this month');
$begin->setTime(0, 0, 0);
$end = new \DateTime('last day of this month');
$end->setTime(23, 59, 59);
$role = User::ROLE_USER;
$client = $this->getClientForAuthenticatedUser($role);
$user = $this->getUserByRole($role);
$factory = DateTimeFactory::createByUser($user);
$begin = $factory->create('first day of this month');
$begin = $begin->setTime(0, 0, 1);
$end = $factory->create('last day of this month');
$end = $end->setTime(23, 59, 59);
$query = [
'customer' => '1',
@@ -287,8 +302,7 @@ class TimesheetControllerTest extends APIControllerBaseTestCase
'exported' => 0,
];
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
$this->importFixtureForUser(User::ROLE_USER);
$this->importFixtureForUser($user);
$this->assertAccessIsGranted($client, '/api/timesheets', 'GET', $query);
$content = $client->getResponse()->getContent();
self::assertIsString($content);
@@ -303,19 +317,23 @@ class TimesheetControllerTest extends APIControllerBaseTestCase
public function testExportedFilter(): void
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
$this->importFixtureForUser(User::ROLE_USER);
$role = User::ROLE_USER;
$client = $this->getClientForAuthenticatedUser($role);
$user = $this->getUserByRole($role);
$factory = DateTimeFactory::createByUser($user);
$this->importFixtureForUser($user);
$fixture = new TimesheetFixtures($this->getUserByRole(User::ROLE_USER), 7);
$fixture = new TimesheetFixtures($user, 7);
$fixture->setExported(true);
$fixture->setStartDate(new \DateTime('first day of this month'));
$fixture->setAllowEmptyDescriptions(false);
$this->importFixture($fixture);
$begin = new \DateTime('first day of this month');
$begin->setTime(0, 0, 0);
$end = new \DateTime('last day of this month');
$end->setTime(23, 59, 59);
$begin = $factory->create('first day of this month');
$begin = $begin->setTime(0, 0, 1);
$end = $factory->create('last day of this month');
$end = $end->setTime(23, 59, 59);
$query = [
'page' => 1,

View File

@@ -403,4 +403,50 @@ class UserControllerTest extends APIControllerBaseTestCase
self::assertEquals('another,testing,bar', $user->getPreferenceValue('metatestmock'));
self::assertEquals('another,testing,bar', $user->getPreferenceValue('metatestmock'));
}
public function testUpdateUserPreferenceWithEnabledPreference(): void
{
$role = User::ROLE_SUPER_ADMIN;
$id = $this->getAuthenticatedUserId($role);
$client = $this->getClientForAuthenticatedUser($role);
$em = $this->getEntityManager();
/** @var User $user */
$user = $em->getRepository(User::class)->find($id);
self::assertEquals(46, $user->getPreferenceValue('hourly_rate'));
$data = [
[
'name' => 'hourly_rate',
'value' => 99
]
];
$this->request($client, '/api/users/' . $id . '/preferences', 'PATCH', [], (string) json_encode($data));
self::assertTrue($client->getResponse()->isSuccessful());
/** @var User $user */
$user = $em->getRepository(User::class)->find($id);
self::assertEquals(99, $user->getPreferenceValue('hourly_rate'));
}
public function testUpdateUserPreferenceWithDisabledPreference(): void
{
$role = User::ROLE_USER;
$id = $this->getAuthenticatedUserId($role);
$client = $this->getClientForAuthenticatedUser($role);
$em = $this->getEntityManager();
/** @var User $user */
$user = $em->getRepository(User::class)->find($id);
self::assertEquals(82, $user->getPreferenceValue('hourly_rate'));
$data = [
[
'name' => 'hourly_rate',
'value' => 99
]
];
$this->request($client, '/api/users/' . $id . '/preferences', 'PATCH', [], (string) json_encode($data));
self::assertApiResponseAccessDenied($client->getResponse());
}
}

View File

@@ -303,6 +303,7 @@ abstract class AbstractControllerBaseTestCase extends WebTestCase
$crawler = $client->request('GET', $this->createUrl($url));
$form = $crawler->filter($formSelector)->form();
if ($disableValidation) {
// needed for setting invalid values e.g. in dropdowns
$form->disableValidation();
}
$result = $client->submit($form, $formData);
@@ -313,7 +314,13 @@ abstract class AbstractControllerBaseTestCase extends WebTestCase
self::assertEquals(
\count($fieldNames),
\count($validationErrors),
\sprintf('Expected %s validation errors, found %s', \count($fieldNames), \count($validationErrors))
\sprintf(
'Expected %s validation errors, found %s. Expected: %s. Found: %s.',
\count($fieldNames),
\count($validationErrors),
implode(',', $fieldNames),
implode(',', $validationErrors->extract(['_text']))
)
);
foreach ($fieldNames as $name) {

View File

@@ -238,11 +238,11 @@ class UserControllerTest extends AbstractControllerBaseTestCase
// invalid fields: username, password_second, email, enabled
[
'user_create' => [
'username' => '',
'plainPassword' => ['first' => 'sdfsdf123'],
'username' => '', // empty
'plainPassword' => ['first' => 'sdfsdf123'], // missing second
'alias' => 'ycvyxcb',
'title' => '34rtwrtewrt',
'email' => '',
'email' => '', // empty email
]
],
[
@@ -251,15 +251,15 @@ class UserControllerTest extends AbstractControllerBaseTestCase
'#user_create_email',
]
],
// invalid fields: username, password, email, enabled
// invalid fields: username, password, email
[
[
'user_create' => [
'username' => 'x',
'plainPassword' => ['first' => 'sdfsdf123', 'second' => 'sdfxxxxxxx'],
'alias' => 'ycvyxcb',
'title' => '34rtwrtewrt',
'email' => 'ydfbvsdfgs',
'username' => 'x', // too short
'plainPassword' => ['first' => 'sdfsdf123', 'second' => 'sdfxxxxxxx'], // do not match
'alias' => 'Boo',
'title' => 'Foo',
'email' => 'ydfbvsdfgs', // invalid email
]
],
[
@@ -283,6 +283,22 @@ class UserControllerTest extends AbstractControllerBaseTestCase
'#user_create_plainPassword_first',
]
],
// invalid fields: alias (special chars), title (special chars, too long)
[
[
'user_create' => [
'username' => 'test1231',
'plainPassword' => ['first' => 'A-Real-Password.1', 'second' => 'A-Real-Password.1'],
'alias' => '""ycvyx<cb""',
'title' => 'sdfgsdfgsd fgsdf "<34rtwrtewrt>" gsdfg sdfg sdfg sdfg sdfg sdfg sdfg sdfgsdfgsd fgsdf gsdfgsgsdfgsdfgsdfgsdfg sdfg sdfg sdfg sdfg sdfg sdfgsdfgsd fgsdf gsdfgsgsdfgsdfgsdfg', // special chars + too long
'email' => 'ydfbvsdfgs@example.com',
]
],
[
'#user_create_alias',
'#user_create_title',
]
],
];
}
}

View File

@@ -87,7 +87,7 @@ final class TimesheetFixtures implements TestFixture
* @param string|\DateTime $date
* @return TimesheetFixtures
*/
public function setStartDate($date): TimesheetFixtures
public function setStartDate(string|\DateTime $date): TimesheetFixtures
{
if (!($date instanceof \DateTime)) {
$date = new \DateTime($date);
@@ -313,8 +313,8 @@ final class TimesheetFixtures implements TestFixture
}
$start = clone $this->startDate;
$start->modify("+ $i days");
$start->modify('+ ' . rand(1, 172800) . ' seconds'); // up to 2 days
$start = $start->modify("+ $i days");
$start = $start->modify('+ ' . rand(1, 172800) . ' seconds'); // up to 2 days
return $start;
}

View File

@@ -412,7 +412,8 @@ class ConfigurationTest extends TestCase
],
'mapping' => [],
'connection' => [
'organization' => []
'organization' => [],
'cleanupLongRelayState' => false
],
'provider' => 'default',
],

View File

@@ -0,0 +1,48 @@
<?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\Form\Type;
use App\Form\Type\WeekDaysType;
use PHPUnit\Framework\Attributes\CoversClass;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\Test\TypeTestCase;
#[CoversClass(WeekDaysType::class)]
class WeekDaysTypeTest extends TypeTestCase
{
public function testSubmitNull(): void
{
$data = ['week_days' => null];
$model = new TypeTestModel(['week_days' => null]);
$form = $this->factory->createBuilder(FormType::class, $model);
$form->add('week_days', WeekDaysType::class);
$form = $form->getForm();
$form->submit($data);
self::assertTrue($form->isSynchronized());
self::assertEquals('', $model->offsetGet('week_days'));
}
public function testSubmitValidData(): void
{
$data = ['week_days' => ['tuesday', 'friday', 'sunday']];
$model = new TypeTestModel(['week_days' => null]);
$form = $this->factory->createBuilder(FormType::class, $model);
$form->add('week_days', WeekDaysType::class);
$form = $form->getForm();
$form->submit($data);
self::assertTrue($form->isSynchronized());
self::assertEquals('tuesday,friday,sunday', $model->offsetGet('week_days'));
}
}

View File

@@ -31,7 +31,7 @@ class DebugRendererTest extends TestCase
}
#[DataProvider('getTestModel')]
public function testRender(callable $invoiceModel, $expectedRate, $expectedRows, $expectedDescriptions, $expectedUser1, $expectedUser2, $expectedUser3, $hasProject, $metaFields = []): void
public function testRender(callable $invoiceModel, string $expectedRate, int $expectedRows, int $expectedDescriptions, int $expectedUser1, int $expectedUser2, int $expectedUser3, bool $hasProject, array $metaFields = []): void
{
/** @var InvoiceModel $model */
$model = $invoiceModel($this);
@@ -67,10 +67,12 @@ class DebugRendererTest extends TestCase
$this->assertModelStructure($data['model'], \count($model->getQuery()->getProjects()), \count($model->getQuery()->getActivities()));
$rows = $data['entries'];
self::assertIsArray($rows);
self::assertEquals($expectedRows, \count($rows));
$i = 0;
foreach ($rows as $row) {
self::assertIsArray($row);
$meta = isset($metaFields[$i]) ? $metaFields[$i++] : [];
$this->assertEntryStructure($row, $meta);
}

View File

@@ -42,7 +42,11 @@ class SystemConfigurationFactory
foreach ($iterator as $value) {
$keys = [];
foreach (range(0, $iterator->getDepth()) as $depth) {
$keys[] = $iterator->getSubIterator($depth)->key();
$key = $iterator->getSubIterator($depth)->key();
if (!\is_scalar($key)) {
continue;
}
$keys[] = $key;
}
$newConfig[implode('.', $keys)] = $value;
}

View File

@@ -22,12 +22,37 @@ use Symfony\Component\Security\Http\HttpUtils;
#[CoversClass(SamlAuthenticationSuccessHandler::class)]
class SamlAuthenticationSuccessHandlerTest extends TestCase
{
public function testRelayStateWithInvalidHost(): void
{
$handler = new SamlAuthenticationSuccessHandler(new HttpUtils($this->getUrlGenerator()));
$response = $handler->onAuthenticationSuccess($this->getRequest('http://example.com/sso/login', 'https://localhost/relayed'), $this->getSamlToken());
self::assertInstanceOf(RedirectResponse::class, $response);
$target = $response->getTargetUrl();
$expected = 'http://example.com/';
self::assertEquals($expected, $target);
self::assertTrue($response->isRedirect($target));
}
public function testRelayState(): void
{
$handler = new SamlAuthenticationSuccessHandler(new HttpUtils($this->getUrlGenerator()));
$response = $handler->onAuthenticationSuccess($this->getRequest('/sso/login', 'http://localhost/relayed'), $this->getSamlToken());
$response = $handler->onAuthenticationSuccess($this->getRequest('/sso/login', '/relayed'), $this->getSamlToken());
self::assertInstanceOf(RedirectResponse::class, $response);
self::assertTrue($response->isRedirect('http://localhost/relayed'));
$target = $response->getTargetUrl();
$expected = 'http://localhost/relayed';
self::assertEquals($expected, $target);
self::assertTrue($response->isRedirect($target));
}
public function testRelayStateWithFullUrls(): void
{
$handler = new SamlAuthenticationSuccessHandler(new HttpUtils($this->getUrlGenerator()));
$response = $handler->onAuthenticationSuccess($this->getRequest('http://example.com/sso/login', 'http://example.com/relayed/123'), $this->getSamlToken());
self::assertInstanceOf(RedirectResponse::class, $response);
$target = $response->getTargetUrl();
$expected = 'http://example.com/relayed/123';
self::assertEquals($expected, $target);
self::assertTrue($response->isRedirect($target));
}
public function testWithoutRelayState(): void
@@ -71,7 +96,7 @@ class SamlAuthenticationSuccessHandlerTest extends TestCase
$params['RelayState'] = $relayState;
}
return Request::create($path, 'get', $params);
return Request::create($path, 'post', $params);
}
private function getSamlToken(): SamlToken

View File

@@ -0,0 +1,52 @@
<?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;
use App\Twig\ContractExtensions;
use App\WorkingTime\WorkingTimeService;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
use Twig\TwigTest;
#[CoversClass(ContractExtensions::class)]
class ContractExtensionsTest extends TestCase
{
private function getSut(): ContractExtensions
{
$service = $this->createMock(WorkingTimeService::class);
$sut = new ContractExtensions($service);
return $sut;
}
public function testDefinedMethods(): void
{
self::assertCount(1, $this->getSut()->getTests());
self::assertCount(0, $this->getSut()->getFilters());
self::assertCount(0, $this->getSut()->getFunctions());
}
public function testGetTests(): void
{
$filters = [
'work_day',
];
$i = 0;
$sut = $this->getSut();
$twigFilters = $sut->getTests();
foreach ($twigFilters as $filter) {
self::assertInstanceOf(TwigTest::class, $filter);
self::assertEquals($filters[$i++], $filter->getName());
}
}
}

View File

@@ -92,6 +92,8 @@ class StrictPolicyTestCase extends TestCase
[new User(), 'getPlainPassword', 'Tried to access user secrets'],
[new User(), 'getConfirmationToken', 'Tried to access user secrets'],
[new User(), 'getTotpAuthenticationConfiguration', 'Tried to access user secrets'],
[new User(), 'getPlainApiToken', 'Tried to access user secrets'],
[new User(), 'getApiToken', 'Tried to access user secrets'],
];
}
}

View File

@@ -0,0 +1,87 @@
<?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\Validator\Constraints;
use App\Validator\Constraints\NoHtmlSpecialCharacters;
use App\Validator\Constraints\NoHtmlSpecialCharactersValidator;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Test\ConstraintValidatorTestCase;
/**
* @extends ConstraintValidatorTestCase<NoHtmlSpecialCharactersValidator>
*/
#[CoversClass(NoHtmlSpecialCharacters::class)]
#[CoversClass(NoHtmlSpecialCharactersValidator::class)]
class NoHtmlSpecialCharactersValidatorTest extends ConstraintValidatorTestCase
{
protected function createValidator(): NoHtmlSpecialCharactersValidator
{
return new NoHtmlSpecialCharactersValidator();
}
public function testConstraintIsInvalid(): void
{
$this->expectException(UnexpectedTypeException::class);
$this->validator->validate('foo', new NotBlank());
}
public function testGetTargets(): void
{
$constraint = new NoHtmlSpecialCharacters();
self::assertEquals('property', $constraint->getTargets());
}
public static function getValidTestData(): array
{
return [
[''],
[null],
['asdf-.,123!§$%&/()=?`4567\'890ß'],
];
}
#[DataProvider('getValidTestData')]
public function testValidInput(string|null $data): void
{
$this->validator = $this->createValidator();
$this->validator->initialize($this->context);
$this->validator->validate($data, new NoHtmlSpecialCharacters());
$this->assertNoViolation();
}
public static function getInvalidTestData(): array
{
return [
['Test" onclick="alert(1)"'],
['Test><a href=#>Foo</a>'],
['Test" broken string'],
];
}
#[DataProvider('getInvalidTestData')]
public function testInvalidInput(string|null $data): void
{
$this->validator = $this->createValidator();
$this->validator->initialize($this->context);
$this->validator->validate($data, new NoHtmlSpecialCharacters());
$this->buildViolation('These characters are not allowed: {{ chars }}')
->setCode(NoHtmlSpecialCharacters::SPECIAL_CHARACTERS_FOUND)
->setParameter('{{ chars }}', '< " >')
->assertRaised();
}
}

View File

@@ -107,11 +107,6 @@ parameters:
count: 1
path: API/APIControllerBaseTestCase.php
-
message: "#^Parameter \\#1 \\$haystack of function stripos expects string, mixed given\\.$#"
count: 1
path: API/APIControllerBaseTestCase.php
-
message: "#^Parameter \\#1 \\$json of function json_decode expects string, string\\|false given\\.$#"
count: 3
@@ -122,11 +117,6 @@ parameters:
count: 1
path: API/APIControllerBaseTestCase.php
-
message: "#^Parameter \\#2 \\$string of function explode expects string, mixed given\\.$#"
count: 1
path: API/APIControllerBaseTestCase.php
-
message: "#^Parameter \\#2 \\.\\.\\.\\$values of function sprintf expects bool\\|float\\|int\\|string\\|null, mixed given\\.$#"
count: 1
@@ -1461,46 +1451,6 @@ parameters:
count: 1
path: Invoice/Renderer/DebugRendererTest.php
-
message: "#^Method App\\\\Tests\\\\Invoice\\\\Renderer\\\\DebugRendererTest\\:\\:testRender\\(\\) has parameter \\$expectedDescriptions with no type specified\\.$#"
count: 1
path: Invoice/Renderer/DebugRendererTest.php
-
message: "#^Method App\\\\Tests\\\\Invoice\\\\Renderer\\\\DebugRendererTest\\:\\:testRender\\(\\) has parameter \\$expectedRate with no type specified\\.$#"
count: 1
path: Invoice/Renderer/DebugRendererTest.php
-
message: "#^Method App\\\\Tests\\\\Invoice\\\\Renderer\\\\DebugRendererTest\\:\\:testRender\\(\\) has parameter \\$expectedRows with no type specified\\.$#"
count: 1
path: Invoice/Renderer/DebugRendererTest.php
-
message: "#^Method App\\\\Tests\\\\Invoice\\\\Renderer\\\\DebugRendererTest\\:\\:testRender\\(\\) has parameter \\$expectedUser1 with no type specified\\.$#"
count: 1
path: Invoice/Renderer/DebugRendererTest.php
-
message: "#^Method App\\\\Tests\\\\Invoice\\\\Renderer\\\\DebugRendererTest\\:\\:testRender\\(\\) has parameter \\$expectedUser2 with no type specified\\.$#"
count: 1
path: Invoice/Renderer/DebugRendererTest.php
-
message: "#^Method App\\\\Tests\\\\Invoice\\\\Renderer\\\\DebugRendererTest\\:\\:testRender\\(\\) has parameter \\$expectedUser3 with no type specified\\.$#"
count: 1
path: Invoice/Renderer/DebugRendererTest.php
-
message: "#^Method App\\\\Tests\\\\Invoice\\\\Renderer\\\\DebugRendererTest\\:\\:testRender\\(\\) has parameter \\$hasProject with no type specified\\.$#"
count: 1
path: Invoice/Renderer/DebugRendererTest.php
-
message: "#^Method App\\\\Tests\\\\Invoice\\\\Renderer\\\\DebugRendererTest\\:\\:testRender\\(\\) has parameter \\$metaFields with no type specified\\.$#"
count: 1
path: Invoice/Renderer/DebugRendererTest.php
-
message: "#^Parameter \\#1 \\$json of function json_decode expects string, string\\|false given\\.$#"
count: 1