"""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