feat: add favorite task templates and optimize evidence exports
This commit is contained in:
1 parent
e1ea099592
commit
a4a5430922
46 files changed
+1714
-188
No files matched your search
@@ -158,6 +158,7 @@ def test_completed_batch_reuses_existing_archive(client, isolated, monkeypatch):
|
||||
path = reports.EXPORT_ROOT / "batches" / batch_id / "批次报告.zip"
|
||||
path.parent.mkdir(parents=True)
|
||||
path.write_bytes(b"existing archive")
|
||||
reports.remember(path, reports.BATCH_ARCHIVE_SIGNATURE)
|
||||
exporter = Mock(side_effect=AssertionError("archive must not be rebuilt"))
|
||||
monkeypatch.setattr(reports, "export_batch", exporter)
|
||||
base = f"/api/automation/batches/{batch_id}/archive"
|
||||
@@ -166,6 +167,21 @@ def test_completed_batch_reuses_existing_archive(client, isolated, monkeypatch):
|
||||
exporter.assert_not_called()
|
||||
|
||||
|
||||
def test_legacy_batch_archive_is_rebuilt_with_current_recording_layout(client, isolated):
|
||||
batch = service.create_batch(payload())
|
||||
batch_id = batch["id"]
|
||||
isolated.put("batch", batch_id, {**batch, "status": "completed", "report_ready": True})
|
||||
path = reports.EXPORT_ROOT / "batches" / batch_id / "批次报告.zip"
|
||||
path.parent.mkdir(parents=True)
|
||||
path.write_bytes(b"legacy archive")
|
||||
base = f"/api/automation/batches/{batch_id}/archive"
|
||||
assert client.get(base + "/status").json()["status"] == "idle"
|
||||
response = client.get(base)
|
||||
assert response.status_code == 200
|
||||
assert zipfile.is_zipfile(BytesIO(response.content))
|
||||
assert client.get(base + "/status").json()["status"] == "ready"
|
||||
|
||||
|
||||
def test_preview_does_not_enqueue(client, isolated):
|
||||
response = client.post(
|
||||
"/api/automation/import/preview",
|
||||
@@ -1021,7 +1037,7 @@ def test_task_material_zip_contains_ordered_evidence_and_reports(
|
||||
assert files.index("1.1/步骤截图/0001_步骤0001_操作前.png") < files.index(
|
||||
"1.1/步骤截图/0002_步骤0002_操作后.png"
|
||||
)
|
||||
assert archive.read("1.1/任务录屏/0001_recording.mp4") == b"recording.mp4"
|
||||
assert archive.read("1.1/任务录屏/安卓/0001_recording.mp4") == b"recording.mp4"
|
||||
manifest = json.loads(archive.read("1.1/文件清单.json"))
|
||||
assert manifest["session_id"] == job["session_id"]
|
||||
assert manifest["artifacts"] == ["安卓截图.png"]
|
||||
@@ -1042,6 +1058,63 @@ def test_batch_zip_contains_each_tasks_android_artifacts(isolated):
|
||||
assert {archive.read(name).decode() for name in names} == {"任务0", "任务1"}
|
||||
|
||||
|
||||
def test_all_material_cache_reuses_reports_and_invalidates_on_review_or_new_files(
|
||||
isolated, monkeypatch
|
||||
):
|
||||
job = isolated.jobs(service.create_batch(payload())["id"])[0]
|
||||
job["status"] = "completed"
|
||||
isolated.save_job(job)
|
||||
word = Mock(wraps=reports.word_report)
|
||||
monkeypatch.setattr(reports, "word_report", word)
|
||||
monkeypatch.setattr(
|
||||
reports,
|
||||
"diagnostics_json",
|
||||
lambda sid: json.dumps({"requests": [], "collected_at": time.time()}),
|
||||
)
|
||||
first = reports.export_session_media(job["session_id"], "all")
|
||||
first_bytes = first.read_bytes()
|
||||
reports.export_session_media(job["session_id"], "all")
|
||||
assert word.call_count == 1
|
||||
assert first.read_bytes() == first_bytes
|
||||
isolated.save_case_review(job["session_id"], "passed", "人工核对")
|
||||
reports.export_session_media(job["session_id"], "all")
|
||||
assert word.call_count == 2
|
||||
folder = reports.TRACES_PATH / job["session_id"] / "artifacts" / "android"
|
||||
folder.mkdir(parents=True)
|
||||
(folder / "新资料.txt").write_text("new evidence", encoding="utf-8")
|
||||
reports.export_session_media(job["session_id"], "all")
|
||||
with zipfile.ZipFile(first) as archive:
|
||||
assert any(name.endswith("任务文件/安卓/新资料.txt") for name in archive.namelist())
|
||||
assert not json.loads(
|
||||
archive.read(next(n for n in archive.namelist() if n.endswith("文件清单.json")))
|
||||
)["snapshot"]
|
||||
# File-only changes rebuild the ZIP while keeping unchanged documents.
|
||||
assert word.call_count == 2
|
||||
|
||||
|
||||
def test_different_tasks_export_concurrently_and_running_exports_are_snapshots(
|
||||
isolated, monkeypatch
|
||||
):
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
||||
jobs = isolated.jobs(service.create_batch(payload())["id"])
|
||||
barrier = threading.Barrier(2)
|
||||
|
||||
def generate(report, path):
|
||||
barrier.wait(timeout=5)
|
||||
path.write_bytes(b"generated report")
|
||||
|
||||
monkeypatch.setattr(reports, "word_report", generate)
|
||||
with ThreadPoolExecutor(max_workers=2) as pool:
|
||||
exports = [pool.submit(reports.export_session, j["session_id"], ["docx"]) for j in jobs]
|
||||
assert all(result.result(timeout=10)[0].is_file() for result in exports)
|
||||
count = Mock(side_effect=lambda report, path: path.write_bytes(b"snapshot"))
|
||||
monkeypatch.setattr(reports, "word_report", count)
|
||||
for _ in range(2):
|
||||
reports.export_session(jobs[0]["session_id"], ["docx"])
|
||||
assert count.call_count == 2
|
||||
|
||||
|
||||
def test_android_artifact_export_does_not_follow_other_session_links(isolated):
|
||||
root = reports.TRACES_PATH / "session" / "artifacts"
|
||||
root.mkdir(parents=True)
|
||||
@@ -1078,8 +1151,8 @@ def test_video_export_accepts_finalized_worker_trace_and_rejects_external_path(
|
||||
with zipfile.ZipFile(archive_path) as archive:
|
||||
names = archive.namelist()
|
||||
assert names == [
|
||||
"任务录屏/0001_web_123_" + session_id[:8] + "_PASS_2026-09-28_recording.mp4",
|
||||
"任务录屏/0002_resume_0001_recording.mp4",
|
||||
"任务录屏/安卓/0001_web_123_" + session_id[:8] + "_PASS_2026-09-28_recording.mp4",
|
||||
"任务录屏/安卓/0002_resume_0001_recording.mp4",
|
||||
"文件清单.json",
|
||||
]
|
||||
assert archive.read(names[0]) == b"first segment"
|
||||
|
||||
Reference in new issue
Block a user