mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-05-07 17:19:57 +00:00
347 lines
9.8 KiB
Go
347 lines
9.8 KiB
Go
package chat
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/ai/tools"
|
|
)
|
|
|
|
func TestNewResolvedContext(t *testing.T) {
|
|
ctx := NewResolvedContext("session-1")
|
|
|
|
if ctx.SessionID != "session-1" {
|
|
t.Errorf("SessionID = %q, want %q", ctx.SessionID, "session-1")
|
|
}
|
|
if ctx.ttl != DefaultResolvedContextTTL {
|
|
t.Errorf("ttl = %v, want %v", ctx.ttl, DefaultResolvedContextTTL)
|
|
}
|
|
if ctx.maxEntries != DefaultResolvedContextMaxEntries {
|
|
t.Errorf("maxEntries = %d, want %d", ctx.maxEntries, DefaultResolvedContextMaxEntries)
|
|
}
|
|
}
|
|
|
|
func TestNewResolvedContextWithConfig(t *testing.T) {
|
|
ctx := NewResolvedContextWithConfig("session-1", 10*time.Minute, 100)
|
|
|
|
if ctx.ttl != 10*time.Minute {
|
|
t.Errorf("ttl = %v, want %v", ctx.ttl, 10*time.Minute)
|
|
}
|
|
if ctx.maxEntries != 100 {
|
|
t.Errorf("maxEntries = %d, want %d", ctx.maxEntries, 100)
|
|
}
|
|
}
|
|
|
|
func TestResolvedContextAddAndGet(t *testing.T) {
|
|
ctx := NewResolvedContext("session-1")
|
|
|
|
// Add a resource
|
|
ctx.AddResolvedResource(tools.ResourceRegistration{
|
|
Kind: "app-container",
|
|
ProviderUID: "abc123",
|
|
Name: "nginx",
|
|
Aliases: []string{"nginx", "abc123", "abc123def456"},
|
|
HostName: "server1",
|
|
Executors: []tools.ExecutorRegistration{{
|
|
ExecutorID: "server1",
|
|
Adapter: "docker",
|
|
Actions: []string{"restart", "stop", "start"},
|
|
Priority: 10,
|
|
}},
|
|
})
|
|
|
|
// Test GetResource by name
|
|
res, ok := ctx.GetResource("nginx")
|
|
if !ok {
|
|
t.Error("GetResource should find nginx")
|
|
}
|
|
if res.Name != "nginx" {
|
|
t.Errorf("Name = %q, want %q", res.Name, "nginx")
|
|
}
|
|
|
|
// Test GetResolvedResourceByAlias
|
|
info, ok := ctx.GetResolvedResourceByAlias("abc123")
|
|
if !ok {
|
|
t.Error("GetResolvedResourceByAlias should find abc123")
|
|
}
|
|
if info.GetProviderUID() != "abc123" {
|
|
t.Errorf("ProviderUID = %q, want %q", info.GetProviderUID(), "abc123")
|
|
}
|
|
|
|
// Test GetResolvedResourceByID
|
|
// Note: canonical ID format uses kind/scope/provider_uid segments.
|
|
info, ok = ctx.GetResolvedResourceByID("app-container:server1:abc123")
|
|
if !ok {
|
|
t.Error("GetResolvedResourceByID should find app-container:server1:abc123")
|
|
}
|
|
if info.GetKind() != "app-container" {
|
|
t.Errorf("Kind = %q, want %q", info.GetKind(), "app-container")
|
|
}
|
|
}
|
|
|
|
func TestResolvedContextLRUEviction(t *testing.T) {
|
|
// Create context with very small max entries
|
|
ctx := NewResolvedContextWithConfig("session-1", 1*time.Hour, 3)
|
|
|
|
// Add 5 resources
|
|
for i := 0; i < 5; i++ {
|
|
ctx.AddResolvedResource(tools.ResourceRegistration{
|
|
Kind: "app-container",
|
|
ProviderUID: string(rune('a' + i)),
|
|
Name: string(rune('a' + i)),
|
|
Executors: []tools.ExecutorRegistration{},
|
|
})
|
|
// Small delay to ensure different timestamps
|
|
time.Sleep(1 * time.Millisecond)
|
|
}
|
|
|
|
// Should have evicted to max 3 entries
|
|
stats := ctx.Stats()
|
|
if stats.UniqueResources > 3 {
|
|
t.Errorf("UniqueResources = %d, want <= 3", stats.UniqueResources)
|
|
}
|
|
|
|
// First two resources (a, b) should be evicted as LRU
|
|
_, okA := ctx.GetResolvedResourceByID("app-container:a")
|
|
_, okB := ctx.GetResolvedResourceByID("app-container:b")
|
|
_, okE := ctx.GetResolvedResourceByID("app-container:e")
|
|
|
|
// Note: exact eviction depends on timing, but 'e' should definitely exist
|
|
if !okE {
|
|
t.Error("Resource 'e' should exist (most recently added)")
|
|
}
|
|
|
|
// At least one of the first two should be evicted
|
|
if okA && okB {
|
|
t.Error("At least one of resources 'a' or 'b' should be evicted")
|
|
}
|
|
}
|
|
|
|
func TestResolvedContextTTLExpiry(t *testing.T) {
|
|
// Create context with very short TTL
|
|
ctx := NewResolvedContextWithConfig("session-1", 10*time.Millisecond, 100)
|
|
|
|
// Add a resource
|
|
ctx.AddResolvedResource(tools.ResourceRegistration{
|
|
Kind: "app-container",
|
|
ProviderUID: "test123",
|
|
Name: "test",
|
|
Executors: []tools.ExecutorRegistration{},
|
|
})
|
|
|
|
// Should find it immediately
|
|
_, ok := ctx.GetResolvedResourceByID("app-container:test123")
|
|
if !ok {
|
|
t.Error("Resource should be found immediately after adding")
|
|
}
|
|
|
|
// Wait for TTL to expire
|
|
time.Sleep(20 * time.Millisecond)
|
|
|
|
// Should be expired now
|
|
_, ok = ctx.GetResolvedResourceByID("app-container:test123")
|
|
if ok {
|
|
t.Error("Resource should be expired after TTL")
|
|
}
|
|
}
|
|
|
|
func TestResolvedContextPinning(t *testing.T) {
|
|
// Create context with very short TTL
|
|
ctx := NewResolvedContextWithConfig("session-1", 10*time.Millisecond, 100)
|
|
|
|
// Add and pin a resource
|
|
ctx.AddResolvedResource(tools.ResourceRegistration{
|
|
Kind: "app-container",
|
|
ProviderUID: "pinned123",
|
|
Name: "pinned",
|
|
Executors: []tools.ExecutorRegistration{},
|
|
})
|
|
ctx.PinResource("app-container:pinned123")
|
|
|
|
// Verify it's pinned
|
|
if !ctx.IsPinned("app-container:pinned123") {
|
|
t.Error("Resource should be pinned")
|
|
}
|
|
|
|
// Wait for TTL to expire
|
|
time.Sleep(20 * time.Millisecond)
|
|
|
|
// Pinned resource should still exist
|
|
_, ok := ctx.GetResolvedResourceByID("app-container:pinned123")
|
|
if !ok {
|
|
t.Error("Pinned resource should survive TTL expiry")
|
|
}
|
|
|
|
// Unpin and verify
|
|
ctx.UnpinResource("app-container:pinned123")
|
|
if ctx.IsPinned("app-container:pinned123") {
|
|
t.Error("Resource should be unpinned")
|
|
}
|
|
}
|
|
|
|
func TestResolvedContextPinningSurvivesLRU(t *testing.T) {
|
|
// Create context with very small max entries
|
|
ctx := NewResolvedContextWithConfig("session-1", 1*time.Hour, 3)
|
|
|
|
// Add and pin first resource
|
|
ctx.AddResolvedResource(tools.ResourceRegistration{
|
|
Kind: "app-container",
|
|
ProviderUID: "first",
|
|
Name: "first",
|
|
Executors: []tools.ExecutorRegistration{},
|
|
})
|
|
ctx.PinResource("app-container:first")
|
|
|
|
// Add more resources to trigger LRU
|
|
for i := 0; i < 5; i++ {
|
|
ctx.AddResolvedResource(tools.ResourceRegistration{
|
|
Kind: "app-container",
|
|
ProviderUID: string(rune('a' + i)),
|
|
Name: string(rune('a' + i)),
|
|
Executors: []tools.ExecutorRegistration{},
|
|
})
|
|
time.Sleep(1 * time.Millisecond)
|
|
}
|
|
|
|
// Pinned resource should still exist
|
|
_, ok := ctx.GetResolvedResourceByID("app-container:first")
|
|
if !ok {
|
|
t.Error("Pinned resource should survive LRU eviction")
|
|
}
|
|
}
|
|
|
|
func TestResolvedContextClear(t *testing.T) {
|
|
ctx := NewResolvedContext("session-1")
|
|
|
|
// Add some resources
|
|
ctx.AddResolvedResource(tools.ResourceRegistration{
|
|
Kind: "app-container",
|
|
ProviderUID: "a",
|
|
Name: "a",
|
|
Executors: []tools.ExecutorRegistration{},
|
|
})
|
|
ctx.AddResolvedResource(tools.ResourceRegistration{
|
|
Kind: "app-container",
|
|
ProviderUID: "b",
|
|
Name: "b",
|
|
Executors: []tools.ExecutorRegistration{},
|
|
})
|
|
ctx.PinResource("app-container:a")
|
|
|
|
// Clear without keeping pinned
|
|
ctx.Clear(false)
|
|
stats := ctx.Stats()
|
|
if stats.UniqueResources != 0 {
|
|
t.Errorf("After Clear(false), UniqueResources = %d, want 0", stats.UniqueResources)
|
|
}
|
|
|
|
// Re-add resources
|
|
ctx.AddResolvedResource(tools.ResourceRegistration{
|
|
Kind: "app-container",
|
|
ProviderUID: "a",
|
|
Name: "a",
|
|
Executors: []tools.ExecutorRegistration{},
|
|
})
|
|
ctx.AddResolvedResource(tools.ResourceRegistration{
|
|
Kind: "app-container",
|
|
ProviderUID: "b",
|
|
Name: "b",
|
|
Executors: []tools.ExecutorRegistration{},
|
|
})
|
|
ctx.PinResource("app-container:a")
|
|
|
|
// Clear keeping pinned
|
|
ctx.Clear(true)
|
|
stats = ctx.Stats()
|
|
if stats.UniqueResources != 1 {
|
|
t.Errorf("After Clear(true), UniqueResources = %d, want 1", stats.UniqueResources)
|
|
}
|
|
_, ok := ctx.GetResolvedResourceByID("app-container:a")
|
|
if !ok {
|
|
t.Error("Pinned resource 'a' should survive Clear(true)")
|
|
}
|
|
}
|
|
|
|
func TestResolvedContextStats(t *testing.T) {
|
|
ctx := NewResolvedContextWithConfig("session-1", 30*time.Minute, 200)
|
|
|
|
ctx.AddResolvedResource(tools.ResourceRegistration{
|
|
Kind: "app-container",
|
|
ProviderUID: "abc",
|
|
Name: "nginx",
|
|
Aliases: []string{"nginx", "web", "abc"},
|
|
Executors: []tools.ExecutorRegistration{},
|
|
})
|
|
ctx.PinResource("app-container:abc")
|
|
|
|
stats := ctx.Stats()
|
|
if stats.UniqueResources != 1 {
|
|
t.Errorf("UniqueResources = %d, want 1", stats.UniqueResources)
|
|
}
|
|
// Aliases include: nginx, web, abc (3 aliases)
|
|
if stats.TotalAliases < 3 {
|
|
t.Errorf("TotalAliases = %d, want >= 3", stats.TotalAliases)
|
|
}
|
|
if stats.PinnedResources != 1 {
|
|
t.Errorf("PinnedResources = %d, want 1", stats.PinnedResources)
|
|
}
|
|
if stats.MaxEntries != 200 {
|
|
t.Errorf("MaxEntries = %d, want 200", stats.MaxEntries)
|
|
}
|
|
if stats.TTL != 30*time.Minute {
|
|
t.Errorf("TTL = %v, want %v", stats.TTL, 30*time.Minute)
|
|
}
|
|
}
|
|
|
|
func TestResolvedContextAccessUpdatesLRU(t *testing.T) {
|
|
ctx := NewResolvedContextWithConfig("session-1", 1*time.Hour, 3)
|
|
|
|
// Add 3 resources
|
|
ctx.AddResolvedResource(tools.ResourceRegistration{
|
|
Kind: "app-container",
|
|
ProviderUID: "oldest",
|
|
Name: "oldest",
|
|
Executors: []tools.ExecutorRegistration{},
|
|
})
|
|
time.Sleep(5 * time.Millisecond)
|
|
|
|
ctx.AddResolvedResource(tools.ResourceRegistration{
|
|
Kind: "app-container",
|
|
ProviderUID: "middle",
|
|
Name: "middle",
|
|
Executors: []tools.ExecutorRegistration{},
|
|
})
|
|
time.Sleep(5 * time.Millisecond)
|
|
|
|
ctx.AddResolvedResource(tools.ResourceRegistration{
|
|
Kind: "app-container",
|
|
ProviderUID: "newest",
|
|
Name: "newest",
|
|
Executors: []tools.ExecutorRegistration{},
|
|
})
|
|
time.Sleep(5 * time.Millisecond)
|
|
|
|
// Access 'oldest' to update its LRU time
|
|
ctx.GetResolvedResourceByID("app-container:oldest")
|
|
time.Sleep(5 * time.Millisecond)
|
|
|
|
// Add one more resource to trigger eviction
|
|
ctx.AddResolvedResource(tools.ResourceRegistration{
|
|
Kind: "app-container",
|
|
ProviderUID: "extra",
|
|
Name: "extra",
|
|
Executors: []tools.ExecutorRegistration{},
|
|
})
|
|
|
|
// 'oldest' should still exist (recently accessed)
|
|
_, okOldest := ctx.GetResolvedResourceByID("app-container:oldest")
|
|
if !okOldest {
|
|
t.Error("'oldest' should survive because it was recently accessed")
|
|
}
|
|
|
|
// 'middle' should be evicted (was LRU at eviction time)
|
|
_, okMiddle := ctx.GetResolvedResourceByID("app-container:middle")
|
|
if okMiddle {
|
|
t.Error("'middle' should be evicted as it was LRU")
|
|
}
|
|
}
|