packages/requirements/tests/Unit/Config/JsonSchemaFileTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Config;
6
7use JsonException;
8use PHPUnit\Framework\Attributes\CoversClass;
9use PHPUnit\Framework\Attributes\DataProvider;
10use PHPUnit\Framework\Attributes\Small;
11use PHPUnit\Framework\TestCase;
12use Requirements\Config\JsonSchemaFile;
13use Requirements\Input\InvalidInputException;
14use Tests\Fake\ProjectDirectory;
15
16#[CoversClass(JsonSchemaFile::class)]
17#[Small]
18final class JsonSchemaFileTest extends TestCase
19{
20    /**
21     * @throws JsonException
22     */
23    #[DataProvider('providerAccepted')]
24    public function testValidateAcceptsConformingDocument(string $schema, mixed $data): void
25    {
26        $project = new ProjectDirectory();
27        (new JsonSchemaFile())->validate($data, $project->put('schema.json', $schema), 'doc.yaml');
28        $this->addToAssertionCount(1);
29    }
30
31    /**
32     * @return array<string, array{string, mixed}>
33     */
34    public static function providerAccepted(): array
35    {
36        return [
37            'object schema' => ['{"type":"object","required":["a"]}', (object) ['a' => 1]],
38            'empty object schema' => ['{}', 'anything'],
39            'true schema' => ['true', (object) ['a' => 1]],
40            'scalar document' => ['{"type":"integer"}', 3],
41        ];
42    }
43
44    /**
45     * @throws JsonException
46     */
47    #[DataProvider('providerRejected')]
48    public function testValidateRejectsDocumentBreakingSchema(string $schema, mixed $data, string $message): void
49    {
50        $project = new ProjectDirectory();
51        $file = $project->put('schema.json', $schema);
52        $this->expectException(InvalidInputException::class);
53        $this->expectExceptionMessage($message);
54        (new JsonSchemaFile())->validate($data, $file, 'doc.yaml');
55    }
56
57    /**
58     * @return array<string, array{string, mixed, string}>
59     */
60    public static function providerRejected(): array
61    {
62        return [
63            'missing property' => ['{"type":"object","required":["a"]}', (object) [], 'doc.yaml: schema validation failed: {"/":["The required properties (a) are missing"]}'],
64            'false schema' => ['false', (object) [], 'doc.yaml: schema validation failed: {"/":["Data not allowed"]}'],
65            'nested path with unescaped slashes' => ['{"properties":{"u":{"const":"a/b"}}}', (object) ['u' => 'x'], 'doc.yaml: schema validation failed: {"/u":["The data must match the const value"]}'],
66        ];
67    }
68
69    /**
70     * @throws JsonException
71     */
72    #[DataProvider('providerUnusableSchemas')]
73    public function testValidateRejectsSchemaThatIsNeitherObjectNorBoolean(string $schema): void
74    {
75        $project = new ProjectDirectory();
76        $file = $project->put('schema.json', $schema);
77        $this->expectException(InvalidInputException::class);
78        $this->expectExceptionMessage("doc.yaml: expected an object or Boolean schema in $file");
79        (new JsonSchemaFile())->validate((object) [], $file, 'doc.yaml');
80    }
81
82    /**
83     * @return array<string, array{string}>
84     */
85    public static function providerUnusableSchemas(): array
86    {
87        return [
88            'number' => ['1'],
89            'string' => ['"object"'],
90            'list' => ['[]'],
91            'null' => ['null'],
92        ];
93    }
94
95    /**
96     * @throws JsonException
97     */
98    public function testValidateRejectsMissingSchema(): void
99    {
100        $project = new ProjectDirectory();
101        $this->expectException(InvalidInputException::class);
102        $this->expectExceptionMessage('doc.yaml: cannot read schema ' . $project->path('missing.json'));
103        (new JsonSchemaFile())->validate((object) [], $project->path('missing.json'), 'doc.yaml');
104    }
105
106    /**
107     * @throws JsonException
108     */
109    public function testValidateRejectsDirectoryAsSchema(): void
110    {
111        $project = new ProjectDirectory();
112        $this->expectException(InvalidInputException::class);
113        $this->expectExceptionMessage('doc.yaml: cannot read schema ' . $project->directory);
114        (new JsonSchemaFile())->validate((object) [], $project->directory, 'doc.yaml');
115    }
116
117    /**
118     * @throws JsonException
119     */
120    public function testValidateRejectsSchemaThatIsNotJson(): void
121    {
122        $project = new ProjectDirectory();
123        $file = $project->put('schema.json', '{"type":');
124        $this->expectException(JsonException::class);
125        (new JsonSchemaFile())->validate((object) [], $file, 'doc.yaml');
126    }
127}
128