//go:build integration package api import ( "context" "database/sql" "os" "path/filepath" "testing" "time" "github.com/cosmic-clash/cosmic-clash/server/domain" "github.com/cosmic-clash/cosmic-clash/server/migrations" "github.com/cosmic-clash/cosmic-clash/server/store" "github.com/cosmic-clash/cosmic-clash/server/workload" _ "github.com/jackc/pgx/v5/stdlib" ) // This binary is deliberately opt-in, matching store's integration suite: it // requires a disposable PostgreSQL instance supplied by // scripts/run_postgres_integration.sh. func openIntegrationPostgres(t *testing.T) *sql.DB { t.Helper() dsn := os.Getenv("COSMIC_CLASH_POSTGRES_DSN") if dsn == "" { t.Skip("COSMIC_CLASH_POSTGRES_DSN is not set") } db, err := sql.Open("pgx", dsn) if err != nil { t.Fatalf("open PostgreSQL: %v", err) } ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() if err := db.PingContext(ctx); err != nil { db.Close() t.Fatalf("ping PostgreSQL: %v", err) } t.Cleanup(func() { db.Close() }) migrationDir := os.Getenv("COSMIC_CLASH_MIGRATIONS_DIR") if migrationDir == "" { migrationDir = filepath.Join("..", "migrations") } if err := migrations.Apply(ctx, db, migrationDir); err != nil { t.Fatalf("apply migrations: %v", err) } return db } // seedRealAllocation claims a real ready server and allocation row, exactly // the durable state a signed workload token must later be cross-checked // against (see store.AllocationBindingStillValid). func seedRealAllocation(t *testing.T, db *sql.DB, allocationID, matchID string, now time.Time) domain.Allocation { t.Helper() ctx := context.Background() serverID := "server-" + allocationID if err := store.RegisterReadyServer(ctx, db, domain.ReadyServer{ServerID: serverID, Region: "EU", Build: "build-1", Protocol: 1, Transport: "enet", State: domain.ServerReady}, now); err != nil { t.Fatalf("register ready server: %v", err) } allocation, err := store.ClaimAllocation(ctx, db, domain.AllocationRequest{AllocationID: allocationID, MatchID: matchID, Region: "EU", Build: "build-1", Protocol: 1, Transport: "enet"}, now) if err != nil { t.Fatalf("claim allocation: %v", err) } return allocation } // TestWorkloadVerifierFromSignedTokenAcceptsARealAllocation proves the full // wired path: a token issued by workload.IssueSignedWorkloadToken for a real // allocation row verifies successfully through // WorkloadVerifierFromSignedToken and returns a binding matching what // serverMutation actually checks (ServerID, MatchID). This is the "wired, // working" counterpart to cmd/control-plane's // TestServerRoutesRequireWorkloadVerifyToBeWired, which pins the // unconfigured-503 case. func TestWorkloadVerifierFromSignedTokenAcceptsARealAllocation(t *testing.T) { db := openIntegrationPostgres(t) now := time.Now().UTC() allocation := seedRealAllocation(t, db, "alloc-verify-1", "match-verify-1", now) secret := []byte("integration-test-secret") token, err := workload.IssueSignedWorkloadToken(secret, allocation.AllocationID, allocation.MatchID, allocation.ServerID, now, time.Minute) if err != nil { t.Fatalf("issue token: %v", err) } verify := WorkloadVerifierFromSignedToken(secret, db) if verify == nil { t.Fatal("WorkloadVerifierFromSignedToken returned nil with a real secret and database") } binding, err := verify(token, now.Add(30*time.Second)) if err != nil { t.Fatalf("verify: %v", err) } if binding.ServerID != allocation.ServerID || binding.MatchID != allocation.MatchID || binding.AllocationID != allocation.AllocationID { t.Fatalf("unexpected binding: %+v, want server=%s match=%s allocation=%s", binding, allocation.ServerID, allocation.MatchID, allocation.AllocationID) } } // TestWorkloadVerifierFromSignedTokenRejectsAnUnknownAllocation proves the // durable cross-check actually runs: a validly-signed, unexpired token whose // allocation was never recorded (e.g. superseded, or simply fabricated) must // still be rejected. Signature and expiry checks alone are not enough. func TestWorkloadVerifierFromSignedTokenRejectsAnUnknownAllocation(t *testing.T) { db := openIntegrationPostgres(t) now := time.Now().UTC() secret := []byte("integration-test-secret") token, err := workload.IssueSignedWorkloadToken(secret, "alloc-never-recorded", "match-never-recorded", "server-never-recorded", now, time.Minute) if err != nil { t.Fatalf("issue token: %v", err) } verify := WorkloadVerifierFromSignedToken(secret, db) if _, err := verify(token, now.Add(time.Second)); err == nil { t.Fatal("expected rejection for a token naming an allocation that was never recorded") } } // TestWorkloadVerifierFromSignedTokenRejectsAMismatchedTriple proves the // cross-check binds all three identifiers together, not each independently: // a real allocation's own allocation_id combined with someone else's // match/server must still fail. func TestWorkloadVerifierFromSignedTokenRejectsAMismatchedTriple(t *testing.T) { db := openIntegrationPostgres(t) now := time.Now().UTC() allocation := seedRealAllocation(t, db, "alloc-verify-2", "match-verify-2", now) secret := []byte("integration-test-secret") token, err := workload.IssueSignedWorkloadToken(secret, allocation.AllocationID, "a-different-match", allocation.ServerID, now, time.Minute) if err != nil { t.Fatalf("issue token: %v", err) } verify := WorkloadVerifierFromSignedToken(secret, db) if _, err := verify(token, now.Add(time.Second)); err == nil { t.Fatal("expected rejection for a real allocation id paired with the wrong match id") } } // TestWorkloadVerifierFromSignedTokenClosesTheDefaultUnwiredGap proves the // 503-by-default gap pinned by // cmd/control-plane.TestServerRoutesRequireWorkloadVerifyToBeWired is // actually closed once a secret and database are wired: a Service built the // same way newAPIHandler builds one now accepts a validly-issued token for a // real allocation, through the exact Service.WorkloadVerify field the HTTP // handler calls. (serverMutation's deeper match-state transition -- // requiring the match to already be ALLOCATING -- is exercised separately by // the store package's own allocation/match integration tests; this test's // job is only the WorkloadVerify boundary itself.) func TestWorkloadVerifierFromSignedTokenClosesTheDefaultUnwiredGap(t *testing.T) { db := openIntegrationPostgres(t) now := time.Now().UTC() allocation := seedRealAllocation(t, db, "alloc-verify-3", "match-verify-3", now) secret := []byte("integration-test-secret") token, err := workload.IssueSignedWorkloadToken(secret, allocation.AllocationID, allocation.MatchID, allocation.ServerID, now, time.Minute) if err != nil { t.Fatalf("issue token: %v", err) } svc := &Service{ ServerRegistrar: ServerRegistrarFromStore(db), WorkloadVerify: WorkloadVerifierFromSignedToken(secret, db), Now: func() time.Time { return now.Add(time.Second) }, } binding, err := svc.WorkloadVerify(token, now.Add(time.Second)) if err != nil { t.Fatalf("WorkloadVerify rejected a validly-issued token for a real allocation: %v", err) } if binding.ServerID != allocation.ServerID { t.Fatalf("binding.ServerID = %q, want %q", binding.ServerID, allocation.ServerID) } }