Short songs (#30): a quiz settings switch and a Seconds per song row (30 by default, 10-120 in steps of 5), saved with the quiz settings. Only part of each quiz song plays, from a random point 10-60% into it, picked once per song so restart and repeat play the same part. The progress bar, time and seeking stay within the part, a hidden beginning counts inside it, and its end counts as the song ending (repeat, stop or go to the next song as set). Random songs (#51): the low-played switch in Settings is now a Normal / Low played / Classic choice, stored as Settings.random_mode and not saved. fetch_random_song(mode) picks /music/rand, /music/rand/low or /music/rand/classic. Closes #30 Closes #51 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
668 lines
25 KiB
GDScript
668 lines
25 KiB
GDScript
extends GdUnitTestSuite
|
|
## MusicServerClient: which paths it asks for, how it handles the answers and how it
|
|
## gets and renews its API token.
|
|
|
|
var _server: FakeHttpTransport
|
|
|
|
|
|
func before_test() -> void:
|
|
_server = FakeHttpTransport.new().install()
|
|
|
|
|
|
func after_test() -> void:
|
|
_server.uninstall()
|
|
|
|
|
|
func test_fetch_song_list_passes_song_infos() -> void:
|
|
var songs: Array = [{"SongNo": 0, "Soundtrack": "Chrono Trigger", "Song": "Corridors of Time"}]
|
|
_server.respond_json("/music/list", songs)
|
|
var received: Array[SongInfo] = []
|
|
|
|
MusicServerClient.fetch_song_list(
|
|
func(data: Array[SongInfo]) -> void: received.append_array(data))
|
|
|
|
assert_int(received.size()).is_equal(1)
|
|
assert_int(received[0].song_no).is_equal(0)
|
|
assert_str(received[0].soundtrack).is_equal("Chrono Trigger")
|
|
assert_str(received[0].song).is_equal("Corridors of Time")
|
|
|
|
|
|
func test_fetch_song_info_passes_song_info() -> void:
|
|
_server.respond_json("/music/info", {"SongNo": 7, "Soundtrack": "Terranigma",
|
|
"Song": "Underworld", "SoundtrackUuid": "st-1", "SongUuid": "so-1"})
|
|
var received: Array[SongInfo] = []
|
|
|
|
MusicServerClient.fetch_song_info(func(info: SongInfo) -> void: received.append(info))
|
|
|
|
assert_int(received[0].song_no).is_equal(7)
|
|
assert_str(received[0].song_uuid).is_equal("so-1")
|
|
|
|
|
|
func test_empty_song_list_sent_as_null_gives_empty_array() -> void:
|
|
_server.respond_bytes("/music/list", "null".to_utf8_buffer())
|
|
var received: Array = []
|
|
|
|
MusicServerClient.fetch_song_list(func(data: Array[SongInfo]) -> void: received.append(data))
|
|
|
|
assert_int(received.size()).is_equal(1)
|
|
assert_array(received[0]).is_empty()
|
|
|
|
|
|
func test_json_list_that_is_not_an_array_calls_on_failed() -> void:
|
|
_server.respond_json("/music/list", {"error": "not a list"})
|
|
var outcome: Array[String] = []
|
|
|
|
MusicServerClient.fetch_song_list(
|
|
func(_data: Array[SongInfo]) -> void: outcome.append("done"),
|
|
func() -> void: outcome.append("failed"))
|
|
|
|
assert_array(outcome).contains_exactly(["failed"])
|
|
|
|
|
|
@warning_ignore("unused_parameter")
|
|
func test_fetch_random_song_uses_the_endpoint_of_its_mode(mode: Settings.RandomMode,
|
|
path: String, test_parameters: Array = [
|
|
[Settings.RandomMode.NORMAL, "/music/rand"],
|
|
[Settings.RandomMode.LOW_PLAYED, "/music/rand/low"],
|
|
[Settings.RandomMode.CLASSIC, "/music/rand/classic"],
|
|
]) -> void:
|
|
MusicServerClient.fetch_random_song(mode, func(_body: PackedByteArray) -> void: pass)
|
|
|
|
assert_array(_server.requested_paths).contains_exactly([path])
|
|
|
|
|
|
func test_fetch_song_passes_raw_bytes() -> void:
|
|
var mp3: PackedByteArray = PackedByteArray([0x49, 0x44, 0x33])
|
|
_server.respond_bytes("/music?song=4", mp3)
|
|
var received: Array[PackedByteArray] = []
|
|
|
|
MusicServerClient.fetch_song(4, func(body: PackedByteArray) -> void: received.append(body))
|
|
|
|
assert_array(received).contains_exactly([mp3])
|
|
|
|
|
|
func test_failed_request_does_not_call_back() -> void:
|
|
_server.respond_json("/music/list", [], 500)
|
|
var called: Array[bool] = [false]
|
|
|
|
MusicServerClient.fetch_song_list(func(_data: Variant) -> void: called[0] = true)
|
|
|
|
assert_bool(called[0]).is_false()
|
|
|
|
|
|
func test_invalid_json_does_not_call_back() -> void:
|
|
_server.respond_bytes("/music/info", "not json".to_utf8_buffer())
|
|
var called: Array[bool] = [false]
|
|
|
|
MusicServerClient.fetch_song_info(func(_data: SongInfo) -> void: called[0] = true)
|
|
|
|
assert_bool(called[0]).is_false()
|
|
|
|
|
|
func test_failed_request_calls_on_failed() -> void:
|
|
_server.respond_bytes("/music/rand", PackedByteArray(), 503)
|
|
var outcome: Array[String] = []
|
|
|
|
MusicServerClient.fetch_random_song(Settings.RandomMode.NORMAL,
|
|
func(_body: PackedByteArray) -> void: outcome.append("done"),
|
|
func() -> void: outcome.append("failed"))
|
|
|
|
assert_array(outcome).contains_exactly(["failed"])
|
|
|
|
|
|
func test_invalid_json_calls_on_failed() -> void:
|
|
_server.respond_bytes("/music/info", "not json".to_utf8_buffer())
|
|
var outcome: Array[String] = []
|
|
|
|
MusicServerClient.fetch_song_info(
|
|
func(_data: SongInfo) -> void: outcome.append("done"),
|
|
func() -> void: outcome.append("failed"))
|
|
|
|
assert_array(outcome).contains_exactly(["failed"])
|
|
|
|
|
|
func test_successful_request_does_not_call_on_failed() -> void:
|
|
_server.respond_bytes("/music/addQue", PackedByteArray())
|
|
var outcome: Array[String] = []
|
|
|
|
MusicServerClient.add_to_queue(
|
|
func() -> void: outcome.append("done"),
|
|
func() -> void: outcome.append("failed"))
|
|
|
|
assert_array(outcome).contains_exactly(["done"])
|
|
|
|
|
|
func test_send_calls_back_without_arguments() -> void:
|
|
_server.respond_bytes("/music/reset", PackedByteArray())
|
|
var called: Array[bool] = [false]
|
|
|
|
MusicServerClient.reset_playlist(func() -> void: called[0] = true)
|
|
|
|
assert_bool(called[0]).is_true()
|
|
|
|
|
|
func test_mark_song_played_sends_put() -> void:
|
|
_server.respond_bytes("/music/played?song=3", PackedByteArray())
|
|
|
|
MusicServerClient.mark_song_played(3)
|
|
|
|
assert_array(_server.requested_paths).contains_exactly(["/music/played?song=3"])
|
|
assert_array(_server.requested_methods).contains_exactly([HTTPClient.METHOD_PUT])
|
|
|
|
|
|
func test_character_name_is_url_encoded() -> void:
|
|
MusicServerClient.fetch_character_image("Paper Mario.png",
|
|
func(_body: PackedByteArray) -> void: pass)
|
|
|
|
assert_array(_server.requested_paths).contains_exactly(["/character?name=Paper%20Mario.png"])
|
|
|
|
|
|
func test_start_sync_sends_post() -> void:
|
|
_server.respond_json("/sync", "Sync started")
|
|
var called: Array[bool] = [false]
|
|
|
|
MusicServerClient.start_sync(func() -> void: called[0] = true)
|
|
|
|
assert_array(_server.requested_paths).contains_exactly(["/sync"])
|
|
assert_array(_server.requested_methods).contains_exactly([HTTPClient.METHOD_POST])
|
|
assert_bool(called[0]).is_true()
|
|
|
|
|
|
func test_start_full_sync_sends_post() -> void:
|
|
MusicServerClient.start_full_sync(func() -> void: pass)
|
|
|
|
assert_array(_server.requested_paths).contains_exactly(["/sync/full"])
|
|
assert_array(_server.requested_methods).contains_exactly([HTTPClient.METHOD_POST])
|
|
|
|
|
|
func test_sync_already_running_calls_on_failed() -> void:
|
|
_server.respond_json("/sync", {"error": "Sync is already running"}, 423)
|
|
var outcome: Array[String] = []
|
|
|
|
MusicServerClient.start_sync(
|
|
func() -> void: outcome.append("done"),
|
|
func() -> void: outcome.append("failed"))
|
|
|
|
assert_array(outcome).contains_exactly(["failed"])
|
|
|
|
|
|
func test_requests_go_to_selected_server_under_api_path() -> void:
|
|
var previous_path: String = Settings.default_path
|
|
Settings.default_path = "http://localhost:8080"
|
|
var requested_urls: Array[String] = []
|
|
MusicServerClient.transport = _RecordingTransport.new(requested_urls)
|
|
|
|
MusicServerClient.fetch_song_info(func(_data: SongInfo) -> void: pass)
|
|
|
|
Settings.default_path = previous_path
|
|
assert_array(requested_urls).contains_exactly([
|
|
"http://localhost:8080/api/v1/token",
|
|
"http://localhost:8080/api/v1/music/info",
|
|
])
|
|
|
|
|
|
# Tokens
|
|
|
|
func test_request_sends_token_in_authorization_header() -> void:
|
|
_server.respond_json("/music/info", {})
|
|
|
|
MusicServerClient.fetch_song_info(func(_data: SongInfo) -> void: pass)
|
|
|
|
assert_int(_server.token_requests).is_equal(1)
|
|
assert_array(_server.requested_headers[0]) \
|
|
.contains_exactly(["Authorization: Bearer fake-token-1"])
|
|
|
|
|
|
func test_token_request_sends_password_and_client_type() -> void:
|
|
var previous_password: String = Settings.api_password
|
|
Settings.api_password = "secret"
|
|
|
|
MusicServerClient.fetch_song_info(func(_data: SongInfo) -> void: pass)
|
|
|
|
Settings.api_password = previous_password
|
|
assert_dict(_server.last_token_request) \
|
|
.is_equal({"password": "secret", "client_type": "godot"})
|
|
|
|
|
|
func test_token_is_reused() -> void:
|
|
_server.respond_json("/music/info", {})
|
|
_server.respond_json("/music/list", [])
|
|
|
|
MusicServerClient.fetch_song_info(func(_data: SongInfo) -> void: pass)
|
|
MusicServerClient.fetch_song_list(func(_data: Array[SongInfo]) -> void: pass)
|
|
MusicServerClient.fetch_song_info(func(_data: SongInfo) -> void: pass)
|
|
|
|
assert_int(_server.token_requests).is_equal(1)
|
|
assert_array(_server.requested_paths) \
|
|
.contains_exactly(["/music/info", "/music/list", "/music/info"])
|
|
|
|
|
|
func test_requests_while_getting_a_token_share_it() -> void:
|
|
_server.respond_json("/music/info", {})
|
|
_server.respond_json("/music/list", [])
|
|
_server.hold("/token")
|
|
var done: Array[String] = []
|
|
|
|
MusicServerClient.fetch_song_info(func(_data: SongInfo) -> void: done.append("info"))
|
|
MusicServerClient.fetch_song_list(func(_data: Array[SongInfo]) -> void: done.append("list"))
|
|
assert_array(_server.requested_paths).is_empty()
|
|
_server.release("/token")
|
|
|
|
assert_int(_server.token_requests).is_equal(1)
|
|
assert_array(done).contains_exactly(["info", "list"])
|
|
|
|
|
|
func test_expired_token_is_renewed_and_request_sent_again() -> void:
|
|
_server.respond_json("/music/info", {"SongNo": 3})
|
|
MusicServerClient.fetch_song_info(func(_data: SongInfo) -> void: pass)
|
|
_server.expire_tokens()
|
|
var received: Array[SongInfo] = []
|
|
|
|
MusicServerClient.fetch_song_info(func(info: SongInfo) -> void: received.append(info))
|
|
|
|
assert_int(_server.token_requests).is_equal(2)
|
|
assert_array(_server.requested_paths) \
|
|
.contains_exactly(["/music/info", "/music/info", "/music/info"])
|
|
assert_array(_server.requested_headers[2]) \
|
|
.contains_exactly(["Authorization: Bearer fake-token-2"])
|
|
assert_int(received.size()).is_equal(1)
|
|
assert_int(received[0].song_no).is_equal(3)
|
|
|
|
|
|
func test_request_is_sent_again_only_once() -> void:
|
|
_server.respond_json("/music/info", {"error": "Unauthorized"}, 401)
|
|
var outcome: Array[String] = []
|
|
|
|
MusicServerClient.fetch_song_info(
|
|
func(_data: SongInfo) -> void: outcome.append("done"),
|
|
func() -> void: outcome.append("failed"))
|
|
|
|
assert_int(_server.token_requests).is_equal(2)
|
|
assert_int(_server.count("/music/info")).is_equal(2)
|
|
assert_array(outcome).contains_exactly(["failed"])
|
|
|
|
|
|
func test_refused_password_calls_on_failed_without_sending_request() -> void:
|
|
_server.respond_json("/token", {"error": "Invalid password"}, 401)
|
|
var outcome: Array[String] = []
|
|
|
|
MusicServerClient.fetch_song_info(
|
|
func(_data: SongInfo) -> void: outcome.append("done"),
|
|
func() -> void: outcome.append("failed"))
|
|
|
|
assert_array(_server.requested_paths).is_empty()
|
|
assert_array(outcome).contains_exactly(["failed"])
|
|
|
|
|
|
func test_rate_limited_token_request_calls_on_failed() -> void:
|
|
_server.respond_json("/token", {"error": "Too many requests"}, 429)
|
|
var outcome: Array[String] = []
|
|
|
|
MusicServerClient.add_to_queue(
|
|
func() -> void: outcome.append("done"),
|
|
func() -> void: outcome.append("failed"))
|
|
|
|
assert_array(outcome).contains_exactly(["failed"])
|
|
|
|
|
|
func test_new_token_after_a_failed_token_request() -> void:
|
|
_server.respond_json("/token", {"error": "Too many requests"}, 429)
|
|
MusicServerClient.add_to_queue(func() -> void: pass)
|
|
_server.respond_json("/token", {"token": "second", "expires_at": "", "client_type": "godot"})
|
|
_server.respond_bytes("/music/addQue", PackedByteArray())
|
|
_server.expire_tokens()
|
|
|
|
MusicServerClient.add_to_queue(func() -> void: pass)
|
|
|
|
assert_int(_server.token_requests).is_equal(2)
|
|
assert_array(_server.requested_headers.back()) \
|
|
.contains_exactly(["Authorization: Bearer second"])
|
|
|
|
|
|
func test_changing_server_gets_a_new_token() -> void:
|
|
var previous_path: String = Settings.default_path
|
|
_server.respond_json("/music/info", {})
|
|
MusicServerClient.fetch_song_info(func(_data: SongInfo) -> void: pass)
|
|
|
|
Settings.default_path = "http://another-server:8080"
|
|
MusicServerClient.fetch_song_info(func(_data: SongInfo) -> void: pass)
|
|
Settings.default_path = previous_path
|
|
|
|
assert_int(_server.token_requests).is_equal(2)
|
|
|
|
|
|
func test_health_needs_no_token() -> void:
|
|
_server.respond_json("/health", {"status": "ok"})
|
|
var received: Array = []
|
|
|
|
MusicServerClient.fetch_health(func(data: Dictionary) -> void: received.append(data))
|
|
|
|
assert_int(_server.token_requests).is_equal(0)
|
|
assert_array(_server.requested_headers[0]).is_empty()
|
|
assert_array(received).contains_exactly([{"status": "ok"}])
|
|
|
|
|
|
func test_delete_token_sends_token_and_forgets_it() -> void:
|
|
_server.respond_json("/music/info", {})
|
|
MusicServerClient.fetch_song_info(func(_data: SongInfo) -> void: pass)
|
|
_server.respond_json("/token", {"status": "deleted"})
|
|
var called: Array[bool] = [false]
|
|
|
|
MusicServerClient.delete_token(func() -> void: called[0] = true)
|
|
|
|
assert_bool(called[0]).is_true()
|
|
assert_int(_server.requested_methods.back()).is_equal(HTTPClient.METHOD_DELETE)
|
|
assert_array(_server.requested_headers.back()) \
|
|
.contains_exactly(["Authorization: Bearer fake-token-1"])
|
|
|
|
|
|
func test_delete_token_without_token_sends_nothing() -> void:
|
|
var called: Array[bool] = [false]
|
|
|
|
MusicServerClient.delete_token(func() -> void: called[0] = true)
|
|
|
|
assert_bool(called[0]).is_true()
|
|
assert_array(_server.requested_paths).is_empty()
|
|
|
|
|
|
# Other endpoints
|
|
|
|
func test_endpoint_paths_and_methods() -> void:
|
|
var noop: Callable = func(_data: Variant = null) -> void: pass
|
|
var calls: Array[Array] = [
|
|
[func() -> void: MusicServerClient.fetch_next_song(noop),
|
|
"/music/next", HTTPClient.METHOD_GET],
|
|
[func() -> void: MusicServerClient.fetch_previous_song(noop),
|
|
"/music/previous", HTTPClient.METHOD_GET],
|
|
[func() -> void: MusicServerClient.fetch_sound_test_song(noop),
|
|
"/music/soundTest", HTTPClient.METHOD_GET],
|
|
[func() -> void: MusicServerClient.mark_current_song_played(noop),
|
|
"/music/addPlayed", HTTPClient.METHOD_GET],
|
|
[func() -> void: MusicServerClient.fetch_all_soundtracks(noop),
|
|
"/music/all", HTTPClient.METHOD_GET],
|
|
[func() -> void: MusicServerClient.fetch_all_soundtracks_ordered(noop),
|
|
"/music/all/order", HTTPClient.METHOD_GET],
|
|
[func() -> void: MusicServerClient.fetch_soundtracks(noop),
|
|
"/soundtracks", HTTPClient.METHOD_GET],
|
|
[func() -> void: MusicServerClient.fetch_soundtrack("a b", noop),
|
|
"/soundtracks/a%20b", HTTPClient.METHOD_GET],
|
|
[func() -> void: MusicServerClient.fetch_song_by_uuid("so-1", noop),
|
|
"/songs/so-1", HTTPClient.METHOD_GET],
|
|
[func() -> void: MusicServerClient.fetch_song_file("so-1", noop),
|
|
"/songs/so-1/file", HTTPClient.METHOD_GET],
|
|
[func() -> void: MusicServerClient.fetch_character_list(noop),
|
|
"/character/list", HTTPClient.METHOD_GET],
|
|
[func() -> void: MusicServerClient.fetch_sync_progress(noop),
|
|
"/sync/progress", HTTPClient.METHOD_GET],
|
|
[func() -> void: MusicServerClient.fetch_version(noop),
|
|
"/version", HTTPClient.METHOD_GET],
|
|
[func() -> void: MusicServerClient.fetch_version_history(noop),
|
|
"/version/history", HTTPClient.METHOD_GET],
|
|
[func() -> void: MusicServerClient.fetch_latest_client_version(noop),
|
|
"/download", HTTPClient.METHOD_GET],
|
|
[func() -> void: MusicServerClient.fetch_client_assets(noop),
|
|
"/download/list", HTTPClient.METHOD_GET],
|
|
[func() -> void: MusicServerClient.download_client_windows(noop),
|
|
"/download/windows", HTTPClient.METHOD_GET],
|
|
[func() -> void: MusicServerClient.download_client_linux(noop),
|
|
"/download/linux", HTTPClient.METHOD_GET],
|
|
[func() -> void: MusicServerClient.cleanup_tokens(noop),
|
|
"/token/cleanup", HTTPClient.METHOD_POST],
|
|
[func() -> void: MusicServerClient.fetch_soundtrack_statistics(noop),
|
|
"/statistics/soundtracks", HTTPClient.METHOD_GET],
|
|
[func() -> void: MusicServerClient.fetch_statistics_summary(noop),
|
|
"/statistics/summary", HTTPClient.METHOD_GET],
|
|
[func() -> void: MusicServerClient.fetch_most_played_soundtracks(5, noop),
|
|
"/statistics/soundtracks/most-played?limit=5", HTTPClient.METHOD_GET],
|
|
[func() -> void: MusicServerClient.fetch_least_played_soundtracks(5, noop),
|
|
"/statistics/soundtracks/least-played?limit=5", HTTPClient.METHOD_GET],
|
|
[func() -> void: MusicServerClient.fetch_last_played_soundtracks(5, noop),
|
|
"/statistics/soundtracks/last-played?limit=5", HTTPClient.METHOD_GET],
|
|
[func() -> void: MusicServerClient.fetch_oldest_played_soundtracks(5, noop),
|
|
"/statistics/soundtracks/oldest-played?limit=5", HTTPClient.METHOD_GET],
|
|
[func() -> void: MusicServerClient.fetch_never_played_soundtracks(noop),
|
|
"/statistics/soundtracks/never-played", HTTPClient.METHOD_GET],
|
|
[func() -> void: MusicServerClient.fetch_most_played_songs(5, noop),
|
|
"/statistics/songs/most-played?limit=5", HTTPClient.METHOD_GET],
|
|
[func() -> void: MusicServerClient.fetch_least_played_songs(5, noop),
|
|
"/statistics/songs/least-played?limit=5", HTTPClient.METHOD_GET],
|
|
]
|
|
for entry: Array in calls:
|
|
_server.requested_paths.clear()
|
|
_server.requested_methods.clear()
|
|
entry[0].call()
|
|
assert_array(_server.requested_paths).contains_exactly([entry[1]])
|
|
assert_array(_server.requested_methods).contains_exactly([entry[2]])
|
|
|
|
|
|
func test_statistics_summary_is_typed() -> void:
|
|
_server.respond_json("/statistics/summary", {"total_soundtracks": 120,
|
|
"played_soundtracks": 100, "avg_soundtrack_plays": 2.5})
|
|
var received: Array[StatisticsSummary] = []
|
|
|
|
MusicServerClient.fetch_statistics_summary(
|
|
func(summary: StatisticsSummary) -> void: received.append(summary))
|
|
|
|
assert_int(received[0].total_soundtracks).is_equal(120)
|
|
assert_int(received[0].played_soundtracks).is_equal(100)
|
|
assert_float(received[0].avg_soundtrack_plays).is_equal(2.5)
|
|
|
|
|
|
func test_most_played_soundtracks_are_typed_with_their_songs() -> void:
|
|
_server.respond_json("/statistics/soundtracks/most-played?limit=2", [
|
|
{"soundtrack_uuid": "st-1", "soundtrack_name": "Chrono Trigger",
|
|
"soundtrack_played": 9, "songs": [{"song_name": "Frog's Theme"}]},
|
|
{"soundtrack_uuid": "st-2", "soundtrack_name": "Lufia II", "soundtrack_played": 4,
|
|
"songs": []},
|
|
])
|
|
var received: Array[SoundtrackWithSongs] = []
|
|
|
|
MusicServerClient.fetch_most_played_soundtracks(2,
|
|
func(list: Array[SoundtrackWithSongs]) -> void: received.append_array(list))
|
|
|
|
assert_int(received.size()).is_equal(2)
|
|
assert_str(received[0].soundtrack_name).is_equal("Chrono Trigger")
|
|
assert_int(received[0].soundtrack_played).is_equal(9)
|
|
assert_str(received[0].songs[0].song_name).is_equal("Frog's Theme")
|
|
assert_array(received[1].songs).is_empty()
|
|
|
|
|
|
func test_soundtrack_statistics_are_typed() -> void:
|
|
_server.respond_json("/statistics/soundtracks", [
|
|
{"soundtrack_name": "Chrono Trigger", "soundtrack_played": 2,
|
|
"songs": [{"song_name": "Frog's Theme", "times_played": 2}]},
|
|
{"soundtrack_name": "Lufia II", "soundtrack_played": 0, "songs": []},
|
|
])
|
|
var received: Array[SoundtrackWithSongs] = []
|
|
|
|
MusicServerClient.fetch_soundtrack_statistics(
|
|
func(list: Array[SoundtrackWithSongs]) -> void: received.append_array(list))
|
|
|
|
assert_int(received.size()).is_equal(2)
|
|
assert_str(received[0].songs[0].song_name).is_equal("Frog's Theme")
|
|
assert_int(received[1].soundtrack_played).is_equal(0)
|
|
|
|
|
|
func test_most_played_songs_are_typed() -> void:
|
|
_server.respond_json("/statistics/songs/most-played?limit=1", [
|
|
{"song_uuid": "so-1", "song_name": "Dancing Mad", "times_played": 12},
|
|
])
|
|
var received: Array[SongInfoForStats] = []
|
|
|
|
MusicServerClient.fetch_most_played_songs(1,
|
|
func(list: Array[SongInfoForStats]) -> void: received.append_array(list))
|
|
|
|
assert_str(received[0].song_name).is_equal("Dancing Mad")
|
|
assert_int(received[0].times_played).is_equal(12)
|
|
|
|
|
|
func test_statistics_calls_on_failed_on_server_error() -> void:
|
|
var outcome: Array[String] = []
|
|
var on_done: Callable = func(_data: Variant) -> void: outcome.append("done")
|
|
var on_failed: Callable = func() -> void: outcome.append("failed")
|
|
for path: String in ["/statistics/summary", "/statistics/soundtracks",
|
|
"/statistics/soundtracks/most-played?limit=3",
|
|
"/statistics/soundtracks/least-played?limit=3",
|
|
"/statistics/soundtracks/last-played?limit=3",
|
|
"/statistics/soundtracks/oldest-played?limit=3",
|
|
"/statistics/soundtracks/never-played",
|
|
"/statistics/songs/most-played?limit=3",
|
|
"/statistics/songs/least-played?limit=3"]:
|
|
_server.respond_json(path, {"error": "Internal Server Error"}, 500)
|
|
|
|
_call_every_statistics_endpoint(on_done, on_failed)
|
|
|
|
assert_array(outcome).contains_exactly(["failed", "failed", "failed", "failed", "failed",
|
|
"failed", "failed", "failed", "failed"])
|
|
|
|
|
|
func test_statistics_calls_on_failed_when_token_is_refused() -> void:
|
|
_server.respond_json("/token", {"error": "Invalid password"}, 401)
|
|
var outcome: Array[String] = []
|
|
|
|
_call_every_statistics_endpoint(func(_data: Variant) -> void: outcome.append("done"),
|
|
func() -> void: outcome.append("failed"))
|
|
|
|
assert_array(_server.requested_paths).is_empty()
|
|
assert_array(outcome).contains_exactly(["failed", "failed", "failed", "failed", "failed",
|
|
"failed", "failed", "failed", "failed"])
|
|
|
|
|
|
func _call_every_statistics_endpoint(on_done: Callable, on_failed: Callable) -> void:
|
|
MusicServerClient.fetch_statistics_summary(on_done, on_failed)
|
|
MusicServerClient.fetch_soundtrack_statistics(on_done, on_failed)
|
|
MusicServerClient.fetch_most_played_soundtracks(3, on_done, on_failed)
|
|
MusicServerClient.fetch_least_played_soundtracks(3, on_done, on_failed)
|
|
MusicServerClient.fetch_last_played_soundtracks(3, on_done, on_failed)
|
|
MusicServerClient.fetch_oldest_played_soundtracks(3, on_done, on_failed)
|
|
MusicServerClient.fetch_never_played_soundtracks(on_done, on_failed)
|
|
MusicServerClient.fetch_most_played_songs(3, on_done, on_failed)
|
|
MusicServerClient.fetch_least_played_songs(3, on_done, on_failed)
|
|
|
|
|
|
# Queue IDs
|
|
|
|
func test_no_queue_id_is_sent_before_the_server_sends_one() -> void:
|
|
_server.respond_json("/music/list", [])
|
|
|
|
MusicServerClient.fetch_song_list(func(_data: Array[SongInfo]) -> void: pass)
|
|
|
|
assert_array(_server.requested_headers[0]) \
|
|
.contains_exactly(["Authorization: Bearer fake-token-1"])
|
|
|
|
|
|
func test_queue_id_from_a_response_is_sent_with_the_next_requests() -> void:
|
|
_server.respond_json("/music/list", [])
|
|
_server.respond_bytes("/music/addQue", PackedByteArray())
|
|
_server.queue_id = "queue-1"
|
|
|
|
MusicServerClient.fetch_song_list(func(_data: Array[SongInfo]) -> void: pass)
|
|
MusicServerClient.add_to_queue(func() -> void: pass)
|
|
|
|
assert_str(MusicServerClient.queue_id()).is_equal("queue-1")
|
|
assert_array(_server.requested_headers[1]) \
|
|
.contains_exactly(["Authorization: Bearer fake-token-1", "X-Queue-Id: queue-1"])
|
|
DirAccess.remove_absolute(Settings.saved_settings_path())
|
|
|
|
|
|
func test_new_queue_id_replaces_the_old_one() -> void:
|
|
_server.respond_bytes("/music/reset", PackedByteArray())
|
|
_server.respond_json("/music/list", [])
|
|
_server.queue_id = "queue-1"
|
|
MusicServerClient.fetch_song_list(func(_data: Array[SongInfo]) -> void: pass)
|
|
|
|
_server.queue_id = "queue-2"
|
|
MusicServerClient.reset_playlist(func() -> void: pass)
|
|
MusicServerClient.fetch_song_list(func(_data: Array[SongInfo]) -> void: pass)
|
|
|
|
assert_array(_server.requested_headers[1]).contains(["X-Queue-Id: queue-1"])
|
|
assert_array(_server.requested_headers[2]).contains(["X-Queue-Id: queue-2"])
|
|
DirAccess.remove_absolute(Settings.saved_settings_path())
|
|
|
|
|
|
func test_queue_id_is_kept_per_server() -> void:
|
|
var previous_path: String = Settings.default_path
|
|
_server.respond_json("/music/list", [])
|
|
_server.queue_id = "queue-1"
|
|
MusicServerClient.fetch_song_list(func(_data: Array[SongInfo]) -> void: pass)
|
|
|
|
_server.queue_id = ""
|
|
Settings.default_path = "http://another-server:8080"
|
|
MusicServerClient.fetch_song_list(func(_data: Array[SongInfo]) -> void: pass)
|
|
var other_server_id: String = MusicServerClient.queue_id()
|
|
Settings.default_path = previous_path
|
|
|
|
assert_str(other_server_id).is_empty()
|
|
assert_array(_server.requested_headers[1]) \
|
|
.not_contains(["X-Queue-Id: queue-1"])
|
|
assert_str(MusicServerClient.queue_id()).is_equal("queue-1")
|
|
DirAccess.remove_absolute(Settings.saved_settings_path())
|
|
|
|
|
|
func test_queue_id_is_saved_for_the_next_start() -> void:
|
|
_server.respond_json("/music/list", [])
|
|
_server.queue_id = "queue-1"
|
|
MusicServerClient.fetch_song_list(func(_data: Array[SongInfo]) -> void: pass)
|
|
|
|
Settings.queue_ids.clear()
|
|
assert_str(MusicServerClient.queue_id()).is_empty()
|
|
Settings.load_saved_settings()
|
|
|
|
assert_str(MusicServerClient.queue_id()).is_equal("queue-1")
|
|
DirAccess.remove_absolute(Settings.saved_settings_path())
|
|
|
|
|
|
func test_queue_id_header_name_is_case_insensitive() -> void:
|
|
var transport: _HeaderTransport = _HeaderTransport.new(["x-queue-id: lower-case"])
|
|
var previous_transport: HttpTransport = MusicServerClient.transport
|
|
MusicServerClient.transport = transport
|
|
|
|
MusicServerClient.fetch_song_list(func(_data: Array[SongInfo]) -> void: pass)
|
|
MusicServerClient.transport = previous_transport
|
|
|
|
assert_str(MusicServerClient.queue_id()).is_equal("lower-case")
|
|
DirAccess.remove_absolute(Settings.saved_settings_path())
|
|
|
|
|
|
# Answers every request with 200, an empty JSON list and the given headers
|
|
class _HeaderTransport:
|
|
extends HttpTransport
|
|
|
|
var _headers: PackedStringArray
|
|
|
|
func _init(headers: PackedStringArray) -> void:
|
|
super(null)
|
|
_headers = headers
|
|
|
|
func request(
|
|
_url: String,
|
|
_method: int,
|
|
on_complete: Callable,
|
|
_request_headers: PackedStringArray = PackedStringArray(),
|
|
_body: String = "",
|
|
) -> void:
|
|
on_complete.call(200, JSON.stringify({"token": "t"} if _url.ends_with("/token") else [])
|
|
.to_utf8_buffer(), _headers)
|
|
|
|
|
|
class _RecordingTransport:
|
|
extends HttpTransport
|
|
|
|
var _urls: Array[String]
|
|
|
|
func _init(urls: Array[String]) -> void:
|
|
super(null)
|
|
_urls = urls
|
|
|
|
func request(
|
|
url: String,
|
|
_method: int,
|
|
on_complete: Callable,
|
|
_headers: PackedStringArray = PackedStringArray(),
|
|
_body: String = "",
|
|
) -> void:
|
|
_urls.append(url)
|
|
if url.ends_with("/token"):
|
|
on_complete.call(200, JSON.stringify({"token": "t"}).to_utf8_buffer(),
|
|
PackedStringArray())
|