Files
rbac/identity_test.go
Rene Nochebuena 0864f031a1 feat(rbac): initial stable release v0.9.0
Foundational identity and permission types for role-based access control — bit-set PermissionMask, immutable Identity value type, and PermissionProvider interface.

What's included:
- `Identity` value type with NewIdentity / WithTenant constructors and SetInContext / FromContext context helpers
- `Permission` (int64 bit position) and `PermissionMask` (int64 bit-set) with O(1) Has and non-mutating Grant
- `PermissionProvider` interface for DB-backed ResolveMask(ctx, uid, resource) resolution

Tested-via: todo-api POC integration
Reviewed-against: docs/adr/
2026-03-18 13:25:43 -06:00

94 lines
2.6 KiB
Go

package rbac
import (
"context"
"testing"
)
func TestNewIdentity(t *testing.T) {
id := NewIdentity("uid-1", "Jane Doe", "jane@example.com")
if id.UID != "uid-1" {
t.Errorf("UID = %q, want uid-1", id.UID)
}
if id.DisplayName != "Jane Doe" {
t.Errorf("DisplayName = %q, want Jane Doe", id.DisplayName)
}
if id.Email != "jane@example.com" {
t.Errorf("Email = %q, want jane@example.com", id.Email)
}
if id.TenantID != "" {
t.Errorf("TenantID must be empty at construction, got %q", id.TenantID)
}
}
func TestIdentity_WithTenant(t *testing.T) {
original := NewIdentity("uid-1", "Jane Doe", "jane@example.com")
enriched := original.WithTenant("tenant-abc")
if enriched.TenantID != "tenant-abc" {
t.Errorf("enriched.TenantID = %q, want tenant-abc", enriched.TenantID)
}
// WithTenant must not mutate the original.
if original.TenantID != "" {
t.Errorf("original.TenantID mutated to %q, must remain empty", original.TenantID)
}
// Other fields must be preserved.
if enriched.UID != original.UID {
t.Errorf("WithTenant changed UID: got %q, want %q", enriched.UID, original.UID)
}
}
func TestContextHelpers_SetAndGet(t *testing.T) {
id := NewIdentity("uid-1", "Jane Doe", "jane@example.com")
ctx := SetInContext(context.Background(), id)
retrieved, ok := FromContext(ctx)
if !ok {
t.Fatal("FromContext returned ok=false, expected to find identity")
}
if retrieved.UID != id.UID {
t.Errorf("retrieved UID = %q, want %q", retrieved.UID, id.UID)
}
}
func TestContextHelpers_MissingReturnsZeroValue(t *testing.T) {
id, ok := FromContext(context.Background())
if ok {
t.Error("FromContext on empty context returned ok=true")
}
if id != (Identity{}) {
t.Errorf("FromContext on empty context returned non-zero Identity: %+v", id)
}
}
func TestContextHelpers_ValueSemanticsOnSet(t *testing.T) {
id := NewIdentity("uid-1", "Jane Doe", "jane@example.com")
ctx := SetInContext(context.Background(), id)
// Mutating the original struct after storing must not affect the context value
// because Identity is a value type.
id.UID = "mutated"
retrieved, _ := FromContext(ctx)
if retrieved.UID == "mutated" {
t.Error("context value was mutated after SetInContext; Identity must be a value type")
}
}
func TestContextHelpers_OverwriteInContext(t *testing.T) {
first := NewIdentity("uid-1", "Jane", "jane@example.com")
second := NewIdentity("uid-2", "John", "john@example.com")
ctx := SetInContext(context.Background(), first)
ctx = SetInContext(ctx, second)
retrieved, ok := FromContext(ctx)
if !ok {
t.Fatal("expected identity in context")
}
if retrieved.UID != "uid-2" {
t.Errorf("expected overwritten identity uid-2, got %s", retrieved.UID)
}
}