package credentialstore import ( "context" "errors" "path/filepath" "strings" "testing" "time" "google.golang.org/protobuf/proto" ) func TestProjectionGenerationCommitsWithMutation(t *testing.T) { ctx := context.Background() registry := newFakeKeyRegistry() registry.RegisterKey("k1", 1) store, err := Open(ctx, filepath.Join(t.TempDir(), "projection-generation.db"), WithEnvelopeKeyRegistry(registry)) if err != nil { t.Fatal(err) } defer store.Close() assertGeneration := func(want uint64) { t.Helper() got, err := store.ProjectionGeneration(ctx) if err != nil { t.Fatal(err) } if got != want { t.Fatalf("generation: got %d want %d", got, want) } } assertGeneration(0) issued, err := store.CreatePrincipalWithToken(ctx, CreatePrincipalInput{Alias: "projection-principal"}) if err != nil { t.Fatal(err) } assertGeneration(1) if _, err := store.CreatePrincipalWithToken(ctx, CreatePrincipalInput{Alias: "projection-principal"}); !errors.Is(err, ErrPrincipalAlreadyExists) { t.Fatalf("duplicate create: got %v", err) } assertGeneration(1) slot, err := store.CreateSlot(ctx, CreateSlotInput{ PrincipalID: issued.Principal.ID, Vendor: "openai", CredentialKind: CredentialKindBearer, Alias: "primary", Envelope: SecretEnvelope{ Algorithm: "AES-256-GCM", KeyID: "k1", KeyVersion: 1, Nonce: []byte("123456789012"), Ciphertext: []byte("opaque-ciphertext"), }, }) if err != nil { t.Fatal(err) } assertGeneration(2) route, err := store.CreateRoute(ctx, CreateRouteInput{ PrincipalID: issued.Principal.ID, SlotID: slot.ID, Alias: "model-primary", ProfileID: "openai", UpstreamModel: "upstream-model", }) if err != nil { t.Fatal(err) } assertGeneration(3) if _, err := store.DisableRoute(ctx, issued.Principal.ID, route.ID, route.Revision); err != nil { t.Fatal(err) } assertGeneration(4) currentSlot, err := store.GetSlot(ctx, issued.Principal.ID, slot.ID) if err != nil { t.Fatal(err) } rotatedSlot, err := store.RotateSlotSecret(ctx, RotateSlotSecretInput{ PrincipalID: issued.Principal.ID, SlotID: slot.ID, CurrentRevision: currentSlot.Revision, Envelope: SecretEnvelope{ Algorithm: "AES-256-GCM", KeyID: "k1", KeyVersion: 1, Nonce: []byte("abcdefghijkl"), Ciphertext: []byte("rotated-opaque-ciphertext"), }, }) if err != nil { t.Fatal(err) } assertGeneration(5) if _, err := store.DisableSlot(ctx, issued.Principal.ID, slot.ID, rotatedSlot.Revision); err != nil { t.Fatal(err) } assertGeneration(6) if _, err := store.DisableToken(ctx, issued.Principal.ID, issued.Token.TokenRef, issued.Token.Revision); err != nil { t.Fatal(err) } assertGeneration(7) } func TestProjectionGenerationPersistsAcrossReopen(t *testing.T) { ctx := context.Background() dbPath := filepath.Join(t.TempDir(), "projection-reopen.db") store, err := Open(ctx, dbPath) if err != nil { t.Fatal(err) } issued, err := store.CreatePrincipalWithToken(ctx, CreatePrincipalInput{Alias: "persistent-projection"}) if err != nil { t.Fatal(err) } want, err := store.ProjectionGeneration(ctx) if err != nil { t.Fatal(err) } if err := store.Close(); err != nil { t.Fatal(err) } reopened, err := Open(ctx, dbPath) if err != nil { t.Fatal(err) } defer reopened.Close() got, err := reopened.ProjectionGeneration(ctx) if err != nil { t.Fatal(err) } if got != want { t.Fatalf("persisted generation: got %d want %d", got, want) } if _, err := reopened.RevokeToken(ctx, issued.Principal.ID, issued.Token.TokenRef, issued.Token.Revision); err != nil { t.Fatal(err) } after, err := reopened.ProjectionGeneration(ctx) if err != nil { t.Fatal(err) } if after != want+1 { t.Fatalf("generation after reopen mutation: got %d want %d", after, want+1) } } func TestProjectionSnapshotOmitsInactiveRecords(t *testing.T) { ctx := context.Background() store, issued, slot, _ := setupRouteTestFixture(t) route, err := store.CreateRoute(ctx, CreateRouteInput{ PrincipalID: issued.Principal.ID, SlotID: slot.ID, Alias: "projected-route", ProfileID: "openai", UpstreamModel: "upstream-model", }) if err != nil { t.Fatal(err) } clock := func() time.Time { return time.Date(2026, 8, 1, 12, 0, 0, 0, time.UTC) } snapshot, err := store.BuildPrincipalProjection(ctx, ProjectionBuildOptions{TTL: time.Minute, Clock: clock}) if err != nil { t.Fatal(err) } if len(snapshot.GetTokens()) != 1 || len(snapshot.GetRoutes()) != 1 { t.Fatalf("active snapshot: tokens=%d routes=%d", len(snapshot.GetTokens()), len(snapshot.GetRoutes())) } if snapshot.GetTokens()[0].GetTokenDigestSha256() != issued.Token.Digest { t.Fatal("snapshot did not contain the active token digest") } projectedRoute := snapshot.GetRoutes()[0] if projectedRoute.GetCredentialSlotRef() != slot.ID || projectedRoute.GetRouteId() != route.ID { t.Fatalf("route projection mismatch: %+v", projectedRoute) } encoded, err := proto.Marshal(snapshot) if err != nil { t.Fatal(err) } if strings.Contains(string(encoded), "cipher1") || strings.Contains(string(encoded), "AES-256-GCM") || strings.Contains(string(encoded), "k1") { t.Fatal("projection contains credential envelope material") } if _, err := store.DisableToken(ctx, issued.Principal.ID, issued.Token.TokenRef, issued.Token.Revision); err != nil { t.Fatal(err) } currentSlot, err := store.GetSlot(ctx, issued.Principal.ID, slot.ID) if err != nil { t.Fatal(err) } if _, err := store.DisableSlot(ctx, issued.Principal.ID, slot.ID, currentSlot.Revision); err != nil { t.Fatal(err) } inactiveSnapshot, err := store.BuildPrincipalProjection(ctx, ProjectionBuildOptions{TTL: time.Minute, Clock: clock}) if err != nil { t.Fatal(err) } if len(inactiveSnapshot.GetTokens()) != 0 || len(inactiveSnapshot.GetRoutes()) != 0 { t.Fatalf("inactive rows leaked: tokens=%d routes=%d", len(inactiveSnapshot.GetTokens()), len(inactiveSnapshot.GetRoutes())) } if inactiveSnapshot.GetGeneration() <= snapshot.GetGeneration() { t.Fatalf("generation did not advance: before=%d after=%d", snapshot.GetGeneration(), inactiveSnapshot.GetGeneration()) } } func TestProjectionSnapshotEnforcesBounds(t *testing.T) { ctx := context.Background() store := newTestStore(t) if _, err := store.CreatePrincipalWithToken(ctx, CreatePrincipalInput{Alias: "bounded-a"}); err != nil { t.Fatal(err) } if _, err := store.CreatePrincipalWithToken(ctx, CreatePrincipalInput{Alias: "bounded-b"}); err != nil { t.Fatal(err) } _, err := store.BuildPrincipalProjection(ctx, ProjectionBuildOptions{ TTL: time.Minute, Limits: ProjectionLimits{MaxTokens: 1, MaxRoutes: 1, MaxBytes: 4096}, }) if !errors.Is(err, ErrProjectionTooLarge) { t.Fatalf("token bound: got %v", err) } _, err = store.BuildPrincipalProjection(ctx, ProjectionBuildOptions{ TTL: time.Minute, Limits: ProjectionLimits{MaxTokens: 2, MaxRoutes: 1, MaxBytes: 1}, }) if !errors.Is(err, ErrProjectionTooLarge) { t.Fatalf("byte bound: got %v", err) } }