"Add soundtrack…" in the statistics toolbar, or a folder dropped anywhere on the window, opens a popup that lists the folder's songs and uploads them to the server, which adds the soundtrack at once (Sansan/MusicServer#33). - The name comes from the folder and can be changed; characters the server refuses are replaced. - Only MP3s are uploaded, as the server takes no other; other song files are listed greyed out as left out. Hidden files are skipped. - Disc folders are flattened with the disc's number first ("1-01 Title.mp3"), as the server wants one folder. With one disc folder, names stay as they are. - Songs are sent one at a time with a progress bar, reading one file at a time. A failed song is sent again twice, then the upload is cancelled and the server's reason shown. Closing the popup cancels it too. - Not offered while the server syncs, offline, or without MP3s. - Afterwards search, the inspiration list and the statistics reload. The API types come from the server's spec (Upload, UploadFile, UploadRequest). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
205 lines
7.2 KiB
GDScript
205 lines
7.2 KiB
GDScript
extends MainWindowTestSuite
|
|
## Adding a soundtrack from a folder: picked in the statistics or dropped on the window,
|
|
## uploaded one song at a time, and found everywhere afterwards.
|
|
|
|
const UPLOADS: String = "/soundtracks/uploads"
|
|
const UPLOAD_ID: String = "0b5e2b1e-3a4f-4d5e-9f00-000000000001"
|
|
|
|
var _popup: UploadPopup
|
|
var _dir: String
|
|
|
|
|
|
func before_test() -> void:
|
|
await super()
|
|
_popup = _window.get_node("UploadPopup")
|
|
_dir = create_temp_dir("upload_%d" % randi()).path_join("Chrono Trigger")
|
|
_song("01 Title.mp3", "ID3 title")
|
|
_song("Disc 2/01 Ending.mp3", "ID3 ending")
|
|
_song("02 Theme.flac", "flac")
|
|
|
|
|
|
func after_test() -> void:
|
|
Settings.currently_syncing = false
|
|
await super()
|
|
|
|
|
|
func _song(relative: String, content: String) -> void:
|
|
var path: String = _dir.path_join(relative)
|
|
DirAccess.make_dir_recursive_absolute(path.get_base_dir())
|
|
var file: FileAccess = FileAccess.open(path, FileAccess.WRITE)
|
|
file.store_string(content)
|
|
file.close()
|
|
|
|
|
|
func _file_path(file_name: String) -> String:
|
|
return "%s/%s/files/%s" % [UPLOADS, UPLOAD_ID, file_name.uri_encode()]
|
|
|
|
|
|
func _server_accepts_everything() -> void:
|
|
_server.respond_json(UPLOADS, {"id": UPLOAD_ID, "name": "Chrono Trigger", "files": []},
|
|
201)
|
|
_server.respond_json(_file_path("01 Title.mp3"), {"name": "01 Title.mp3", "size": 9})
|
|
_server.respond_json(_file_path("01 Ending.mp3"), {"name": "01 Ending.mp3", "size": 10})
|
|
_server.respond_json("%s/%s/finish" % [UPLOADS, UPLOAD_ID], {"uuid": "st-new",
|
|
"name": "Chrono Trigger", "songs": [{"name": "01 Title"}, {"name": "01 Ending"}]})
|
|
_server.respond_bytes("%s/%s" % [UPLOADS, UPLOAD_ID], PackedByteArray(), 204)
|
|
|
|
|
|
func _upload_requests() -> Array[String]:
|
|
var paths: Array[String] = []
|
|
for path: String in _server.requested_paths:
|
|
if path.begins_with(UPLOADS):
|
|
paths.append(path)
|
|
return paths
|
|
|
|
|
|
func test_folder_lists_its_songs_and_what_is_left_out() -> void:
|
|
_popup.show_folder(_dir)
|
|
|
|
assert_bool(_popup.visible).is_true()
|
|
assert_str(_popup.name_field.text).is_equal("Chrono Trigger")
|
|
assert_int(_popup.song_list.get_child_count()).is_equal(3)
|
|
assert_str(_popup.summary_label.text).starts_with("2 songs, ")
|
|
assert_str(_popup.summary_label.text).contains("1 file left out")
|
|
assert_bool(_popup.upload_button.disabled).is_false()
|
|
|
|
|
|
func test_upload_sends_each_song_then_finishes() -> void:
|
|
_server_accepts_everything()
|
|
var added: Array[String] = []
|
|
_popup.soundtrack_added.connect(
|
|
func(soundtrack: SoundtrackWithSongList) -> void: added.append(soundtrack.name))
|
|
var searches_before: int = _server.count("/soundtracks")
|
|
_popup.show_folder(_dir)
|
|
_popup.name_field.text = "Chrono Trigger (SNES)"
|
|
|
|
_popup.upload()
|
|
|
|
assert_array(_upload_requests()).contains_exactly([UPLOADS,
|
|
_file_path("01 Title.mp3"), _file_path("01 Ending.mp3"),
|
|
"%s/%s/finish" % [UPLOADS, UPLOAD_ID]])
|
|
var start: int = _server.requested_paths.find(UPLOADS)
|
|
assert_str(JSON.parse_string(_server.requested_bodies[start]).name) \
|
|
.is_equal("Chrono Trigger (SNES)")
|
|
var first_song: int = _server.requested_paths.find(_file_path("01 Title.mp3"))
|
|
assert_int(_server.requested_methods[first_song]).is_equal(HTTPClient.METHOD_PUT)
|
|
assert_str(_server.requested_bodies[first_song]).is_equal("ID3 title")
|
|
assert_array(_server.requested_headers[first_song]).contains(["Content-Type: audio/mpeg"])
|
|
assert_array(added).contains_exactly(["Chrono Trigger"])
|
|
assert_str(_popup.status_label.text).is_equal("Added Chrono Trigger with 2 songs.")
|
|
assert_bool(_popup.upload_button.visible).is_false()
|
|
assert_str(_popup.cancel_button.text).is_equal("Close")
|
|
# Search finds the new soundtrack
|
|
assert_int(_server.count("/soundtracks")).is_greater(searches_before)
|
|
|
|
|
|
func test_song_that_fails_once_is_sent_again() -> void:
|
|
_server_accepts_everything()
|
|
_server.fail_first(_file_path("01 Title.mp3"), 1)
|
|
_popup.show_folder(_dir)
|
|
|
|
_popup.upload()
|
|
|
|
assert_int(_server.count(_file_path("01 Title.mp3"))).is_equal(2)
|
|
assert_int(_server.count("%s/%s/finish" % [UPLOADS, UPLOAD_ID])).is_equal(1)
|
|
|
|
|
|
func test_song_that_keeps_failing_cancels_the_upload() -> void:
|
|
_server_accepts_everything()
|
|
_server.respond_json(_file_path("01 Title.mp3"),
|
|
{"error": "\"01 Title.mp3\" is not an MP3 file"}, 400)
|
|
_popup.show_folder(_dir)
|
|
|
|
_popup.upload()
|
|
|
|
assert_int(_server.count(_file_path("01 Title.mp3"))).is_equal(UploadPopup.RETRIES + 1)
|
|
var cancel: int = _server.requested_paths.find("%s/%s" % [UPLOADS, UPLOAD_ID])
|
|
assert_int(_server.requested_methods[cancel]).is_equal(HTTPClient.METHOD_DELETE)
|
|
assert_int(_server.count("%s/%s/finish" % [UPLOADS, UPLOAD_ID])).is_equal(0)
|
|
assert_str(_popup.status_label.text).is_equal(
|
|
"Could not upload 01 Title.mp3: \"01 Title.mp3\" is not an MP3 file.")
|
|
assert_bool(_popup.upload_button.disabled).is_false()
|
|
|
|
|
|
func test_taken_name_says_so() -> void:
|
|
_server.respond_json(UPLOADS,
|
|
{"error": "already exists: a soundtrack called \"Chrono Trigger\""}, 409)
|
|
_popup.show_folder(_dir)
|
|
|
|
_popup.upload()
|
|
|
|
assert_str(_popup.status_label.text).contains("a soundtrack called \"Chrono Trigger\"")
|
|
assert_array(_upload_requests()).contains_exactly([UPLOADS])
|
|
assert_bool(_popup.name_field.editable).is_true()
|
|
|
|
|
|
func test_closing_during_the_upload_cancels_it() -> void:
|
|
_server_accepts_everything()
|
|
_server.hold(_file_path("01 Title.mp3"))
|
|
_popup.show_folder(_dir)
|
|
_popup.upload()
|
|
|
|
_popup.close()
|
|
_server.release(_file_path("01 Title.mp3"))
|
|
|
|
assert_int(_server.count("%s/%s" % [UPLOADS, UPLOAD_ID])).is_equal(1)
|
|
assert_int(_server.count(_file_path("01 Ending.mp3"))).is_equal(0)
|
|
|
|
|
|
func test_no_upload_while_the_server_syncs() -> void:
|
|
Settings.currently_syncing = true
|
|
_popup.show_folder(_dir)
|
|
|
|
_popup.upload()
|
|
|
|
assert_array(_upload_requests()).is_empty()
|
|
assert_str(_popup.status_label.text).contains("syncing")
|
|
|
|
|
|
func test_folder_without_mp3s_cannot_be_uploaded() -> void:
|
|
var empty: String = _dir.get_base_dir().path_join("Empty")
|
|
DirAccess.make_dir_recursive_absolute(empty)
|
|
|
|
_popup.show_folder(empty)
|
|
|
|
assert_bool(_popup.upload_button.disabled).is_true()
|
|
assert_str(_popup.status_label.text).contains("no MP3 files")
|
|
|
|
|
|
func test_empty_name_cannot_be_uploaded() -> void:
|
|
_popup.show_folder(_dir)
|
|
|
|
_popup.name_field.text = " .. "
|
|
_popup.name_field.text_changed.emit(_popup.name_field.text)
|
|
|
|
assert_bool(_popup.upload_button.disabled).is_true()
|
|
|
|
|
|
func test_dropping_a_folder_opens_it() -> void:
|
|
_window.on_files_dropped(PackedStringArray([_dir]))
|
|
|
|
assert_bool(_popup.visible).is_true()
|
|
assert_str(_popup.folder.path).is_equal(_dir)
|
|
|
|
|
|
func test_dropping_songs_opens_their_folder() -> void:
|
|
_window.on_files_dropped(PackedStringArray([_dir.path_join("01 Title.mp3")]))
|
|
|
|
assert_str(_popup.folder.path).is_equal(_dir)
|
|
|
|
|
|
func test_statistics_has_the_button() -> void:
|
|
var statistics: StatisticsView = _window.get_node("StatisticsView")
|
|
|
|
assert_bool(statistics.add_soundtrack_pressed.is_connected(_popup.choose_folder)).is_true()
|
|
assert_str(statistics.add_soundtrack_button.text).is_equal("Add soundtrack…")
|
|
|
|
|
|
@warning_ignore("unused_parameter")
|
|
func test_short_path(path: String, shown: String, test_parameters: Array = [
|
|
["/home/me/Music/Chrono Trigger", "…/Music/Chrono Trigger"],
|
|
["C:\\Music\\Soundtracks\\Chrono Trigger", "…/Soundtracks/Chrono Trigger"],
|
|
["/Music", "/Music"],
|
|
]) -> void:
|
|
assert_str(UploadPopup.short_path(path)).is_equal(shown)
|