Skip to content

Commit

Permalink
[BUGFIX] Correct responses
Browse files Browse the repository at this point in the history
  • Loading branch information
benjaminkott committed Dec 2, 2024
1 parent 625f44b commit b172f82
Show file tree
Hide file tree
Showing 10 changed files with 267 additions and 183 deletions.
319 changes: 161 additions & 158 deletions composer.lock

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions config/packages/framework.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ framework:
php_errors:
log: true

serializer:
name_converter: 'serializer.name_converter.camel_case_to_snake_case'
default_context:
skip_null_values: true

when@test:
framework:
test: true
Expand Down
5 changes: 5 additions & 0 deletions config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,8 @@ services:
App\Service\CacheWarmupService:
arguments:
$baseUrl: '%env(BASE_URL)%'

serializer.normalizer.json_serializable:
class: Symfony\Component\Serializer\Normalizer\JsonSerializableNormalizer
tags:
- { name: 'serializer.normalizer', priority: -2048 }
15 changes: 8 additions & 7 deletions src/Entity/MajorVersion.php
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,13 @@ public function setReleases(Collection $releases): void
*/
public function getReleases(): Collection
{
return $this->releases;
$sorted = $this->releases->toArray();
usort($sorted, function ($a, $b) {
return version_compare($a->getVersion(), $b->getVersion());
});
$sorted = array_reverse($sorted);

return new ArrayCollection($sorted);
}

public function setTitle(string $title): void
Expand Down Expand Up @@ -376,14 +382,9 @@ public function jsonSerialize(): array
$releaseData[$release->getVersion()] = $release;
}

uksort(
$releaseData,
static fn(string $a, string $b): int => version_compare($a, $b)
);
$desc = array_reverse($releaseData);
$latest = $this->getLatestRelease();
return [
'releases' => $desc,
'releases' => $releaseData,
'latest' => $latest !== null ? $latest->getVersion() : '',
'stable' => $latest !== null ? $latest->getVersion() : '',
'active' => $this->isActive(),
Expand Down
51 changes: 46 additions & 5 deletions src/Entity/Release.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@
use App\Repository\ReleaseRepository;
use Doctrine\ORM\Mapping as ORM;
use OpenApi\Attributes as OA;
use Symfony\Component\Serializer\Attribute\Context;
use Symfony\Component\Serializer\Attribute\Groups;
use Symfony\Component\Serializer\Attribute\SerializedName;
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
use Symfony\Component\Validator\Constraints as Assert;

#[OA\Schema(description: 'TYPO3 release', title: 'Release')]
Expand All @@ -39,17 +42,16 @@
class Release implements \JsonSerializable, \Stringable
{
#[OA\Property(example: '8.7.12')]
#[Assert\Regex(
'/^(\d+\.\d+\.\d+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/'
)]
#[Assert\Regex('/^(\d+\.\d+\.\d+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/')]
#[ORM\Id]
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::STRING)]
#[Groups(['content', 'data'])]
private string $version;

#[OA\Property(example: '2017-12-12T16:48:22+00:00')]
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::DATETIME_MUTABLE)]
#[Groups(['data', 'content'])]
#[Groups(['content', 'data'])]
#[Context([DateTimeNormalizer::FORMAT_KEY => 'Y-m-d\\TH:i:sP'])]
private \DateTimeInterface $date;

#[Assert\Choice(callback: [ReleaseTypeEnum::class, 'getAvailableOptions'])]
Expand All @@ -60,7 +62,7 @@ class Release implements \JsonSerializable, \Stringable
#[OA\Property(example: true)]
#[Assert\Type('boolean')]
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::BOOLEAN, options: ['default' => 0])]
#[Groups(['data', 'content'])]
#[Groups(['content', 'data'])]
private bool $elts = false;

#[Assert\Valid]
Expand Down Expand Up @@ -171,6 +173,45 @@ public function isElts(): bool
return $this->elts;
}

