Files
CosmicClash/server/domain/proposal_test.go
T
2026-08-31 20:29:11 +01:00

80 lines
2.8 KiB
Go

package domain
import (
"errors"
"testing"
"time"
)
func TestProposalRequiresUnanimousAcceptance(t *testing.T) {
now := time.Unix(1000, 0)
p, err := NewProposal("proposal-123456789", Casual, []string{"b", "a"}, now)
if err != nil {
t.Fatal(err)
}
if _, err = p.Respond("a", "response-a-123456", true, 0, now); err != nil {
t.Fatal(err)
}
if p.State != Open || p.Revision != 1 {
t.Fatalf("partial acceptance closed proposal: %+v", p)
}
if _, err = p.Respond("b", "response-b-123456", true, 1, now); err != nil {
t.Fatal(err)
}
if p.State != Accepted || p.Revision != 2 {
t.Fatalf("unanimous acceptance not committed: %+v", p)
}
}
func TestProposalResponseReplayIsStableAndPayloadReuseConflicts(t *testing.T) {
now := time.Unix(1000, 0)
p, err := NewProposal("proposal-123456789", Casual, []string{"a", "b"}, now)
if err != nil {
t.Fatal(err)
}
first, err := p.Respond("a", "response-a-123456", true, 0, now)
if err != nil {
t.Fatal(err)
}
replay, err := p.Respond("a", "response-a-123456", true, 0, now.Add(20*time.Second))
if err != nil || replay.Revision != first.Revision {
t.Fatalf("replay = %+v, %v", replay, err)
}
if _, err = p.Respond("a", "response-a-123456", false, 1, now); !errors.Is(err, ErrConflict) {
t.Fatalf("payload reuse error = %v", err)
}
}
func TestProposalExpiryTimesOutPendingParticipantsAndClosesRace(t *testing.T) {
now := time.Unix(1000, 0)
p, err := NewProposal("proposal-123456789", Ranked, []string{"a", "b", "c", "d", "e", "f"}, now)
if err != nil {
t.Fatal(err)
}
if !p.Expire(now.Add(ProposalWindow)) || p.State != Expired || p.Revision != 1 {
t.Fatalf("expiry failed: %+v", p)
}
for _, participant := range p.Participants {
if participant.Response != TimedOutResponse {
t.Fatalf("pending participant not timed out: %+v", participant)
}
}
if _, err = p.Respond("a", "late-response-123", true, 1, now.Add(ProposalWindow)); !errors.Is(err, ErrProposalClosed) {
t.Fatalf("late response error = %v", err)
}
}
func TestRankedProposalAndCooldownEscalation(t *testing.T) {
now := time.Unix(1000, 0)
if _, err := NewProposal("proposal-123456789", Ranked, []string{"a", "b"}, now); err == nil {
t.Fatal("ranked proposal accepted fewer than six")
}
if got := CooldownUntil([]CooldownEvent{{At: now, Playlist: Ranked, Kind: DeclinedResponse}}, Ranked, now); !got.Equal(now.Add(2 * time.Minute)) {
t.Fatalf("ranked decline cooldown = %v", got)
}
events := []CooldownEvent{{At: now, Playlist: Ranked, Kind: TimedOutResponse}, {At: now.Add(time.Minute), Playlist: Ranked, Kind: DeclinedResponse}, {At: now.Add(2 * time.Minute), Playlist: Ranked, Kind: TimedOutResponse}}
if got := CooldownUntil(events, Ranked, now.Add(2*time.Minute)); !got.Equal(now.Add(17 * time.Minute)) {
t.Fatalf("ranked escalation cooldown = %v", got)
}
}