feat: add product archive metadata store
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
import sys
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
BACKEND_DIR = Path(__file__).resolve().parents[1]
|
||||
if str(BACKEND_DIR) not in sys.path:
|
||||
sys.path.insert(0, str(BACKEND_DIR))
|
||||
|
||||
from service.product_archive_store import ProductArchiveStore # noqa: E402
|
||||
from service.schemas import ProductInput # noqa: E402
|
||||
|
||||
|
||||
NOW = datetime(2026, 9, 12, 8, 0, tzinfo=timezone.utc)
|
||||
|
||||
|
||||
def test_external_product_id_updates_one_product(tmp_path):
|
||||
store = ProductArchiveStore(tmp_path / "service_products")
|
||||
|
||||
first = store.upsert_product(
|
||||
ProductInput("external", "sku-42", "笔盒", "B-42", "黄色")
|
||||
)
|
||||
second = store.upsert_product(
|
||||
ProductInput("external", "sku-42", "笔盒新版", "B-42", "黄色")
|
||||
)
|
||||
|
||||
assert first.product_id == second.product_id
|
||||
assert store.list_products(query="新版")[0].name == "笔盒新版"
|
||||
|
||||
|
||||
def test_manual_product_has_internal_id_and_soft_delete_window(tmp_path):
|
||||
store = ProductArchiveStore(tmp_path / "service_products")
|
||||
|
||||
product = store.upsert_product(
|
||||
ProductInput("manual", None, "校长笔盒", "", "166 × 47 mm")
|
||||
)
|
||||
store.mark_pending_cleanup(product.product_id, now=NOW)
|
||||
|
||||
assert product.product_id.startswith("prod_")
|
||||
assert store.get_product(product.product_id).status == "pending_cleanup"
|
||||
assert store.get_product(product.product_id).purge_after == NOW + timedelta(days=30)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"product_input",
|
||||
[
|
||||
ProductInput("manual", None, "", "B-42", "黄色"),
|
||||
ProductInput("external", None, "笔盒", "B-42", "黄色"),
|
||||
ProductInput("external", "", "笔盒", "B-42", "黄色"),
|
||||
],
|
||||
)
|
||||
def test_upsert_rejects_invalid_product_inputs(tmp_path, product_input):
|
||||
store = ProductArchiveStore(tmp_path / "service_products")
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
store.upsert_product(product_input)
|
||||
|
||||
|
||||
def test_unknown_product_operations_are_rejected(tmp_path):
|
||||
store = ProductArchiveStore(tmp_path / "service_products")
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
store.get_product("prod_missing")
|
||||
with pytest.raises(ValueError):
|
||||
store.mark_pending_cleanup("prod_missing", now=NOW)
|
||||
|
||||
|
||||
def test_purge_requires_due_pending_cleanup_and_lists_due_products(tmp_path):
|
||||
store = ProductArchiveStore(tmp_path / "service_products")
|
||||
product = store.upsert_product(ProductInput("manual", None, "待归档笔盒"))
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
store.purge_product(product.product_id, now=NOW)
|
||||
|
||||
store.mark_pending_cleanup(product.product_id, now=NOW)
|
||||
future = NOW + timedelta(days=29)
|
||||
with pytest.raises(ValueError):
|
||||
store.purge_product(product.product_id, now=future)
|
||||
assert store.due_product_cleanups(now=future) == []
|
||||
|
||||
due = NOW + timedelta(days=30)
|
||||
assert [record.product_id for record in store.due_product_cleanups(now=due)] == [
|
||||
product.product_id
|
||||
]
|
||||
assert store.purge_product(product.product_id, now=due).status == "purged"
|
||||
|
||||
|
||||
def test_restore_removes_product_from_due_cleanup_selection(tmp_path):
|
||||
store = ProductArchiveStore(tmp_path / "service_products")
|
||||
product = store.upsert_product(ProductInput("manual", None, "恢复笔盒"))
|
||||
store.mark_pending_cleanup(product.product_id, now=NOW)
|
||||
|
||||
restored = store.restore_product(product.product_id, now=NOW + timedelta(days=1))
|
||||
|
||||
assert restored.status == "active"
|
||||
assert restored.purge_after is None
|
||||
assert store.due_product_cleanups(now=NOW + timedelta(days=31)) == []
|
||||
|
||||
|
||||
def test_version_bound_operations_reject_unknown_or_mismatched_versions(tmp_path):
|
||||
store = ProductArchiveStore(tmp_path / "service_products")
|
||||
first = store.upsert_product(ProductInput("manual", None, "第一个笔盒"))
|
||||
second = store.upsert_product(ProductInput("manual", None, "第二个笔盒"))
|
||||
version = store.create_version(first.product_id)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
store.add_image(first.product_id, "ver_missing")
|
||||
with pytest.raises(ValueError):
|
||||
store.add_wordcloud_archive(first.product_id, "ver_missing")
|
||||
with pytest.raises(ValueError):
|
||||
store.add_image(second.product_id, version.version_id)
|
||||
with pytest.raises(ValueError):
|
||||
store.add_wordcloud_archive(second.product_id, version.version_id)
|
||||
Reference in New Issue
Block a user