- #79 publish SKU/create via WooClient (User-Agent + typed 403 unrecoverable) - #82 stale_field current_options from live taxonomy + owner labels - #83 create allowlist/extra-key safe; taxonomy+enum 4xx on create - #84 health woo_reachable real probe (non-failing); version 0.5.0-m3 - #89 review persists assembled title/short after taxonomy lock; UI applies - #98 live-edge from synced term; attrs resolved by slug then name
This commit is contained in:
@@ -300,3 +300,63 @@ def test_partial_media_failure_502(env, monkeypatch):
|
||||
# sync_log written, status failed
|
||||
assert conn.execute("SELECT status FROM slabs WHERE id='s1'").fetchone()["status"] == "failed"
|
||||
assert conn.execute("SELECT COUNT(*) c FROM sync_log WHERE status='failed'").fetchone()["c"] >= 1
|
||||
|
||||
|
||||
def test_publish_sends_user_agent_on_sku_and_create(env, monkeypatch):
|
||||
"""#79: every publish Woo hop carries User-Agent (same transport as client)."""
|
||||
client, conn = env
|
||||
s = _full_slab()
|
||||
_insert_slab(conn, s)
|
||||
conn.commit()
|
||||
seen = []
|
||||
|
||||
def fake_request(url, method, data, headers):
|
||||
seen.append({"url": url, "method": method, "headers": dict(headers or {})})
|
||||
if "/products?sku=" in url:
|
||||
return 200, {}, b"[]"
|
||||
if url.endswith("/wp-json/wc/v3/products") and method == "POST":
|
||||
return 201, {}, json.dumps({"id": 42}).encode()
|
||||
return 200, {}, b"[]"
|
||||
|
||||
from backend.woo import WooClient, _USER_AGENT
|
||||
|
||||
orig_init = WooClient.__init__
|
||||
|
||||
def patched_init(self, *a, **k):
|
||||
orig_init(self, *a, **k)
|
||||
self._request = fake_request
|
||||
|
||||
monkeypatch.setattr(WooClient, "__init__", patched_init)
|
||||
r = client.post("/api/v1/slabs/s1/publish")
|
||||
assert r.status_code == 200, r.text
|
||||
sku_hops = [h for h in seen if "/products?sku=" in h["url"]]
|
||||
create_hops = [h for h in seen if h["url"].endswith("/wp-json/wc/v3/products") and h["method"] == "POST"]
|
||||
assert sku_hops and create_hops
|
||||
for hop in sku_hops + create_hops:
|
||||
assert hop["headers"].get("User-Agent") == _USER_AGENT
|
||||
|
||||
|
||||
def test_publish_403_is_unrecoverable(env, monkeypatch):
|
||||
"""#79: WAF 403 on SKU lookup is unrecoverable — do not invite retry."""
|
||||
client, conn = env
|
||||
s = _full_slab()
|
||||
_insert_slab(conn, s)
|
||||
conn.commit()
|
||||
from backend.woo import WooClient, WooForbiddenError
|
||||
|
||||
def fake_request(url, method, data, headers):
|
||||
raise WooForbiddenError("request forbidden (403)")
|
||||
|
||||
orig_init = WooClient.__init__
|
||||
|
||||
def patched_init(self, *a, **k):
|
||||
orig_init(self, *a, **k)
|
||||
self._request = fake_request
|
||||
|
||||
monkeypatch.setattr(WooClient, "__init__", patched_init)
|
||||
r = client.post("/api/v1/slabs/s1/publish")
|
||||
assert r.status_code == 502
|
||||
body = r.json()
|
||||
assert body["error"]["code"] == "woo_forbidden"
|
||||
assert body["error"]["detail"]["recoverable"] is False
|
||||
assert "retry" not in body["error"]["message"].lower() or "will not help" in body["error"]["message"].lower()
|
||||
|
||||
@@ -340,18 +340,45 @@ def test_create_rejects_unknown_fields(client, db):
|
||||
|
||||
|
||||
def test_update_stale_field_returns_live_options(client, db):
|
||||
# #82: stale_field error returns the LIVE taxonomy ids, not the submitted value.
|
||||
# #82: stale_field returns LIVE taxonomy options (id+name), not the submitted id.
|
||||
slab_id = str(uuid.uuid4())
|
||||
payload = {"id": slab_id, "sku": "SKU001", "length_in":10,"thickness_in":2,"sqft":20,"bdft":5,"width_min_in":1,"width_max_in":2,"width_avg_in":1.5,"species_id":1,"wood_category_ids":json.dumps([2]),"edge_type_term_id":1,"figure_term_ids":json.dumps([1]),"grade_term_id":1,"thickness_term_id":1,"moisture_term_id":1,"fig_tag_ids":json.dumps([1]),"feat_tag_ids":json.dumps([2]),"price":100,"price_source":"recommendation","title":"Test","short_description":"short","description":"desc","content_source":"manual"}
|
||||
client.post("/api/v1/slabs", json=payload)
|
||||
# Submit a wood_category_id that doesn't exist in the taxonomy (id=999).
|
||||
resp = client.put(f"/api/v1/slabs/{slab_id}", json={"client_rev": 1, "wood_category_ids": json.dumps([999])})
|
||||
payload = {
|
||||
"id": slab_id, "sku": "SKU-STALE", "length_in": 10, "thickness_in": 2, "sqft": 20, "bdft": 5,
|
||||
"width_min_in": 1, "width_max_in": 2, "width_avg_in": 1.5,
|
||||
}
|
||||
assert client.post("/api/v1/slabs", json=payload).status_code == 201
|
||||
resp = client.put(f"/api/v1/slabs/{slab_id}", json={"client_rev": 1, "edge_type_term_id": 999})
|
||||
assert resp.status_code == 422
|
||||
body = resp.json()
|
||||
assert body["error"]["code"] == "stale_field"
|
||||
detail = body["error"]["detail"]
|
||||
assert detail["submitted"] == [999]
|
||||
# current_options must be the LIVE ids from woo_categories, not [999].
|
||||
assert 999 not in detail["current_options"]
|
||||
# The seeded taxonomy has ids 1 and 2 in woo_categories.
|
||||
assert set(detail["current_options"]) == {1, 2}
|
||||
opts = body["error"]["detail"]["current_options"]
|
||||
assert isinstance(opts, list) and opts
|
||||
assert all(isinstance(o, dict) and "id" in o and "name" in o for o in opts)
|
||||
assert 999 not in [o["id"] for o in opts]
|
||||
assert "Edge type" in body["error"]["message"] or "edge" in body["error"]["message"].lower()
|
||||
|
||||
|
||||
def test_create_extra_key_and_bad_taxonomy_are_4xx(client, db):
|
||||
"""#83: server-managed/extra keys and bad taxonomy/enums are 4xx, not 500."""
|
||||
base = {
|
||||
"id": str(uuid.uuid4()), "sku": "SKU-XTRA", "length_in": 10, "thickness_in": 2, "sqft": 20, "bdft": 5,
|
||||
"width_min_in": 1, "width_max_in": 2, "width_avg_in": 1.5,
|
||||
"status": "published", # server-managed → unknown field 400
|
||||
}
|
||||
r = client.post("/api/v1/slabs", json=base)
|
||||
assert r.status_code == 400, r.text
|
||||
assert "status" in r.json()["error"]["detail"]["fields"]
|
||||
# Bad taxonomy on create
|
||||
r2 = client.post("/api/v1/slabs", json={
|
||||
"id": str(uuid.uuid4()), "sku": "SKU-BADTAX", "length_in": 10, "thickness_in": 2, "sqft": 20, "bdft": 5,
|
||||
"width_min_in": 1, "width_max_in": 2, "width_avg_in": 1.5, "species_id": 999,
|
||||
})
|
||||
assert r2.status_code == 422
|
||||
assert r2.json()["error"]["code"] == "stale_field"
|
||||
# Bad enum
|
||||
r3 = client.post("/api/v1/slabs", json={
|
||||
"id": str(uuid.uuid4()), "sku": "SKU-BADENUM", "length_in": 10, "thickness_in": 2, "sqft": 20, "bdft": 5,
|
||||
"width_min_in": 1, "width_max_in": 2, "width_avg_in": 1.5, "price_source": "not-a-source",
|
||||
})
|
||||
assert r3.status_code == 400
|
||||
assert r3.json()["error"]["code"] == "validation"
|
||||
|
||||
Reference in New Issue
Block a user