Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
parameters:
ignoreErrors:
-
message: '#^Call to static method PHPUnit\\Framework\\Assert\:\:assertIsString\(\) with string will always evaluate to true\.$#'
identifier: staticMethod.alreadyNarrowedType
count: 1
path: src/contracts/BaseRestWebTestCase.php

-
message: '#^Call to static method PHPUnit\\Framework\\Assert\:\:assertInstanceOf\(\) with ''Ibexa\\\\Contracts\\\\Test\\\\Rest\\\\Request\\\\Value\\\\EndpointRequestDefinition'' and Ibexa\\Contracts\\Test\\Rest\\Request\\Value\\EndpointRequestDefinition will always evaluate to true\.$#'
identifier: staticMethod.alreadyNarrowedType
Expand Down
4 changes: 4 additions & 0 deletions src/bundle/Resources/config/services/rest_schema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,9 @@ services:
$uriRetriever: '@ibexa.test.rest.json_schema.uri_retriever'
$schemas: !tagged_iterator ibexa.test.rest.schema_provider

Ibexa\Test\Rest\Schema\ErrorSchemaProvider:
tags:
- { name: ibexa.test.rest.schema_provider }

ibexa.test.rest.json_schema.uri_retriever:
class: JsonSchema\Uri\UriRetriever
27 changes: 10 additions & 17 deletions src/contracts/BaseRestWebTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,19 +45,13 @@ public function testEndpoint(EndpointRequestDefinition $endpointDefinition): voi
if (null === $expectedStatusCode) {
self::assertResponseIsSuccessful();
} else {
$actualStatusCode = $response->getStatusCode();
self::assertSame(
$actualStatusCode,
$expectedStatusCode,
"Expected HTTP $expectedStatusCode, got HTTP $actualStatusCode status code"
);
self::assertResponseStatusCodeSame($expectedStatusCode);
Copy link

Copilot AI Sep 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The refactoring from manual status code assertion to assertResponseStatusCodeSame() is good, but the custom error message providing context about expected vs actual status codes has been lost. Consider preserving the descriptive error message for better test failure diagnostics.

Suggested change
self::assertResponseStatusCodeSame($expectedStatusCode);
self::assertResponseStatusCodeSame(
$expectedStatusCode,
sprintf(
'Expected status code %d but got %d for endpoint [%s %s]',
$expectedStatusCode,
$this->client?->getResponse()?->getStatusCode(),
$endpointDefinition->getMethod(),
$endpointDefinition->getUri()
)
);

Copilot uses AI. Check for mistakes.
}

$content = (string)$response->getContent();
$this->assertResponseIsValid(
$content,
$endpointDefinition->getExpectedResourceType(),
$endpointDefinition->extractFormatFromAcceptHeader()
$endpointDefinition,
);

$snapshotName = $endpointDefinition->getSnapshotName();
Expand Down Expand Up @@ -112,15 +106,12 @@ protected function performRequest(EndpointRequestDefinition $endpointDefinition)
return $this->client->getResponse();
}

/**
* @phpstan-param 'xml'|'json' $format
*/
protected function assertResponseIsValid(
string $response,
?string $resourceType,
string $format
EndpointRequestDefinition $endpointDefinition
): void {
self::assertIsString($response);
$resourceType = $endpointDefinition->getExpectedResourceType();
$format = $endpointDefinition->extractFormatFromAcceptHeader();

if (null !== $resourceType) {
self::assertStringContainsString($resourceType, $response);
Expand All @@ -129,7 +120,8 @@ protected function assertResponseIsValid(
self::generateMediaTypeString($resourceType, $format)
);

$this->validateAgainstSchema($response, $resourceType, $format);
$schemaLocation = $endpointDefinition->getSchemaLocation() ?? $this->getSchemaFileBasePath($resourceType, $format);
$this->validateAgainstSchema($response, $resourceType, $format, $schemaLocation);
} else {
self::assertEmpty($response, "The response for '$format' format is not empty");
}
Expand Down Expand Up @@ -173,11 +165,12 @@ protected static function generateMediaTypeString(string $typeString, ?string $f
private function validateAgainstSchema(
string $response,
string $resourceType,
string $format
string $format,
string $schemaLocation
): void {
try {
$validator = $this->getSchemaValidator($format);
$validator->validate($response, $this->getSchemaFileBasePath($resourceType, $format));
$validator->validate($response, $schemaLocation);
} catch (\Throwable $e) {
self::fail(
sprintf(
Expand Down
19 changes: 18 additions & 1 deletion src/contracts/Request/Value/EndpointRequestDefinition.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ final class EndpointRequestDefinition implements Stringable

private ?string $name;

private ?string $schemaLocation = null;

/**
* Snapshot name or path relative to Snapshot directory defined by overriding
* \Ibexa\Contracts\Test\Rest\BaseRestWebTestCase::getSnapshotDirectory.
Expand All @@ -54,7 +56,8 @@ public function __construct(
?InputPayload $payload = null,
?string $name = null,
?string $snapshotName = null,
?int $expectedStatusCode = null
?int $expectedStatusCode = null,
?string $schemaLocation = null
) {
$this->method = $method;
$this->uri = $uri;
Expand All @@ -65,6 +68,7 @@ public function __construct(
$this->name = $name;
$this->snapshotName = $snapshotName;
$this->expectedStatusCode = $expectedStatusCode;
$this->schemaLocation = $schemaLocation;
}

public function getMethod(): string
Expand Down Expand Up @@ -131,6 +135,11 @@ public function getName(): ?string
return $this->name;
}

public function getSchemaLocation(): ?string
{
return $this->schemaLocation;
}

/**
* @return 'xml'|'json'
*/
Expand Down Expand Up @@ -183,6 +192,14 @@ public function withSnapshotName(?string $snapshotName): self
return $endpointDefinition;
}

public function withSchemaLocation(?string $schemaLocation): self
{
$endpointDefinition = clone $this;
$endpointDefinition->schemaLocation = $schemaLocation;

return $endpointDefinition;
}

public function withExpectedStatusCode(?int $expectedStatusCode): self
{
$endpointDefinition = clone $this;
Expand Down
47 changes: 47 additions & 0 deletions src/lib/Schema/ErrorSchemaProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

/**
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace Ibexa\Test\Rest\Schema;

use Ibexa\Contracts\Test\Rest\Schema\SchemaProviderInterface;

final class ErrorSchemaProvider implements SchemaProviderInterface
{
public function provideSchemas(): iterable
{
yield 'ibexa/rest/ErrorMessage' => (object)[
Copy link

Copilot AI Sep 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using an array instead of casting to object with (object). The schema structure is already defined as an associative array, and the object cast adds unnecessary complexity without clear benefit.

Suggested change
yield 'ibexa/rest/ErrorMessage' => (object)[
yield 'ibexa/rest/ErrorMessage' => [

Copilot uses AI. Check for mistakes.
'type' => 'object',
'properties' => [
'ErrorMessage' => [
'type' => 'object',
'properties' => [
'_media-type' => [
'type' => 'string',
],
'errorCode' => [
'type' => 'integer',
],
'errorMessage' => [
'type' => 'string',
],
'errorDescription' => [
'type' => 'string',
],
],
'required' => [
'_media-type',
'errorCode',
'errorMessage',
'errorDescription',
],
],
],
'required' => ['ErrorMessage'],
];
}
}
Loading