package chat import ( "context" "strings" "testing" "trankilou.fr/lassistanoque/backend/internal/domain" ) type fakeEngine struct{ summary string } func (f *fakeEngine) ListProviderTypes() []domain.Item { return nil } func (f *fakeEngine) ListModelsFromProvider(ctx context.Context, p *domain.Provider) ([]string, error) { return nil, nil } func (f *fakeEngine) Stream(ctx context.Context, p *domain.Provider, m string, params *domain.LLMParams, msgs []*domain.Message) *domain.Message { return &domain.Message{} } func (f *fakeEngine) Generate(ctx context.Context, p *domain.Provider, m string, msgs []*domain.Message) (string, error) { return f.summary, nil } func mk(role, content string) *domain.Message { return &domain.Message{Role: role, Content: content} } func TestEstimateTokens(t *testing.T) { if got := estimateTokens(""); got != 0 { t.Errorf("empty = %d", got) } if got := estimateTokens("abcd"); got != 1 { t.Errorf("abcd = %d", got) } if got := estimateTokens("abcde"); got != 2 { t.Errorf("abcde = %d", got) } } func TestShouldCompactThreshold(t *testing.T) { s := &ChatSession{ messages: []*domain.Message{mk(string(domain.RoleUser), strings.Repeat("a", 320))}, // 80 tokens contextMaxTokens: 100, maxResponseTokens: 20, } if !s.shouldCompact("system") { // 80 + 1 >= 100-20 t.Errorf("should compact at contextMax - maxResponse boundary") } s.maxResponseTokens = 40 s.contextMaxTokens = 130 // seuil 130-40=90 > 82 : pas de compaction if s.shouldCompact("system") { t.Errorf("should not compact below threshold") } } func TestCompactContext(t *testing.T) { s := &ChatSession{ provider: &domain.Provider{}, llmEngine: &fakeEngine{summary: "resume des echanges"}, contextMaxTokens: 100, maxResponseTokens: 10, messages: []*domain.Message{ mk(string(domain.RoleUser), "ancienne question 1"), mk(string(domain.RoleAssistant), "ancienne reponse 1"), mk(string(domain.RoleUser), "question courante"), mk(string(domain.RoleAssistant), "reponse courante"), }, } s.compactContext(context.Background()) if len(s.messages) != 3 { t.Fatalf("expected 3 messages after compaction, got %d", len(s.messages)) } if !strings.Contains(s.messages[0].Content, "resume des echanges") { t.Errorf("first message should be the summary, got %q", s.messages[0].Content) } if s.messages[0].Role != string(domain.RoleUser) { t.Errorf("summary role = %s", s.messages[0].Role) } if s.messages[1].Content != "question courante" || s.messages[2].Content != "reponse courante" { t.Errorf("tail not preserved: %v", s.messages) } } func TestCompactContextNoUserCut(t *testing.T) { s := &ChatSession{ messages: []*domain.Message{ mk(string(domain.RoleUser), "seule question"), mk(string(domain.RoleAssistant), "reponse"), }, } s.compactContext(context.Background()) if len(s.messages) != 2 { t.Errorf("nothing to compact, expected 2 messages, got %d", len(s.messages)) } }