Skip to content

Commit

Permalink
Force checking protected endpoints in all tests, protected endpoints …
Browse files Browse the repository at this point in the history
…are also checked with no scopes, deletion tests have been improved with a failing delete to make sure the entity is really not deleted, and client is always created on the fly to prevent Response caching issues
  • Loading branch information
jolelievre committed Feb 22, 2024
1 parent acbeeea commit 44a9649
Show file tree
Hide file tree
Showing 7 changed files with 161 additions and 118 deletions.
98 changes: 54 additions & 44 deletions tests/Integration/ApiPlatform/ApiClientEndpointTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,25 +36,6 @@ public static function setUpBeforeClass(): void
self::createApiClient(['api_client_write', 'api_client_read']);
}

/**
* @dataProvider getProtectedEndpoints
*
* @param string $method
* @param string $uri
*/
public function testProtectedEndpoints(string $method, string $uri, string $contentType = 'application/json'): void
{
$options['headers']['content-type'] = $contentType;
// Check that endpoints are not accessible without a proper Bearer token
$client = static::createClient([], $options);
$response = $client->request($method, $uri);
self::assertResponseStatusCodeSame(401);

$content = $response->getContent(false);
$this->assertNotEmpty($content);
$this->assertEquals('No Authorization header provided', $content);
}

public function getProtectedEndpoints(): iterable
{
yield 'get endpoint' => [
Expand All @@ -68,22 +49,20 @@ public function getProtectedEndpoints(): iterable
];

yield 'update endpoint' => [
'PUT',
'PATCH',
'/api/api-client/1',
];

yield 'delete endpoint' => [
'DELETE',
'/api/api-client/1',
'application/merge-patch+json',
];
}

