Flux source URL, flux-git rotator verify repo, review-bot REPOS, pipeline API, clone and push URLs, the heating-can source label and the review-bot and kuma-sync Go module paths. Merge right after the transfer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
69 lines
3.1 KiB
Go
69 lines
3.1 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// A stream that breaks off after the conversation started is answered from its stored history.
|
|
func TestBrokenStreamIsAnsweredFromTheHistory(t *testing.T) {
|
|
f := newFixture(t)
|
|
f.breakStreams = 1
|
|
f.history = `{"conversation_id":"conv_1","entries":[
|
|
{"type":"message.input","content":"Review pull request #7"},
|
|
{"type":"tool.execution","name":"conn_get_file_contents"},
|
|
{"type":"message.output","content":"## Summary\n\nfine\n\n## Recommendation\n\nLooks reasonable.\n\nVerdict: comment"}]}`
|
|
if err := f.rev.Review(context.Background(), "konairius/homelab", 7); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if f.convCalls != 1 || f.historyCalls != 1 || len(f.postedReviews()) != 1 {
|
|
t.Errorf("conversations %d, history reads %d, reviews %d; want 1, 1 and 1", f.convCalls, f.historyCalls, len(f.postedReviews()))
|
|
}
|
|
}
|
|
|
|
// Without a finished answer in the history, a broken stream is asked again.
|
|
func TestBrokenStreamWithoutHistoryIsAskedAgain(t *testing.T) {
|
|
f := newFixture(t)
|
|
f.breakStreams = 1
|
|
if err := f.rev.Review(context.Background(), "konairius/homelab", 7); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if f.convCalls != 2 || f.historyCalls != 2 || len(f.postedReviews()) != 1 {
|
|
t.Errorf("conversations %d, history reads %d, reviews %d; want 2, 2 and 1", f.convCalls, f.historyCalls, len(f.postedReviews()))
|
|
}
|
|
}
|
|
|
|
func TestStreamErrorEventIsNotRetried(t *testing.T) {
|
|
f := newFixture(t)
|
|
f.streamError = "Failed to generate response."
|
|
err := f.rev.Review(context.Background(), "konairius/homelab", 7)
|
|
if err == nil || !strings.Contains(err.Error(), "Failed to generate response.") {
|
|
t.Fatalf("want the stream error, got %v", err)
|
|
}
|
|
if f.convCalls != 1 || len(f.postedReviews()) != 0 {
|
|
t.Errorf("conversations %d, reviews %d; want 1 and 0", f.convCalls, len(f.postedReviews()))
|
|
}
|
|
}
|
|
|
|
func TestReadStreamJoinsDeltasOfOneMessage(t *testing.T) {
|
|
stream := "event: conversation.response.started\ndata: {\"type\":\"conversation.response.started\",\"conversation_id\":\"c9\"}\n\n" +
|
|
"data: {\"type\":\"message.output.delta\",\"id\":\"m1\",\"content\":\"## Sum\"}\n\n" +
|
|
"data: {\"type\":\"message.output.delta\",\"id\":\"m1\",\"content\":{\"type\":\"thinking\",\"thinking\":[]}}\n\n" +
|
|
"data: {\"type\":\"message.output.delta\",\"id\":\"m1\",\"content\":{\"type\":\"text\",\"text\":\"mary\"}}\n\n" +
|
|
"data: {\"type\":\"tool.execution.started\",\"id\":\"t1\",\"name\":\"conn_list_commits\",\"arguments\":\"{}\"}\n\n" +
|
|
"data: {\"type\":\"message.output.delta\",\"id\":\"m2\",\"content\":\"next\"}\r\n\r\n" +
|
|
"data: {\"type\":\"conversation.response.done\"}\n\n"
|
|
conv, a, err := readStream(strings.NewReader(stream))
|
|
if err != nil || conv != "c9" || a.Text != "## Summary\nnext" || a.ToolCalls != 1 {
|
|
t.Errorf("conv %q, answer %+v, err %v", conv, a, err)
|
|
}
|
|
}
|
|
|
|
func TestReadStreamWithoutDoneIsBroken(t *testing.T) {
|
|
_, _, err := readStream(strings.NewReader("data: {\"type\":\"conversation.response.started\",\"conversation_id\":\"c9\"}\n\n"))
|
|
if !retryable(err) {
|
|
t.Errorf("a stream that ends without done must be retryable, got %v", err)
|
|
}
|
|
}
|