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