mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-07-26 12:18:47 +00:00
Tenant config changes for builtin tools and skills silently failed to invalidate cached agent Loops, leaving tenants stuck on stale tool/skill sets until the 10-minute TTL expired or an unrelated event wiped the cache. Master-level skill CRUD had the same gap in the opposite direction. - Add CacheInvalidatePayload.TenantID so events can scope to one tenant - Add Router.InvalidateTenant(tenantID) with prefix match on "tenantID:agentKey" cache keys; uuid.Nil is a no-op - Rework emitCacheInvalidate helpers in builtin_tools + skills handlers to carry tenant scope; add defense-in-depth uuid.Nil guards in four tenant-config handlers - Update TopicCacheBuiltinTools + TopicCacheSkills subscribers to branch on payload.TenantID (tenant event wipes that tenant, global event keeps the existing InvalidateAll path) - Wire emitCacheInvalidate into master skill CRUD paths (update, delete, toggle, upload, install-deps, rescan-deps, import) that previously only called BumpVersion - Document the system-owner bypass in requireTenantAdmin so handlers keep guarding uuid.Nil themselves
82 lines
2.3 KiB
Go
82 lines
2.3 KiB
Go
package http
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"github.com/nextlevelbuilder/goclaw/internal/bus"
|
|
"github.com/nextlevelbuilder/goclaw/pkg/protocol"
|
|
)
|
|
|
|
// TestSkillsHandler_EmitCacheInvalidate_TenantScopedPropagatesTenantID
|
|
// verifies tenant-config handlers emit payloads carrying the caller's tenant.
|
|
func TestSkillsHandler_EmitCacheInvalidate_TenantScopedPropagatesTenantID(t *testing.T) {
|
|
mb := bus.New()
|
|
defer mb.Close()
|
|
|
|
h := &SkillsHandler{msgBus: mb}
|
|
|
|
var captured bus.CacheInvalidatePayload
|
|
var gotEvent bool
|
|
mb.Subscribe("test-capture", func(e bus.Event) {
|
|
if e.Name != protocol.EventCacheInvalidate {
|
|
return
|
|
}
|
|
if p, ok := e.Payload.(bus.CacheInvalidatePayload); ok {
|
|
captured = p
|
|
gotEvent = true
|
|
}
|
|
})
|
|
|
|
tid := uuid.New()
|
|
skillID := uuid.New().String()
|
|
h.emitCacheInvalidate(bus.CacheKindSkills, skillID, tid)
|
|
|
|
if !gotEvent {
|
|
t.Fatal("no cache invalidate event received")
|
|
}
|
|
if captured.Kind != bus.CacheKindSkills {
|
|
t.Errorf("kind = %q, want %q", captured.Kind, bus.CacheKindSkills)
|
|
}
|
|
if captured.Key != skillID {
|
|
t.Errorf("key = %q, want %q", captured.Key, skillID)
|
|
}
|
|
if captured.TenantID != tid {
|
|
t.Errorf("tenantID = %v, want %v", captured.TenantID, tid)
|
|
}
|
|
}
|
|
|
|
// TestSkillsHandler_EmitCacheInvalidate_GrantsGlobal verifies grant-change
|
|
// callers pass uuid.Nil so subscribers treat the event as global.
|
|
func TestSkillsHandler_EmitCacheInvalidate_GrantsGlobal(t *testing.T) {
|
|
mb := bus.New()
|
|
defer mb.Close()
|
|
|
|
h := &SkillsHandler{msgBus: mb}
|
|
|
|
var captured bus.CacheInvalidatePayload
|
|
mb.Subscribe("test-capture", func(e bus.Event) {
|
|
if p, ok := e.Payload.(bus.CacheInvalidatePayload); ok {
|
|
captured = p
|
|
}
|
|
})
|
|
|
|
h.emitCacheInvalidate(bus.CacheKindSkillGrants, "", uuid.Nil)
|
|
|
|
if captured.Kind != bus.CacheKindSkillGrants {
|
|
t.Errorf("kind = %q, want %q", captured.Kind, bus.CacheKindSkillGrants)
|
|
}
|
|
if captured.TenantID != uuid.Nil {
|
|
t.Errorf("tenantID = %v, want uuid.Nil for grant-change global event", captured.TenantID)
|
|
}
|
|
}
|
|
|
|
// TestSkillsHandler_EmitCacheInvalidate_NilMsgBusNoop verifies the helper is
|
|
// safe with an unset msgBus.
|
|
func TestSkillsHandler_EmitCacheInvalidate_NilMsgBusNoop(t *testing.T) {
|
|
h := &SkillsHandler{msgBus: nil}
|
|
// Must not panic.
|
|
h.emitCacheInvalidate(bus.CacheKindSkills, "x", uuid.New())
|
|
}
|