public function testAddApiClient(): int
{
$bearerToken = $this->getBearerToken(['api_client_write']);
$client = static::createClient();
$response = $client->request('POST', '/api/api-client', [
$response = static::createClient()->request('POST', '/api/api-client', [
'auth_bearer' => $bearerToken,
'json' => [
'clientId' => 'client_id_test',
Expand Down Expand Up @@ -125,8 +104,7 @@ public function testAddApiClient(): int
public function testGetApiClient(int $apiClientId): int
{
$bearerToken = $this->getBearerToken(['api_client_read']);
$client = static::createClient();
$response = $client->request('GET', '/api/api-client/' . $apiClientId, [
$response = static::createClient()->request('GET', '/api/api-client/' . $apiClientId, [
'auth_bearer' => $bearerToken,
]);
self::assertResponseStatusCodeSame(200);
Expand Down Expand Up @@ -162,10 +140,9 @@ public function testGetApiClient(int $apiClientId): int
public function testUpdateApiClient(int $apiClientId): int
{
$bearerToken = $this->getBearerToken(['api_client_write']);
$client = static::createClient();

// Update product with partial data, even multilang fields can be updated language by language
$response = $client->request('PUT', '/api/api-client/' . $apiClientId, [
// Update API client
$response = static::createClient()->request('PATCH', '/api/api-client/' . $apiClientId, [
'auth_bearer' => $bearerToken,
'json' => [
'clientId' => 'client_id_test_updated',
Expand Down Expand Up @@ -200,6 +177,35 @@ public function testUpdateApiClient(int $apiClientId): int
$decodedResponse
);

// Update partially API client
$response = static::createClient()->request('PATCH', '/api/api-client/' . $apiClientId, [
'auth_bearer' => $bearerToken,
'json' => [
'description' => 'Client description test partially updated',
'lifetime' => 900,
],
]);
self::assertResponseStatusCodeSame(200);

$decodedResponse = json_decode($response->getContent(), true);
$this->assertNotFalse($decodedResponse);
// Returned data has modified fields, the others haven't changed
$this->assertEquals(
[
'apiClientId' => $apiClientId,
'clientId' => 'client_id_test_updated',
'clientName' => 'Client name test updated',
'description' => 'Client description test partially updated',
'enabled' => false,
'lifetime' => 900,
'scopes' => [
'api_client_write',
'hook_read',
],
],
$decodedResponse
);

return $apiClientId;
}

Expand All @@ -213,8 +219,7 @@ public function testUpdateApiClient(int $apiClientId): int
public function testGetUpdatedApiClient(int $apiClientId): int
{
$bearerToken = $this->getBearerToken(['api_client_read']);
$client = static::createClient();
$response = $client->request('GET', '/api/api-client/' . $apiClientId, [
$response = static::createClient()->request('GET', '/api/api-client/' . $apiClientId, [
'auth_bearer' => $bearerToken,
]);
self::assertResponseStatusCodeSame(200);
Expand All @@ -226,9 +231,9 @@ public function testGetUpdatedApiClient(int $apiClientId): int
'apiClientId' => $apiClientId,
'clientId' => 'client_id_test_updated',
'clientName' => 'Client name test updated',
'description' => 'Client description test updated',
'description' => 'Client description test partially updated',
'enabled' => false,
'lifetime' => 1800,
'lifetime' => 900,
'scopes' => [
'api_client_write',
'hook_read',
Expand All @@ -247,34 +252,39 @@ public function testGetUpdatedApiClient(int $apiClientId): int
*/
public function testDeleteApiClient(int $apiClientId): void
{
$bearerToken = $this->getBearerToken(['api_client_read', 'api_client_write']);
$client = static::createClient();

// Delete API client without token
$client->request('DELETE', '/api/api-client/' . $apiClientId);
static::createClient()->request('DELETE', '/api/api-client/' . $apiClientId);
self::assertResponseStatusCodeSame(401);
// Delete API client without token
$client->request('DELETE', '/api/api-client/' . $apiClientId, [
static::createClient()->request('DELETE', '/api/api-client/' . $apiClientId, [
'auth_bearer' => 'toto',
]);
self::assertResponseStatusCodeSame(401);

// Try to delete with a token with only read scope
$readBearerToken = $this->getBearerToken(['api_client_read']);
$response = static::createClient()->request('DELETE', '/api/api-client/' . $apiClientId, [
'auth_bearer' => $readBearerToken,
]);
$this->assertEquals(403, $response->getStatusCode());
self::assertResponseStatusCodeSame(403);

// Check that API client was not deleted
$client->request('GET', '/api/api-client/' . $apiClientId, [
'auth_bearer' => $bearerToken,
static::createClient()->request('GET', '/api/api-client/' . $apiClientId, [
'auth_bearer' => $readBearerToken,
]);
self::assertResponseStatusCodeSame(200);

// Delete API client with valid token
$response = $client->request('DELETE', '/api/api-client/' . $apiClientId, [
'auth_bearer' => $bearerToken,
$writeBearerToken = $this->getBearerToken(['api_client_write']);
$response = static::createClient()->request('DELETE', '/api/api-client/' . $apiClientId, [
'auth_bearer' => $writeBearerToken,
]);
self::assertResponseStatusCodeSame(204);
$this->assertEmpty($response->getContent());

$client = static::createClient();
$client->request('GET', '/api/api-client/' . $apiClientId, [
'auth_bearer' => $bearerToken,
static::createClient()->request('GET', '/api/api-client/' . $apiClientId, [
'auth_bearer' => $readBearerToken,
]);
self::assertResponseStatusCodeSame(404);
}
Expand Down
53 changes: 47 additions & 6 deletions tests/Integration/ApiPlatform/ApiTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,50 @@ public static function tearDownAfterClass(): void
self::$clientSecret = null;
}

/**
* @dataProvider getProtectedEndpoints
*
* @param string $method
* @param string $uri
*/
public function testProtectedEndpoints(string $method, string $uri, string $contentType = 'application/json'): void
{
$options['headers']['content-type'] = $contentType;
// Check that endpoints are not accessible without a proper Bearer token
$response = static::createClient([], $options)->request($method, $uri);
self::assertResponseStatusCodeSame(401);

$content = $response->getContent(false);
$this->assertNotEmpty($content);
$this->assertEquals('No Authorization header provided', $content);

// Test same endpoint with a token but without scopes
$emptyBearerToken = $this->getBearerToken();
static::createClient([], $options)->request($method, $uri, ['auth_bearer' => $emptyBearerToken]);
self::assertResponseStatusCodeSame(403);
}

/**
* You must provide a list of protected endpoints that will we automatically checked,
* the test will check that the endpoints are not accessible when no token is specified
* AND that they are not accessible when the no particular scope is specified.
*
* You should use yield return like this:
*
* yield 'get endpoint' => [
* 'GET',
* '/api/product/1',
* ];
*
* Since all Api Platform resources should likely have some protected endpoints this provider
* method was made abstract to force its implementation. In the unlikely event you need to use
* this class on a resouce with absolutely no protected endpoints you can still implement this
* method and return new \EmptyIterator();
*
* @return iterable
*/
abstract public function getProtectedEndpoints(): iterable;

protected static function createClient(array $kernelOptions = [], array $defaultOptions = []): Client
{
if (!isset($defaultOptions['headers']['accept'])) {
Expand All @@ -74,7 +118,6 @@ protected function getBearerToken(array $scopes = []): string
if (null === self::$clientSecret) {
self::createApiClient($scopes);
}
$client = static::createClient();
$parameters = ['parameters' => [
'client_id' => static::CLIENT_ID,
'client_secret' => static::$clientSecret,
Expand All @@ -87,14 +130,13 @@ protected function getBearerToken(array $scopes = []): string
'content-type' => 'application/x-www-form-urlencoded',
],
];
$response = $client->request('POST', '/api/oauth2/token', $options);
$response = static::createClient()->request('POST', '/api/oauth2/token', $options);

return json_decode($response->getContent())->access_token;
}

protected static function createApiClient(array $scopes = [], int $lifetime = 10000): void
{
$client = static::createClient();
$command = new AddApiClientCommand(
static::CLIENT_NAME,
static::CLIENT_ID,
Expand All @@ -104,7 +146,7 @@ protected static function createApiClient(array $scopes = [], int $lifetime = 10
$scopes
);

$container = $client->getContainer();
$container = static::createClient()->getContainer();
$commandBus = $container->get('prestashop.core.command_bus');
$createdApiClient = $commandBus->handle($command);

Expand All @@ -113,7 +155,6 @@ protected static function createApiClient(array $scopes = [], int $lifetime = 10

protected static function addLanguageByLocale(string $locale): int
{
$client = static::createClient();
$isoCode = substr($locale, 0, strpos($locale, '-'));

// Copy resource assets into tmp folder to mimic an upload file path
Expand All @@ -140,7 +181,7 @@ protected static function addLanguageByLocale(string $locale): int
[1]
);

$container = $client->getContainer();
$container = static::createClient()->getContainer();
$commandBus = $container->get('prestashop.core.command_bus');

return $commandBus->handle($command)->getValue();
Expand Down
32 changes: 5 additions & 27 deletions tests/Integration/ApiPlatform/CustomerGroupApiTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,23 +47,6 @@ public static function tearDownAfterClass(): void
DatabaseDump::restoreTables(['group', 'group_lang', 'group_reduction', 'group_shop', 'category_group']);
}

/**
* @dataProvider getProtectedEndpoints
*
* @param string $method
* @param string $uri
*/
public function testProtectedEndpoints(string $method, string $uri): void
{
$client = static::createClient();
$response = $client->request($method, $uri);
self::assertResponseStatusCodeSame(401);

$content = $response->getContent(false);
$this->assertNotEmpty($content);
$this->assertEquals('No Authorization header provided', $content);
}

public function getProtectedEndpoints(): iterable
{
yield 'get endpoint' => [
Expand Down Expand Up @@ -92,8 +75,7 @@ public function testAddCustomerGroup(): int
$numberOfGroups = count(\Group::getGroups(\Context::getContext()->language->id));

$bearerToken = $this->getBearerToken(['customer_group_write']);
$client = static::createClient();
$response = $client->request('POST', '/api/customers/group', [
$response = static::createClient()->request('POST', '/api/customers/group', [
'auth_bearer' => $bearerToken,
'json' => [
'localizedNames' => [
Expand Down Expand Up @@ -141,9 +123,8 @@ public function testUpdateCustomerGroup(int $customerGroupId): int
$numberOfGroups = count(\Group::getGroups(\Context::getContext()->language->id));

$bearerToken = $this->getBearerToken(['customer_group_write']);
$client = static::createClient();
// Update customer group with partial data
$response = $client->request('PUT', '/api/customers/group/' . $customerGroupId, [
$response = static::createClient()->request('PUT', '/api/customers/group/' . $customerGroupId, [
'auth_bearer' => $bearerToken,
'json' => [
'localizedNames' => [
Expand Down Expand Up @@ -187,8 +168,7 @@ public function testUpdateCustomerGroup(int $customerGroupId): int
public function testGetCustomerGroup(int $customerGroupId): int
{
$bearerToken = $this->getBearerToken(['customer_group_read']);
$client = static::createClient();
$response = $client->request('GET', '/api/customers/group/' . $customerGroupId, [
$response = static::createClient()->request('GET', '/api/customers/group/' . $customerGroupId, [
'auth_bearer' => $bearerToken,
]);
self::assertResponseStatusCodeSame(200);
Expand Down Expand Up @@ -223,16 +203,14 @@ public function testGetCustomerGroup(int $customerGroupId): int
public function testDeleteCustomerGroup(int $customerGroupId): void
{
$bearerToken = $this->getBearerToken(['customer_group_read', 'customer_group_write']);
$client = static::createClient();
// Update customer group with partial data
$response = $client->request('DELETE', '/api/customers/group/' . $customerGroupId, [
$response = static::createClient()->request('DELETE', '/api/customers/group/' . $customerGroupId, [
'auth_bearer' => $bearerToken,
]);
self::assertResponseStatusCodeSame(204);
$this->assertEmpty($response->getContent());

$client = static::createClient();
$client->request('GET', '/api/customers/group/' . $customerGroupId, [
static::createClient()->request('GET', '/api/customers/group/' . $customerGroupId, [
'auth_bearer' => $bearerToken,
]);
self::assertResponseStatusCodeSame(404);
Expand Down
13 changes: 13 additions & 0 deletions tests/Integration/ApiPlatform/GetHookStatusTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,19 @@ public static function tearDownAfterClass(): void
DatabaseDump::restoreTables(['hook']);
}

public function getProtectedEndpoints(): iterable
{
yield 'get endpoint' => [
'GET',
'/api/hook-status/1',
];

yield 'put endpoint' => [
'PUT',
'/api/hook-status',
];
}

public function testGetHookStatus(): void
{
$inactiveHook = new \Hook();
Expand Down
8 changes: 8 additions & 0 deletions tests/Integration/ApiPlatform/GetHookTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@ public static function tearDownAfterClass(): void
DatabaseDump::restoreTables(['hook']);
}

public function getProtectedEndpoints(): iterable
{
yield 'get endpoint' => [
'GET',
'/api/hooks/1',
];
}

public function testGetHook(): void
{
$hook = new \Hook();
Expand Down

0 comments on commit 44a9649

Please sign in to comment.