package context import ( "testing" "github.com/edgeai/gateway/internal/config" "github.com/edgeai/gateway/pkg/api" ) func TestTokenEstimator(t *testing.T) { est := NewTokenEstimator() // Empty string if got := est.EstimateText(""); got != 0 { t.Errorf("empty string: expected 0, got %d", got) } // English text tokens := est.EstimateText("Hello world, this is a test.") if tokens <= 0 { t.Errorf("expected positive tokens for English, got %d", tokens) } // Chinese text (each char ~1 token) cjkTokens := est.EstimateText("你好世界") if cjkTokens != 4 { t.Errorf("expected 4 tokens for 4 CJK chars, got %d", cjkTokens) } } func TestEstimateKVCache(t *testing.T) { // 1000 tokens, 32 layers, 4096 hidden dim, 2 bytes/element result := EstimateKVCache(1000, 32, 4096, 2) expected := int64(1000) * 32 * 2 * 4096 * 2 if result != expected { t.Errorf("expected %d, got %d", expected, result) } } func TestAssemblerNoTrim(t *testing.T) { cfg := &config.ContextConfig{SafetyMarginRatio: 0.08} a := NewAssembler(cfg) history := []api.Message{ {Role: "user", Content: "Hi"}, {Role: "assistant", Content: "Hello!"}, } newMsgs := []api.Message{ {Role: "user", Content: "How are you?"}, } result := a.Assemble(history, newMsgs, 1000, 100, "summary_and_recent") if result.Trimmed { t.Error("expected no trimming for small context") } if len(result.Messages) != 3 { t.Errorf("expected 3 messages, got %d", len(result.Messages)) } } func TestAssemblerTrimRecentOnly(t *testing.T) { cfg := &config.ContextConfig{SafetyMarginRatio: 0.08} a := NewAssembler(cfg) // Create many messages that exceed budget msgs := make([]api.Message, 20) for i := range msgs { msgs[i] = api.Message{Role: "user", Content: "This is message number " + string(rune('A'+i))} } result := a.Assemble(msgs, []api.Message{}, 50, 10, "recent_only") if !result.Trimmed { t.Error("expected trimming for large context") } if len(result.Messages) >= 20 { t.Error("expected fewer messages after trimming") } } func TestAssemblerSummaryAndRecent(t *testing.T) { cfg := &config.ContextConfig{SafetyMarginRatio: 0.08} a := NewAssembler(cfg) msgs := make([]api.Message, 0, 22) msgs = append(msgs, api.Message{Role: "system", Content: "You are a helpful assistant."}) for i := 0; i < 20; i++ { msgs = append(msgs, api.Message{Role: "user", Content: "Message " + string(rune('A'+i%26))}) msgs = append(msgs, api.Message{Role: "assistant", Content: "Response " + string(rune('A'+i%26))}) } result := a.Assemble(msgs, []api.Message{}, 80, 20, "summary_and_recent") if !result.Trimmed { t.Error("expected trimming") } // System message should be preserved hasSystem := false hasSummary := false for _, m := range result.Messages { if m.Role == "system" { if content, ok := m.Content.(string); ok { if content == "You are a helpful assistant." { hasSystem = true } if contains(content, "summarized") { hasSummary = true } } } } if !hasSystem { t.Error("system message should be preserved") } if !hasSummary { t.Error("summary placeholder should be present when trimmed") } } func contains(s, substr string) bool { return len(s) >= len(substr) && (s == substr || (len(s) > len(substr) && (indexOf(s, substr) >= 0))) } func indexOf(s, substr string) int { for i := 0; i <= len(s)-len(substr); i++ { if s[i:i+len(substr)] == substr { return i } } return -1 }