Release 2.40.0 (#5621)

This commit is contained in:
Kevin Papst
2025-09-26 10:49:06 +02:00
committed by GitHub
parent 6d78c6ba36
commit 7937fa281a
54 changed files with 531 additions and 399 deletions

View File

@@ -68,7 +68,7 @@ abstract class APIControllerBaseTestCase extends AbstractControllerBaseTestCase
$data = [
'message' => 'Unauthorized',
'code' => 401
'code' => Response::HTTP_UNAUTHORIZED
];
self::assertEquals(
@@ -100,7 +100,7 @@ abstract class APIControllerBaseTestCase extends AbstractControllerBaseTestCase
]);
}
public function request(HttpKernelBrowser $client, string $url, string $method = 'GET', array $parameters = [], string $content = null): Crawler
public function request(HttpKernelBrowser $client, string $url, string $method = 'GET', array $parameters = [], ?string $content = null): Crawler
{
$server = ['HTTP_CONTENT_TYPE' => 'application/json', 'CONTENT_TYPE' => 'application/json'];
@@ -276,6 +276,7 @@ abstract class APIControllerBaseTestCase extends AbstractControllerBaseTestCase
$fieldName = implode('.', $parts);
}
self::assertIsString($fieldName);
self::assertArrayHasKey($fieldName, $data, \sprintf('Could not find validation error for field "%s" in list: %s', $fieldName, implode(', ', $failedFields)));
self::assertArrayHasKey('errors', $data[$fieldName], \sprintf('Field %s has no validation problem', $fieldName));
foreach ($messages as $i => $message) {
@@ -356,6 +357,7 @@ abstract class APIControllerBaseTestCase extends AbstractControllerBaseTestCase
'enabled' => 'bool',
'apiToken' => 'bool',
'color' => '@string',
'avatar' => '@string',
'alias' => '@string',
'accountNumber' => '@string',
'initials' => '@string',
@@ -431,6 +433,9 @@ abstract class APIControllerBaseTestCase extends AbstractControllerBaseTestCase
'color' => '@string',
'number' => '@string',
'comment' => '@string',
'currency' => 'string', // since 2.40.0
'country' => 'string', // since 2.40.0
'company' => '@string', // since 2.40.0
];
// if a list of customers is loaded
@@ -446,6 +451,13 @@ abstract class APIControllerBaseTestCase extends AbstractControllerBaseTestCase
'metaFields' => ['result' => 'array', 'type' => 'CustomerMeta'],
'teams' => ['result' => 'array', 'type' => 'Team'],
'currency' => 'string', // since 1.10
'country' => 'string',
'company' => '@string',
'homepage' => '@string',
'fax' => '@string',
'mobile' => '@string',
'phone' => '@string',
'timezone' => 'string',
];
// if a customer is loaded explicitly
@@ -490,6 +502,8 @@ abstract class APIControllerBaseTestCase extends AbstractControllerBaseTestCase
'orderNumber' => '@string',
'globalActivities' => 'bool',
'comment' => '@string',
'start' => '@datetime',
'end' => '@datetime',
];
// if a project is embedded in an expanded collection (here timesheet)
@@ -505,6 +519,8 @@ abstract class APIControllerBaseTestCase extends AbstractControllerBaseTestCase
'orderNumber' => '@string',
'globalActivities' => 'bool',
'comment' => '@string',
'start' => '@datetime',
'end' => '@datetime',
];
// if a collection of projects is loaded

View File

@@ -407,7 +407,7 @@ class ActivityControllerTest extends APIControllerBaseTestCase
public function testMetaActionThrowsExceptionOnMissingName(): void
{
$this->assertExceptionForPatchAction(User::ROLE_ADMIN, '/api/activities/1/meta', ['value' => 'X'], [
'code' => 400,
'code' => Response::HTTP_BAD_REQUEST,
'message' => 'Bad Request'
]);
}
@@ -415,7 +415,7 @@ class ActivityControllerTest extends APIControllerBaseTestCase
public function testMetaActionThrowsExceptionOnMissingValue(): void
{
$this->assertExceptionForPatchAction(User::ROLE_ADMIN, '/api/activities/1/meta', ['name' => 'X'], [
'code' => 400,
'code' => Response::HTTP_BAD_REQUEST,
'message' => 'Bad Request'
]);
}
@@ -423,7 +423,7 @@ class ActivityControllerTest extends APIControllerBaseTestCase
public function testMetaActionThrowsExceptionOnMissingMetafield(): void
{
$this->assertExceptionForPatchAction(User::ROLE_ADMIN, '/api/activities/1/meta', ['name' => 'X', 'value' => 'Y'], [
'code' => 404,
'code' => Response::HTTP_NOT_FOUND,
'message' => 'Not Found'
]);
}

