feat(campaigns): add pending_approval state to campaign domain entity

This commit is contained in:
kitos
2026-07-02 16:00:18 +02:00
parent 62e3bfebe8
commit a113f3687c
2 changed files with 99 additions and 1 deletions
+47 -1
View File
@@ -33,6 +33,8 @@ class CampaignStatus(str, enum.Enum):
# Assign draft = "draft"
draft = "draft"
# Assign pending_approval = "pending_approval"
pending_approval = "pending_approval"
# Assign active = "active"
active = "active"
# Assign completed = "completed"
@@ -57,7 +59,8 @@ class CampaignType(str, enum.Enum):
# Assign VALID_TRANSITIONS = {
VALID_TRANSITIONS: dict[CampaignStatus, list[CampaignStatus]] = {
CampaignStatus.draft: [CampaignStatus.active],
CampaignStatus.draft: [CampaignStatus.pending_approval, CampaignStatus.active],
CampaignStatus.pending_approval: [CampaignStatus.active, CampaignStatus.draft],
CampaignStatus.active: [CampaignStatus.completed],
CampaignStatus.completed: [CampaignStatus.archived],
CampaignStatus.archived: [],
@@ -132,6 +135,49 @@ class CampaignEntity:
# Assign self.status = CampaignStatus.active
self.status = CampaignStatus.active
def submit_for_approval(self) -> None:
"""Transition the campaign from ``draft`` to ``pending_approval``.
Returns:
None
"""
if not self.can_transition_to(CampaignStatus.pending_approval):
raise InvalidStateTransition(
self.status.value, CampaignStatus.pending_approval.value,
[s.value for s in VALID_TRANSITIONS[self.status]],
)
if self.test_count == 0:
raise BusinessRuleViolation(
"Campaign must have at least one test to submit for approval"
)
self.status = CampaignStatus.pending_approval
def approve(self) -> None:
"""Transition the campaign from ``pending_approval`` to ``active``.
Returns:
None
"""
if self.status != CampaignStatus.pending_approval:
raise InvalidStateTransition(
self.status.value, CampaignStatus.active.value,
[s.value for s in VALID_TRANSITIONS[self.status]],
)
self.status = CampaignStatus.active
def reject(self) -> None:
"""Transition the campaign from ``pending_approval`` back to ``draft``.
Returns:
None
"""
if not self.can_transition_to(CampaignStatus.draft):
raise InvalidStateTransition(
self.status.value, CampaignStatus.draft.value,
[s.value for s in VALID_TRANSITIONS[self.status]],
)
self.status = CampaignStatus.draft
# Define function complete
def complete(self) -> None:
"""Transition the campaign from ``active`` to ``completed``.
+52
View File
@@ -173,3 +173,55 @@ def test_from_orm_handles_none_tags():
orm.tags = None
e = CampaignEntity.from_orm(orm)
assert e.tags == []
# ── 9. Submit for approval ────────────────────────────────────────────
def test_submit_for_approval_from_draft_with_tests_success():
e = _entity("draft", test_count=1)
e.submit_for_approval()
assert e.status == CampaignStatus.pending_approval
def test_submit_for_approval_from_draft_with_zero_tests_raises():
e = _entity("draft", test_count=0)
with pytest.raises(BusinessRuleViolation, match="at least one test"):
e.submit_for_approval()
assert e.status == CampaignStatus.draft
def test_submit_for_approval_from_active_raises():
e = _entity("active", test_count=1)
with pytest.raises(InvalidStateTransition):
e.submit_for_approval()
# ── 10. Approve ────────────────────────────────────────────────────────
def test_approve_from_pending_approval_success():
e = _entity("pending_approval", test_count=1)
e.approve()
assert e.status == CampaignStatus.active
def test_approve_from_draft_raises():
e = _entity("draft", test_count=1)
with pytest.raises(InvalidStateTransition):
e.approve()
# ── 11. Reject ─────────────────────────────────────────────────────────
def test_reject_from_pending_approval_returns_to_draft():
e = _entity("pending_approval", test_count=1)
e.reject()
assert e.status == CampaignStatus.draft
def test_reject_from_draft_raises():
e = _entity("draft", test_count=1)
with pytest.raises(InvalidStateTransition):
e.reject()