207 lines
6.5 KiB
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
|
|
}
|