mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-11 00:14:00 +00:00
feat: promote accepted proposals from API
This commit is contained in:
@@ -41,6 +41,18 @@ type resultSubmitterSpy struct {
|
||||
result domain.MatchResult
|
||||
}
|
||||
|
||||
type proposalPromoterSpy struct {
|
||||
calls int
|
||||
proposal domain.Proposal
|
||||
err error
|
||||
}
|
||||
|
||||
func (p *proposalPromoterSpy) Promote(_ context.Context, proposal domain.Proposal, _ time.Time) error {
|
||||
p.calls++
|
||||
p.proposal = proposal
|
||||
return p.err
|
||||
}
|
||||
|
||||
func (r *resultSubmitterSpy) SubmitResult(_ context.Context, key string, result domain.MatchResult, _ domain.WorkloadBinding, _ []byte, _ time.Time) error {
|
||||
r.calls++
|
||||
r.key, r.result = key, result
|
||||
@@ -409,6 +421,49 @@ func TestStateChangingAPIActionsPublishTargetedEvents(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestFinalProposalAcceptancePromotesDurableMatchAndFailsRetryably(t *testing.T) {
|
||||
now := time.Unix(1000, 0).UTC()
|
||||
proposal, err := domain.NewProposal("proposal-promote-123456", domain.Casual, []string{"player-1", "player-2"}, now)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
backend := &proposalBackendSpy{proposal: proposal}
|
||||
promoter := &proposalPromoterSpy{}
|
||||
sessions := domain.NewSessionStore()
|
||||
session1, token1, err := sessions.Issue("player-1", time.Hour, now)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
session2, token2, err := sessions.Issue("player-2", time.Hour, now)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
service := &Service{Sessions: sessions, ProposalBackend: backend, ProposalPromoter: promoter, Now: func() time.Time { return now }}
|
||||
respond := func(credential, key, revision string) int {
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1/proposals/"+proposal.ProposalID+"/accept", nil)
|
||||
req.Header.Set("Authorization", "Bearer "+credential)
|
||||
req.Header.Set("Idempotency-Key", key)
|
||||
req.Header.Set("If-Match-Revision", revision)
|
||||
recorder := httptest.NewRecorder()
|
||||
service.proposalMutation(recorder, req)
|
||||
return recorder.Code
|
||||
}
|
||||
credential1 := session1.SessionID + ":" + token1
|
||||
credential2 := session2.SessionID + ":" + token2
|
||||
if status := respond(credential1, "proposal-promote-first", "0"); status != http.StatusOK || promoter.calls != 0 {
|
||||
t.Fatalf("first acceptance status/calls = %d/%d", status, promoter.calls)
|
||||
}
|
||||
if status := respond(credential2, "proposal-promote-final", "1"); status != http.StatusOK || promoter.calls != 1 || promoter.proposal.State != domain.Accepted {
|
||||
t.Fatalf("final acceptance status/promoter = %d/%+v", status, promoter)
|
||||
}
|
||||
promoter.err = errors.New("database unavailable")
|
||||
// A duplicate response is replayed by the durable proposal backend and
|
||||
// retries promotion instead of asking the player to accept again.
|
||||
if status := respond(credential2, "proposal-promote-final", "1"); status != http.StatusServiceUnavailable || promoter.calls != 2 {
|
||||
t.Fatalf("promotion retry status/calls = %d/%d", status, promoter.calls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProposalRecoveryUsesDurableBackendAndRemainsParticipantScoped(t *testing.T) {
|
||||
now := time.Unix(1000, 0).UTC()
|
||||
proposal, err := domain.NewProposal("proposal-1234567890123456", domain.Casual, []string{"player-1", "player-2"}, now)
|
||||
|
||||
Reference in New Issue
Block a user