mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-11 00:14:00 +00:00
fix(multiplayer): complete live results atomically
This commit is contained in:
@@ -2,6 +2,7 @@ package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -13,11 +14,15 @@ func TestResultSQLPreservesReceiptConflictAndAtomicCommitBoundaries(t *testing.T
|
||||
ResultReceiptInsertSQL: {"ON CONFLICT DO NOTHING", "payload_digest", "integrity_state"},
|
||||
ResultReceiptSelectSQL: {"FOR UPDATE", "committed_at"},
|
||||
ResultCommitLockSQL: {"server_id = $2", "FOR UPDATE"},
|
||||
ResultMatchPendingSQL: {"state = 'RESULT_PENDING'", "state = 'LIVE'", "revision = revision + 1"},
|
||||
ResultTicketsPendingSQL: {"queue_tickets", "match_participants", "participation_active", "state = 'LIVE'"},
|
||||
ResultMatchCompleteSQL: {"state = 'RESULT_PENDING'", "revision = revision + 1"},
|
||||
ResultTicketsCompleteSQL: {"state = 'COMPLETED'", "state = 'RESULT_PENDING'", "match_participants", "participation_active"},
|
||||
ResultParticipantCountSQL: {"count(*)", "match_participants", "match_id = $1", "participation_active"},
|
||||
ResultReceiptCommitSQL: {"COALESCE(committed_at", "committed_at"},
|
||||
ResultOutboxSQL: {"match_completed", "aggregate_id", "revision"},
|
||||
RatingLockSQL: {"ORDER BY player_id", "FOR UPDATE"},
|
||||
MatchParticipantRatingsSQL: {"match_participants", "JOIN ratings", "ORDER BY mp.player_id"},
|
||||
MatchParticipantRatingsSQL: {"match_participants", "abandoned_at", "JOIN ratings", "participation_active", "ORDER BY mp.player_id"},
|
||||
RatingValuesSQL: {"player_id = ANY($1)", "ORDER BY player_id"},
|
||||
RatingUpdateSQL: {"ranked_games = ranked_games + $5", "revision = revision + 1"},
|
||||
}
|
||||
@@ -46,6 +51,18 @@ func TestCompleteResultWithResultRejectsReceiptResultMismatchBeforeDatabaseUse(t
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompleteResultRejectsIncompleteReceiptBeforeDatabaseUse(t *testing.T) {
|
||||
now := time.Unix(100, 0).UTC()
|
||||
receipt := domain.ResultReceipt{ResultID: "result", MatchID: "match", ResultNonce: "nonce-1234567890123456", IntegrityState: domain.IntegrityCertified, ReceivedAt: now}
|
||||
if err := CompleteResult(context.Background(), nil, receipt, "server", "event", []byte("payload"), now); err == nil {
|
||||
t.Fatal("nil database accepted")
|
||||
}
|
||||
receipt.ReceivedAt = time.Time{}
|
||||
if err := CompleteResult(context.Background(), &sql.DB{}, receipt, "server", "event", []byte("payload"), now); err == nil {
|
||||
t.Fatal("zero receipt time accepted")
|
||||
}
|
||||
}
|
||||
|
||||
func contains(value, fragment string) bool {
|
||||
for i := 0; i+len(fragment) <= len(value); i++ {
|
||||
if value[i:i+len(fragment)] == fragment {
|
||||
|
||||
Reference in New Issue
Block a user