Files
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

205 lines
5.6 KiB
Go

package tuttiagentauth
import (
"context"
"errors"
"strings"
"sync"
"testing"
"time"
)
func TestReconcileIsIdempotentWhenCredentialMaterialReady(t *testing.T) {
authorizer := &fakeAuthorizer{}
store := &fakeStore{state: CredentialState{MaterialReady: true}}
runner := &fakeRunner{}
result, err := (&Reconciler{}).Reconcile(context.Background(), authorizer, store, runner, time.Now())
if err != nil || result.Changed || authorizer.issueCalls != 0 || runner.calls != 0 {
t.Fatalf("Reconcile() = %#v, %v; authorizer=%#v runner=%#v", result, err, authorizer, runner)
}
}
func TestReconcileCompensatesFailedLoginWithoutLeakingTokens(t *testing.T) {
now := time.Now().UTC()
authorizer := &fakeAuthorizer{bundle: validBundle(now)}
store := &fakeStore{}
runner := &fakeRunner{err: errors.New("runner output contained lat_secret lrt_secret")}
_, err := (&Reconciler{}).Reconcile(context.Background(), authorizer, store, runner, now)
if err == nil {
t.Fatal("Reconcile() error = nil")
}
if strings.Contains(err.Error(), "lat_secret") || strings.Contains(err.Error(), "lrt_secret") {
t.Fatalf("Reconcile() leaked credential in error: %v", err)
}
if store.removeCalls != 1 || authorizer.revokeCalls != 1 || authorizer.lastReason != "login_failed" {
t.Fatalf("compensation calls: store=%#v authorizer=%#v", store, authorizer)
}
}
func TestReconcileVerifiesCredentialMaterialAfterLogin(t *testing.T) {
now := time.Now().UTC()
store := &fakeStore{}
runner := &fakeRunner{afterLogin: store.setReady}
result, err := (&Reconciler{}).Reconcile(context.Background(), &fakeAuthorizer{bundle: validBundle(now)}, store, runner, now)
if err != nil || !result.Changed {
t.Fatalf("Reconcile() = %#v, %v", result, err)
}
}
func TestRemoveLocalDeletesBeforeReturningRevokeTarget(t *testing.T) {
target := RevokeTarget{RefreshToken: "lrt_secret"}
store := &fakeStore{state: CredentialState{MaterialReady: true, RevokeTarget: target}}
got, err := (&Reconciler{}).RemoveLocal(context.Background(), store)
if err != nil || got != target || store.removeCalls != 1 {
t.Fatalf("RemoveLocal() = %#v, %v; store=%#v", got, err, store)
}
}
func TestReconcilerSerializesConcurrentCredentialMutation(t *testing.T) {
now := time.Now().UTC()
reconciler := &Reconciler{}
authorizer := &fakeAuthorizer{bundle: validBundle(now)}
store := &fakeStore{}
runner := &fakeRunner{afterLogin: store.setReady}
start := make(chan struct{})
errorsByCall := make(chan error, 2)
for range 2 {
go func() {
<-start
_, err := reconciler.Reconcile(context.Background(), authorizer, store, runner, now)
errorsByCall <- err
}()
}
close(start)
for range 2 {
if err := <-errorsByCall; err != nil {
t.Fatal(err)
}
}
if authorizer.issueCount() != 1 || runner.callCount() != 1 {
t.Fatalf("concurrent reconcile issued %d token(s) and ran %d login(s)", authorizer.issueCount(), runner.callCount())
}
}
func TestReconcileCompensationSurvivesCallerCancellation(t *testing.T) {
now := time.Now().UTC()
ctx, cancel := context.WithCancel(context.Background())
authorizer := &fakeAuthorizer{bundle: validBundle(now)}
store := &fakeStore{}
runner := &fakeRunner{
err: errors.New("login failed"),
afterLogin: cancel,
}
_, err := (&Reconciler{}).Reconcile(ctx, authorizer, store, runner, now)
if err == nil {
t.Fatal("Reconcile() error = nil")
}
if store.removeContextError() != nil || authorizer.revokeContextError() != nil {
t.Fatalf("compensation inherited cancellation: remove=%v revoke=%v", store.removeContextError(), authorizer.revokeContextError())
}
}
func validBundle(now time.Time) TokenBundle {
return TokenBundle{
AppID: "233749", AccountBaseURL: "https://account.example", AccessToken: "lat_secret",
AccessTokenExpiresAt: now.Add(time.Hour).Unix(), RefreshToken: "lrt_secret",
RefreshTokenExpiresAt: now.Add(24 * time.Hour).Unix(), TokenType: "Bearer",
Scopes: []string{ScopeModels, ScopeChat},
}
}
type fakeAuthorizer struct {
mu sync.Mutex
bundle TokenBundle
issueErr error
issueCalls int
revokeCalls int
lastReason string
revokeCtxErr error
}
func (f *fakeAuthorizer) Issue(context.Context) (TokenBundle, error) {
f.mu.Lock()
defer f.mu.Unlock()
f.issueCalls++
return f.bundle, f.issueErr
}
func (f *fakeAuthorizer) Revoke(ctx context.Context, _ RevokeTarget, reason string) error {
f.mu.Lock()
defer f.mu.Unlock()
f.revokeCalls++
f.lastReason = reason
f.revokeCtxErr = ctx.Err()
return nil
}
func (f *fakeAuthorizer) issueCount() int {
f.mu.Lock()
defer f.mu.Unlock()
return f.issueCalls
}
func (f *fakeAuthorizer) revokeContextError() error {
f.mu.Lock()
defer f.mu.Unlock()
return f.revokeCtxErr
}
type fakeStore struct {
mu sync.Mutex
state CredentialState
inspectErr error
removeErr error
removeCalls int
removeCtxErr error
}
func (f *fakeStore) Inspect(context.Context) (CredentialState, error) {
f.mu.Lock()
defer f.mu.Unlock()
return f.state, f.inspectErr
}
func (f *fakeStore) Remove(ctx context.Context) error {
f.mu.Lock()
defer f.mu.Unlock()
f.removeCalls++
f.removeCtxErr = ctx.Err()
return f.removeErr
}
func (f *fakeStore) setReady() {
f.mu.Lock()
defer f.mu.Unlock()
f.state.MaterialReady = true
}
func (f *fakeStore) removeContextError() error {
f.mu.Lock()
defer f.mu.Unlock()
return f.removeCtxErr
}
type fakeRunner struct {
mu sync.Mutex
err error
calls int
afterLogin func()
}
func (f *fakeRunner) Login(context.Context, TokenBundle) error {
f.mu.Lock()
f.calls++
f.mu.Unlock()
if f.afterLogin != nil {
f.afterLogin()
}
return f.err
}
func (f *fakeRunner) callCount() int {
f.mu.Lock()
defer f.mu.Unlock()
return f.calls
}