mirror of
https://github.com/lukaszraczylo/traefik-llmgateway.git
synced 2026-10-19 05:47:48 +00:00
feat: per-group cache TTL
GroupConfig gains CacheTTL (json cacheTTL, empty inherits the global cache.ttl). Parsed and validated at construction (newAuthStore, groupCacheTTL in cache.go): a malformed duration, a duration <= 0, or CacheTTL set while the global cache block is not configured is a constructor error naming the group, mirroring Cache:true's existing "nothing to inherit from" check. group carries the resolved time.Duration (0 means inherit). responseCache.store gains an explicit ttl parameter; routes_unified.go computes effectiveTTL(g.cache, grp) immediately before each store call, so setEx's Redis-native EX seconds reflect the group's own override. Tests: constructor-error table, empty-inherits and valid-override resolution, effectiveTTL inheritance/override, a scripted-RESP-server proof that store's SET...EX carries a different value per group, and an end-to-end Gateway.ServeHTTP test with two groups asserting the actual wire EX value (30 vs 60) differs per group. README: GroupConfig.cacheTTL config row and a Caching-section paragraph.
This commit is contained in:
@@ -190,6 +190,74 @@ func TestNewAuthStore_NoUsers_OK(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// --- GroupConfig.CacheTTL: constructor validation (item B) ---
|
||||
|
||||
// TestNewAuthStore_GroupCacheTTL_ConstructorErrors is the constructor-error
|
||||
// table for GroupConfig.CacheTTL: a malformed duration, a zero duration, a
|
||||
// negative duration, and CacheTTL set while the global cache block is not
|
||||
// configured (nothing to inherit ttl from, the same reasoning
|
||||
// newAuthStore already applies to Cache:true) must all fail construction.
|
||||
func TestNewAuthStore_GroupCacheTTL_ConstructorErrors(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
cacheTTL string
|
||||
globalCache bool
|
||||
}{
|
||||
{name: "malformed duration", cacheTTL: "not-a-duration", globalCache: true},
|
||||
{name: "zero duration", cacheTTL: "0s", globalCache: true},
|
||||
{name: "negative duration", cacheTTL: "-5s", globalCache: true},
|
||||
{name: "set without global cache enabled", cacheTTL: "5m", globalCache: false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cfg := &Config{
|
||||
Providers: map[string]*ProviderConfig{"openai": {Type: "openai", APIKey: "k"}},
|
||||
Groups: map[string]*GroupConfig{"default": {CacheTTL: tt.cacheTTL}},
|
||||
Cache: CacheConfig{Enabled: tt.globalCache},
|
||||
}
|
||||
if _, err := newAuthStore(cfg); err == nil {
|
||||
t.Fatalf("newAuthStore: want a constructor error for cacheTTL=%q (globalCache=%v)", tt.cacheTTL, tt.globalCache)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestNewAuthStore_GroupCacheTTL_EmptyInheritsZero proves an omitted
|
||||
// CacheTTL resolves to group.cacheTTL == 0 — effectiveTTL's (cache.go)
|
||||
// signal to inherit the global cache TTL rather than override it.
|
||||
func TestNewAuthStore_GroupCacheTTL_EmptyInheritsZero(t *testing.T) {
|
||||
cfg := &Config{
|
||||
Providers: map[string]*ProviderConfig{"openai": {Type: "openai", APIKey: "k"}},
|
||||
Groups: map[string]*GroupConfig{"default": {}},
|
||||
}
|
||||
a, err := newAuthStore(cfg)
|
||||
if err != nil {
|
||||
t.Fatalf("newAuthStore: %v", err)
|
||||
}
|
||||
if got := a.groups["default"].cacheTTL; got != 0 {
|
||||
t.Errorf("groups[default].cacheTTL = %v, want 0 (inherit)", got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestNewAuthStore_GroupCacheTTL_ValidOverride_ResolvedOnGroup proves a
|
||||
// well-formed, positive CacheTTL with the global cache block configured
|
||||
// both constructs cleanly and resolves onto the group struct exactly,
|
||||
// ready for effectiveTTL to read.
|
||||
func TestNewAuthStore_GroupCacheTTL_ValidOverride_ResolvedOnGroup(t *testing.T) {
|
||||
cfg := &Config{
|
||||
Providers: map[string]*ProviderConfig{"openai": {Type: "openai", APIKey: "k"}},
|
||||
Groups: map[string]*GroupConfig{"default": {CacheTTL: "30s"}},
|
||||
Cache: CacheConfig{Enabled: true},
|
||||
}
|
||||
a, err := newAuthStore(cfg)
|
||||
if err != nil {
|
||||
t.Fatalf("newAuthStore: %v", err)
|
||||
}
|
||||
if got := a.groups["default"].cacheTTL; got != 30*time.Second {
|
||||
t.Errorf("groups[default].cacheTTL = %v, want 30s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGroup_AllowsProvider_EmptyListAllowsAll(t *testing.T) {
|
||||
grp := &group{name: "eng"}
|
||||
if !grp.allowsProvider("openai") {
|
||||
|
||||
Reference in New Issue
Block a user