124 lines
3.5 KiB
Python
124 lines
3.5 KiB
Python
"""Pengujian mesin status Order: transisi valid, tidak valid, dan guard pelaku.
|
|
|
|
Kebutuhan: FR-ORD-04, FR-ORD-06, FR-ORD-09, FR-ORD-10.
|
|
"""
|
|
|
|
from datetime import datetime, timedelta
|
|
|
|
import pytest
|
|
|
|
from umkm.order import (
|
|
TRANSITIONS,
|
|
InvalidTransitionError,
|
|
Order,
|
|
OrderStatus as S,
|
|
PermissionDeniedError,
|
|
)
|
|
|
|
BUYER, SELLER = "buyer-1", "seller-1"
|
|
T0 = datetime(2026, 11, 1, 10, 0)
|
|
|
|
|
|
def new_order():
|
|
return Order("o-1", BUYER, SELLER)
|
|
|
|
|
|
def order_in(status):
|
|
"""Bawa order ke status yang diminta lewat transisi sungguhan."""
|
|
order = new_order()
|
|
if status in (S.PAID, S.SHIPPED, S.COMPLETED):
|
|
order.pay()
|
|
if status in (S.SHIPPED, S.COMPLETED):
|
|
order.ship(SELLER, T0)
|
|
if status is S.COMPLETED:
|
|
order.confirm_received(BUYER)
|
|
if status is S.CANCELLED:
|
|
order.cancel(BUYER)
|
|
if status is S.EXPIRED:
|
|
order.expire()
|
|
assert order.status is status
|
|
return order
|
|
|
|
|
|
# Semua event yang dapat dipicu; auto_complete memakai waktu tepat jatuh tempo.
|
|
EVENTS = {
|
|
"pay": lambda o: o.pay(),
|
|
"cancel": lambda o: o.cancel(BUYER),
|
|
"expire": lambda o: o.expire(),
|
|
"ship": lambda o: o.ship(SELLER, T0),
|
|
"complete": lambda o: o.confirm_received(BUYER),
|
|
"auto_complete": lambda o: o.auto_complete(T0 + timedelta(days=3)),
|
|
}
|
|
VALID = dict(TRANSITIONS)
|
|
VALID[(S.SHIPPED, "auto_complete")] = S.COMPLETED
|
|
INVALID = [(s, e) for s in S for e in EVENTS if (s, e) not in VALID]
|
|
|
|
|
|
def test_order_baru_berstatus_pending_payment():
|
|
assert new_order().status is S.PENDING_PAYMENT
|
|
|
|
|
|
@pytest.mark.parametrize(("src", "event"), list(VALID))
|
|
def test_transisi_valid(src, event):
|
|
order = order_in(src)
|
|
EVENTS[event](order)
|
|
assert order.status is VALID[(src, event)]
|
|
|
|
|
|
@pytest.mark.parametrize(("src", "event"), INVALID)
|
|
def test_transisi_tidak_valid_ditolak_dan_status_tetap(src, event):
|
|
order = order_in(src)
|
|
with pytest.raises(InvalidTransitionError):
|
|
EVENTS[event](order)
|
|
assert order.status is src
|
|
|
|
|
|
def test_jumlah_kombinasi_lengkap():
|
|
"""6 status x 6 event = 36 kombinasi: 6 valid + 30 tidak valid."""
|
|
assert len(VALID) == 6
|
|
assert len(INVALID) == 30
|
|
|
|
|
|
@pytest.mark.parametrize("final", [S.COMPLETED, S.CANCELLED, S.EXPIRED])
|
|
def test_status_akhir_tidak_punya_transisi_keluar(final):
|
|
assert not [key for key in VALID if key[0] is final]
|
|
|
|
|
|
def test_seller_tidak_boleh_menyelesaikan_order():
|
|
"""FR-ORD-10: hanya pembeli yang mengonfirmasi."""
|
|
order = order_in(S.SHIPPED)
|
|
with pytest.raises(PermissionDeniedError):
|
|
order.confirm_received(SELLER)
|
|
assert order.status is S.SHIPPED
|
|
|
|
|
|
def test_hanya_seller_pemilik_toko_boleh_mengirim():
|
|
order = order_in(S.PAID)
|
|
with pytest.raises(PermissionDeniedError):
|
|
order.ship("seller-lain", T0)
|
|
assert order.status is S.PAID
|
|
|
|
|
|
def test_hanya_pembeli_pemilik_order_boleh_membatalkan():
|
|
order = new_order()
|
|
with pytest.raises(PermissionDeniedError):
|
|
order.cancel("buyer-lain")
|
|
assert order.status is S.PENDING_PAYMENT
|
|
|
|
|
|
def test_auto_complete_ditolak_sebelum_3_hari():
|
|
order = order_in(S.SHIPPED)
|
|
with pytest.raises(InvalidTransitionError):
|
|
order.auto_complete(T0 + timedelta(days=3) - timedelta(seconds=1))
|
|
assert order.status is S.SHIPPED
|
|
|
|
|
|
def test_auto_complete_tepat_3_hari_berhasil():
|
|
order = order_in(S.SHIPPED)
|
|
order.auto_complete(T0 + timedelta(days=3))
|
|
assert order.status is S.COMPLETED
|
|
|
|
|
|
def test_ship_mencatat_waktu_pengiriman():
|
|
assert order_in(S.SHIPPED).shipped_at == T0
|