1+ <?php
2+
3+ namespace SAMSPlugin \Tests \ApiSpecs ;
4+
5+ use PHPUnit \Framework \TestCase ;
6+
7+ final class ApiSpecValidationTest extends TestCase {
8+
9+ private const API_SPECS_DIR = __DIR__ . '/../../api-specs ' ;
10+ private const EXPECTED_VERSION = '2.1 ' ;
11+
12+ private const EXPECTED_SERVERS = [
13+ 'flvb.sams-server.de ' ,
14+ 'hessen-volley.de ' ,
15+ 'nwvv.sams-server.de ' ,
16+ 'vvb.sams-server.de ' ,
17+ 'vvsa.sams-server.de ' ,
18+ 'wvv.sams-server.de ' ,
19+ 'www.dvv-ligen.de ' ,
20+ 'www.shvv.de ' ,
21+ 'www.ssvb.org ' ,
22+ 'www.tv-v.de ' ,
23+ 'www.vlw-online.de ' ,
24+ 'www.volley-saar.de ' ,
25+ 'www.volleyball-baden.de ' ,
26+ 'www.volleyball-bundesliga.de ' ,
27+ 'www.vvrp.de ' ,
28+ ];
29+
30+ /**
31+ * Test that all expected API spec files exist
32+ */
33+ public function testAllExpectedApiSpecFilesExist () {
34+ foreach (self ::EXPECTED_SERVERS as $ server ) {
35+ $ filePath = self ::API_SPECS_DIR . '/ ' . $ server . '/ ' . self ::EXPECTED_VERSION . '.json ' ;
36+ $ this ->assertFileExists (
37+ $ filePath ,
38+ "API spec file for {$ server } version " . self ::EXPECTED_VERSION . " should exist "
39+ );
40+ }
41+ }
42+
43+ /**
44+ * Test that all API spec files contain valid JSON
45+ */
46+ public function testAllApiSpecFilesContainValidJson () {
47+ foreach (self ::EXPECTED_SERVERS as $ server ) {
48+ $ filePath = self ::API_SPECS_DIR . '/ ' . $ server . '/ ' . self ::EXPECTED_VERSION . '.json ' ;
49+ $ content = file_get_contents ($ filePath );
50+
51+ $ this ->assertNotFalse ($ content , "Should be able to read {$ filePath }" );
52+
53+ $ decoded = json_decode ($ content , true );
54+ $ this ->assertNotNull (
55+ $ decoded ,
56+ "API spec file {$ filePath } should contain valid JSON. Error: " . json_last_error_msg ()
57+ );
58+ $ this ->assertIsArray ($ decoded , "Decoded JSON should be an array/object " );
59+ }
60+ }
61+
62+ /**
63+ * Test that all API spec files are valid OpenAPI 3.x specifications
64+ */
65+ public function testAllApiSpecFilesAreValidOpenApiSpecs () {
66+ foreach (self ::EXPECTED_SERVERS as $ server ) {
67+ $ filePath = self ::API_SPECS_DIR . '/ ' . $ server . '/ ' . self ::EXPECTED_VERSION . '.json ' ;
68+ $ content = file_get_contents ($ filePath );
69+ $ spec = json_decode ($ content , true );
70+
71+ // Check required OpenAPI fields
72+ $ this ->assertArrayHasKey (
73+ 'openapi ' ,
74+ $ spec ,
75+ "{$ filePath } should have 'openapi' field "
76+ );
77+ $ this ->assertArrayHasKey (
78+ 'info ' ,
79+ $ spec ,
80+ "{$ filePath } should have 'info' field "
81+ );
82+ $ this ->assertArrayHasKey (
83+ 'paths ' ,
84+ $ spec ,
85+ "{$ filePath } should have 'paths' field "
86+ );
87+
88+ // Verify OpenAPI version is 3.x
89+ $ this ->assertMatchesRegularExpression (
90+ '/^3\.\d+\.\d+$/ ' ,
91+ $ spec ['openapi ' ],
92+ "{$ filePath } should use OpenAPI version 3.x "
93+ );
94+ }
95+ }
96+
97+ /**
98+ * Test that API version in file content matches filename
99+ */
100+ public function testApiVersionInFileMatchesFilename () {
101+ foreach (self ::EXPECTED_SERVERS as $ server ) {
102+ $ filePath = self ::API_SPECS_DIR . '/ ' . $ server . '/ ' . self ::EXPECTED_VERSION . '.json ' ;
103+ $ content = file_get_contents ($ filePath );
104+ $ spec = json_decode ($ content , true );
105+
106+ $ this ->assertArrayHasKey (
107+ 'info ' ,
108+ $ spec ,
109+ "{$ filePath } should have 'info' field "
110+ );
111+ $ this ->assertArrayHasKey (
112+ 'version ' ,
113+ $ spec ['info ' ],
114+ "{$ filePath } should have 'info.version' field "
115+ );
116+ $ this ->assertEquals (
117+ self ::EXPECTED_VERSION ,
118+ $ spec ['info ' ]['version ' ],
119+ "API version in {$ filePath } should match filename "
120+ );
121+ }
122+ }
123+
124+ /**
125+ * Test that all API specs have consistent required info fields
126+ */
127+ public function testAllApiSpecsHaveConsistentInfoFields () {
128+ foreach (self ::EXPECTED_SERVERS as $ server ) {
129+ $ filePath = self ::API_SPECS_DIR . '/ ' . $ server . '/ ' . self ::EXPECTED_VERSION . '.json ' ;
130+ $ content = file_get_contents ($ filePath );
131+ $ spec = json_decode ($ content , true );
132+
133+ $ this ->assertArrayHasKey (
134+ 'title ' ,
135+ $ spec ['info ' ],
136+ "{$ filePath } should have 'info.title' field "
137+ );
138+ $ this ->assertArrayHasKey (
139+ 'version ' ,
140+ $ spec ['info ' ],
141+ "{$ filePath } should have 'info.version' field "
142+ );
143+
144+ $ this ->assertIsString ($ spec ['info ' ]['title ' ], "Title should be a string " );
145+ $ this ->assertNotEmpty ($ spec ['info ' ]['title ' ], "Title should not be empty " );
146+ }
147+ }
148+
149+ /**
150+ * Test that all API specs have paths defined
151+ */
152+ public function testAllApiSpecsHavePathsDefined () {
153+ foreach (self ::EXPECTED_SERVERS as $ server ) {
154+ $ filePath = self ::API_SPECS_DIR . '/ ' . $ server . '/ ' . self ::EXPECTED_VERSION . '.json ' ;
155+ $ content = file_get_contents ($ filePath );
156+ $ spec = json_decode ($ content , true );
157+
158+ $ this ->assertIsArray (
159+ $ spec ['paths ' ],
160+ "{$ filePath } paths should be an array/object "
161+ );
162+ $ this ->assertNotEmpty (
163+ $ spec ['paths ' ],
164+ "{$ filePath } should have at least one path defined "
165+ );
166+ }
167+ }
168+
169+ /**
170+ * Test that all API specs have servers defined
171+ */
172+ public function testAllApiSpecsHaveServersDefined () {
173+ foreach (self ::EXPECTED_SERVERS as $ server ) {
174+ $ filePath = self ::API_SPECS_DIR . '/ ' . $ server . '/ ' . self ::EXPECTED_VERSION . '.json ' ;
175+ $ content = file_get_contents ($ filePath );
176+ $ spec = json_decode ($ content , true );
177+
178+ $ this ->assertArrayHasKey (
179+ 'servers ' ,
180+ $ spec ,
181+ "{$ filePath } should have 'servers' field "
182+ );
183+ $ this ->assertIsArray (
184+ $ spec ['servers ' ],
185+ "{$ filePath } servers should be an array "
186+ );
187+ $ this ->assertNotEmpty (
188+ $ spec ['servers ' ],
189+ "{$ filePath } should have at least one server defined "
190+ );
191+
192+ // Check first server has required url field
193+ $ this ->assertArrayHasKey (
194+ 'url ' ,
195+ $ spec ['servers ' ][0 ],
196+ "{$ filePath } first server should have 'url' field "
197+ );
198+ }
199+ }
200+
201+ /**
202+ * Test that all API specs have components defined
203+ */
204+ public function testAllApiSpecsHaveComponentsDefined () {
205+ foreach (self ::EXPECTED_SERVERS as $ server ) {
206+ $ filePath = self ::API_SPECS_DIR . '/ ' . $ server . '/ ' . self ::EXPECTED_VERSION . '.json ' ;
207+ $ content = file_get_contents ($ filePath );
208+ $ spec = json_decode ($ content , true );
209+
210+ $ this ->assertArrayHasKey (
211+ 'components ' ,
212+ $ spec ,
213+ "{$ filePath } should have 'components' field "
214+ );
215+ $ this ->assertIsArray (
216+ $ spec ['components ' ],
217+ "{$ filePath } components should be an array/object "
218+ );
219+ }
220+ }
221+
222+ /**
223+ * Test that API spec files are not empty
224+ */
225+ public function testApiSpecFilesAreNotEmpty () {
226+ foreach (self ::EXPECTED_SERVERS as $ server ) {
227+ $ filePath = self ::API_SPECS_DIR . '/ ' . $ server . '/ ' . self ::EXPECTED_VERSION . '.json ' ;
228+ $ fileSize = filesize ($ filePath );
229+
230+ $ this ->assertGreaterThan (
231+ 100 ,
232+ $ fileSize ,
233+ "{$ filePath } should not be empty (should be > 100 bytes) "
234+ );
235+ }
236+ }
237+
238+ /**
239+ * Test that API specs directory structure is correct
240+ */
241+ public function testApiSpecsDirectoryStructureIsCorrect () {
242+ $ this ->assertDirectoryExists (
243+ self ::API_SPECS_DIR ,
244+ "API specs directory should exist "
245+ );
246+
247+ foreach (self ::EXPECTED_SERVERS as $ server ) {
248+ $ serverDir = self ::API_SPECS_DIR . '/ ' . $ server ;
249+ $ this ->assertDirectoryExists (
250+ $ serverDir ,
251+ "Server directory {$ serverDir } should exist "
252+ );
253+ }
254+ }
255+
256+ /**
257+ * Test that all API specs have SAMS API title
258+ */
259+ public function testAllApiSpecsHaveSamsApiTitle () {
260+ foreach (self ::EXPECTED_SERVERS as $ server ) {
261+ $ filePath = self ::API_SPECS_DIR . '/ ' . $ server . '/ ' . self ::EXPECTED_VERSION . '.json ' ;
262+ $ content = file_get_contents ($ filePath );
263+ $ spec = json_decode ($ content , true );
264+
265+ $ title = $ spec ['info ' ]['title ' ];
266+ $ this ->assertStringContainsString (
267+ 'SAMS ' ,
268+ $ title ,
269+ "{$ filePath } title should contain 'SAMS' "
270+ );
271+ }
272+ }
273+
274+ /**
275+ * Test that all API specs are readable and parseable in a single pass
276+ */
277+ public function testAllApiSpecsAreCompletelyParseable () {
278+ foreach (self ::EXPECTED_SERVERS as $ server ) {
279+ $ filePath = self ::API_SPECS_DIR . '/ ' . $ server . '/ ' . self ::EXPECTED_VERSION . '.json ' ;
280+
281+ // Test that the entire file can be loaded and parsed without errors
282+ $ content = @file_get_contents ($ filePath );
283+ $ this ->assertNotFalse ($ content , "Should be able to read {$ filePath }" );
284+
285+ json_decode ($ content , true );
286+ $ jsonError = json_last_error ();
287+
288+ $ this ->assertEquals (
289+ JSON_ERROR_NONE ,
290+ $ jsonError ,
291+ "JSON in {$ filePath } should be completely valid. Error: " . json_last_error_msg ()
292+ );
293+ }
294+ }
295+
296+ /**
297+ * Test boundary case: Non-existent version file should not exist
298+ */
299+ public function testNonExistentVersionFilesDoNotExist () {
300+ $ nonExistentVersion = '999.999 ' ;
301+ $ server = self ::EXPECTED_SERVERS [0 ];
302+ $ filePath = self ::API_SPECS_DIR . '/ ' . $ server . '/ ' . $ nonExistentVersion . '.json ' ;
303+
304+ $ this ->assertFileDoesNotExist (
305+ $ filePath ,
306+ "Non-existent version file should not exist "
307+ );
308+ }
309+
310+ /**
311+ * Test regression: Ensure each spec has at least one GET endpoint
312+ * This ensures the API specs are functional and usable
313+ */
314+ public function testAllApiSpecsHaveAtLeastOneGetEndpoint () {
315+ foreach (self ::EXPECTED_SERVERS as $ server ) {
316+ $ filePath = self ::API_SPECS_DIR . '/ ' . $ server . '/ ' . self ::EXPECTED_VERSION . '.json ' ;
317+ $ content = file_get_contents ($ filePath );
318+ $ spec = json_decode ($ content , true );
319+
320+ $ hasGetEndpoint = false ;
321+ foreach ($ spec ['paths ' ] as $ path => $ methods ) {
322+ if (isset ($ methods ['get ' ])) {
323+ $ hasGetEndpoint = true ;
324+ break ;
325+ }
326+ }
327+
328+ $ this ->assertTrue (
329+ $ hasGetEndpoint ,
330+ "{$ filePath } should have at least one GET endpoint defined "
331+ );
332+ }
333+ }
334+ }
0 commit comments