feat(validation): add structural pre-flight validator

validate_structural(order) runs before save_orders_batch insert.
Catches malformed payloads (MISSING_FIELD, INVALID_DATE, EMPTY_ITEMS,
INVALID_QUANTITY, INVALID_PRICE) that would otherwise crash the batch
insert or downstream pipeline. 17 unit tests cover each rule.

Does NOT validate SKU existence — redundant with _dedup_items_by_sku
pass-through and validate_skus Oracle lookup.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-04-22 08:52:32 +00:00
parent f6d283b743
commit 38498bec6d
2 changed files with 232 additions and 0 deletions

View File

@@ -0,0 +1,166 @@
"""Unit tests for validation_service.validate_structural().
Structural pre-flight validator — only catches malformed payloads that
would crash downstream inserts. Does NOT check SKU existence (handled
by validate_skus) or duplicate SKUs (handled by _dedup_items_by_sku).
"""
import os
import sys
import pytest
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
from app.services.validation_service import validate_structural
def _valid_order(**overrides):
base = {
"order_number": "123456",
"order_date": "2026-04-22 10:00:00",
"items": [{"sku": "ABC", "quantity": 2, "price": 15.50}],
}
base.update(overrides)
return base
@pytest.mark.unit
def test_valid_order_passes():
ok, err_type, err_msg = validate_structural(_valid_order())
assert ok is True
assert err_type is None
assert err_msg is None
@pytest.mark.unit
def test_missing_order_number():
ok, err_type, _ = validate_structural(_valid_order(order_number=""))
assert ok is False
assert err_type == "MISSING_FIELD"
ok, err_type, _ = validate_structural(_valid_order(order_number=None))
assert ok is False
assert err_type == "MISSING_FIELD"
@pytest.mark.unit
def test_non_dict_order():
ok, err_type, _ = validate_structural("not a dict")
assert ok is False
assert err_type == "MISSING_FIELD"
@pytest.mark.unit
def test_invalid_date_unparseable():
ok, err_type, _ = validate_structural(_valid_order(order_date="not-a-date"))
assert ok is False
assert err_type == "INVALID_DATE"
@pytest.mark.unit
def test_invalid_date_missing():
ok, err_type, _ = validate_structural(_valid_order(order_date=None))
assert ok is False
assert err_type == "INVALID_DATE"
@pytest.mark.unit
def test_date_iso_format_passes():
ok, _, _ = validate_structural(_valid_order(order_date="2026-04-22T10:00:00"))
assert ok is True
@pytest.mark.unit
def test_empty_items():
ok, err_type, _ = validate_structural(_valid_order(items=[]))
assert ok is False
assert err_type == "EMPTY_ITEMS"
ok, err_type, _ = validate_structural(_valid_order(items=None))
assert ok is False
assert err_type == "EMPTY_ITEMS"
@pytest.mark.unit
def test_items_not_list():
ok, err_type, _ = validate_structural(_valid_order(items="ABC"))
assert ok is False
assert err_type == "EMPTY_ITEMS"
@pytest.mark.unit
def test_item_not_dict():
ok, err_type, _ = validate_structural(_valid_order(items=["just-a-string"]))
assert ok is False
assert err_type == "EMPTY_ITEMS"
@pytest.mark.unit
def test_invalid_quantity_zero():
ok, err_type, _ = validate_structural(
_valid_order(items=[{"sku": "A", "quantity": 0, "price": 1}])
)
assert ok is False
assert err_type == "INVALID_QUANTITY"
@pytest.mark.unit
def test_invalid_quantity_negative():
ok, err_type, _ = validate_structural(
_valid_order(items=[{"sku": "A", "quantity": -3, "price": 1}])
)
assert ok is False
assert err_type == "INVALID_QUANTITY"
@pytest.mark.unit
def test_invalid_quantity_non_numeric():
ok, err_type, _ = validate_structural(
_valid_order(items=[{"sku": "A", "quantity": "abc", "price": 1}])
)
assert ok is False
assert err_type == "INVALID_QUANTITY"
@pytest.mark.unit
def test_invalid_quantity_missing():
ok, err_type, _ = validate_structural(
_valid_order(items=[{"sku": "A", "price": 1}])
)
assert ok is False
assert err_type == "INVALID_QUANTITY"
@pytest.mark.unit
def test_invalid_price_non_numeric():
ok, err_type, _ = validate_structural(
_valid_order(items=[{"sku": "A", "quantity": 1, "price": "NaN-text"}])
)
assert ok is False
assert err_type == "INVALID_PRICE"
@pytest.mark.unit
def test_invalid_price_missing():
ok, err_type, _ = validate_structural(
_valid_order(items=[{"sku": "A", "quantity": 1}])
)
assert ok is False
assert err_type == "INVALID_PRICE"
@pytest.mark.unit
def test_price_zero_allowed():
"""Complex sets can legitimately have price=0 on one leg."""
ok, _, _ = validate_structural(
_valid_order(items=[{"sku": "A", "quantity": 1, "price": 0}])
)
assert ok is True
@pytest.mark.unit
def test_sku_null_passes_structural():
"""SKU validation is handled downstream, NOT here."""
ok, _, _ = validate_structural(
_valid_order(items=[{"sku": None, "quantity": 1, "price": 1}])
)
assert ok is True