test(exit): M3 exit wave — inference-off suite + Woo-mock integration + Compose E2E + UAT walk

- test_exit_inference_off.py (QUALITY-20/120): full create->photo->review->publish
  happy path with inference OFF, asserts zero inference_log rows and no raw codes/
  tracebacks reach the owner; not-ready publish 422 with plain field-level message.
- test_integration_woo_mock.py (QUALITY-35): Woo mock covering success, error,
  duplicate SKU, stale field (no raw code), and taxonomy anchor bump on change.
- m3-create.spec.ts (QUALITY-50): Playwright E2E — all M3 dev screens render,
  Review/Publish controls load, Generate control visible only when enabled+reachable.
- deploy/uat_walk.sh (QUALITY-110): ready-to-run human walk for the real SLAB-UAT-*
  draft (inference off) against the live store — NOT a mock.

Verified: pytest 61 passed; frontend unit 88 passed; e2e 4 passed; gates exit 0.
This commit is contained in:
2026-09-19 17:12:14 -07:00
parent 8a8a89ed3e
commit 7575471397
4 changed files with 394 additions and 0 deletions
+130
View File
@@ -0,0 +1,130 @@
"""Exit wave (QUALITY-20, QUALITY-120): full create-to-publish happy path with
inference OFF, asserting zero inference calls and no raw codes reach the owner.
"""
import base64
import json
import os
import shutil
import tempfile
import pytest
from fastapi.testclient import TestClient
import backend.db as db_mod
@pytest.fixture
def env(monkeypatch):
tmp = tempfile.mkdtemp(prefix="exit_")
os.environ["SLAB_DB_PATH"] = os.path.join(tmp, "e.db")
os.environ["SLAB_IMAGES_DIR"] = tmp
os.environ["WOO_BASE_URL"] = "https://store.example.com"
os.environ["SLAB_AES_KEY"] = base64.b64encode(b"0123456789abcdef0123456789abcdef").decode()
db_mod._reset_for_tests()
from backend.crypto import encrypt
conn = db_mod.init_db()
conn.execute("INSERT INTO woo_categories(id,name,slug,parent_id,woo_id) VALUES (1,'Black Walnut','black-walnut',NULL,100)")
conn.execute("INSERT INTO woo_categories(id,name,slug,parent_id,woo_id) VALUES (2,'Live edge wood','live-edge-wood',NULL,101)")
conn.execute("INSERT INTO woo_attributes(id,name,slug,woo_id) VALUES (8,'Edge Type','edge-type',200)")
conn.execute("INSERT INTO woo_attribute_terms(id,attribute_id,name,slug,woo_id) VALUES (1,8,'Flat','flat',300)")
conn.execute("INSERT OR REPLACE INTO settings(key,value,value_enc) VALUES ('woo_wp_username','admin',NULL)")
conn.execute("INSERT OR REPLACE INTO settings(key,value,value_enc) VALUES ('woo_app_password',NULL,?)", (encrypt("pw"),))
conn.execute("INSERT OR REPLACE INTO settings(key,value,value_enc) VALUES ('inference_enabled','false',NULL)")
conn.execute("INSERT OR REPLACE INTO settings(key,value,value_enc) VALUES ('content_llm_enabled','false',NULL)")
from datetime import datetime, timezone
conn.execute("INSERT OR REPLACE INTO settings(key,value,value_enc) VALUES ('taxonomy_last_sync_at',?,NULL)",
(datetime.now(timezone.utc).replace(tzinfo=None).isoformat(),))
conn.commit()
from backend.app import app
yield TestClient(app), conn
db_mod._reset_for_tests()
shutil.rmtree(tmp, ignore_errors=True)
def _mock(monkeypatch, sku_exists=0, product_id=777):
from backend.woo import WooClient
def fake_request(url, method, data, headers):
if "/products?sku=" in url:
return 200, {}, json.dumps([{"id": 1}] * sku_exists).encode()
if url.endswith("/wp-json/wp/v2/media"):
return 201, {}, json.dumps({"id": 42}).encode()
if url.endswith("/wp-json/wc/v3/products"):
return 201, {}, json.dumps({"id": product_id}).encode()
return 200, {}, b"[]"
orig = WooClient.__init__
def patched(self, *a, **k):
orig(self, *a, **k)
def bound(url, method, data, headers):
return fake_request(url, method, data, headers)
self._request = bound
monkeypatch.setattr(WooClient, "__init__", patched)
def test_full_happy_path_inference_off(env, monkeypatch):
"""Create slab -> attach photo -> review -> publish, inference OFF.
Zero inference_log rows; all responses carry plain messages (QUALITY-120)."""
client, conn = env
_mock(monkeypatch)
# 1. create slab
r = client.post("/api/v1/slabs", json={
"id": "s1", "sku": "SLAB-UAT-7", "length_in": 114.0, "thickness_in": 1.75, "sqft": 30.0,
"bdft": 18.4, "width_min_in": 18.0, "width_max_in": 24.3, "width_avg_in": 24.3,
"species_id": 1, "wood_category_ids": [2], "edge_type_term_id": 1, "figure_term_ids": [],
"grade_term_id": 1, "thickness_term_id": 1, "moisture_term_id": 1,
"fig_tag_ids": [], "feat_tag_ids": [], "price": 184.0, "price_source": "recommendation",
"title": "Black Walnut Live Edge Slab", "short_description": "slab, 9ft 6in",
"description": "A fine slab.", "content_source": "manual",
"species_confidence": None, "figure_confidence": None, "grade_confidence": None,
"inference_status": None, "calibration_photo_id": None,
})
assert r.status_code in (201, 200), r.text
# 2. attach photo
r = client.post("/api/v1/slabs/s1/photos", data={"photo_id": "p1"},
files={"original": ("o.png", b"\x89PNG", "image/png")})
assert r.status_code == 201, r.text
# 3. review (deterministic copy)
r = client.post("/api/v1/slabs/s1/review", json={})
assert r.status_code == 200
# 4. publish
r = client.post("/api/v1/slabs/s1/publish")
assert r.status_code == 200, r.text
body = r.json()
assert body["ok"] is True and body["woo_product_id"] == 777 and body["draft"] is True
# QUALITY-20: zero inference calls in off mode
assert conn.execute("SELECT COUNT(*) c FROM inference_log").fetchone()["c"] == 0
# QUALITY-120: response has no raw status codes / stack traces in owner-facing message
assert "Traceback" not in json.dumps(body)
# success log
assert conn.execute("SELECT status FROM sync_log").fetchone()["status"] == "success"
def test_not_ready_publish_422_plain_message(env, monkeypatch):
"""Failure path returns a plain field-level message, no raw code (QUALITY-120)."""
client, conn = env
_mock(monkeypatch)
client.post("/api/v1/slabs", json={
"id": "s1", "sku": "SLAB-01", "length_in": 100, "thickness_in": 2, "sqft": 20, "bdft": 8,
"width_min_in": 10, "width_max_in": 12, "width_avg_in": 11, "species_id": 1,
"wood_category_ids": [2], "edge_type_term_id": 1, "figure_term_ids": [], "grade_term_id": 1,
"thickness_term_id": 1, "moisture_term_id": 1, "fig_tag_ids": [], "feat_tag_ids": [],
"price": None, "price_source": None, "title": "T", "short_description": "S",
"description": "D", "content_source": "manual",
"species_confidence": None, "figure_confidence": None, "grade_confidence": None,
"inference_status": None, "calibration_photo_id": None,
})
r = client.post("/api/v1/slabs/s1/publish")
assert r.status_code == 422
body = r.json()
assert "missing" in body["error"]["detail"]
assert "Traceback" not in json.dumps(body)
assert "price" in body["error"]["detail"]["missing"]