package httpapi import ( "context" "encoding/json" "errors" "net/http" "net/http/httptest" "os" "path/filepath" "runtime" "testing" "git.nianxx.cn/wangxuming/NianAIGC/backend/internal/identity" ) func TestPlatformAuthorizerMatchesSharedContract(t *testing.T) { contract := loadPlatformHTTPAuthContract(t) for _, contractCase := range contract.Cases { t.Run(contractCase.Name, func(t *testing.T) { resolver := &platformSessionResolverStub{outcome: contractCase.ResolverOutcome} if contractCase.Session != nil { resolver.session = fixturePlatformSession(*contractCase.Session) } authorizer, err := NewPlatformAuthorizer(AuthState(contractCase.State), resolver) if err != nil { t.Fatalf("NewPlatformAuthorizer: %v", err) } request := httptest.NewRequest(http.MethodGet, "/protected", nil) if contractCase.ResolverOutcome != "not_called" { request.AddCookie(&http.Cookie{Name: contract.CookieName, Value: "signed-cookie"}) } session, err := authorizer.Authorize(request, PlatformRequirement(contractCase.Requirement)) assertPlatformAuthOutcome(t, contractCase, session, err) wantCalls := 0 if contractCase.ResolverOutcome != "not_called" { wantCalls = 1 } if resolver.calls != wantCalls { t.Fatalf("resolver calls = %d, want %d", resolver.calls, wantCalls) } }) } } func TestPlatformAuthorizerUsesSharedChunkReader(t *testing.T) { resolver := &platformSessionResolverStub{outcome: "authenticated", session: fixturePlatformSession(platformHTTPFixtureSession{AuthMode: "user", Role: "user"})} authorizer, err := NewPlatformAuthorizer(AuthState{Required: true, Configured: true}, resolver) if err != nil { t.Fatal(err) } request := httptest.NewRequest(http.MethodGet, "/protected", nil) request.AddCookie(&http.Cookie{Name: identity.SessionCookieName, Value: "first"}) request.AddCookie(&http.Cookie{Name: identity.SessionCookieName, Value: "last"}) request.AddCookie(&http.Cookie{Name: identity.SessionCookieName + ".1", Value: "tail"}) if _, err := authorizer.Authorize(request, PlatformApp); err != nil { t.Fatalf("Authorize: %v", err) } if resolver.value != "lasttail" { t.Fatalf("resolver value = %q, want lasttail", resolver.value) } } func TestPlatformAuthorizerCanDisableAnonymousAdministratorFallback(t *testing.T) { authorizer, err := NewPlatformAuthorizer( AuthState{Required: false, Configured: false}, nil, WithLocalDevelopmentFallback(false), ) if err != nil { t.Fatal(err) } _, err = authorizer.Authorize(httptest.NewRequest(http.MethodGet, "/protected", nil), PlatformSuperAdmin) var authErr *PlatformAuthError if !errors.As(err, &authErr) || authErr.Status != http.StatusUnauthorized || authErr.Kind != PlatformUnauthenticated { t.Fatalf("error=%#v, want unauthenticated/401", err) } } type platformSessionResolverStub struct { outcome string session identity.Session calls int value string } func (stub *platformSessionResolverStub) Resolve(_ context.Context, value string) (identity.Session, error) { stub.calls++ stub.value = value switch stub.outcome { case "authenticated": return stub.session, nil case "unauthenticated": return identity.Session{}, identity.ErrUnauthenticated case "infrastructure_error": return identity.Session{}, errors.New("database secret must not leak") default: return identity.Session{}, errors.New("unexpected resolver call") } } type platformHTTPAuthContract struct { Version int `json:"version"` CookieName string `json:"cookieName"` Cases []platformHTTPAuthCase `json:"cases"` } type platformHTTPAuthCase struct { Name string `json:"name"` State AuthState `json:"state"` Requirement string `json:"requirement"` ResolverOutcome string `json:"resolverOutcome"` Session *platformHTTPFixtureSession `json:"session"` Expected struct { Outcome string `json:"outcome"` Status int `json:"status"` Role string `json:"role"` AuthMode string `json:"authMode"` } `json:"expected"` } type platformHTTPFixtureSession struct { AuthMode string `json:"authMode"` Role string `json:"role"` } func loadPlatformHTTPAuthContract(t *testing.T) platformHTTPAuthContract { t.Helper() _, filename, _, ok := runtime.Caller(0) if !ok { t.Fatal("locate platform_auth_test.go") } raw, err := os.ReadFile(filepath.Join(filepath.Dir(filename), "..", "..", "..", "contracts", "auth", "platform-http-auth-v1.json")) if err != nil { t.Fatal(err) } var contract platformHTTPAuthContract if err := json.Unmarshal(raw, &contract); err != nil { t.Fatal(err) } if contract.Version != 1 || contract.CookieName != identity.SessionCookieName { t.Fatalf("invalid contract header: %+v", contract) } return contract } func fixturePlatformSession(input platformHTTPFixtureSession) identity.Session { return identity.Session{ Version: 1, AuthMode: identity.AuthMode(input.AuthMode), User: identity.User{ ID: "fixture-user", Subject: "fixture-user", DisplayName: "Fixture User", ClientID: "platform", Role: input.Role, Status: "active", }, } } func assertPlatformAuthOutcome(t *testing.T, contractCase platformHTTPAuthCase, session identity.Session, err error) { t.Helper() switch contractCase.Expected.Outcome { case "authenticated": if err != nil { t.Fatalf("Authorize error = %v", err) } if session.User.Role != contractCase.Expected.Role || string(session.AuthMode) != contractCase.Expected.AuthMode { t.Fatalf("session role/mode = %q/%q, want %q/%q", session.User.Role, session.AuthMode, contractCase.Expected.Role, contractCase.Expected.AuthMode) } case "infrastructure_error": var authErr *PlatformAuthError if err == nil || errors.As(err, &authErr) { t.Fatalf("error = %v, want non-auth infrastructure error", err) } default: var authErr *PlatformAuthError if !errors.As(err, &authErr) || authErr.Status != contractCase.Expected.Status || string(authErr.Kind) != contractCase.Expected.Outcome { t.Fatalf("error = %#v, want %s/%d", err, contractCase.Expected.Outcome, contractCase.Expected.Status) } } }