internal/opencode: runner (idle/hard timeout, process-group kill, resume-fallback) + verdict/json parsing
All checks were successful
build-test / build (push) Successful in 1m21s

This commit is contained in:
Hermes
2026-08-14 13:29:05 +05:00
parent 53cf901707
commit 4ebafc48d0
8 changed files with 681 additions and 1 deletions

View File

@@ -0,0 +1,115 @@
package opencode
import (
"strings"
"testing"
)
func TestExtractVerdict(t *testing.T) {
cases := []struct {
name string
in string
want string
}{
{
name: "один text-парт NDJSON",
in: `{"type":"text","part":{"text":"hello"}}`,
want: "hello",
},
{
name: "несколько партов — берём последний text",
in: "{\"type\":\"step_start\",\"part\":{}}\n{\"type\":\"text\",\"part\":{\"text\":\"первый\"}}\n{\"type\":\"reasoning\",\"part\":{}}\n{\"type\":\"text\",\"part\":{\"text\":\"финал\"}}",
want: "финал",
},
{
name: "нет JSON — возвращаем строку как есть",
in: "простой текст без json",
want: "простой текст без json",
},
{
name: "пустая строка",
in: "",
want: "",
},
{
name: "text с пустым текстом пропускается",
in: "{\"type\":\"text\",\"part\":{\"text\":\"\"}}\n{\"type\":\"text\",\"part\":{\"text\":\"вал\"}}",
want: "вал",
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
if got := ExtractVerdict(c.in); got != c.want {
t.Errorf("ExtractVerdict(%q) = %q, want %q", c.in, got, c.want)
}
})
}
}
func TestExtractJSON(t *testing.T) {
cases := []struct {
name string
in string
want map[string]string
ok bool
}{
{
name: "fenced json",
in: "Вот ответ:\n```json\n{\"decision\":\"go\"}\n```",
want: map[string]string{"decision": "go"},
ok: true,
},
{
name: "fenced json без тега",
in: "```\n{\"a\":1}\n```",
want: map[string]string{"a": "1"},
ok: true,
},
{
name: "голый json-объект в тексте",
in: "Решение: {\"phase\":\"propose\"}",
want: map[string]string{"phase": "propose"},
ok: true,
},
{
name: "нет json",
in: "просто текст",
ok: false,
},
{
name: "невалидный json в fence",
in: "```json\n{no:json}\n```",
ok: false,
},
{
name: "пустая строка",
in: " ",
ok: false,
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
got, ok := ExtractJSON(c.in)
if ok != c.ok {
t.Fatalf("ExtractJSON(%q) ok = %v, want %v", c.in, ok, c.ok)
}
if !ok {
return
}
for k, v := range c.want {
if !strings.Contains(string(got[k]), v) {
t.Errorf("ExtractJSON(%q)[%s] = %s, want to contain %s", c.in, k, got[k], v)
}
}
})
}
}
func TestSessionIDFromOutput(t *testing.T) {
if s, ok := SessionIDFromOutput(`{"session_id":"abc123"}`); !ok || s != "abc123" {
t.Fatalf("got %q %v", s, ok)
}
if _, ok := SessionIDFromOutput("no session here"); ok {
t.Fatal("expected no match")
}
}