Closes #38, #39 and #40; adds the client side of #41. - Every request goes to /api/v1. MusicServerClient gets a token with Settings.api_password the first time one is needed, reuses it, and on a 401 gets a new one and sends the request once more. Switching server gets a new token too. Callers never see the token. - Sync is started with POST, as MusicServer 5.0 requires. - The client has every endpoint in the server's OpenAPI spec, including statistics, soundtracks and songs by UUID, version, downloads and tokens. - tools/generate_api_types.gd generates a typed class per schema in server/openapi.json into server/generated/. Song info, statistics and versions are passed to callers as these classes. A list the server sends as null is an empty list. - FakeHttpTransport hands out tokens and answers 401 without one, so every test goes through the token flow. New tests cover the token handling, every endpoint and each generated class.
3.5 KiB
Testing
Tests use GdUnit4 6.2.1, committed in
addons/gdUnit4/. The whole suite runs headless in about 10 seconds.
Running the tests
./run_tests.sh # everything
./run_tests.sh res://tests/unit # only unit tests
./run_tests.sh res://tests/unit/test_search.gd # one suite
The script finds Godot through $GODOT, then godot on your PATH, then
/Applications/Godot.app or /Applications/Godot_mono.app. Reports are written to
reports/ (ignored by git).
In the editor, open the GdUnit4 panel or right-click the tests folder and choose
Run Tests.
CI runs the suite in the test job of .gitea/workflows/gitea-build.yaml on every push
and pull request to main and develop. The build job only starts once the tests
pass.
Layout
| Folder | Contents |
|---|---|
tests/unit/ |
Logic without scenes: playlist, songs, player scoring, time formatting, search, the server client |
tests/integration/ |
Real scenes (MainWindow.tscn, SyncPopup.tscn) run against a fake server, including the point keybinds |
tests/fakes/ |
FakeHttpTransport, AutoloadSnapshot and the MainWindowTestSuite base class |
tests/data/ |
Files the fake server sends, e.g. a short MP3 |
Faking the MusicServer
All server calls go through the MusicServerClient autoload, which sends requests with
a swappable transport. Tests install a FakeHttpTransport that answers from canned
responses and records what was requested:
var server: FakeHttpTransport = FakeHttpTransport.new().install()
server.respond_json("/music/list", [{"SongNo": 0, "Soundtrack": "Chrono Trigger", "Song": "Frog's Theme"}])
server.respond_bytes("/music/rand", mp3_bytes)
# ... run the code under test ...
assert_int(server.count("/music/rand")).is_equal(1)
server.uninstall()
Paths are relative to the API (/music/list is <server>/api/v1/music/list). Paths
without a response get a 404, so the code under test sees a failed request.
Like the real server, the fake hands out tokens on POST /token and answers 401 to
requests without a valid one, so the client's token handling runs in every test. Token
requests are counted in server.token_requests, not in requested_paths. Use
server.expire_tokens() to make the client renew its token, server.hold("/token") to
keep it waiting for one, and server.respond_json("/token", {...}, 401) for a refused
password.
API classes
The classes in server/generated/ (SongInfo, StatisticsSummary, ...) are generated
from the server's OpenAPI spec, committed as server/openapi.json. To update them, copy
a new spec there (just openapi in MusicServer, or <server>/openapi.json) and run:
godot --headless --path . -s tools/generate_api_types.gd
tests/unit/test_api_types.gd parses a sample response with each class.
Writing tests
- Settings and Playlist are global. Tests that change them restore them afterwards, either
by hand or with
AutoloadSnapshot.take([Settings, Playlist])andrestore(). - Main window tests extend
MainWindowTestSuite, which starts the window against the fake server and has helpers such as_add_player(),_remove_player()and_give_point_with_keybind(). - Player ids are UUID strings that never change. A player's position in
Settings.player_arrayonly decides their keybind (Alt+1 for the first row and so on), so tests check both: that points land on the right player id, and that keybinds follow the rows when players are added or removed.