View File

@@ -105,6 +105,7 @@ class ApiDocControllerTest extends AbstractControllerBaseTestCase
'/api/users/{id}',
'/api/users/me',
'/api/users/api-token/{id}',
'/api/users/{id}/preferences',
];
self::assertArrayHasKey('openapi', $json);

View File

@@ -390,7 +390,7 @@ class CustomerControllerTest extends APIControllerBaseTestCase
public function testMetaActionThrowsExceptionOnMissingName(): void
{
$this->assertExceptionForPatchAction(User::ROLE_ADMIN, '/api/customers/1/meta', ['value' => 'X'], [
'code' => 400,
'code' => Response::HTTP_BAD_REQUEST,
'message' => 'Bad Request'
]);
}
@@ -398,7 +398,7 @@ class CustomerControllerTest extends APIControllerBaseTestCase
public function testMetaActionThrowsExceptionOnMissingValue(): void
{
$this->assertExceptionForPatchAction(User::ROLE_ADMIN, '/api/customers/1/meta', ['name' => 'X'], [
'code' => 400,
'code' => Response::HTTP_BAD_REQUEST,
'message' => 'Bad Request'
]);
}
@@ -406,7 +406,7 @@ class CustomerControllerTest extends APIControllerBaseTestCase
public function testMetaActionThrowsExceptionOnMissingMetafield(): void
{
$this->assertExceptionForPatchAction(User::ROLE_ADMIN, '/api/customers/1/meta', ['name' => 'X', 'value' => 'Y'], [
'code' => 404,
'code' => Response::HTTP_NOT_FOUND,
'message' => 'Not Found'
]);
}

View File

@@ -566,7 +566,7 @@ class ProjectControllerTest extends APIControllerBaseTestCase
public function testMetaActionThrowsExceptionOnMissingName(): void
{
$this->assertExceptionForPatchAction(User::ROLE_ADMIN, '/api/projects/1/meta', ['value' => 'X'], [
'code' => 400,
'code' => Response::HTTP_BAD_REQUEST,
'message' => 'Bad Request'
]);
}
@@ -574,7 +574,7 @@ class ProjectControllerTest extends APIControllerBaseTestCase
public function testMetaActionThrowsExceptionOnMissingValue(): void
{
$this->assertExceptionForPatchAction(User::ROLE_ADMIN, '/api/projects/1/meta', ['name' => 'X'], [
'code' => 400,
'code' => Response::HTTP_BAD_REQUEST,
'message' => 'Bad Request'
]);
}
@@ -582,7 +582,7 @@ class ProjectControllerTest extends APIControllerBaseTestCase
public function testMetaActionThrowsExceptionOnMissingMetafield(): void
{
$this->assertExceptionForPatchAction(User::ROLE_ADMIN, '/api/projects/1/meta', ['name' => 'X', 'value' => 'Y'], [
'code' => 404,
'code' => Response::HTTP_NOT_FOUND,
'message' => 'Not Found'
]);
}

View File

