package domain import ( "errors" "testing" ) func TestApplyIsAtomicOnIllegalTransitionAndStaleRevision(t *testing.T) { r := NewRecord(QueueTicket, "ticket_1234567890123456", Queued) if _, err := r.Apply("k1", []byte(`{"state":"LIVE"}`), 0, Live); !errors.Is(err, ErrIllegalTransition) { t.Fatalf("illegal transition error = %v", err) } if r.State != Queued || r.Revision != 0 { t.Fatalf("illegal transition mutated record: %+v", r) } if _, err := r.Apply("k2", []byte(`{}`), 99, Proposed); !errors.Is(err, ErrStaleRevision) { t.Fatalf("stale revision error = %v", err) } if r.State != Queued || r.Revision != 0 { t.Fatalf("stale revision mutated record: %+v", r) } } func TestApplyReplaysIdenticalIdempotencyWithoutNewRevision(t *testing.T) { r := NewRecord(QueueTicket, "ticket_1234567890123456", Queued) payload := []byte(`{"state":"PROPOSED"}`) first, err := r.Apply("same-key-123456", payload, 0, Proposed) if err != nil { t.Fatal(err) } second, err := r.Apply("same-key-123456", payload, 0, Proposed) if err != nil { t.Fatal(err) } if first != second || r.Revision != 1 { t.Fatalf("replay advanced or changed result: first=%+v second=%+v record=%+v", first, second, r) } } func TestApplyRejectsIdempotencyKeyPayloadConfusion(t *testing.T) { r := NewRecord(QueueTicket, "ticket_1234567890123456", Queued) if _, err := r.Apply("same-key-123456", []byte("a"), 0, Proposed); err != nil { t.Fatal(err) } if _, err := r.Apply("same-key-123456", []byte("b"), 1, Accepted); !errors.Is(err, ErrConflict) { t.Fatalf("conflicting replay error = %v", err) } if r.State != Proposed || r.Revision != 1 { t.Fatalf("conflicting replay mutated record: %+v", r) } } func TestTerminalStatesCannotAdvance(t *testing.T) { for _, state := range []State{Completed, Cancelled, Expired, Failed} { r := NewRecord(QueueTicket, "ticket_1234567890123456", state) if _, err := r.Apply("terminal-key-123", []byte("x"), 0, Live); !errors.Is(err, ErrIllegalTransition) { t.Fatalf("%s transition error = %v", state, err) } } }