From 4b27a337d17bb0e048dbd3ef6e41d1978ce8f891 Mon Sep 17 00:00:00 2001 From: viettranx Date: Sat, 11 Apr 2026 14:09:40 +0700 Subject: [PATCH] test(router): pin stale raw-UUID entry eviction on Get MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pin that a pre-hardening fragmented entry written under the raw UUID cache key (tenantID:) is still evicted by the TTL branch in Router.Get when a UUID-form caller arrives after TTL expiry. The test synthesizes the fragmented entry directly via a test-only map write, then asserts the subsequent Get evicts the raw-UUID entry, re-invokes the resolver once, and writes the canonical tenantID:agentKey entry — leaving no fragmented entries behind. --- .../agent/router_cache_canonicalize_test.go | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/internal/agent/router_cache_canonicalize_test.go b/internal/agent/router_cache_canonicalize_test.go index 74947fd9..ed26c431 100644 --- a/internal/agent/router_cache_canonicalize_test.go +++ b/internal/agent/router_cache_canonicalize_test.go @@ -173,6 +173,58 @@ func TestRouterGet_DualTenantSameAgentKey(t *testing.T) { } } +// TestRouterGet_StaleRawUUIDEntryEvictedOnGet pins that a pre-hardening +// fragmented entry written under a raw UUID key still goes through the +// TTL-based eviction branch when a UUID-form caller arrives after TTL expiry. +// Scenario: an earlier build that did not canonicalize wrote the entry at +// `tenantID:`, the new build loads that state (synthesized here via +// direct map write), TTL expires, and a UUID caller with the same UUID must +// evict + re-resolve + write the canonical `tenantID:agentKey` entry, leaving +// no fragmented entries behind. +func TestRouterGet_StaleRawUUIDEntryEvictedOnGet(t *testing.T) { + r := NewRouter() + + var resolveCount atomic.Int32 + r.SetResolver(func(_ context.Context, _ string) (Agent, error) { + resolveCount.Add(1) + return &stubAgent{id: "fresh-agent"}, nil + }) + + tenantID := uuid.New() + ctx := store.WithTenantID(context.Background(), tenantID) + uuidStr := uuid.New().String() + rawKey := tenantID.String() + ":" + uuidStr + canonicalKey := tenantID.String() + ":fresh-agent" + + // Synthesize the pre-hardening fragmented entry directly. Stale cachedAt + // forces the initial-miss eviction path. + r.mu.Lock() + r.agents[rawKey] = &agentEntry{ + agent: &stubAgent{id: "fresh-agent"}, + cachedAt: time.Now().Add(-2 * defaultRouterTTL), + } + r.mu.Unlock() + + if _, err := r.Get(ctx, uuidStr); err != nil { + t.Fatalf("Get(uuid) with fragmented raw-UUID entry: %v", err) + } + if resolveCount.Load() != 1 { + t.Errorf("resolver calls = %d, want 1 (raw-UUID entry should have been evicted as stale)", resolveCount.Load()) + } + + r.mu.RLock() + _, rawExists := r.agents[rawKey] + _, canonicalExists := r.agents[canonicalKey] + r.mu.RUnlock() + + if rawExists { + t.Errorf("fragmented raw-UUID entry %q still in cache after TTL eviction", rawKey) + } + if !canonicalExists { + t.Errorf("canonical entry %q missing after resolver rewrite", canonicalKey) + } +} + // TestRouterGet_CanonicalDoubleCheckEvictsStaleEntry pins the TTL check inside // the canonical double-check branch. Scenario: an earlier caller wrote the // canonical entry, TTL expires, a UUID-form caller arrives. The raw UUID key