feat(greenfield): rebuild SlabUploader M1-M3 on clean foundation, all gates green
Clean re-implementation of the full product (not a port of the old M3 tree): - P0 foundation: 13-table schema + per-call sqlite factory (thread-safe), FastAPI app (health, JSON logging + secret redaction, CORS, startup guard). - P1 M1 Capture: mask/sheetKeep, geometry/rect (min-area rect), measure, png crop/inventory/overlay, EXIF decodeOrientedBitmap. - P2 M2 Photos: stateful capture session (batch, pick-cal, apply mask/crop to batch) + photos-ready lineup. - P3 M3 Create: real backend (crypto, woo client, taxonomy sync, pricing, inference Call1/Call2, review, publish, settings, slabs, photos) + real FE review/publish/settings screens consuming the API (no hardcoded Woo ids). - P4 gate exit: check_requirements + check_coverage both exit 0 (137/155 IDs, 0 gaps); backend 130/130, frontend 150/150, build green. Cite: docs/REQUIREMENTS.md (155 rows), docs/MILESTONES.md M1-M3.
This commit is contained in:
@@ -15,25 +15,54 @@ import backend.db as db_mod
|
||||
@pytest.fixture
|
||||
def env(monkeypatch):
|
||||
tmp = tempfile.mkdtemp(prefix="inf_")
|
||||
os.environ["SLAB_DB_PATH"] = os.path.join(tmp, "i.db")
|
||||
os.environ["SLAB_IMAGES_DIR"] = tmp
|
||||
os.environ["WOO_BASE_URL"] = "https://x.com"
|
||||
os.environ["SLAB_AES_KEY"] = base64.b64encode(b"0123456789abcdef0123456789abcdef").decode()
|
||||
db_mod._reset_for_tests()
|
||||
monkeypatch.setenv("SLAB_DB_PATH", os.path.join(tmp, "i.db"))
|
||||
monkeypatch.setenv("SLAB_IMAGES_DIR", tmp)
|
||||
monkeypatch.setenv("WOO_BASE_URL", "https://x.com")
|
||||
monkeypatch.setenv("SLAB_AES_KEY", base64.b64encode(b"0123456789abcdef0123456789abcdef").decode())
|
||||
from backend.crypto import encrypt
|
||||
|
||||
conn = db_mod.init_db()
|
||||
db_mod.init_db()
|
||||
|
||||
conn = db_mod.get_connection()
|
||||
conn.execute("INSERT OR REPLACE INTO settings(key,value,value_enc) VALUES ('inference_base_url',NULL,?)", (encrypt("https://infer.example.com"),))
|
||||
conn.execute("INSERT OR REPLACE INTO settings(key,value,value_enc) VALUES ('inference_api_key',NULL,?)", (encrypt("sk-test"),))
|
||||
conn.execute("INSERT OR REPLACE INTO settings(key,value,value_enc) VALUES ('inference_model','gpt-test',NULL)")
|
||||
conn.commit()
|
||||
conn.close()
|
||||
from backend.app import app
|
||||
|
||||
yield TestClient(app), conn
|
||||
db_mod._reset_for_tests()
|
||||
# Yield a lazy connection: opening it eagerly would hold a WAL read slot
|
||||
# during the app's first write (prompt seed), whose recovery checkpoint
|
||||
# needs a brief exclusive lock and would deadlock on the open reader.
|
||||
lazy = _LazyConn()
|
||||
with TestClient(app) as c:
|
||||
yield c, lazy
|
||||
shutil.rmtree(tmp, ignore_errors=True)
|
||||
|
||||
|
||||
class _LazyConn:
|
||||
"""Defers the sqlite connection to first use (see fixture note)."""
|
||||
|
||||
def __init__(self):
|
||||
self._conn = None
|
||||
|
||||
def _get(self):
|
||||
if self._conn is None:
|
||||
self._conn = db_mod.get_connection()
|
||||
return self._conn
|
||||
|
||||
def execute(self, *a, **k):
|
||||
return self._get().execute(*a, **k)
|
||||
|
||||
def commit(self):
|
||||
return self._get().commit()
|
||||
|
||||
def close(self):
|
||||
if self._conn is not None:
|
||||
self._conn.close()
|
||||
self._conn = None
|
||||
|
||||
|
||||
def test_prompt_seed_and_revision(env):
|
||||
client, conn = env
|
||||
r = client.get("/api/v1/prompts/call1")
|
||||
@@ -77,19 +106,19 @@ def test_call1_seed_is_locked_expert_prompt(env):
|
||||
assert "character\":" not in CALL1_SEED.replace(" ", "")
|
||||
|
||||
|
||||
def test_taxonomy_lists_call1_closed_shape():
|
||||
def test_taxonomy_lists_call1_closed_shape(monkeypatch):
|
||||
"""#86: Call 1 lists are closed Woo picks only — no grade/thickness/moisture; feat names stripped."""
|
||||
import tempfile, os, shutil
|
||||
tmp = tempfile.mkdtemp(prefix="tax_")
|
||||
os.environ["SLAB_DB_PATH"] = os.path.join(tmp, "t.db")
|
||||
os.environ["SLAB_IMAGES_DIR"] = tmp
|
||||
os.environ.setdefault("WOO_BASE_URL", "https://x.com")
|
||||
os.environ.setdefault(
|
||||
monkeypatch.setenv("SLAB_DB_PATH", os.path.join(tmp, "t.db"))
|
||||
monkeypatch.setenv("SLAB_IMAGES_DIR", tmp)
|
||||
monkeypatch.setenv("WOO_BASE_URL", "https://x.com")
|
||||
monkeypatch.setenv(
|
||||
"SLAB_AES_KEY",
|
||||
base64.b64encode(b"0123456789abcdef0123456789abcdef").decode(),
|
||||
)
|
||||
db_mod._reset_for_tests()
|
||||
conn = db_mod.init_db()
|
||||
db_mod.init_db()
|
||||
conn = db_mod.get_connection()
|
||||
conn.execute("INSERT INTO woo_categories(id,name,slug,parent_id,woo_id) VALUES (10,'Species','species',NULL,10)")
|
||||
conn.execute("INSERT INTO woo_categories(id,name,slug,parent_id,woo_id) VALUES (11,'Walnut','walnut',10,11)")
|
||||
conn.execute("INSERT INTO woo_categories(id,name,slug,parent_id,woo_id) VALUES (20,'Wcat','wcat',NULL,20)")
|
||||
@@ -120,7 +149,6 @@ def test_taxonomy_lists_call1_closed_shape():
|
||||
assert "grade_terms" not in tax
|
||||
assert "thickness_terms" not in tax
|
||||
assert "moisture_terms" not in tax
|
||||
db_mod._reset_for_tests()
|
||||
shutil.rmtree(tmp, ignore_errors=True)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user