diff --git a/context.go b/context.go index 9ebf698..bbc04ad 100644 --- a/context.go +++ b/context.go @@ -119,6 +119,19 @@ func (ctx *Context) SetService(svc Translator) *Context { return ctx } +// SetEntitlements updates the feature gate callback used by Entitled nodes and +// returns the same context. +// Example: ctx.SetEntitlements(func(feature string) bool { return feature == "premium" }). +func (ctx *Context) SetEntitlements(entitlements func(feature string) bool) *Context { + if ctx == nil { + return nil + } + + ensureContextDefaults(ctx) + ctx.Entitlements = entitlements + return ctx +} + // SetIdentity updates the context identity and returns the same context. // Example: ctx.SetIdentity("user-123"). func (ctx *Context) SetIdentity(identity string) *Context { @@ -188,6 +201,17 @@ func (ctx *Context) WithService(svc Translator) *Context { return clone } +// WithEntitlements returns a cloned context with a different feature gate callback. +// Example: next := ctx.WithEntitlements(func(feature string) bool { return feature == "premium" }). +func (ctx *Context) WithEntitlements(entitlements func(feature string) bool) *Context { + clone := ctx.Clone() + if clone == nil { + return nil + } + clone.SetEntitlements(entitlements) + return clone +} + // SetLocale updates the context locale and reapplies it to the active // translator. // Example: ctx.SetLocale("en-US"). diff --git a/context_test.go b/context_test.go index 63401be..35408a1 100644 --- a/context_test.go +++ b/context_test.go @@ -96,6 +96,20 @@ func TestContext_SetService_ReappliesCurrentLocale(t *testing.T) { } } +func TestContext_SetEntitlements_UpdatesFeatureGate(t *testing.T) { + ctx := NewContext() + + if got := ctx.SetEntitlements(func(feature string) bool { return feature == "premium" }); got != ctx { + t.Fatalf("SetEntitlements should return the same context") + } + if ctx.Entitlements == nil { + t.Fatal("SetEntitlements should store the callback") + } + if !ctx.Entitlements("premium") { + t.Fatal("SetEntitlements should preserve the requested callback") + } +} + func TestContext_SetIdentity_UpdatesIdentity(t *testing.T) { ctx := NewContext() @@ -258,6 +272,40 @@ func TestContext_WithServiceReturnsClonedContext(t *testing.T) { } } +func TestContext_WithEntitlementsReturnsClonedContext(t *testing.T) { + ctx := NewContext() + ctx.SetIdentity("user-001") + ctx.SetData("theme", "dark") + ctx.SetEntitlements(func(feature string) bool { return feature == "basic" }) + + next := ctx.WithEntitlements(func(feature string) bool { return feature == "premium" }) + + if next == ctx { + t.Fatal("WithEntitlements should return a cloned context") + } + if ctx.Entitlements == nil { + t.Fatal("WithEntitlements should not clear the original callback") + } + if !ctx.Entitlements("basic") { + t.Fatal("WithEntitlements should preserve the original callback") + } + if next.Entitlements == nil { + t.Fatal("WithEntitlements should store the new callback on the clone") + } + if next.Entitlements("basic") { + t.Fatal("WithEntitlements should replace the callback on the clone") + } + if !next.Entitlements("premium") { + t.Fatal("WithEntitlements should preserve the new callback") + } + if got := next.Data["theme"]; got != "dark" { + t.Fatalf("WithEntitlements should preserve existing data on the clone, got %v", got) + } + if got := next.Identity; got != "user-001" { + t.Fatalf("WithEntitlements should preserve identity on the clone, got %q", got) + } +} + func TestContext_Setters_NilReceiver(t *testing.T) { var ctx *Context @@ -273,12 +321,18 @@ func TestContext_Setters_NilReceiver(t *testing.T) { if got := ctx.SetService(&localeTranslator{}); got != nil { t.Fatalf("nil Context.SetService should return nil, got %v", got) } + if got := ctx.SetEntitlements(func(string) bool { return true }); got != nil { + t.Fatalf("nil Context.SetEntitlements should return nil, got %v", got) + } if got := ctx.WithLocale("en-GB"); got != nil { t.Fatalf("nil Context.WithLocale should return nil, got %v", got) } if got := ctx.WithService(&localeTranslator{}); got != nil { t.Fatalf("nil Context.WithService should return nil, got %v", got) } + if got := ctx.WithEntitlements(func(string) bool { return true }); got != nil { + t.Fatalf("nil Context.WithEntitlements should return nil, got %v", got) + } } func TestText_RenderFallsBackToDefaultTranslator(t *testing.T) {