Watch
1
0
Fork
You've already forked Seyyed_arc
0
forked from hesabix/arc
Seyyed_arc/hesabixAPI/tests/test_document_code_reservation_service.py

275 lines
7.9 KiB
Python

"""تست سرویس رزرو شماره سند."""
from datetime import date, datetime, timedelta, timezone
from unittest.mock import MagicMock, patch
import pytest
from adapters.db.models.document_numbering import DocumentCodeReservation
from app.core.responses import ApiError
from app.services.document_code_reservation_service import (
cancel_document_code_reservation,
mark_reservation_used,
reserve_document_code,
validate_reservation_for_invoice_create,
)
def _active_reservation(**overrides):
now = datetime.now(timezone.utc)
data = {
"id": "res-1",
"business_id": 10,
"reserved_by_user_id": 5,
"document_type": "invoice_sales",
"document_date": date(2026, 7, 14),
"code": "INV-20260714-0001",
"status": "active",
"expires_at": now + timedelta(hours=4),
"created_at": now,
"used_at": None,
"cancelled_at": None,
}
data.update(overrides)
return DocumentCodeReservation(**data)
def _api_error_code(exc: ApiError) -> str:
return exc.detail["error"]["code"]
@patch("app.services.document_code_reservation_service.generate_document_code")
@patch("app.services.document_code_reservation_service.expire_stale_reservations")
def test_reserve_document_code_returns_payload(mock_expire, mock_generate):
mock_generate.return_value = "INV-20260714-0007"
db = MagicMock()
db.flush.side_effect = lambda: None
result = reserve_document_code(
db,
business_id=10,
user_id=5,
document_type="invoice_sales",
document_date=date(2026, 7, 14),
)
assert result["code"] == "INV-20260714-0007"
assert result["document_type"] == "invoice_sales"
assert result["document_date"] == "2026-07-14"
assert result["status"] == "active"
assert result["reservation_id"]
db.add.assert_called_once()
mock_expire.assert_called_once_with(db, business_id=10)
def test_reserve_document_code_rejects_invalid_type():
db = MagicMock()
with pytest.raises(ApiError) as exc:
reserve_document_code(
db,
business_id=10,
user_id=5,
document_type="receipt",
document_date=date(2026, 7, 14),
)
assert _api_error_code(exc.value) == "INVALID_DOCUMENT_TYPE"
def test_validate_reservation_for_invoice_create_checks_mismatch():
db = MagicMock()
reservation = _active_reservation()
query = MagicMock()
query.filter.return_value = query
query.with_for_update.return_value = query
query.first.return_value = reservation
db.query.return_value = query
with pytest.raises(ApiError) as exc:
validate_reservation_for_invoice_create(
db,
business_id=10,
user_id=5,
reservation_id="res-1",
invoice_type="invoice_purchase",
document_date=date(2026, 7, 14),
)
assert _api_error_code(exc.value) == "CODE_RESERVATION_MISMATCH"
def test_validate_reservation_for_invoice_create_success():
db = MagicMock()
reservation = _active_reservation()
query = MagicMock()
query.filter.return_value = query
query.with_for_update.return_value = query
query.first.return_value = reservation
db.query.return_value = query
result = validate_reservation_for_invoice_create(
db,
business_id=10,
user_id=5,
reservation_id="res-1",
invoice_type="invoice_sales",
document_date=date(2026, 7, 14),
)
assert result.code == "INV-20260714-0001"
@patch("app.services.document_code_reservation_service.reclaim_trailing_unused_document_codes")
def test_cancel_document_code_reservation_marks_cancelled(mock_reclaim):
db = MagicMock()
reservation = _active_reservation()
query = MagicMock()
query.filter.return_value = query
query.with_for_update.return_value = query
query.first.return_value = reservation
query.all.return_value = []
db.query.return_value = query
cancelled = cancel_document_code_reservation(
db,
business_id=10,
user_id=5,
reservation_id="res-1",
)
assert cancelled is True
assert reservation.status == "cancelled"
assert reservation.cancelled_at is not None
mock_reclaim.assert_called_once()
def test_mark_reservation_used():
db = MagicMock()
reservation = _active_reservation()
mark_reservation_used(db, reservation)
assert reservation.status == "used"
assert reservation.used_at is not None
def test_reserve_and_cancel_integration_roundtrip():
from datetime import date
from adapters.db.session import get_db_session
from adapters.db.models.document_numbering import DocumentCodeReservation
from app.services.document_code_reservation_service import (
cancel_document_code_reservation,
reserve_document_code,
)
with get_db_session() as db:
result = reserve_document_code(
db,
business_id=1,
user_id=1,
document_type="invoice_sales",
document_date=date(2026, 7, 14),
)
db.commit()
reservation_id = result["reservation_id"]
assert result["code"]
assert result["status"] == "active"
row = db.get(DocumentCodeReservation, reservation_id)
assert row is not None
assert row.code == result["code"]
assert row.status == "active"
cancelled = cancel_document_code_reservation(
db,
business_id=1,
user_id=1,
reservation_id=reservation_id,
)
db.commit()
assert cancelled is True
db.refresh(row)
assert row.status == "cancelled"
def test_reserve_assigns_unique_codes_sequentially():
from datetime import date
from adapters.db.session import get_db_session
from app.services.document_code_reservation_service import (
cancel_document_code_reservation,
reserve_document_code,
)
with get_db_session() as db:
first = reserve_document_code(
db,
business_id=1,
user_id=1,
document_type="invoice_sales",
document_date=date(2026, 7, 14),
)
second = reserve_document_code(
db,
business_id=1,
user_id=1,
document_type="invoice_sales",
document_date=date(2026, 7, 14),
)
db.commit()
assert first["code"] != second["code"]
for rid in (first["reservation_id"], second["reservation_id"]):
cancel_document_code_reservation(
db,
business_id=1,
user_id=1,
reservation_id=rid,
)
db.commit()
def test_cancel_last_reservation_reuses_same_code():
from datetime import date
from adapters.db.session import get_db_session
from app.services.document_code_reservation_service import (
cancel_document_code_reservation,
reserve_document_code,
)
with get_db_session() as db:
first = reserve_document_code(
db,
business_id=1,
user_id=1,
document_type="invoice_waste",
document_date=date(2026, 8, 15),
)
db.commit()
first_code = first["code"]
cancelled = cancel_document_code_reservation(
db,
business_id=1,
user_id=1,
reservation_id=first["reservation_id"],
)
db.commit()
assert cancelled is True
second = reserve_document_code(
db,
business_id=1,
user_id=1,
document_type="invoice_waste",
document_date=date(2026, 8, 15),
)
db.commit()
assert second["code"] == first_code
cancel_document_code_reservation(
db,
business_id=1,
user_id=1,
reservation_id=second["reservation_id"],
)
db.commit()