package directory_test import ( "context" "testing" "time" "github.com/netkingdom/flex-auth/internal/adapters/directory" "github.com/netkingdom/flex-auth/pkg/api" ) func TestGraphResolverDetectsTokenOverage(t *testing.T) { now := time.Date(2026, 5, 17, 5, 0, 0, 0, time.UTC) resolver := directory.GraphResolver{ Client: graphClient{groups: []directory.ExternalGroup{{ID: "group:platform", DisplayName: "Platform"}}}, OrganizationRelation: api.OrganizationRelationCustomer, MaxAge: time.Hour, } got, err := resolver.ResolveGroups(context.Background(), directory.ResolveRequest{ Subject: api.SubjectRef{ID: "user:alice"}, Claims: map[string]any{ "_claim_names": map[string]any{"groups": "src1"}, }, Now: now, }) if err != nil { t.Fatalf("ResolveGroups: %v", err) } if !got.Overage.Detected || got.Overage.Claim != "_claim_names.groups" { t.Fatalf("overage = %+v", got.Overage) } if len(got.Groups) != 1 || got.Groups[0].Source != directory.SourceGraph { t.Fatalf("groups = %+v", got.Groups) } if got.Freshness.ExpiresAt != now.Add(time.Hour) { t.Fatalf("freshness = %+v", got.Freshness) } } func TestSCIMResolverCarriesDescriptorProvenance(t *testing.T) { descriptor := caringDescriptor() resolver := directory.SCIMResolver{ Client: scimClient{groups: []directory.ExternalGroup{{ID: "group:customers", Descriptor: descriptor}}}, OrganizationRelation: api.OrganizationRelationCustomer, } got, err := resolver.ResolveGroups(context.Background(), directory.ResolveRequest{Subject: api.SubjectRef{ID: "user:alice"}}) if err != nil { t.Fatalf("ResolveGroups: %v", err) } if got.Groups[0].Descriptor == nil || got.Groups[0].Descriptor.CanonicalRole != api.CanonicalRoleDoer { t.Fatalf("descriptor = %+v", got.Groups[0].Descriptor) } if got.Groups[0].Claim != "groups" { t.Fatalf("claim = %q", got.Groups[0].Claim) } } func TestLDAPResolverUsesDistinguishedNameClaim(t *testing.T) { client := ldapClient{groups: []directory.ExternalGroup{{ID: "cn=platform,ou=groups,dc=example,dc=test"}}} resolver := directory.LDAPResolver{Client: client} _, err := resolver.ResolveGroups(context.Background(), directory.ResolveRequest{ Subject: api.SubjectRef{ID: "user:alice"}, Claims: map[string]any{"distinguished_name": "cn=alice,ou=users,dc=example,dc=test"}, }) if err != nil { t.Fatalf("ResolveGroups: %v", err) } if client.lastDN != "" { t.Fatal("value receiver should not update original client") } } func TestMergeResultsAndApplyToSubject(t *testing.T) { subject := api.SubjectRef{ID: "user:alice"} enrichment := directory.MergeResults(subject, directory.ResolveResult{ Source: directory.SourceGraph, Groups: []directory.GroupGrant{ {ID: "group:b", Source: directory.SourceGraph}, {ID: "group:a", Source: directory.SourceGraph, Descriptor: caringDescriptor()}, }, Overage: directory.OverageMetadata{Detected: true, Claim: "_claim_names.groups"}, }, directory.ResolveResult{ Source: directory.SourceKeycloak, Groups: []directory.GroupGrant{ {ID: "group:a", Source: directory.SourceKeycloak}, }, Roles: []directory.RoleGrant{ {Role: api.CanonicalRoleDoer, Source: directory.SourceKeycloak}, }, }, ) if len(enrichment.Groups) != 2 || enrichment.Groups[0] != "group:a" || enrichment.Groups[1] != "group:b" { t.Fatalf("groups = %+v", enrichment.Groups) } if len(enrichment.Roles) != 1 || enrichment.Roles[0] != api.CanonicalRoleDoer { t.Fatalf("roles = %+v", enrichment.Roles) } if len(enrichment.Descriptors) != 1 || len(enrichment.Overage) != 1 { t.Fatalf("enrichment = %+v", enrichment) } applied := directory.ApplyToSubject(api.Subject{ID: "user:alice", Groups: []string{"group:existing"}}, enrichment) if len(applied.Groups) != 3 || applied.Metadata["directory_overage"] == nil { t.Fatalf("applied subject = %+v", applied) } } func caringDescriptor() *api.CaringAccessDescriptor { return &api.CaringAccessDescriptor{ ID: "descriptor:directory", Profile: api.CaringProfileCaring040RC2, SubjectType: api.SubjectTypeGroup, OrganizationRelation: api.OrganizationRelationCustomer, CanonicalRole: api.CanonicalRoleDoer, Scope: api.CaringScope{Level: api.ScopeLevelTenant, ID: "tenant:alpha"}, Planes: []api.Plane{api.PlaneIdentity}, Capabilities: []api.Capability{api.CapabilityUse}, } } type graphClient struct { groups []directory.ExternalGroup } func (c graphClient) GetMemberGroups(context.Context, string) ([]directory.ExternalGroup, directory.OverageMetadata, error) { return c.groups, directory.OverageMetadata{Total: len(c.groups)}, nil } type scimClient struct { groups []directory.ExternalGroup } func (c scimClient) GroupsForUser(context.Context, string) ([]directory.ExternalGroup, error) { return c.groups, nil } type ldapClient struct { groups []directory.ExternalGroup lastDN string } func (c ldapClient) GroupsForDN(_ context.Context, dn string) ([]directory.ExternalGroup, error) { c.lastDN = dn return c.groups, nil }