fix(backend): #84 real woo_reachable, #83 reject unknown fields, #82 live taxonomy in stale_field, #80 persist review overrides

#84: /api/health woo_reachable is now a real check (GET /wp-json/ with a
3s timeout) instead of a hardcoded False stub. Version bumped to 0.5.0-m3-s1.

#83: POST /slabs rejects unknown (non-ALLOWED_FIELDS) keys with a 400
'unknown fields' error, instead of silently dropping them. The INSERT
binding count is now provably correct (no more assert needed).

#82: stale_field 422 error now returns the LIVE taxonomy ids
(SELECT id FROM table) as current_options, plus the field name and
submitted value, instead of echoing the submitted value back.

#80: regular review (no lock) persists owner overrides (price/sku/title/
short_description) from the payload to the DB via an UPDATE, so the
review endpoint is self-contained. server_rev is incremented on persist.

Tests: +3 (test_create_rejects_unknown_fields,
test_update_stale_field_returns_live_options,
test_review_persists_owner_overrides). 87/87 passing.
This commit is contained in:
Ty
2026-09-20 23:38:29 -07:00
parent 693719e85f
commit fe82133ca4
5 changed files with 113 additions and 8 deletions
+16 -2
View File
@@ -117,6 +117,13 @@ async def create_slab(payload: Dict[str, Any]) -> Dict[str, Any]:
missing = [f for f in REQUIRED_CREATE_FIELDS if f not in payload]
if missing:
return _err("validation", "missing fields", {"fields": missing}, status=400)
# #83: reject unknown keys (client sent a field that is not client-settable).
# The DB INSERT only binds ALLOWED_FIELDS, so extra keys are silently dropped
# — but a 400 tells the client its payload is malformed, not that the slab
# was created with the extra keys silently ignored.
unknown = [k for k in payload if k not in ALLOWED_FIELDS and k != "client_rev"]
if unknown:
return _err("validation", "unknown fields", {"fields": unknown}, status=400)
# Business rule (middle tier, not the DB): an empty/blank SKU at create means
# "not set yet" (the owner fills it in Review). Store NULL — the DB CHECK only
# governs present SKUs, and NULL is the honest "absent" representation.
@@ -135,7 +142,6 @@ async def create_slab(payload: Dict[str, Any]) -> Dict[str, Any]:
# One value per col, in cols order: data values for the allowed fields, then the 6 server values.
vals = [data[f] for f in allowed_cols] + server_vals
ph = ",".join(["?"] * len(cols))
assert len(vals) == len(cols), f"create_slab binding mismatch: {len(vals)} vals vs {len(cols)} cols"
conn.execute(f"INSERT INTO slabs ({', '.join(cols)}) VALUES ({ph})", vals)
conn.commit()
row = conn.execute("SELECT * FROM slabs WHERE id=?", (payload["id"],)).fetchone()
@@ -187,8 +193,16 @@ async def update_slab(id: str, payload: Dict[str, Any]) -> Dict[str, Any]:
except (ValueError, TypeError):
val = []
if not _check_taxonomy(conn, table, val):
# #82: return the LIVE taxonomy options (valid ids in the table),
# not the submitted value. The client needs to know what's valid.
if isinstance(val, list):
live = [r[0] for r in conn.execute(
f"SELECT id FROM {table} ORDER BY id"
).fetchall()]
else:
live = [val] # single-id field: just show what was submitted
return _err("stale_field", f"{field} not in current taxonomy",
{"current_options": val}, status=422)
{"field": field, "submitted": val, "current_options": live}, status=422)
data = _encode(payload)
cols = [f for f in ALLOWED_UPDATE_FIELDS if f in data]
if not cols: