test: cover PostgreSQL proposal transactions

This commit is contained in:
Josh Creek
2026-09-01 09:04:16 +01:00
parent c8a542a3af
commit 9d210d254a
2 changed files with 85 additions and 1 deletions
+84
View File
@@ -5,6 +5,7 @@ package store
import (
"context"
"database/sql"
"fmt"
"os"
"path/filepath"
"testing"
@@ -159,6 +160,89 @@ func TestPostgreSQLAssignmentPersistenceIsPlayerScopedAndExpiryBound(t *testing.
}
}
func TestPostgreSQLProposalClaimAndResponseAreAtomic(t *testing.T) {
db := openIntegrationPostgres(t)
applyIntegrationMigrations(t, db)
now := time.Now().UTC().Truncate(time.Microsecond)
ctx := context.Background()
for _, player := range []string{"proposal-player-a", "proposal-player-b"} {
if _, err := db.ExecContext(ctx, `INSERT INTO identities (player_id, steam_id) VALUES ($1, $1)`, player); err != nil {
t.Fatal(err)
}
}
for i, player := range []string{"proposal-player-a", "proposal-player-b"} {
if _, err := db.ExecContext(ctx, `INSERT INTO queue_tickets (ticket_id, player_id, playlist, state, client_build, protocol_version, enqueued_at, expires_at) VALUES ($1, $2, 'casual', 'QUEUED', 'integration-build', 1, $3, $4)`, fmt.Sprintf("proposal-ticket-%d", i), player, now, now.Add(time.Minute)); err != nil {
t.Fatal(err)
}
}
proposal, err := domain.NewProposal("proposal-integration", domain.Casual, []string{"proposal-player-a", "proposal-player-b"}, now)
if err != nil {
t.Fatal(err)
}
if err := CreateProposal(ctx, db, proposal, map[string]string{"proposal-player-a": "proposal-ticket-0", "proposal-player-b": "proposal-ticket-1"}, now); err != nil {
t.Fatalf("create proposal: %v", err)
}
var proposed int
if err := db.QueryRow(`SELECT count(*) FROM queue_tickets WHERE state = 'PROPOSED'`).Scan(&proposed); err != nil || proposed != 2 {
t.Fatalf("proposed queue tickets = %d, err = %v", proposed, err)
}
recovered, err := GetProposal(ctx, db, "proposal-player-a", proposal.ProposalID, now)
if err != nil {
t.Fatalf("recover proposal: %v", err)
}
if len(recovered.Participants) != 2 || recovered.Revision != 0 {
t.Fatalf("unexpected recovered proposal: %+v", recovered)
}
accepted, err := RespondToProposal(ctx, db, "proposal-player-a", proposal.ProposalID, "proposal-response-a-0001", true, 0, now)
if err != nil {
t.Fatalf("first proposal acceptance: %v", err)
}
if accepted.Revision != 1 || accepted.State != domain.Open {
t.Fatalf("unexpected first acceptance: %+v", accepted)
}
accepted, err = RespondToProposal(ctx, db, "proposal-player-b", proposal.ProposalID, "proposal-response-b-0001", true, 1, now)
if err != nil {
t.Fatalf("second proposal acceptance: %v", err)
}
if accepted.State != domain.Accepted || accepted.Revision != 2 {
t.Fatalf("proposal did not close after unanimous acceptance: %+v", accepted)
}
}
func TestPostgreSQLProposalCreationRollsBackPartialClaims(t *testing.T) {
db := openIntegrationPostgres(t)
applyIntegrationMigrations(t, db)
now := time.Now().UTC().Truncate(time.Microsecond)
ctx := context.Background()
if _, err := db.ExecContext(ctx, `INSERT INTO identities (player_id, steam_id) VALUES ('rollback-player', 'rollback-steam')`); err != nil {
t.Fatal(err)
}
if _, err := db.ExecContext(ctx, `INSERT INTO queue_tickets (ticket_id, player_id, playlist, state, client_build, protocol_version, enqueued_at, expires_at) VALUES ('rollback-ticket', 'rollback-player', 'casual', 'QUEUED', 'integration-build', 1, $1, $2)`, now, now.Add(time.Minute)); err != nil {
t.Fatal(err)
}
proposal, err := domain.NewProposal("rollback-proposal", domain.Casual, []string{"rollback-player", "missing-player"}, now)
if err != nil {
t.Fatal(err)
}
if err := CreateProposal(ctx, db, proposal, map[string]string{"rollback-player": "rollback-ticket"}, now); err == nil {
t.Fatal("proposal with missing ticket mapping was accepted")
}
var proposals, participants, proposed int
if err := db.QueryRow(`SELECT count(*) FROM proposals WHERE proposal_id = 'rollback-proposal'`).Scan(&proposals); err != nil {
t.Fatal(err)
}
if err := db.QueryRow(`SELECT count(*) FROM proposal_participants WHERE proposal_id = 'rollback-proposal'`).Scan(&participants); err != nil {
t.Fatal(err)
}
if err := db.QueryRow(`SELECT count(*) FROM queue_tickets WHERE ticket_id = 'rollback-ticket' AND state = 'PROPOSED'`).Scan(&proposed); err != nil {
t.Fatal(err)
}
if proposals != 0 || participants != 0 || proposed != 0 {
t.Fatalf("partial proposal claim was not rolled back: proposals=%d participants=%d proposed=%d", proposals, participants, proposed)
}
}
func TestPostgreSQLMigrationsAreForwardExecutable(t *testing.T) {
db := openIntegrationPostgres(t)
applyIntegrationMigrations(t, db)