Files
ratatoskr-go/internal/events/log_test.go
ki.sagidullin 8cf4fc9f7c
Some checks failed
CI / test (pull_request) Failing after 1m21s
CI / build-and-package (amd64, linux) (pull_request) Failing after 1m1s
CI / build-and-package (amd64, windows) (pull_request) Successful in 23s
fix(opencode): reasoning-fallback в вердикте + API-логи в debug-уровень
- verdict(): если в завершённом assistant-сообщении нет text-парта, но есть
  reasoning — вердикт собирается из reasoning (fallback), а не падает с
  'нет text-части в ответе'. Решает сбой dev/reviewer/postmortem на моделях,
  отвечающих только thinking (например, через прокси tokentool).
- Отладочные логи API-вызовов (запрос/ответ) помечены маркером 'debug',
  чтобы в панели «Логи» они классифицировались как debug, а не info.
2026-08-23 14:45:20 +05:00

125 lines
3.4 KiB
Go

package events
import (
"strings"
"testing"
"time"
)
func TestLogWriterLines(t *testing.T) {
lbus := NewLogBus(16)
ch, unsub := lbus.Subscribe()
defer unsub()
w := NewLogWriter(lbus)
if _, err := w.Write([]byte("first line\nsecond line\n")); err != nil {
t.Fatalf("Write: %v", err)
}
first := receiveOne(t, ch)
ll, ok := first.(LogLine)
if !ok {
t.Fatalf("got %T, want LogLine", first)
}
// строка без маркеров ошибок/отладки — информационная
if ll.Text != "first line" || ll.Level != LevelInfo {
t.Fatalf("unexpected first line: %+v", ll)
}
second := receiveOne(t, ch)
if ll, ok := second.(LogLine); !ok || ll.Text != "second line" {
t.Fatalf("unexpected second line: %+v", second)
}
}
func TestLogWriterPartialLine(t *testing.T) {
lbus := NewLogBus(16)
ch, unsub := lbus.Subscribe()
defer unsub()
w := NewLogWriter(lbus)
if _, err := w.Write([]byte("partial")); err != nil {
t.Fatalf("Write: %v", err)
}
select {
case got := <-ch:
t.Fatalf("partial line should not be published yet, got %#v", got)
case <-time.After(150 * time.Millisecond):
}
if _, err := w.Write([]byte(" line\n")); err != nil {
t.Fatalf("Write: %v", err)
}
got := receiveOne(t, ch)
ll, ok := got.(LogLine)
if !ok || ll.Text != "partial line" {
t.Fatalf("unexpected: %#v", got)
}
}
func TestLogWriterMultiSplit(t *testing.T) {
lbus := NewLogBus(16)
ch, unsub := lbus.Subscribe()
defer unsub()
w := NewLogWriter(lbus)
if _, err := w.Write([]byte("line1\nline2\nline3\n")); err != nil {
t.Fatalf("Write: %v", err)
}
var texts []string
for i := 0; i < 3; i++ {
ev := receiveOne(t, ch)
texts = append(texts, ev.(LogLine).Text)
}
if strings.Join(texts, ",") != "line1,line2,line3" {
t.Fatalf("got %v", texts)
}
}
// ClassifyLevel определяет уровень строки по маркерам в тексте.
func TestClassifyLevel(t *testing.T) {
cases := []struct {
line string
want string
}{
{"app: db opened /tmp/r.db", LevelInfo},
{"app: worker started", LevelInfo},
{"opencode: debug: poll request", LevelDebug},
{"opencode api debug: prompt -> POST http://127.0.0.1:4096/api/session", LevelDebug},
{"opencode api debug: messages response (512 bytes)", LevelDebug},
{"trace: session resumed", LevelDebug},
{"tg: warn: long poll timeout", LevelWarning},
{"ПРЕДУПРЕЖДЕНИЕ: конфиг не задан", LevelWarning},
{"app: process /start #5: run failed: exit status 1", LevelError},
{"ERROR: connection refused", LevelError},
{"worker: job failed after 3 retries", LevelError},
{"panic: runtime error", LevelError},
}
for _, c := range cases {
if got := ClassifyLevel(c.line); got != c.want {
t.Errorf("ClassifyLevel(%q) = %q, want %q", c.line, got, c.want)
}
}
}
// LogWriter публикует уровень, определённый по тексту строки.
func TestLogWriterClassifiesLevels(t *testing.T) {
lbus := NewLogBus(16)
ch, unsub := lbus.Subscribe()
defer unsub()
w := NewLogWriter(lbus)
if _, err := w.Write([]byte("all good\nERROR: boom\n")); err != nil {
t.Fatalf("Write: %v", err)
}
first := receiveOne(t, ch).(LogLine)
if first.Level != LevelInfo || first.Text != "all good" {
t.Fatalf("unexpected first: %+v", first)
}
second := receiveOne(t, ch).(LogLine)
if second.Level != LevelError || second.Text != "ERROR: boom" {
t.Fatalf("unexpected second: %+v", second)
}
}