/**
* @return array{
* zip: Package,
* tar: Package,
* }
*/
#[SerializedName('checksums')]
#[Groups(['content'])]
public function getChecksums(): array
{
return [
'zip' => $this->zipPackage,
'tar' => $this->tarPackage,
];
}

/**
* @return array{
* zip: string,
* tar: string,
* }
*/
#[SerializedName('url')]
#[Groups(['content'])]
public function getUrls(): array
{
$baseUrl = $_SERVER['BASE_URL'] ?? $_ENV['BASE_URL'] ?? '';
if (!is_string($baseUrl)) {
$baseUrl = '';
}

return [
// todo: try to inject the env var see
// https://symfony.com/doc/current/configuration.html#accessing-configuration-parameters
'zip' => $baseUrl . '/' . $this->version . '/zip',
'tar' => $baseUrl . '/' . $this->version,
];
}

/**
* @return array{
* version: string,
Expand Down
45 changes: 32 additions & 13 deletions tests/Functional/Controller/Api/ApiCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -99,76 +99,95 @@ protected function createRequirementFromJson(string $filePath, string $majorVers
return $this->client->getResponse();
}

protected function assertArrayStructure(array $expectedStructure, $actualArray)
/**
* @param array<int|string, mixed> $expectedStructure
* @param array<int|string, mixed> $actualArray
*/
protected function assertArrayStructure(array $expectedStructure, array $actualArray): void
{
// If the expected structure is an array (root level as list)
if (isset($expectedStructure[0])) {
self::assertIsArray($actualArray);

// Validate each item in the list
foreach ($actualArray as $item) {
self::assertIsArray($item);
/** @var array<int|string, array<int|string, mixed>> $expectedStructure */
/** @var array<int|string, mixed> $item */
$this->assertArrayStructure($expectedStructure[0], $item);
}
} else {
// Validate each key in the structure
foreach ($expectedStructure as $key => $value) {
$isOptional = str_starts_with($key, '?');
$isOptional = is_string($key) && str_starts_with($key, '?');
$actualKey = $isOptional ? ltrim($key, '?') : $key;

if (array_key_exists($actualKey, $actualArray)) {
// If the key exists, validate its structure or type
if (is_array($value)) {
if ($this->isListStructure($value)) {
// Validate a list of items
self::assertIsArray($actualArray[$actualKey]);
foreach ($actualArray[$actualKey] as $item) {
/** @var array<int|string, array<int|string, mixed>> $value */
/** @var array<int|string, mixed> $item */
$this->assertArrayStructure($value[0], $item);
}
} else {
// Validate a single nested structure
/** @var array<int|string, mixed> $value */
/** @var array<int|string, array<int|string, mixed>> $actualArray */
$this->assertArrayStructure($value, $actualArray[$actualKey]);
}
} else {
/** @var string $value */
$this->assertIsType($value, $actualArray[$actualKey], "Key '$actualKey' does not match the expected type.");
}
} elseif (!$isOptional) {
// If the key is not optional, it must exist
$this->fail("Missing required key: $actualKey");
self::fail("Missing required key: $actualKey");
}
}
}
}

protected function assertIsType(string $type, $value, string $message)
protected function assertIsType(string $type, mixed $value, string $message): void
{
switch ($type) {
case 'string':
$this->assertIsString($value, $message);
self::assertIsString($value, $message);
break;
case 'boolean':
$this->assertIsBool($value, $message);
self::assertIsBool($value, $message);
break;
case 'integer':
$this->assertIsInt($value, $message);
self::assertIsInt($value, $message);
break;
case 'float':
$this->assertIsFloat($value, $message); // New check for float
self::assertIsFloat($value, $message); // New check for float
break;
case 'array':
$this->assertIsArray($value, $message);
self::assertIsArray($value, $message);
break;
case 'datetime':
$this->assertValidDateTime($value, $message);
self::assertIsString($value, $message);
self::assertValidDateTime($value, $message);
break;
default:
$this->fail("Unsupported type: $type");
self::fail("Unsupported type: $type");
}
}

