"""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"]