Files
ARTEX/server/task_llm_escalation_test.go
BaaRai a195dfee2a fix(test): escalation test to the ctx-aware hooks.current signature
Seam fix after merging #111 (escalation) with #114 (ctx-threaded
hooks.current): the escalation test predates the signature change and kept
compiling on its own branch, so the merge left main red.
2026-10-21 15:16:25 +02:00

81 lines
2.7 KiB
Go

package server
import (
"context"
"errors"
"iter"
"testing"
"github.com/Autumn-27/artex/db"
"github.com/Autumn-27/norma/llm"
)
type failingProvider struct{ err error }
func (f *failingProvider) Stream(ctx context.Context, req llm.CompletionRequest) iter.Seq2[llm.StreamEvent, error] {
return nil
}
func (f *failingProvider) Complete(ctx context.Context, req llm.CompletionRequest) (llm.Message, string, llm.Usage, error) {
return llm.Message{}, "", llm.Usage{}, f.err
}
func TestEscalationAdvancesAfterConsecutiveFailures(t *testing.T) {
resetLLMFailStreaksForTest()
prov := &failingProvider{err: errors.New("boom: upstream 500")}
profile := int64(7)
nextProfile := int64(8)
exhaustCalls := 0
hooks := taskLLMStreamHooks{
current: func(ctx context.Context) (taskLLMSelection, error) {
return taskLLMSelection{task: &Task{ID: "1"}, profileID: profile, revision: 1, provider: prov}, nil
},
exhaust: func(sel taskLLMSelection, cause error) (db.TaskLLMTransition, error) {
exhaustCalls++
if exhaustCalls == 1 {
return db.TaskLLMTransition{Advanced: true, NextProfileID: &nextProfile}, nil
}
return db.TaskLLMTransition{ChainExhausted: true}, nil
},
}
// first two failures: passthrough, chain untouched
for i := 1; i <= 2; i++ {
_, _, _, err := completeTaskLLM(context.Background(), "1", llm.CompletionRequest{}, hooks)
if err == nil || errors.Is(err, err) == false {
t.Fatalf("call %d: expected the raw failure", i)
}
if exhaustCalls != 0 {
t.Fatalf("call %d: exhaust fired early (%d)", i, exhaustCalls)
}
}
// third consecutive failure: escalates, advances to profile 8, which itself
// fails (streak 1) and returns the raw error
_, _, _, err := completeTaskLLM(context.Background(), "1", llm.CompletionRequest{}, hooks)
if err == nil {
t.Fatal("expected the follow-up failure from the next profile")
}
if exhaustCalls != 1 {
t.Fatalf("exhaust calls=%d want 1", exhaustCalls)
}
// The escalation inside call 3 already replayed once on the same profile
// (streak 1); the fake current() keeps reporting profile 7, so the streak
// key is unchanged. One more failure reaches streak 2, and the NEXT one
// fires the second escalation, which the fake exhaust answers with a fully
// exhausted chain.
_, _, _, err = completeTaskLLM(context.Background(), "1", llm.CompletionRequest{}, hooks)
if err == nil {
t.Fatal("expected raw failure at streak 2")
}
if exhaustCalls != 1 {
t.Fatalf("exhaust calls=%d want 1 at streak 2", exhaustCalls)
}
_, _, _, err = completeTaskLLM(context.Background(), "1", llm.CompletionRequest{}, hooks)
if !isTaskLLMChainExhausted(err) {
t.Fatalf("expected chain-exhausted classification, got %v", err)
}
if exhaustCalls != 2 {
t.Fatalf("exhaust calls=%d want 2", exhaustCalls)
}
}