62 lines
2.3 KiB
Go
62 lines
2.3 KiB
Go
package identity
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"errors"
|
||
|
|
"os"
|
||
|
|
"testing"
|
||
|
|
|
||
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
||
|
|
)
|
||
|
|
|
||
|
|
const (
|
||
|
|
localIssuer = "http://localhost:58180/realms/voice-local"
|
||
|
|
ownerSubject = "00000000-0000-0000-0000-000000000101"
|
||
|
|
outsiderSubject = "00000000-0000-0000-0000-000000000103"
|
||
|
|
firstHouseholdID = "10000000-0000-0000-0000-000000000211"
|
||
|
|
secondHouseholdID = "20000000-0000-0000-0000-000000000211"
|
||
|
|
)
|
||
|
|
|
||
|
|
func TestRepositoryEnforcesTenantAndRoleBoundaries(t *testing.T) {
|
||
|
|
databaseURL := os.Getenv("TEST_DATABASE_URL")
|
||
|
|
if databaseURL == "" {
|
||
|
|
t.Skip("TEST_DATABASE_URL is not set")
|
||
|
|
}
|
||
|
|
ctx := context.Background()
|
||
|
|
pool, err := pgxpool.New(ctx, databaseURL)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("connect integration database: %v", err)
|
||
|
|
}
|
||
|
|
defer pool.Close()
|
||
|
|
repository := NewRepository(pool)
|
||
|
|
|
||
|
|
owner := Principal{Issuer: localIssuer, Subject: ownerSubject}
|
||
|
|
currentUser, err := repository.CurrentUser(ctx, owner)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("load owner: %v", err)
|
||
|
|
}
|
||
|
|
if len(currentUser.Accounts) != 1 || len(currentUser.Accounts[0].Memberships) != 1 {
|
||
|
|
t.Fatalf("unexpected owner account: %#v", currentUser)
|
||
|
|
}
|
||
|
|
if currentUser.Accounts[0].Memberships[0].Role != RoleOwner {
|
||
|
|
t.Fatalf("owner role not resolved: %#v", currentUser.Accounts[0].Memberships[0])
|
||
|
|
}
|
||
|
|
if _, err := repository.RequireHouseholdCapability(ctx, owner, firstHouseholdID, CapabilityHouseholdManage); err != nil {
|
||
|
|
t.Fatalf("owner should manage own household: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
outsider := Principal{Issuer: localIssuer, Subject: outsiderSubject}
|
||
|
|
if _, err := repository.RequireHouseholdCapability(ctx, outsider, firstHouseholdID, CapabilityHouseholdRead); !errors.Is(err, ErrAccessDenied) {
|
||
|
|
t.Fatalf("cross-tenant access error = %v, want ErrAccessDenied", err)
|
||
|
|
}
|
||
|
|
if _, err := repository.RequireHouseholdCapability(ctx, outsider, secondHouseholdID, CapabilityHouseholdRead); err != nil {
|
||
|
|
t.Fatalf("viewer should read own household: %v", err)
|
||
|
|
}
|
||
|
|
if _, err := repository.RequireHouseholdCapability(ctx, outsider, secondHouseholdID, CapabilityHouseholdManage); !errors.Is(err, ErrAccessDenied) {
|
||
|
|
t.Fatalf("viewer manage error = %v, want ErrAccessDenied", err)
|
||
|
|
}
|
||
|
|
if _, err := repository.RequireHouseholdCapability(ctx, owner, firstHouseholdID, CapabilityRecordingPlay); !errors.Is(err, ErrAccessDenied) {
|
||
|
|
t.Fatalf("owner recording access error = %v, want ErrAccessDenied", err)
|
||
|
|
}
|
||
|
|
}
|