Files
NianAIGC/backend/internal/postgres/identity_test.go

207 lines
6.5 KiB
Go

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
}