package postgres import ( "context" "database/sql" "errors" "reflect" "testing" "git.nianxx.cn/wangxuming/NianAIGC/backend/internal/identity" ) func TestFindAuthorizationSnapshotLoadsAccountAndOrganizationInOneQuery(t *testing.T) { const wantSQL = `SELECT u.id, u.phone, u.display_name, u.role, u.organization_id, u.status, u.session_version, o.id, o.name, o.status FROM public.platform_users AS u LEFT JOIN public.platform_organizations AS o ON o.id = u.organization_id WHERE u.id = $1::text` rows := &identityRows{rows: [][]any{{ "account-1", "13800138000", "Zhang San", "organization_admin", "organization-1", "active", 7, "organization-1", "Acme", "active", }}} querier := &identityQuerier{rows: rows} db := NewDatabase(Config{Backend: BackendPostgres}, querier) got, found, err := db.FindAuthorizationSnapshot(context.Background(), "account-1") if err != nil { t.Fatalf("FindAuthorizationSnapshot() error = %v", err) } if !found { t.Fatal("FindAuthorizationSnapshot() found = false, want true") } if querier.sql != wantSQL || !reflect.DeepEqual(querier.args, []any{"account-1"}) { t.Fatalf("query = %q args = %#v, want exact SQL and identity argument", querier.sql, querier.args) } want := identity.AuthorizationSnapshot{ Account: identity.AccountSnapshot{ ID: "account-1", Phone: "13800138000", DisplayName: "Zhang San", Role: "organization_admin", OrganizationID: "organization-1", Status: "active", SessionVersion: 7, }, Organization: &identity.OrganizationSnapshot{ID: "organization-1", Name: "Acme", Status: "active"}, } if !reflect.DeepEqual(got, want) { t.Fatalf("FindAuthorizationSnapshot() = %#v, want %#v", got, want) } } func TestFindAuthorizationSnapshotLoadsUnboundSuperAdminWithNoOrganization(t *testing.T) { querier := &identityQuerier{rows: &identityRows{rows: [][]any{{ "super-1", "13900139000", "Root", "super_admin", nil, "active", 4, nil, nil, nil, }}}} db := NewDatabase(Config{Backend: BackendPostgres}, querier) got, found, err := db.FindAuthorizationSnapshot(context.Background(), "super-1") if err != nil { t.Fatalf("FindAuthorizationSnapshot() error = %v", err) } if !found || got.Account.OrganizationID != "" || got.Organization != nil { t.Fatalf("FindAuthorizationSnapshot() = (%#v, %v), want unbound account and nil organization", got, found) } } func TestFindAuthorizationSnapshotReturnsNotFoundForNoAccount(t *testing.T) { db := NewDatabase(Config{Backend: BackendPostgres}, &identityQuerier{rows: &identityRows{}}) got, found, err := db.FindAuthorizationSnapshot(context.Background(), "missing") if err != nil || found || !reflect.DeepEqual(got, identity.AuthorizationSnapshot{}) { t.Fatalf("FindAuthorizationSnapshot() = (%#v, %v, %v), want zero, false, nil", got, found, err) } } func TestFindAuthorizationSnapshotPropagatesDatabaseFailures(t *testing.T) { queryErr := errors.New("query failed") scanErr := errors.New("scan failed") rowsErr := errors.New("rows failed") tests := []struct { name string querier *identityQuerier wantErr error }{ {name: "query", querier: &identityQuerier{err: queryErr}, wantErr: queryErr}, {name: "scan", querier: &identityQuerier{rows: &identityRows{rows: [][]any{{nil}}, scanErr: scanErr}}, wantErr: scanErr}, {name: "rows before first row", querier: &identityQuerier{rows: &identityRows{err: rowsErr}}, wantErr: rowsErr}, {name: "rows after scan", querier: &identityQuerier{rows: &identityRows{ rows: [][]any{{"account-1", "13800138000", "Name", "user", "org-1", "active", 1, "org-1", "Org", "active"}}, err: rowsErr, }}, wantErr: rowsErr}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { db := NewDatabase(Config{Backend: BackendPostgres}, test.querier) got, found, err := db.FindAuthorizationSnapshot(context.Background(), "account-1") if !errors.Is(err, test.wantErr) { t.Fatalf("FindAuthorizationSnapshot() error = %v, want wrapping %v", err, test.wantErr) } if found || !reflect.DeepEqual(got, identity.AuthorizationSnapshot{}) { t.Fatalf("FindAuthorizationSnapshot() = (%#v, %v), want fail-closed zero result", got, found) } }) } } func TestFindAuthorizationSnapshotFailsClosedWithoutPostgres(t *testing.T) { tests := []struct { name string config Config querier Querier }{ {name: "local backend", config: Config{Backend: BackendLocal}, querier: &identityQuerier{err: errors.New("must not query")}}, {name: "unavailable pool", config: Config{Backend: BackendPostgres}}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { db := NewDatabase(test.config, test.querier) got, found, err := db.FindAuthorizationSnapshot(context.Background(), "account-1") if err == nil || found || !reflect.DeepEqual(got, identity.AuthorizationSnapshot{}) { t.Fatalf("FindAuthorizationSnapshot() = (%#v, %v, %v), want fail-closed error", got, found, err) } if querier, ok := test.querier.(*identityQuerier); ok && querier.called { t.Fatal("FindAuthorizationSnapshot() queried while PostgreSQL was unavailable") } }) } } var _ identity.AuthorizationSnapshotLoader = (*Database)(nil) type identityQuerier struct { rows *identityRows err error sql string args []any called bool } func (q *identityQuerier) Query(_ context.Context, query string, args ...any) (Rows, error) { q.called = true q.sql = query q.args = args return q.rows, q.err } type identityRows struct { rows [][]any idx int err error scanErr error } func (r *identityRows) Close() {} func (r *identityRows) Err() error { return r.err } func (r *identityRows) Next() bool { return r.idx < len(r.rows) } func (r *identityRows) Scan(dest ...any) error { if r.scanErr != nil { return r.scanErr } if r.idx >= len(r.rows) { return errors.New("scan past end") } row := r.rows[r.idx] r.idx++ if len(dest) != len(row) { return errors.New("scan arity mismatch") } for index, target := range dest { value := row[index] switch target := target.(type) { case *string: text, ok := value.(string) if !ok { return errors.New("scan string type mismatch") } *target = text case *int: number, ok := value.(int) if !ok { return errors.New("scan int type mismatch") } *target = number case *sql.NullString: if value == nil { *target = sql.NullString{} continue } text, ok := value.(string) if !ok { return errors.New("scan nullable string type mismatch") } *target = sql.NullString{String: text, Valid: true} default: return errors.New("unsupported scan target") } } return nil }