@@ -19,6 +19,7 @@ use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\ErrorHandler\Exception\FlattenException;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Validator\ConstraintViolationList;
use Symfony\Contracts\Translation\TranslatorInterface;
@@ -54,7 +55,7 @@ class ValidationFailedExceptionErrorHandlerTest extends TestCase
$validations = new ValidationFailedException($constraints, 'Uuups, that is broken');
$expected = [
'code' => '400',
'code' => Response::HTTP_BAD_REQUEST,
'message' => null,
'errors' => [
'children' => []
@@ -98,7 +99,7 @@ class ValidationFailedExceptionErrorHandlerTest extends TestCase
$context->setAttribute('exception', $validations);
$expected = [
'code' => '400',
'code' => Response::HTTP_BAD_REQUEST,
'message' => 'Uuups, that is broken',
'errors' => [
'children' => [

View File

@@ -263,7 +263,7 @@ class TimesheetControllerTest extends APIControllerBaseTestCase
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
$this->importFixtureForUser(User::ROLE_USER);
$this->request($client, '/api/timesheets', 'GET', $query);
$this->assertApiException($client->getResponse(), ['code' => 404, 'message' => 'Not Found']);
$this->assertApiException($client->getResponse(), ['code' => Response::HTTP_NOT_FOUND, 'message' => 'Not Found']);
}
public function testGetCollectionWithSingleParamsQuery(): void
@@ -1434,7 +1434,7 @@ class TimesheetControllerTest extends APIControllerBaseTestCase
$id = $timesheets[0]->getId();
$this->assertExceptionForMethod($client, '/api/timesheets/' . $id . '/meta', 'PATCH', ['value' => 'X'], [
'code' => 400,
'code' => Response::HTTP_BAD_REQUEST,
'message' => 'Bad Request'
]);
}
@@ -1446,7 +1446,7 @@ class TimesheetControllerTest extends APIControllerBaseTestCase
$id = $timesheets[0]->getId();
$this->assertExceptionForMethod($client, '/api/timesheets/' . $id . '/meta', 'PATCH', ['name' => 'X'], [
'code' => 404,
'code' => Response::HTTP_NOT_FOUND,
'message' => 'Not Found'
]);
}
@@ -1458,7 +1458,7 @@ class TimesheetControllerTest extends APIControllerBaseTestCase
$id = $timesheets[0]->getId();
$this->assertExceptionForMethod($client, '/api/timesheets/' . $id . '/meta', 'PATCH', ['name' => 'X', 'value' => 'Y'], [
'code' => 404,
'code' => Response::HTTP_NOT_FOUND,
'message' => 'Not Found'
]);
}

View File

@@ -10,8 +10,11 @@
namespace App\Tests\API;
use App\Entity\User;
use App\Tests\Mocks\PrepareUserEventSubscriberMock;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Group;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\HttpFoundation\Response;
#[Group('integration')]
class UserControllerTest extends APIControllerBaseTestCase
@@ -178,7 +181,7 @@ class UserControllerTest extends APIControllerBaseTestCase
'ROLE_ADMIN'
],
];
$this->request($client, '/api/users', 'POST', [], json_encode($data));
$this->request($client, '/api/users', 'POST', [], (string) json_encode($data));
self::assertTrue($client->getResponse()->isSuccessful());
$content = $client->getResponse()->getContent();
@@ -211,7 +214,7 @@ class UserControllerTest extends APIControllerBaseTestCase
'ROLE_ADMIN'
],
];
$this->request($client, '/api/users', 'POST', [], json_encode($data));
$this->request($client, '/api/users', 'POST', [], (string) json_encode($data));
$response = $client->getResponse();
self::assertEquals(400, $response->getStatusCode());
@@ -231,7 +234,7 @@ class UserControllerTest extends APIControllerBaseTestCase
'ABC',
],
];
$this->request($client, '/api/users', 'POST', [], json_encode($data));
$this->request($client, '/api/users', 'POST', [], (string) json_encode($data));
$response = $client->getResponse();
self::assertEquals(400, $response->getStatusCode());
@@ -249,7 +252,7 @@ class UserControllerTest extends APIControllerBaseTestCase
'language' => 'ru',
'timezone' => 'Europe/Paris',
];
$this->request($client, '/api/users', 'POST', [], json_encode($data));
$this->request($client, '/api/users', 'POST', [], (string) json_encode($data));
$response = $client->getResponse();
$this->assertApiResponseAccessDenied($response, 'Access denied.');
}
@@ -269,11 +272,12 @@ class UserControllerTest extends APIControllerBaseTestCase
'ROLE_ADMIN'
],
];
$this->request($client, '/api/users', 'POST', [], json_encode($data));
$this->request($client, '/api/users', 'POST', [], (string) json_encode($data));
self::assertTrue($client->getResponse()->isSuccessful());
$content = $client->getResponse()->getContent();
self::assertIsString($content);
$result = json_decode($content, true);
self::assertIsArray($result);
self::assertFalse($result['enabled']);
$data = [
@@ -287,7 +291,7 @@ class UserControllerTest extends APIControllerBaseTestCase
];
$id = $result['id'];
self::assertIsNumeric($id);
$this->request($client, '/api/users/' . $id, 'PATCH', [], json_encode($data));
$this->request($client, '/api/users/' . $id, 'PATCH', [], (string) json_encode($data));
self::assertTrue($client->getResponse()->isSuccessful());
$content = $client->getResponse()->getContent();
@@ -312,7 +316,7 @@ class UserControllerTest extends APIControllerBaseTestCase
public function testPatchActionWithInvalidUser(): void
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
$this->request($client, '/api/users/1', 'PATCH', [], json_encode(['language' => 'hu']));
$this->request($client, '/api/users/1', 'PATCH', [], (string) json_encode(['language' => 'hu']));
$this->assertApiResponseAccessDenied($client->getResponse(), 'Not allowed to edit user');
}
@@ -330,10 +334,73 @@ class UserControllerTest extends APIControllerBaseTestCase
'ABC',
],
];
$this->request($client, '/api/users/1', 'PATCH', [], json_encode($data));
$this->request($client, '/api/users/1', 'PATCH', [], (string) json_encode($data));
$response = $client->getResponse();
self::assertEquals(400, $response->getStatusCode());
$this->assertApiCallValidationError($response, ['email', 'language', 'timezone', 'roles'], true);
}
// ------------------------------------- [USER PREFERENCES] -------------------------------------
public function testUpdateUserPreferenceThrowsNotFound(): void
{
$this->assertEntityNotFoundForPatch(User::ROLE_ADMIN, '/api/users/42/preferences', []);
}
public function testUpdateUserPreferenceThrowsExceptionOnWrongStructure(): void
{
$this->assertExceptionForPatchAction(User::ROLE_SUPER_ADMIN, '/api/users/1/preferences', ['name' => 'X', 'value' => 'X'], [
'code' => Response::HTTP_BAD_REQUEST,
'message' => 'Bad Request'
]);
}
public function testUpdateUserPreferenceThrowsExceptionOnMissingName(): void
{
$this->assertExceptionForPatchAction(User::ROLE_SUPER_ADMIN, '/api/users/1/preferences', [['value' => 'X']], [
'code' => Response::HTTP_BAD_REQUEST,
'message' => 'Bad Request'
]);
}
public function testUpdateUserPreferenceThrowsExceptionOnMissingValue(): void
{
$this->assertExceptionForPatchAction(User::ROLE_SUPER_ADMIN, '/api/users/1/preferences', [['name' => 'X']], [
'code' => Response::HTTP_BAD_REQUEST,
'message' => 'Bad Request'
]);
}
public function testUpdateUserPreferenceThrowsExceptionOnMissingMetafield(): void
{
$this->assertExceptionForPatchAction(User::ROLE_SUPER_ADMIN, '/api/users/1/preferences', [['name' => 'X', 'value' => 'Y']], [
'code' => Response::HTTP_NOT_FOUND,
'message' => 'Not Found'
]);
}
public function testUpdateUserPreference(): void
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_SUPER_ADMIN);
/** @var EventDispatcher $dispatcher */
$dispatcher = static::getContainer()->get('event_dispatcher');
$dispatcher->addSubscriber(new PrepareUserEventSubscriberMock());
$data = [
[
'name' => 'metatestmock',
'value' => 'another,testing,bar'
]
];
$this->request($client, '/api/users/1/preferences', 'PATCH', [], (string) json_encode($data));
self::assertTrue($client->getResponse()->isSuccessful());
$em = $this->getEntityManager();
/** @var User $user */
$user = $em->getRepository(User::class)->find(1);
self::assertEquals('another,testing,bar', $user->getPreferenceValue('metatestmock'));
self::assertEquals('another,testing,bar', $user->getPreferenceValue('metatestmock'));
}
}