protected function assertValidDateTime(string $value, string $message)
protected function assertValidDateTime(string $value, string $message): void
{
// ISO 8601 datetime regex
$pattern = '/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(\+\d{2}:\d{2}|Z)$/';
$this->assertMatchesRegularExpression($pattern, $value, $message);
self::assertMatchesRegularExpression($pattern, $value, $message);
}

/**
* @param array<int|string, mixed> $structure
*/
protected function isListStructure(array $structure): bool
{
// Determines if the given structure is a list of items (e.g., [ { ... } ])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public function getReleasesByMajorVersionStructureTest(): void
$response = $this->client->getResponse();
$responseContent = json_decode((string)$response->getContent(), true, 512, JSON_THROW_ON_ERROR);

self::assertIsArray($responseContent);
$this->assertArrayStructure(
[
[
Expand Down Expand Up @@ -83,6 +84,7 @@ public function getLatestReleaseByMajorVersionStructureTest(): void
$response = $this->client->getResponse();
$responseContent = json_decode((string)$response->getContent(), true, 512, JSON_THROW_ON_ERROR);

self::assertIsArray($responseContent);
$this->assertArrayStructure(
[
'version' => 'string',
Expand Down Expand Up @@ -119,6 +121,7 @@ public function getLatestSecurityReleaseByMajorVersionStructureTest(): void
$response = $this->client->getResponse();
$responseContent = json_decode((string)$response->getContent(), true, 512, JSON_THROW_ON_ERROR);

self::assertIsArray($responseContent);
$this->assertArrayStructure(
[
'version' => 'string',
Expand Down Expand Up @@ -155,6 +158,7 @@ public function getLatestReleaseContentByMajorVersionStructureTest(): void
$response = $this->client->getResponse();
$responseContent = json_decode((string)$response->getContent(), true, 512, JSON_THROW_ON_ERROR);

self::assertIsArray($responseContent);
$this->assertArrayStructure(
[
'version' => 'string',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ public function getRequirementsByMajorVersionStructureTest(): void
$response = $this->client->getResponse();
$responseContent = json_decode((string)$response->getContent(), true, 512, JSON_THROW_ON_ERROR);

self::assertIsArray($responseContent);
$this->assertArrayStructure(
[
[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ public function getMajorReleasesStructureTest(): void
$response = $this->client->getResponse();
$responseContent = json_decode((string)$response->getContent(), true, 512, JSON_THROW_ON_ERROR);

self::assertIsArray($responseContent);
$this->assertArrayStructure(
[
[
Expand Down Expand Up @@ -108,6 +109,7 @@ public function getMajorReleaseWithVersionStructureTest(): void
$response = $this->client->getResponse();
$responseContent = json_decode((string)$response->getContent(), true, 512, JSON_THROW_ON_ERROR);

self::assertIsArray($responseContent);
$this->assertArrayStructure(
[
'version' => 'float',
Expand Down
3 changes: 3 additions & 0 deletions tests/Functional/Controller/Api/ReleaseControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ public function getReleaseStructureTest(): void
$response = $this->client->getResponse();
$responseContent = json_decode((string)$response->getContent(), true, 512, JSON_THROW_ON_ERROR);

self::assertIsArray($responseContent);
$this->assertArrayStructure(
[
[
Expand Down Expand Up @@ -115,6 +116,7 @@ public function getReleaseWithVersionStructureTest(): void
$response = $this->client->getResponse();
$responseContent = json_decode((string)$response->getContent(), true, 512, JSON_THROW_ON_ERROR);

self::assertIsArray($responseContent);
$this->assertArrayStructure(
[
'version' => 'string',
Expand Down Expand Up @@ -151,6 +153,7 @@ public function getContentForVersionStructureTest(): void
$response = $this->client->getResponse();
$responseContent = json_decode((string)$response->getContent(), true, 512, JSON_THROW_ON_ERROR);

self::assertIsArray($responseContent);
$this->assertArrayStructure(
[
'version' => 'string',
Expand Down

0 comments on commit b172f82

Please sign in to comment.