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());
}
}