internal/server/store/identity_test.go
Ref: Size: 4.5 KiB History
package store
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestTenantByIdentity(t *testing.T) {
s := newStore(t)
// Not found before any binding.
_, ok, err := s.TenantByIdentity("https://idp", "sub-1")
require.NoError(t, err)
assert.False(t, ok)
created, err := s.CreateTenantForIdentity("https://idp", "sub-1", "alex@a.com")
require.NoError(t, err)
assert.Equal(t, "alex", created.ID)
assert.Equal(t, "https://idp", created.OIDCIssuer)
assert.Equal(t, "sub-1", created.OIDCSubject)
assert.Equal(t, "alex@a.com", created.Email)
assert.False(t, created.CreatedAt.IsZero())
// Found after create, same row.
got, ok, err := s.TenantByIdentity("https://idp", "sub-1")
require.NoError(t, err)
require.True(t, ok)
assert.Equal(t, created.ID, got.ID)
assert.Equal(t, "sub-1", got.OIDCSubject)
}
func TestTenantByID(t *testing.T) {
s := newStore(t)
// Unknown handle: not found, no error.
_, ok, err := s.TenantByID("nobody")
require.NoError(t, err)
assert.False(t, ok)
created, err := s.CreateTenantForIdentity("https://idp", "sub-1", "alex@a.com")
require.NoError(t, err)
got, ok, err := s.TenantByID(created.ID)
require.NoError(t, err)
require.True(t, ok)
assert.Equal(t, created.ID, got.ID)
assert.Equal(t, "alex@a.com", got.Email)
assert.Equal(t, "sub-1", got.OIDCSubject)
// The harness-provisioned tenant is readable and carries its binding.
def, ok, err := s.TenantByID(testTenant)
require.NoError(t, err)
require.True(t, ok)
assert.Equal(t, testTenant, def.ID)
assert.Equal(t, testTenant+"@test.local", def.Email)
}
func TestCreateTenantForIdentityHandleCollision(t *testing.T) {
s := newStore(t)
a, err := s.CreateTenantForIdentity("https://idp", "sub-a", "alex@a.com")
require.NoError(t, err)
assert.Equal(t, "alex", a.ID)
// Same local part, different identity → numeric suffix.
b, err := s.CreateTenantForIdentity("https://idp", "sub-b", "alex@b.com")
require.NoError(t, err)
assert.Equal(t, "alex-2", b.ID)
c, err := s.CreateTenantForIdentity("https://idp", "sub-c", "alex@c.com")
require.NoError(t, err)
assert.Equal(t, "alex-3", c.ID)
}
func TestCreateTenantForIdentityDotFree(t *testing.T) {
s := newStore(t)
// Handles appear in <tenant>.<vm>; dots in the local part flatten to '-'.
got, err := s.CreateTenantForIdentity("https://idp", "sub-1", "first.last@x.com")
require.NoError(t, err)
assert.Equal(t, "first-last", got.ID)
assert.NotContains(t, got.ID, ".", "tenant handle must be dot-free")
}
func TestCreateTenantForIdentityReservedSystem(t *testing.T) {
s := newStore(t)
// SystemTenant is reserved (the audit scope for tenant-less events); JIT
// never assigns it — a principal holding it could read system audit rows.
got, err := s.CreateTenantForIdentity("https://idp", "sub-1", "system@x.com")
require.NoError(t, err)
assert.Equal(t, "system-2", got.ID)
}
func TestCreateTenantForIdentityDuplicateRejected(t *testing.T) {
s := newStore(t)
_, err := s.CreateTenantForIdentity("https://idp", "sub-1", "alex@a.com")
require.NoError(t, err)
// The same identity must not mint a second tenant.
_, err = s.CreateTenantForIdentity("https://idp", "sub-1", "alex@a.com")
require.Error(t, err)
}
func TestUnboundTenantsCoexist(t *testing.T) {
s := newStore(t)
// The identity index is partial (WHERE oidc_subject != ''): unbound rows —
// which exist only in databases predating the OIDC binding — must coexist
// without tripping it, or such a database fails on open.
_, err := s.db.Exec(
`INSERT INTO tenants (id, name, created_at) VALUES ('spare', 'spare', '2026-07-28T00:00:00Z')`)
require.NoError(t, err)
_, err = s.db.Exec(
`INSERT INTO tenants (id, name, created_at) VALUES ('spare2', 'spare2', '2026-07-28T00:00:00Z')`)
require.NoError(t, err, "a second unbound tenant must not trip the identity index")
}
func TestHandleFromEmail(t *testing.T) {
cases := map[string]string{
"alex@a.com": "alex",
"first.last@x.com": "first-last",
"UPPER@x.com": "upper",
"a+b@x.com": "a-b",
"weird__name@x.com": "weird--name",
"-lead-trail-@x.com": "lead-trail",
"@x.com": "user",
"...@x.com": "user",
}
for in, want := range cases {
got := handleFromEmail(in)
assert.Equal(t, want, got, "handleFromEmail(%q)", in)
assert.NotContains(t, got, ".", "handle must be dot-free")
assert.False(t, strings.HasPrefix(got, "-"), "no leading dash")
assert.False(t, strings.HasSuffix(got, "-"), "no trailing dash")
}
}