internal/accessauth/accessauth_test.go
Rama main · réplica de lectura del repositorio original.
package accessauth
import (
"context"
"database/sql"
"fmt"
"os"
"strings"
"testing"
"time"
_ "github.com/jackc/pgx/v5/stdlib"
"github.com/sarathsp06/sparrow/pkg/access"
"github.com/sarathsp06/sparrow/pkg/access/memstore"
"github.com/sarathsp06/sparrow/pkg/access/pgstore"
"github.com/sarathsp06/sparrow/pkg/access/storetest"
)
// The migrations must ship exactly the library's schema, one part each, or
// pgstore queries could drift from the tables Sparrow creates.
func TestMigrationsMatchPgstoreSchema(t *testing.T) {
migrations := []string{"000027_access_tokens.up.sql", "000028_access_token_idempotency.up.sql", "000029_access_token_external_id.up.sql"}
if len(migrations) != len(pgstore.SchemaParts()) {
t.Fatalf("pgstore has %d schema parts but %d migrations are checked; add the new migration here", len(pgstore.SchemaParts()), len(migrations))
}
for i, name := range migrations {
b, err := os.ReadFile("../../db/migrations/" + name)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(b), pgstore.SchemaParts()[i]) {
t.Fatalf("db/migrations/%s no longer contains pgstore schema part %d verbatim; copy it over", name, i+1)
}
}
}
func TestTTLRules(t *testing.T) {
acme := "acme"
cases := []struct {
consumer *string
requested time.Duration
never bool
def time.Duration
want time.Duration
wantErr bool
}{
{nil, 0, false, TenantTokenDefaultTTL, TenantTokenDefaultTTL, false}, // tenant-wide: operator default
{nil, 0, false, 0, 0, false}, // default 0 = never (opt-out)
{nil, 0, true, TenantTokenDefaultTTL, 0, false}, // explicit never
{nil, time.Hour, true, TenantTokenDefaultTTL, 0, true}, // both: rejected
{nil, 10 * 365 * 24 * time.Hour, false, TenantTokenDefaultTTL, 10 * 365 * 24 * time.Hour, false},
{&acme, 0, false, TenantTokenDefaultTTL, ConsumerTokenDefaultTTL, false},
{&acme, time.Hour, false, 0, time.Hour, false},
{&acme, 0, true, 0, 0, true}, // consumer tokens always expire
{&acme, ConsumerTokenMaxTTL + time.Second, false, 0, 0, true},
}
for _, c := range cases {
got, err := TokenTTL(c.consumer, c.requested, c.never, c.def)
if got != c.want || (err != nil) != c.wantErr {
t.Errorf("TokenTTL(%v, %v) = %v, %v", c.consumer, c.requested, got, err)
}
}
if got, _ := InviteTTL(0); got != InviteDefaultTTL {
t.Errorf("InviteTTL(0) = %v", got)
}
if _, err := InviteTTL(InviteMaxTTL + time.Second); err == nil || err.Error() != "invites can live at most 7 days" {
t.Errorf("InviteTTL above max: %v", err)
}
if _, err := TokenTTL(&acme, ConsumerTokenMaxTTL+time.Second, false, 0); err == nil || err.Error() != "consumer tokens can live at most 30 days" {
t.Errorf("TokenTTL above max: %v", err)
}
}
func TestMasterKeyIsRootInDefaultTenant(t *testing.T) {
svc, err := NewWithStore(memstore.New(), "master", nil)
if err != nil {
t.Fatal(err)
}
p, err := svc.Authenticate(context.Background(), "master")
if err != nil || !p.Root || p.Realm != RootRealm() || p.Name != MasterKeyName {
t.Fatalf("master key principal = %+v, %v", p, err)
}
open, _ := NewWithStore(memstore.New(), "", nil)
if _, err := open.Authenticate(context.Background(), ""); err == nil {
t.Fatal("empty credential authenticated without a master key")
}
_, secret, err := svc.CreateToken(context.Background(), access.CreateTokenRequest{Realm: RootRealm(), Name: "x", CreatedBy: MasterKeyName})
if err != nil || !strings.HasPrefix(secret, TokenPrefix) {
t.Fatalf("token secret %q, %v", secret, err)
}
}
// TestPgstoreConformance runs the library's store contract against real
// Postgres, in a throwaway schema. Needs DATABASE_URL (CI provides one).
func TestPgstoreConformance(t *testing.T) {
url := os.Getenv("DATABASE_URL")
if url == "" {
t.Skip("DATABASE_URL not set")
}
admin, err := sql.Open("pgx", url)
if err != nil {
t.Fatal(err)
}
defer admin.Close() //nolint:errcheck
if err := admin.Ping(); err != nil {
t.Skipf("postgres unreachable: %v", err)
}
n := 0
storetest.Run(t, func(t *testing.T) access.Store {
n++
schema := fmt.Sprintf("access_conformance_%d_%d", time.Now().UnixNano(), n)
if _, err := admin.Exec(`CREATE SCHEMA ` + schema); err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _, _ = admin.Exec(`DROP SCHEMA ` + schema + ` CASCADE`) })
sep := "?"
if strings.Contains(url, "?") {
sep = "&"
}
db, err := sql.Open("pgx", url+sep+"search_path="+schema)
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = db.Close() })
if _, err := db.Exec(pgstore.Schema()); err != nil {
t.Fatal(err)
}
return pgstore.New(db)
})
}
func TestValidateConsumer(t *testing.T) {
for _, ok := range []string{"acme", "team-a", "Team_B.2", "ünïcode", strings.Repeat("x", 255)} {
if err := ValidateConsumer(ok); err != nil {
t.Errorf("ValidateConsumer(%q) = %v", ok, err)
}
}
for _, bad := range []string{"", " acme", "acme ", ".", "..", "../../v1", "a/b", `a\b`, "a?b", "a#b", "a%2fb", "a\nb", "_sparrow", strings.Repeat("x", 256)} {
if err := ValidateConsumer(bad); err == nil {
t.Errorf("ValidateConsumer(%q) accepted", bad)
}
}
}