# Testing Tests use [GdUnit4](https://github.com/godot-gdunit-labs/gdUnit4) 6.2.1, committed in `addons/gdUnit4/`. The whole suite runs headless in about 10 seconds. ## Running the tests ```bash ./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 and the API classes | | `tests/integration/` | Real scenes (`MainWindow.tscn`, `SyncPopup.tscn`) run against a fake server, including the point keybinds and the Statistics tab | | `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: ```gdscript 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 `/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 `/openapi.json`) and run: ```bash 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])` and `restore()`. - 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_array` only 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.