fix(sync): sticky exclusion for DELETED_IN_ROA orders
Orders deleted via "Sterge" button were re-imported on the next sync because classify step only checked Oracle (sters=0), not SQLite status. Adds a filter step after cancellation handling that drops orders already marked DELETED_IN_ROA before validation. "Reimporta" remains the explicit override. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
140
api/tests/test_sticky_deleted_filter.py
Normal file
140
api/tests/test_sticky_deleted_filter.py
Normal file
@@ -0,0 +1,140 @@
|
||||
"""
|
||||
Sticky DELETED_IN_ROA Filter Tests
|
||||
===================================
|
||||
Unit tests for get_deleted_in_roa_order_numbers() helper and integration
|
||||
test for the sticky-exclusion filter applied in sync_service before
|
||||
order classification.
|
||||
|
||||
Run:
|
||||
cd api && python -m pytest tests/test_sticky_deleted_filter.py -v
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
import pytest
|
||||
|
||||
pytestmark = pytest.mark.unit
|
||||
|
||||
_tmpdir = tempfile.mkdtemp()
|
||||
os.environ.setdefault("FORCE_THIN_MODE", "true")
|
||||
os.environ.setdefault("SQLITE_DB_PATH", os.path.join(_tmpdir, "test_sticky_deleted.db"))
|
||||
os.environ.setdefault("ORACLE_DSN", "dummy")
|
||||
os.environ.setdefault("ORACLE_USER", "dummy")
|
||||
os.environ.setdefault("ORACLE_PASSWORD", "dummy")
|
||||
os.environ.setdefault("JSON_OUTPUT_DIR", _tmpdir)
|
||||
|
||||
_api_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
if _api_dir not in sys.path:
|
||||
sys.path.insert(0, _api_dir)
|
||||
|
||||
from app import database
|
||||
from app.services import sqlite_service
|
||||
from app.constants import OrderStatus
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
async def _clean_orders():
|
||||
"""Ensure schema exists, clear orders table before each test."""
|
||||
database.init_sqlite()
|
||||
db = await database.get_sqlite()
|
||||
try:
|
||||
await db.execute("DELETE FROM orders")
|
||||
await db.commit()
|
||||
finally:
|
||||
await db.close()
|
||||
yield
|
||||
|
||||
|
||||
async def _insert_order(order_number: str, status: str, id_comanda: int | None = None):
|
||||
db = await database.get_sqlite()
|
||||
try:
|
||||
await db.execute(
|
||||
"""
|
||||
INSERT INTO orders (order_number, order_date, customer_name, status, id_comanda)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
""",
|
||||
(order_number, "2026-04-22", "Test Customer", status, id_comanda),
|
||||
)
|
||||
await db.commit()
|
||||
finally:
|
||||
await db.close()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_returns_empty_set_when_no_orders():
|
||||
"""Helper unit: empty table → empty set."""
|
||||
result = await sqlite_service.get_deleted_in_roa_order_numbers()
|
||||
assert result == set()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_returns_only_deleted_in_roa_status():
|
||||
"""Helper unit: filters only DELETED_IN_ROA, ignores other statuses."""
|
||||
await _insert_order("ORD-1", OrderStatus.IMPORTED.value, id_comanda=100)
|
||||
await _insert_order("ORD-2", OrderStatus.DELETED_IN_ROA.value)
|
||||
await _insert_order("ORD-3", OrderStatus.CANCELLED.value)
|
||||
await _insert_order("ORD-4", OrderStatus.ERROR.value)
|
||||
await _insert_order("ORD-5", OrderStatus.DELETED_IN_ROA.value)
|
||||
await _insert_order("ORD-6", OrderStatus.SKIPPED.value)
|
||||
|
||||
result = await sqlite_service.get_deleted_in_roa_order_numbers()
|
||||
assert result == {"ORD-2", "ORD-5"}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mark_order_deleted_then_helper_returns_it():
|
||||
"""Integration: mark_order_deleted_in_roa → helper picks it up."""
|
||||
await _insert_order("ORD-100", OrderStatus.IMPORTED.value, id_comanda=500)
|
||||
|
||||
before = await sqlite_service.get_deleted_in_roa_order_numbers()
|
||||
assert "ORD-100" not in before
|
||||
|
||||
await sqlite_service.mark_order_deleted_in_roa("ORD-100")
|
||||
|
||||
after = await sqlite_service.get_deleted_in_roa_order_numbers()
|
||||
assert "ORD-100" in after
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_filter_excludes_deleted_orders():
|
||||
"""Integration: simulates sync filter step.
|
||||
|
||||
Pre-mark ORD-2 as DELETED_IN_ROA, run the same filter logic from
|
||||
sync_service:478-489, assert ORD-2 is excluded.
|
||||
"""
|
||||
await _insert_order("ORD-1", OrderStatus.IMPORTED.value, id_comanda=100)
|
||||
await _insert_order("ORD-2", OrderStatus.DELETED_IN_ROA.value)
|
||||
await _insert_order("ORD-3", OrderStatus.IMPORTED.value, id_comanda=300)
|
||||
|
||||
incoming = [
|
||||
type("O", (), {"number": "ORD-1", "date": "2026-04-22"})(),
|
||||
type("O", (), {"number": "ORD-2", "date": "2026-04-22"})(),
|
||||
type("O", (), {"number": "ORD-3", "date": "2026-04-22"})(),
|
||||
type("O", (), {"number": "ORD-NEW", "date": "2026-04-22"})(),
|
||||
]
|
||||
|
||||
deleted_set = await sqlite_service.get_deleted_in_roa_order_numbers()
|
||||
excluded = [o for o in incoming if o.number in deleted_set]
|
||||
survivors = [o for o in incoming if o.number not in deleted_set]
|
||||
|
||||
assert {o.number for o in excluded} == {"ORD-2"}
|
||||
assert {o.number for o in survivors} == {"ORD-1", "ORD-3", "ORD-NEW"}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_filter_with_no_deleted_is_noop():
|
||||
"""Integration: deleted_set empty → all orders pass through."""
|
||||
await _insert_order("ORD-1", OrderStatus.IMPORTED.value, id_comanda=100)
|
||||
|
||||
incoming = [
|
||||
type("O", (), {"number": "ORD-1", "date": "2026-04-22"})(),
|
||||
type("O", (), {"number": "ORD-NEW", "date": "2026-04-22"})(),
|
||||
]
|
||||
|
||||
deleted_set = await sqlite_service.get_deleted_in_roa_order_numbers()
|
||||
survivors = [o for o in incoming if o.number not in deleted_set]
|
||||
|
||||
assert deleted_set == set()
|
||||
assert {o.number for o in survivors} == {"ORD-1", "ORD-NEW"}
|
||||
Reference in New Issue
Block a user