fix: persist product archive cover and provenance
This commit is contained in:
@@ -55,6 +55,7 @@ def product_archive_client(tmp_path, monkeypatch):
|
||||
monkeypatch.setattr(service_app, "product_archive_store", store, raising=False)
|
||||
client = TestClient(service_app.app)
|
||||
client.archive_root = archive_root
|
||||
client.product_store = store
|
||||
return client
|
||||
|
||||
|
||||
@@ -89,6 +90,15 @@ def prepared_wordcloud_job(tmp_path, monkeypatch):
|
||||
)
|
||||
|
||||
|
||||
def archive_product_version(client, product_id, document, preview=PNG_BYTES):
|
||||
return client.post(
|
||||
f"/api/products/{product_id}/versions",
|
||||
data={"document_json": json.dumps(document)},
|
||||
files={"preview": ("design-preview.png", preview, "image/png")},
|
||||
headers=orders_auth_header(),
|
||||
)
|
||||
|
||||
|
||||
def test_scanner_keeps_only_visible_wordcloud_assets_and_deduplicates():
|
||||
document = {
|
||||
"layers": [{"id": "shown", "visible": True}, {"id": "hidden", "visible": False}],
|
||||
@@ -252,3 +262,88 @@ def test_archive_version_handles_document_without_wordcloud_sources(product_arch
|
||||
|
||||
assert response.status_code == 201
|
||||
assert response.json()["wordcloud_count"] == 0
|
||||
|
||||
|
||||
def test_archive_version_rejects_invalid_png_bytes_with_png_mime(product_archive_client):
|
||||
product = create_product(product_archive_client)
|
||||
|
||||
response = archive_product_version(product_archive_client, product["product_id"], {"elements": []}, b"\x89PNG\r\n\x1a\nnot-an-image")
|
||||
|
||||
assert response.status_code == 400
|
||||
|
||||
|
||||
def test_archive_version_persists_each_multi_source_provenance(product_archive_client, prepared_wordcloud_job, monkeypatch):
|
||||
second_db = prepared_wordcloud_job.workspace / "output" / "second.sqlite"
|
||||
with sqlite3.connect(second_db) as connection:
|
||||
connection.execute("CREATE TABLE word_locations (id INTEGER PRIMARY KEY, name TEXT)")
|
||||
second_asset = "asset_second"
|
||||
second_dir = service_app._asset_dir(second_asset)
|
||||
second_dir.mkdir(parents=True)
|
||||
(second_dir / "meta.json").write_text(json.dumps({"type": "wordcloud", "job_id": "job-second"}), encoding="utf-8")
|
||||
monkeypatch.setattr(
|
||||
service_app, "_resolve_job_status",
|
||||
lambda job_id: SimpleNamespace(status="success", artifacts={"db": str(prepared_wordcloud_job.db_path if job_id == "job-success" else second_db)}),
|
||||
)
|
||||
product = create_product(product_archive_client)
|
||||
response = archive_product_version(product_archive_client, product["product_id"], {
|
||||
"elements": [
|
||||
{"type": "sticker", "assetId": prepared_wordcloud_job.asset_id},
|
||||
{"type": "sticker", "assetId": second_asset},
|
||||
]
|
||||
})
|
||||
|
||||
assert response.status_code == 201
|
||||
archives = {item["source_job_id"]: item for item in response.json()["wordcloud_archives"]}
|
||||
assert archives["job-success"]["source_asset_id"] == prepared_wordcloud_job.asset_id
|
||||
assert archives["job-second"]["source_asset_id"] == second_asset
|
||||
assert archives["job-success"]["db_checksum"] == hashlib.sha256(Path(archives["job-success"]["db_path"]).read_bytes()).hexdigest()
|
||||
assert archives["job-second"]["db_checksum"] == hashlib.sha256(Path(archives["job-second"]["db_path"]).read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def test_later_archive_version_becomes_current_cover_in_product_contract(product_archive_client):
|
||||
product = create_product(product_archive_client)
|
||||
first = archive_product_version(product_archive_client, product["product_id"], {"elements": []})
|
||||
first_cover = product_archive_client.get(f"/api/products/{product['product_id']}", headers=orders_auth_header()).json()["cover_image_id"]
|
||||
second = archive_product_version(product_archive_client, product["product_id"], {"elements": []})
|
||||
detail = product_archive_client.get(f"/api/products/{product['product_id']}", headers=orders_auth_header()).json()
|
||||
listed = product_archive_client.get("/api/products", headers=orders_auth_header()).json()[0]
|
||||
|
||||
assert first.status_code == second.status_code == 201
|
||||
assert detail["cover_image_id"] == listed["cover_image_id"]
|
||||
assert detail["cover_image_id"] != first_cover
|
||||
|
||||
|
||||
def test_product_routes_require_orders_auth(product_archive_client):
|
||||
product = create_product(product_archive_client)
|
||||
requests = [
|
||||
product_archive_client.post("/api/products", json={"name": "未授权", "source": "manual"}),
|
||||
product_archive_client.get("/api/products"),
|
||||
product_archive_client.get(f"/api/products/{product['product_id']}"),
|
||||
product_archive_client.post(f"/api/products/{product['product_id']}/versions"),
|
||||
product_archive_client.delete(f"/api/products/{product['product_id']}"),
|
||||
product_archive_client.post(f"/api/products/{product['product_id']}/restore"),
|
||||
]
|
||||
|
||||
assert [response.status_code for response in requests] == [403] * 6
|
||||
|
||||
|
||||
def test_archive_failure_during_final_move_removes_staging_files(product_archive_client, prepared_wordcloud_job, monkeypatch):
|
||||
product = create_product(product_archive_client)
|
||||
service = service_app._product_archive_service()
|
||||
monkeypatch.setattr(service, "_move_staging", lambda *_: (_ for _ in ()).throw(RuntimeError("move failed")))
|
||||
|
||||
with pytest.raises(RuntimeError, match="move failed"):
|
||||
service.archive_version(product["product_id"], prepared_wordcloud_job.document, PNG_BYTES)
|
||||
|
||||
assert not list((product_archive_client.archive_root / product["product_id"]).iterdir())
|
||||
|
||||
|
||||
def test_archive_failure_during_metadata_transaction_removes_final_files(product_archive_client, prepared_wordcloud_job, monkeypatch):
|
||||
product = create_product(product_archive_client)
|
||||
monkeypatch.setattr(product_archive_client.product_store, "_before_archive_commit", lambda: (_ for _ in ()).throw(RuntimeError("metadata failed")))
|
||||
|
||||
with pytest.raises(RuntimeError, match="metadata failed"):
|
||||
service_app._product_archive_service().archive_version(product["product_id"], prepared_wordcloud_job.document, PNG_BYTES)
|
||||
|
||||
assert not list((product_archive_client.archive_root / product["product_id"]).iterdir())
|
||||
assert product_archive_client.product_store._fetchall("SELECT * FROM product_versions") == []
|
||||
|
||||
Reference in New Issue
Block a user