From 544134f0534d2e4e4fd7ab823d8a0da543fa80ef Mon Sep 17 00:00:00 2001 From: historia Date: Sat, 12 Sep 2026 21:18:34 -0400 Subject: fix: output wavs from gui go into correct directory --- lib/project/tests/test_gui.py | 87 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 69 insertions(+), 18 deletions(-) (limited to 'lib/project/tests/test_gui.py') diff --git a/lib/project/tests/test_gui.py b/lib/project/tests/test_gui.py index 2f17549..83a8220 100644 --- a/lib/project/tests/test_gui.py +++ b/lib/project/tests/test_gui.py @@ -13,7 +13,7 @@ import soundfile as sf pytest.importorskip("fastapi") from fastapi.testclient import TestClient -from producer.config import Settings +from producer.config import Settings, resolve from producer import gui as gui_module from producer.gui import create_app from producer import gui_jobs @@ -24,20 +24,21 @@ URL = "http://127.0.0.1:8765" @pytest.fixture def client(tmp_path): with TestClient(create_app(8765, output_dir=tmp_path), base_url=URL) as client: + # Per-server upload cache; module-global caches would leak stale + # upload ids across tests (id() of dead Path objects gets reused). + client._upload_cache = {} yield client -_UPLOADED = {} - - def upload(client, path, name=None): """Upload once per (file, name) and reuse the entry, like the browser UI.""" - key = (id(path), name) - if key not in _UPLOADED: + cache = client._upload_cache + key = (str(path), name) + if key not in cache: response = client.post("/api/uploads", params={"name": name or path.name}, content=path.read_bytes()) assert response.status_code == 200, response.text - _UPLOADED[key] = response.json() - return _UPLOADED[key] + cache[key] = response.json() + return cache[key] def wait(client, job_id): @@ -200,16 +201,20 @@ def test_real_preview_context_exact_crop_bypass_and_export_trim(client, tmp_path assert repeated["cached"] and repeated["result"]["result_id"] == result["result_id"] changed = wait(client, submit(client, source, "preview", settings=requested | {"highpass": True}, start=2, duration=3)["id"]) assert not changed["cached"] and changed["result"]["result_id"] != result["result_id"] - for _ in range(2): - exported = wait(client, submit(client, source, "export", settings=requested, - output_dir=str(tmp_path))["id"]) - assert exported["status"] == "succeeded", exported - assert exported["result"]["settings"] == requested - assert len(exported["result"]["paths"]) == 3 - assert "not auditioned" in exported["result"]["notice"] - for path in exported["result"]["paths"]: - assert Path(path).is_absolute() and Path(path).is_file() - assert len(list(tmp_path.glob("producer-export-*"))) == 2 + exported = wait(client, submit(client, source, "export", settings=requested, + output_dir=str(tmp_path))["id"]) + assert exported["status"] == "succeeded", exported + assert exported["result"]["settings"] == requested + assert len(exported["result"]["paths"]) == 3 + assert "not auditioned" in exported["result"]["notice"] + # Audio directly in the output directory; the timestamped report in + # the shared logs directory. No producer-export-* folders anywhere. + for path in exported["result"]["paths"]: + assert Path(path).is_absolute() and Path(path).is_file() + assert Path(exported["result"]["paths"][0]) == tmp_path / "input.custom.wav" + assert Path(exported["result"]["paths"][1]) == tmp_path / "input.custom.mp3" + report = Path(exported["result"]["paths"][2]) + assert report.parent.name == "logs" and report.name.startswith("input.custom.") assert hashlib.sha256(source.read_bytes()).hexdigest() == before @@ -221,6 +226,12 @@ def test_export_output_directory_rules(client, wav, bypass, tmp_path): "settings": requested} assert client.post("/api/jobs", json={**base, "output_dir": "relative/folder"}).status_code == 400 assert client.post("/api/jobs", json={**base, "output_dir": str(tmp_path / "missing")}).status_code == 400 + assert client.post("/api/jobs", json={**base, "overwrite": "yes"}).status_code == 400 + assert client.post("/api/export/check", + json={**base, "input_id": "stale"}).status_code == 400 + assert client.post("/api/export/check", + json={"upload_id": entry["upload_id"], "input_id": entry["input_id"], + "settings": requested, "output_dir": "relative"}).status_code == 400 missing = client.post("/api/jobs", json={**base}) assert missing.status_code == 202 job = wait(client, missing.json()["id"]) @@ -229,6 +240,46 @@ def test_export_output_directory_rules(client, wav, bypass, tmp_path): assert Path(path).is_absolute() and Path(path).is_file() Path(path).relative_to(tmp_path) + +def test_export_check_exists_and_overwrite_replaces_audio(client, wav, bypass, tmp_path, logs): + source = wav() + entry = upload(client, source) + requested = asdict(bypass) + body = {"upload_id": entry["upload_id"], "input_id": entry["input_id"], + "settings": requested, "output_dir": str(tmp_path)} + check = client.post("/api/export/check", json=body) + assert check.status_code == 200 + assert check.json() == {"exists": False, "paths": [str(tmp_path / "input.custom.wav")]} + first = wait(client, submit(client, source, "export", settings=requested, + output_dir=str(tmp_path))["id"]) + assert first["status"] == "succeeded", first + assert first["result"]["paths"][0] == str(tmp_path / "input.custom.wav") + assert client.post("/api/export/check", json=body).json() == { + "exists": True, "paths": [str(tmp_path / "input.custom.wav")]} + # Without confirmation the export refuses to replace the audio; with the + # browser-confirmed overwrite flag it replaces it. + refused = wait(client, submit(client, source, "export", settings=requested, + output_dir=str(tmp_path))["id"]) + assert refused["status"] == "failed" and "Output exists" in refused["error"] + replaced = wait(client, submit(client, source, "export", settings=requested, + output_dir=str(tmp_path), overwrite=True)["id"]) + assert replaced["status"] == "succeeded", replaced + assert [path for path in tmp_path.glob("input.custom.*")] == [tmp_path / "input.custom.wav"] + # Reports are timestamped, so both exports left one report each. + assert len(list(logs.glob("input.custom.*.report.json"))) == 2 + + +def test_export_name_matches_pure_builtin_profile(client, wav, tmp_path): + # Naming comes from the server: settings exactly equal to a built-in + # profile keep its name; anything else is 'custom' (no processing needed). + source = wav() + entry = upload(client, source) + pure = asdict(resolve(profile="natural")) + body = {"upload_id": entry["upload_id"], "input_id": entry["input_id"], + "settings": pure, "output_dir": str(tmp_path)} + check = client.post("/api/export/check", json=body) + assert check.json() == {"exists": False, "paths": [str(tmp_path / "input.natural.wav")]} + def test_preview_bounds_and_empty_inputs(client, wav): source = wav() entry = upload(client, source) -- cgit v1.2.3