fix(m3): uuid secure-context fallback + middle-tier SKU business rules
- frontend: uuid() helper (crypto.randomUUID -> getRandomValues) for plain-HTTP LAN UAT - slabs.py: empty SKU at create -> NULL (no-SKU-yet is transient, filled in Review) - review.py: ready-gate enforces REVIEW-45 (SKU 3-24 alnum+hyphen) in the middle tier - DB CHECK stays as backstop only (referential/structural), per layering principle
This commit is contained in:
@@ -180,6 +180,50 @@ def test_soft_delete(client, db):
|
||||
assert resp_get.status_code == 404
|
||||
|
||||
|
||||
def test_create_empty_sku_stored_as_null(client, db):
|
||||
# Business rule (middle tier): empty SKU at create = "not set yet" -> stored as NULL,
|
||||
# not rejected by the DB CHECK. This is the UAT "Continue to Review" path.
|
||||
slab_id = str(uuid.uuid4())
|
||||
payload = {"id": slab_id, "sku": "", "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","species_confidence":0.9,"figure_confidence":0.8,"grade_confidence":0.7,"inference_status":"inferring","calibration_photo_id":None}
|
||||
resp = client.post("/api/v1/slabs", json=payload)
|
||||
assert resp.status_code == 201
|
||||
assert resp.json()["sku"] is None
|
||||
# A second empty-SKU slab is allowed (NULLs are not unique-conflicts).
|
||||
resp2 = client.post("/api/v1/slabs", json={**payload, "id": str(uuid.uuid4())})
|
||||
assert resp2.status_code == 201
|
||||
|
||||
|
||||
def test_create_null_sku_allowed(client, db):
|
||||
slab_id = str(uuid.uuid4())
|
||||
payload = {"id": slab_id, "sku": None, "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","species_confidence":0.9,"figure_confidence":0.8,"grade_confidence":0.7,"inference_status":"inferring","calibration_photo_id":None}
|
||||
resp = client.post("/api/v1/slabs", json=payload)
|
||||
assert resp.status_code == 201
|
||||
assert resp.json()["sku"] is None
|
||||
|
||||
|
||||
def test_ready_gate_rejects_malformed_sku(client, db):
|
||||
# REVIEW-45 enforced in the middle tier: a present-but-out-of-range/char-set SKU is not ready.
|
||||
from backend.review import compute_ready, sku_valid
|
||||
base = {"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,"price":100,"price_source":"recommendation","title":"Test","short_description":"short","description":"desc","content_source":"manual"}
|
||||
# valid
|
||||
assert sku_valid("AB-1") is True
|
||||
ready, missing = compute_ready({**base, "sku": "AB-1"})
|
||||
assert ready is True and "sku" not in missing
|
||||
# too short
|
||||
assert sku_valid("AB") is False
|
||||
ready, missing = compute_ready({**base, "sku": "AB"})
|
||||
assert ready is False and "sku" in missing
|
||||
# bad char
|
||||
ready, missing = compute_ready({**base, "sku": "AB!"})
|
||||
assert ready is False and "sku" in missing
|
||||
# too long
|
||||
ready, missing = compute_ready({**base, "sku": "A" * 25})
|
||||
assert ready is False and "sku" in missing
|
||||
# missing (None)
|
||||
ready, missing = compute_ready({**base, "sku": None})
|
||||
assert ready is False and "sku" in missing
|
||||
|
||||
|
||||
def test_delete_published_conflict(client, db):
|
||||
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","species_confidence":0.9,"figure_confidence":0.8,"grade_confidence":0.7,"inference_status":"inferring","calibration_photo_id":None}
|
||||
|
||||
Reference in New Issue
Block a user