package gitcredentials import ( "context" "errors" "sync" "testing" "time" ) type recordingAuthorizer struct { mu sync.Mutex calls int last Scope err error } func (a *recordingAuthorizer) AuthorizeGitCredential(_ context.Context, scope Scope) error { a.mu.Lock() defer a.mu.Unlock() a.calls++ a.last = scope return a.err } type rotatingProvider struct { mu sync.Mutex binding string username string secret string calls int err error disabled bool started chan struct{} release chan struct{} } func (p *rotatingProvider) Supports(providerID string) bool { p.mu.Lock() defer p.mu.Unlock() return providerID == "bitbucket" && !p.disabled } func (p *rotatingProvider) Binding(context.Context, Scope) (string, error) { p.mu.Lock() defer p.mu.Unlock() return p.binding, p.err } func (p *rotatingProvider) Resolve(ctx context.Context, _ Scope) (Credential, error) { if p.started != nil { select { case p.started <- struct{}{}: case <-ctx.Done(): return Credential{}, ctx.Err() } select { case <-p.release: case <-ctx.Done(): return Credential{}, ctx.Err() } } p.mu.Lock() defer p.mu.Unlock() p.calls++ if p.err != nil { return Credential{}, p.err } return Credential{Username: p.username, Password: p.secret}, nil } func testScope() Scope { return Scope{ ProviderID: "bitbucket", WorkspaceID: "workspace-1", TaskID: "task-1", SessionID: "session-1", RepositoryID: "repository-1", Host: "bitbucket.example", Path: "/scm/ENG/widgets.git", } } func testRedemption(lease string) Redemption { return Redemption{ Lease: lease, TaskID: "task-1", SessionID: "session-1", RepositoryID: "repository-1", Host: "bitbucket.example", Path: "/scm/ENG/widgets.git", } } func TestBrokerIssuesExactScopedLeaseAndReresolves(t *testing.T) { provider := &rotatingProvider{binding: "generation-1", username: "x-token-auth", secret: "first"} authorizer := &recordingAuthorizer{} broker := NewBroker(provider, authorizer) lease, err := broker.Issue(t.Context(), testScope()) if err != nil { t.Fatalf("Issue() error = %v", err) } credential, err := broker.Redeem(t.Context(), testRedemption(lease.Token)) if err != nil { t.Fatalf("Redeem() error = %v", err) } if credential.Username != "x-token-auth" || credential.Password != "first" { t.Fatalf("credential = %#v", credential) } provider.mu.Lock() provider.secret = "rotated" provider.mu.Unlock() credential, err = broker.Redeem(t.Context(), testRedemption(lease.Token)) if err != nil { t.Fatalf("second Redeem() error = %v", err) } if credential.Password != "rotated" || provider.calls != 2 { t.Fatalf("second credential = %#v, provider calls = %d", credential, provider.calls) } if authorizer.calls != 3 { t.Fatalf("authorizer calls = %d, want issue + two redemptions", authorizer.calls) } } func TestBrokerRejectsExactScopeMismatch(t *testing.T) { provider := &rotatingProvider{binding: "generation-1", username: "user", secret: "secret"} broker := NewBroker(provider, &recordingAuthorizer{}) lease, err := broker.Issue(t.Context(), testScope()) if err != nil { t.Fatalf("Issue() error = %v", err) } for name, mismatch := range map[string]func(*Redemption){ "task": func(request *Redemption) { request.TaskID = "other-task" }, "session": func(request *Redemption) { request.SessionID = "other-session" }, "repository": func(request *Redemption) { request.RepositoryID = "other-repository" }, "host": func(request *Redemption) { request.Host = "other.example" }, "path": func(request *Redemption) { request.Path = "/scm/ENG/other.git" }, } { t.Run(name, func(t *testing.T) { request := testRedemption(lease.Token) mismatch(&request) if _, err := broker.Redeem(t.Context(), request); !errors.Is(err, ErrScopeDenied) { t.Fatalf("%s mismatch error = %v, want ErrScopeDenied", name, err) } }) } } func TestBrokerRejectsRepositoryPathCaseMismatch(t *testing.T) { provider := &rotatingProvider{binding: "generation-1", username: "user", secret: "secret"} broker := NewBroker(provider, &recordingAuthorizer{}) scope := testScope() scope.Path = "/Team/Repo.git" lease, err := broker.Issue(t.Context(), scope) if err != nil { t.Fatalf("Issue() error = %v", err) } request := testRedemption(lease.Token) request.Path = "/team/repo.git" if _, err := broker.Redeem(t.Context(), request); !errors.Is(err, ErrScopeDenied) { t.Fatalf("Redeem() error = %v, want ErrScopeDenied", err) } } func TestBrokerRejectsRepositoryGitSuffixMismatch(t *testing.T) { provider := &rotatingProvider{binding: "generation-1", username: "user", secret: "secret"} broker := NewBroker(provider, &recordingAuthorizer{}) lease, err := broker.Issue(t.Context(), testScope()) if err != nil { t.Fatalf("Issue() error = %v", err) } request := testRedemption(lease.Token) request.Path = "/scm/ENG/widgets" if _, err := broker.Redeem(t.Context(), request); !errors.Is(err, ErrScopeDenied) { t.Fatalf("Redeem() error = %v, want ErrScopeDenied", err) } } func TestBrokerExpiresAndRevokesLease(t *testing.T) { provider := &rotatingProvider{binding: "generation-1", username: "user", secret: "secret"} broker := NewBroker(provider, &recordingAuthorizer{}) now := time.Date(2026, 7, 31, 12, 0, 0, 0, time.UTC) broker.now = func() time.Time { return now } scope := testScope() scope.TTL = time.Minute lease, err := broker.Issue(t.Context(), scope) if err != nil { t.Fatalf("Issue() error = %v", err) } now = now.Add(2 * time.Minute) if _, err := broker.Redeem(t.Context(), testRedemption(lease.Token)); !errors.Is(err, ErrLeaseExpired) { t.Fatalf("expired redemption error = %v, want ErrLeaseExpired", err) } now = now.Add(-2 * time.Minute) lease, err = broker.Issue(t.Context(), scope) if err != nil { t.Fatalf("Issue() error = %v", err) } broker.RevokeSession(scope.SessionID) if _, err := broker.Redeem(t.Context(), testRedemption(lease.Token)); !errors.Is(err, ErrLeaseInvalid) { t.Fatalf("revoked redemption error = %v, want ErrLeaseInvalid", err) } } func TestBrokerClampsRequestedTTLToDefaultMaximum(t *testing.T) { provider := &rotatingProvider{binding: "generation-1", username: "user", secret: "secret"} broker := NewBroker(provider, &recordingAuthorizer{}) now := time.Date(2026, 7, 31, 12, 0, 0, 0, time.UTC) broker.now = func() time.Time { return now } scope := testScope() scope.TTL = defaultLeaseTTL + time.Hour lease, err := broker.Issue(t.Context(), scope) if err != nil { t.Fatalf("Issue() error = %v", err) } if want := now.Add(defaultLeaseTTL); !lease.ExpiresAt.Equal(want) { t.Fatalf("ExpiresAt = %s, want clamped maximum %s", lease.ExpiresAt, want) } } func TestBrokerActiveLeaseCountSweepsExpiredRecords(t *testing.T) { provider := &rotatingProvider{binding: "generation-1", username: "user", secret: "secret"} broker := NewBroker(provider, &recordingAuthorizer{}) now := time.Date(2026, 7, 31, 12, 0, 0, 0, time.UTC) broker.now = func() time.Time { return now } scope := testScope() scope.TTL = time.Minute if _, err := broker.Issue(t.Context(), scope); err != nil { t.Fatalf("Issue() error = %v", err) } now = now.Add(2 * time.Minute) if count := broker.ActiveLeaseCount(); count != 0 { t.Fatalf("ActiveLeaseCount() = %d, want expired record swept", count) } broker.mu.Lock() retained := len(broker.leases) broker.mu.Unlock() if retained != 0 { t.Fatalf("retained lease records = %d, want 0 after sweep", retained) } } func TestBrokerRenewsLeaseOnlyAfterSuccessfulRedemption(t *testing.T) { provider := &rotatingProvider{binding: "generation-1", username: "user", secret: "secret"} broker := NewBroker(provider, &recordingAuthorizer{}) issuedAt := time.Date(2026, 7, 31, 12, 0, 0, 0, time.UTC) now := issuedAt broker.now = func() time.Time { return now } scope := testScope() scope.TTL = time.Minute lease, err := broker.Issue(t.Context(), scope) if err != nil { t.Fatalf("Issue() error = %v", err) } now = issuedAt.Add(30 * time.Second) if _, err := broker.Redeem(t.Context(), testRedemption(lease.Token)); err != nil { t.Fatalf("Redeem() error = %v", err) } now = issuedAt.Add(75 * time.Second) if _, err := broker.Redeem(t.Context(), testRedemption(lease.Token)); err != nil { t.Fatalf("Redeem() after original expiry error = %v", err) } } func TestBrokerConcurrentRevocationNeverReturnsCredential(t *testing.T) { provider := &rotatingProvider{ binding: "generation-1", username: "user", secret: "secret", started: make(chan struct{}), release: make(chan struct{}), } broker := NewBroker(provider, &recordingAuthorizer{}) lease, err := broker.Issue(t.Context(), testScope()) if err != nil { t.Fatalf("Issue() error = %v", err) } result := make(chan struct { credential Credential err error }, 1) go func() { credential, redeemErr := broker.Redeem(context.Background(), testRedemption(lease.Token)) result <- struct { credential Credential err error }{credential, redeemErr} }() select { case <-provider.started: case <-time.After(5 * time.Second): t.Fatal("Redeem() did not reach provider resolution") } broker.RevokeSession("session-1") close(provider.release) var got struct { credential Credential err error } select { case got = <-result: case <-time.After(5 * time.Second): t.Fatal("Redeem() did not return after provider release") } if got.credential.Password != "" || !errors.Is(got.err, ErrLeaseRevoked) { t.Fatalf("Redeem() = %#v, %v; want no credential and ErrLeaseRevoked", got.credential, got.err) } } func TestCompositeResolverRevokesLeaseWhenProviderDisables(t *testing.T) { provider := &rotatingProvider{binding: "generation-1", username: "user", secret: "secret"} resolver := NewCompositeResolver(provider) broker := NewBroker(resolver, &recordingAuthorizer{}) lease, err := broker.Issue(t.Context(), testScope()) if err != nil { t.Fatalf("Issue() error = %v", err) } provider.mu.Lock() provider.disabled = true provider.mu.Unlock() if _, err := broker.Redeem(t.Context(), testRedemption(lease.Token)); !errors.Is(err, ErrLeaseRevoked) { t.Fatalf("disabled provider redemption error = %v, want ErrLeaseRevoked", err) } } func TestBrokerRevokesProviderLeases(t *testing.T) { provider := &rotatingProvider{binding: "generation-1", username: "user", secret: "secret"} broker := NewBroker(provider, &recordingAuthorizer{}) lease, err := broker.Issue(t.Context(), testScope()) if err != nil { t.Fatalf("Issue() error = %v", err) } broker.RevokeProvider("bitbucket") if _, err := broker.Redeem(t.Context(), testRedemption(lease.Token)); !errors.Is(err, ErrLeaseInvalid) { t.Fatalf("provider-revoked redemption error = %v, want ErrLeaseInvalid", err) } }