m2-market/indexer/tests/test_reindex_flow.py
m2 (AI Agent) 1d10589cef T015: catalog indexer (reindex + watch)
Registry (Forgejo) + memory-api clients under m2_market_indexer.client, with
memory-api paths/creds as env-overridable constants (config.py). reindex.py
drops market:catalog and rebuilds from published listings on main, skipping
schema-invalid ones. watch.py polls the registry tip commit against a cached
state.json and upserts changed/new published listings, deletes delisted ones.
2026-07-02 02:51:30 +02:00

145 lines
4.9 KiB
Python

"""Inline httpx MockTransport test for the reindex flow (T015 verify step).
Registry fixture: one `published` + one `delisted` listing. Asserts the
resulting memory-api calls are exactly: drop partition, then upsert the
published listing only (the delisted one is silently omitted by a
drop-then-upsert-published rebuild — contracts/catalog-index.md "reindex").
Run directly: `python indexer/tests/test_reindex_flow.py`
Or via pytest: `pytest indexer/tests/test_reindex_flow.py`
"""
from __future__ import annotations
import base64
import json
import sys
from pathlib import Path
import httpx
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
from jsonschema import Draft202012Validator # noqa: E402
from m2_market_indexer.client import MemoryClient, RegistryClient # noqa: E402
from m2_market_indexer.reindex import run # noqa: E402
SCHEMA_PATH = Path(__file__).resolve().parents[2] / "schemas" / "listing.schema.json"
LISTING_PUBLISHED = {
"schema_version": "m2.listing.v1",
"listing_id": "lst_pdf-export",
"solution_id": "sol_pdf-export",
"solution_version": "1.2.0",
"inventory_type": "solution",
"name": "PDF Export",
"summary": "Branded PDF report generation",
"category": "reporting",
"keywords": ["pdf", "report", "branding"],
"price": {"amount": 50, "currency": "m2cr", "model": "one_time"},
"seller": "op_nasr",
"evidence_summary": "12 installs, 4.6 avg rating",
"tenant_visibility": ["*"],
"stats": {"installs": 12, "proposals_shown": 20, "proposals_accepted": 12},
"status": "published",
"install_ref": "lst_pdf-export-v1.2.0",
}
LISTING_DELISTED = {
**LISTING_PUBLISHED,
"listing_id": "lst_old-tool",
"name": "Old Tool",
"status": "delisted",
"install_ref": "lst_old-tool-v0.9.0",
}
SOLUTION_PUBLISHED = {"intent": "generate a branded PDF report from a data export"}
def _b64_contents(payload: dict) -> dict:
encoded = base64.b64encode(json.dumps(payload).encode("utf-8")).decode("ascii")
return {"content": encoded}
def _make_handlers():
calls: list[tuple[str, str]] = []
def registry_handler(request: httpx.Request) -> httpx.Response:
calls.append(("registry", f"{request.method} {request.url.path}"))
path = request.url.path
if path == "/api/v1/repos/m2/market-registry/contents/listings":
return httpx.Response(
200,
json=[
{"name": "lst_pdf-export", "type": "dir"},
{"name": "lst_old-tool", "type": "dir"},
],
)
listings_base = "/api/v1/repos/m2/market-registry/contents/listings"
if path == f"{listings_base}/lst_pdf-export/listing.json":
return httpx.Response(200, json=_b64_contents(LISTING_PUBLISHED))
if path == f"{listings_base}/lst_pdf-export/solution.json":
return httpx.Response(200, json=_b64_contents(SOLUTION_PUBLISHED))
if path == f"{listings_base}/lst_old-tool/listing.json":
return httpx.Response(200, json=_b64_contents(LISTING_DELISTED))
return httpx.Response(404, json={"message": "not found"})
def memory_handler(request: httpx.Request) -> httpx.Response:
calls.append(("memory", f"{request.method} {request.url.path}"))
return httpx.Response(200, json={"ok": True})
return calls, registry_handler, memory_handler
def _exercise_reindex_flow():
calls, registry_handler, memory_handler = _make_handlers()
registry = RegistryClient(
"https://git.machinemachine.ai",
"frg_test",
"m2/market-registry",
"main",
transport=httpx.MockTransport(registry_handler),
)
memory = MemoryClient(
"https://memory.machinemachine.ai",
"mem_test",
"market:catalog",
partition_path="/partitions/{partition}",
document_path="/partitions/{partition}/documents/{doc_id}",
transport=httpx.MockTransport(memory_handler),
)
with SCHEMA_PATH.open() as f:
validator = Draft202012Validator(json.load(f))
counts = run(registry, memory, validator)
assert counts == {
"scanned": 2,
"invalid": 0,
"not_published": 1,
"upserted": 1,
}, counts
memory_calls = [c for kind, c in calls if kind == "memory"]
assert memory_calls == [
"DELETE /partitions/market:catalog",
"PUT /partitions/market:catalog/documents/lst_pdf-export",
], memory_calls
registry_calls = [c for kind, c in calls if kind == "registry"]
assert any("lst_pdf-export/solution.json" in c for c in registry_calls)
assert not any("lst_old-tool/solution.json" in c for c in registry_calls)
return counts, memory_calls
def test_reindex_flow_upserts_published_only():
_exercise_reindex_flow()
if __name__ == "__main__":
counts, memory_calls = _exercise_reindex_flow()
print("OK: reindex counts ->", counts)
print("OK: memory-api calls ->", memory_calls)