Files
MusicPlayer/tests/integration/test_upload_popup.gd
T
SansanandClaude Opus 5.5 c86dacb890
Build / test (pull_request) Successful in 2m3s
Build / build (pull_request) Skipped
Add a soundtrack by picking or dropping a folder (#50)
"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>
2026-10-15 21:43:59 +02:00

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)