test: przypięcie powierzchni API

Panel jest osobnym klientem HTTP, więc zmiana ścieżki albo metody psuje go
po cichu - w kodzie Pythona nic nie przestaje się kompilować. Test buduje
aplikację, generuje specyfikację OpenAPI i porównuje ją z listą 22
endpointów w obie strony:

- brak oczekiwanego endpointu to błąd
- nowy endpoint spoza listy również, żeby rozszerzenie API było świadomą
  decyzją, a nie skutkiem ubocznym

Sprawdzany jest też sam dokument OpenAPI, co wyłapuje błędy w deklaracjach
tras i modelach żądań.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
lgooseandClaude Bot committed 2026-08-21 01:25:40 +02:00
1 parent 2a961e1a8a
commit 9ae22704ff
1 file changed
+73
+73
View File
@@ -0,0 +1,73 @@
"""Przypina powierzchnię API - panel jest osobnym klientem, więc kontrakt musi być stabilny."""
from __future__ import annotations
import tempfile
import unittest
from zyrarbot.application import Application
from zyrarbot.settings import Settings
EXPECTED_ENDPOINTS = {
("get", "/api/status"),
("post", "/api/play"),
("post", "/api/play_file"),
("post", "/api/pause"),
("post", "/api/resume"),
("post", "/api/skip"),
("post", "/api/stop"),
("post", "/api/volume"),
("post", "/api/loop"),
("post", "/api/loop_playlist"),
("post", "/api/loop_cycle"),
("post", "/api/jump"),
("post", "/api/remove"),
("post", "/api/reorder"),
("post", "/api/queue/plays"),
("get", "/api/uploads"),
("post", "/api/upload"),
("delete", "/api/uploads/{filename}"),
("post", "/api/connect"),
("post", "/api/disconnect"),
("post", "/api/settings/text_channel"),
("post", "/api/settings/default_plays"),
}
class ApiSurfaceTest(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
cls._workdir = tempfile.TemporaryDirectory()
settings = Settings(
discord_token="test",
config_dir=f"{cls._workdir.name}/config",
uploads_dir=f"{cls._workdir.name}/uploads",
static_dir=f"{cls._workdir.name}/static",
)
cls.spec = Application(settings).api.openapi()
@classmethod
def tearDownClass(cls) -> None:
cls._workdir.cleanup()
def _declared(self) -> set[tuple[str, str]]:
return {
(method, path)
for path, operations in self.spec["paths"].items()
for method in operations
}
def test_all_expected_endpoints_are_declared(self):
self.assertEqual(set(), EXPECTED_ENDPOINTS - self._declared())
def test_no_endpoint_added_without_updating_this_test(self):
# Panel jest odrębnym klientem - nowa trasa powinna trafić tu świadomie.
self.assertEqual(set(), self._declared() - EXPECTED_ENDPOINTS)
def test_openapi_document_is_well_formed(self):
self.assertTrue(self.spec["openapi"].startswith("3."))
self.assertIn("ŻyrarBOT", self.spec["info"]["title"])
if __name__ == "__main__":
unittest.main()