Skip to content

Commit 94b40e4

Browse files
cconard96cedric-anne
authored andcommitted
add hlapi schema snapshot test
1 parent e14e6cc commit 94b40e4

File tree

3 files changed

+176
-0
lines changed

3 files changed

+176
-0
lines changed

tests/fixtures/hlapi/snapshots/v2_0_0.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

tests/fixtures/hlapi/snapshots/v2_1_0.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

tests/functional/Glpi/Api/HL/OpenAPIGeneratorTest.php

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,4 +95,178 @@ public function testExpandedAttributesNoParameter()
9595
$this->assertEmpty(array_filter($openapi['paths'][$endpoint['path']]['get']['parameters'], static fn($v) => $v['name'] === $endpoint['placeholder']));
9696
}
9797
}
98+
99+
private function diffSchemaPaths($snapshot, $schema)
100+
{
101+
// Compare OpenAPI route paths and return differences
102+
$differences = [];
103+
// ignore "/Assets/Custom' paths
104+
$snapshot_paths = array_filter($snapshot['paths'] ?? [], static fn($p) => !str_starts_with($p, '/Assets/Custom'), ARRAY_FILTER_USE_KEY);
105+
$schema_paths = array_filter($schema['paths'] ?? [], static fn($p) => !str_starts_with($p, '/Assets/Custom'), ARRAY_FILTER_USE_KEY);
106+
$common_paths = array_intersect(array_keys($snapshot_paths), array_keys($schema_paths));
107+
108+
if (count($common_paths) < count($snapshot_paths) || count($common_paths) < count($schema_paths)) {
109+
$missing_in_schema = array_diff(array_keys($snapshot_paths), array_keys($schema_paths));
110+
$missing_in_snapshot = array_diff(array_keys($schema_paths), array_keys($snapshot_paths));
111+
if (!empty($missing_in_schema)) {
112+
$differences[] = 'Paths missing in schema: ' . implode(', ', $missing_in_schema);
113+
}
114+
if (!empty($missing_in_snapshot)) {
115+
$differences[] = 'Paths missing in snapshot: ' . implode(', ', $missing_in_snapshot);
116+
}
117+
}
118+
119+
foreach ($common_paths as $path) {
120+
foreach (['get', 'post', 'put', 'delete', 'patch'] as $method) {
121+
$snapshot_method = $snapshot_paths[$path][$method] ?? null;
122+
$schema_method = $schema_paths[$path][$method] ?? null;
123+
if ($snapshot_method === null && $schema_method === null) {
124+
continue;
125+
} elseif ($snapshot_method === null) {
126+
$differences[] = "Method '$method' for path '$path' is missing in the snapshot";
127+
continue;
128+
} elseif ($schema_method === null) {
129+
$differences[] = "Method '$method' for path '$path' is missing in the schema";
130+
continue;
131+
}
132+
unset($snapshot_method['description'], $schema_method['description'], $snapshot_method['tags'], $schema_method['tags']);
133+
if ($snapshot_method !== $schema_method) {
134+
$differences[] = "Method '$method' for path '$path' differs between snapshot and schema";
135+
}
136+
}
137+
}
138+
return $differences;
139+
}
140+
141+
private function diffSchemaProperties($snapshot_props, $schema_props, $parent_path = '')
142+
{
143+
$differences = [];
144+
$common_props = array_intersect(array_keys($snapshot_props), array_keys($schema_props));
145+
146+
if (count($common_props) < count($snapshot_props) || count($common_props) < count($schema_props)) {
147+
$missing_in_schema = array_diff(array_keys($snapshot_props), array_keys($schema_props));
148+
$missing_in_snapshot = array_diff(array_keys($schema_props), array_keys($snapshot_props));
149+
if (!empty($missing_in_schema)) {
150+
$differences[] = 'Properties missing in schema at ' . $parent_path . ': ' . implode(', ', $missing_in_schema);
151+
}
152+
if (!empty($missing_in_snapshot)) {
153+
$differences[] = 'Properties missing in snapshot at ' . $parent_path . ': ' . implode(', ', $missing_in_snapshot);
154+
}
155+
}
156+
157+
foreach ($common_props as $prop_name) {
158+
$snapshot_prop = $snapshot_props[$prop_name];
159+
$schema_prop = $schema_props[$prop_name];
160+
unset($snapshot_prop['description'], $schema_prop['description']);
161+
162+
// Recursively compare nested properties
163+
if (isset($snapshot_prop['properties'], $schema_prop['properties'])) {
164+
$nested_diffs = $this->diffSchemaProperties(
165+
$snapshot_prop['properties'],
166+
$schema_prop['properties'],
167+
$parent_path . $prop_name . '.'
168+
);
169+
$differences = array_merge($differences, $nested_diffs);
170+
} elseif (isset($snapshot_prop['items']['properties'], $schema_prop['items']['properties'])) {
171+
$nested_diffs = $this->diffSchemaProperties(
172+
$snapshot_prop['items']['properties'],
173+
$schema_prop['items']['properties'],
174+
$parent_path . $prop_name . '[]' . '.'
175+
);
176+
$differences = array_merge($differences, $nested_diffs);
177+
} elseif ($snapshot_prop != $schema_prop) {
178+
$differences[] = "Property '$parent_path$prop_name' differs between snapshot and schema";
179+
}
180+
}
181+
182+
return $differences;
183+
}
184+
185+
private function diffComponentSchemas($snapshot, $schema)
186+
{
187+
// Compare OpenAPI component schemas and return differences
188+
$differences = [];
189+
// Ignore custom assets
190+
$snapshot_schemas = array_filter($snapshot['components']['schemas'] ?? [], static fn($k) => !str_starts_with($k, 'Custom'), ARRAY_FILTER_USE_KEY);
191+
$schema_schemas = array_filter($schema['components']['schemas'] ?? [], static fn($k) => !str_starts_with($k, 'Custom'), ARRAY_FILTER_USE_KEY);
192+
$common_schemas = array_intersect(array_keys($snapshot_schemas), array_keys($schema_schemas));
193+
194+
if (count($common_schemas) < count($snapshot_schemas) || count($common_schemas) < count($schema_schemas)) {
195+
$missing_in_schema = array_diff(array_keys($snapshot_schemas), array_keys($schema_schemas));
196+
$missing_in_snapshot = array_diff(array_keys($schema_schemas), array_keys($snapshot_schemas));
197+
if (!empty($missing_in_schema)) {
198+
$differences[] = 'Component schemas missing in schema: ' . implode(', ', $missing_in_schema);
199+
}
200+
if (!empty($missing_in_snapshot)) {
201+
$differences[] = 'Component schemas missing in snapshot: ' . implode(', ', $missing_in_snapshot);
202+
}
203+
}
204+
205+
foreach ($common_schemas as $schema_name) {
206+
$snapshot_schema = $snapshot_schemas[$schema_name];
207+
$schema_schema = $schema_schemas[$schema_name];
208+
unset($snapshot_schema['description'], $schema_schema['description']);
209+
210+
// Compare properties recursively
211+
if (isset($snapshot_schema['properties'], $schema_schema['properties'])) {
212+
$prop_diffs = $this->diffSchemaProperties(
213+
$snapshot_schema['properties'],
214+
$schema_schema['properties'],
215+
$schema_name . '.'
216+
);
217+
$differences = array_merge($differences, $prop_diffs);
218+
}
219+
unset($snapshot_schema['properties'], $schema_schema['properties']);
220+
if ($snapshot_schema !== $schema_schema) {
221+
$differences[] = "Component schema '$schema_name' differs between snapshot and schema";
222+
}
223+
}
224+
return $differences;
225+
}
226+
227+
private function assertSchemaMatchesSnapshot(array $snapshot, array $schema)
228+
{
229+
$path_differences = $this->diffSchemaPaths($snapshot, $schema);
230+
$component_differences = $this->diffComponentSchemas($snapshot, $schema);
231+
232+
if (!empty($path_differences) || !empty($component_differences)) {
233+
$version = $schema['info']['version'];
234+
$this->fail("Schema for v{$version} does not match snapshot:\n" . implode("\n", $path_differences + $component_differences));
235+
}
236+
}
237+
238+
/**
239+
* Ensure schemas do not change unexpectedly for API versions
240+
* @return void
241+
*/
242+
public function testSchemaSnapshot()
243+
{
244+
$this->login();
245+
246+
$snapshot_dir = __DIR__ . '/../../../../fixtures/hlapi/snapshots';
247+
$this->assertDirectoryExists($snapshot_dir, "Snapshot directory does not exist: $snapshot_dir");
248+
249+
$router = Router::getInstance();
250+
$api_versions = $router::getAPIVersions();
251+
// Only care about the initial minor versions (2.0.0 and 2.1.0 but not 2.1.1, etc)
252+
$initial_minor_versions = [];
253+
foreach ($api_versions as $version_info) {
254+
if ((int) $version_info['api_version'] === 1) {
255+
continue;
256+
}
257+
$version = $version_info['version'];
258+
if (preg_match('/\d+\.\d+\.0$/', $version)) {
259+
$initial_minor_versions[] = $version;
260+
}
261+
}
262+
263+
foreach ($initial_minor_versions as $version) {
264+
$openapi_generator = new OpenAPIGenerator($router, $version);
265+
$schema = $openapi_generator->getSchema();
266+
$snapshot_file = $snapshot_dir . '/v' . str_replace('.', '_', $version) . '.json';
267+
$this->assertFileExists($snapshot_file, "Snapshot file does not exist for version $version: $snapshot_file");
268+
$expected_schema = json_decode(file_get_contents($snapshot_file), true);
269+
$this->assertSchemaMatchesSnapshot($expected_schema, $schema);
270+
}
271+
}
98272
}

0 commit comments

Comments
 (0)