@@ -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_pathsarray_filter ($ snapshot'paths ' ] ?? [], static  fn ($ pstr_starts_with ($ p'/Assets/Custom ' ), ARRAY_FILTER_USE_KEY );
105+         $ schema_pathsarray_filter ($ schema'paths ' ] ?? [], static  fn ($ pstr_starts_with ($ p'/Assets/Custom ' ), ARRAY_FILTER_USE_KEY );
106+         $ common_pathsarray_intersect (array_keys ($ snapshot_pathsarray_keys ($ schema_paths
107+ 
108+         if  (count ($ common_pathscount ($ snapshot_pathscount ($ common_pathscount ($ schema_paths
109+             $ missing_in_schemaarray_diff (array_keys ($ snapshot_pathsarray_keys ($ schema_paths
110+             $ missing_in_snapshotarray_diff (array_keys ($ schema_pathsarray_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_pathsas  $ path
120+             foreach  (['get ' , 'post ' , 'put ' , 'delete ' , 'patch ' ] as  $ method
121+                 $ snapshot_method$ snapshot_paths$ path$ methodnull ;
122+                 $ schema_method$ schema_paths$ path$ methodnull ;
123+                 if  ($ snapshot_methodnull  && $ schema_methodnull ) {
124+                     continue ;
125+                 } elseif  ($ snapshot_methodnull ) {
126+                     $ differences"Method ' $ method' for path ' $ path' is missing in the snapshot " ;
127+                     continue ;
128+                 } elseif  ($ schema_methodnull ) {
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_propsarray_intersect (array_keys ($ snapshot_propsarray_keys ($ schema_props
145+ 
146+         if  (count ($ common_propscount ($ snapshot_propscount ($ common_propscount ($ schema_props
147+             $ missing_in_schemaarray_diff (array_keys ($ snapshot_propsarray_keys ($ schema_props
148+             $ missing_in_snapshotarray_diff (array_keys ($ schema_propsarray_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_propsas  $ 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+                 $ differencesarray_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+                 $ differencesarray_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_schemasarray_filter ($ snapshot'components ' ]['schemas ' ] ?? [], static  fn ($ kstr_starts_with ($ k'Custom ' ), ARRAY_FILTER_USE_KEY );
191+         $ schema_schemasarray_filter ($ schema'components ' ]['schemas ' ] ?? [], static  fn ($ kstr_starts_with ($ k'Custom ' ), ARRAY_FILTER_USE_KEY );
192+         $ common_schemasarray_intersect (array_keys ($ snapshot_schemasarray_keys ($ schema_schemas
193+ 
194+         if  (count ($ common_schemascount ($ snapshot_schemascount ($ common_schemascount ($ schema_schemas
195+             $ missing_in_schemaarray_diff (array_keys ($ snapshot_schemasarray_keys ($ schema_schemas
196+             $ missing_in_snapshotarray_diff (array_keys ($ schema_schemasarray_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_schemasas  $ 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+                 $ differencesarray_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  $ snapshotarray  $ schema
228+     {
229+         $ path_differences$ this diffSchemaPaths ($ snapshot$ schema
230+         $ component_differences$ this diffComponentSchemas ($ snapshot$ schema
231+ 
232+         if  (!empty ($ path_differencesempty ($ 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+         $ routergetInstance ();
250+         $ api_versions$ routergetAPIVersions ();
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_versionsas  $ 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_versionsas  $ version
264+             $ openapi_generatornew  OpenAPIGenerator ($ router$ version
265+             $ schema$ openapi_generatorgetSchema ();
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_schemajson_decode (file_get_contents ($ snapshot_filetrue );
269+             $ this assertSchemaMatchesSnapshot ($ expected_schema$ schema
270+         }
271+     }
98272}
0 commit comments