Команды (спец 12.2): - ui.Commands: действия UI → текстовые команды канала (start/cancel/skip/ retry/continue/approve/send), единый путь через chat.Channel. - ui.Window = chat.Channel + View; NilWindow для headless/тестов. Snapshots (спец 12.5): - ui.Store: чтение-модель, возвращает только копии (ListTasks/GetTask/ GetHistory/GetTraces); ui.DBStore поверх storage. Fyne-окно (internal/ui/desktop, build-tag cgo): - список задач слева, сплиты рабочей области и панели «Логи»/«Состояние», ввод+кнопки команд, тёмная тема, fullscreen, сохранение layout в Preferences, сворачивание при закрытии крестиком. - Колбэки View через fyne.Do (спец 12.4). Интеграция: - app.New(..., noUI); флаг --noui; UI собирается только с cgo (ui_cgo/ ui_noui фабрики), приложение headless без него. - Run: окно блокирует главную горутину; «Завершить» → cancel → graceful shutdown (спец 12.6). - go.mod: fyne.io/fyne/v2 v2.6.0 (direct).
108 lines
2.5 KiB
Go
108 lines
2.5 KiB
Go
package ui
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/kamelion/ratatoskr-go/internal/storage"
|
|
)
|
|
|
|
func newTestStore(t *testing.T) *storage.Storage {
|
|
t.Helper()
|
|
s, err := storage.Open(context.Background(), ":memory:")
|
|
if err != nil {
|
|
t.Fatalf("open storage: %v", err)
|
|
}
|
|
t.Cleanup(func() { s.Close() })
|
|
return s
|
|
}
|
|
|
|
func TestDBStoreListTasksReturnsCopies(t *testing.T) {
|
|
s := newTestStore(t)
|
|
ctx := context.Background()
|
|
|
|
id1, err := s.CreateTask(ctx, &storage.Task{ChatID: "ui://local", Title: "A"})
|
|
if err != nil {
|
|
t.Fatalf("create: %v", err)
|
|
}
|
|
if _, err := s.CreateTask(ctx, &storage.Task{ChatID: "tg://1", Title: "B"}); err != nil {
|
|
t.Fatalf("create: %v", err)
|
|
}
|
|
|
|
st := NewDBStore(s)
|
|
tasks, err := st.ListTasks(ctx)
|
|
if err != nil {
|
|
t.Fatalf("ListTasks: %v", err)
|
|
}
|
|
if len(tasks) != 2 {
|
|
t.Fatalf("got %d tasks, want 2", len(tasks))
|
|
}
|
|
|
|
// мутация полученной копии не влияет на БД
|
|
tasks[0].Title = "mutated"
|
|
fetched, err := s.GetTask(ctx, id1)
|
|
if err != nil {
|
|
t.Fatalf("get: %v", err)
|
|
}
|
|
if fetched.Title == "mutated" {
|
|
t.Fatal("mutating snapshot leaked into DB")
|
|
}
|
|
}
|
|
|
|
func TestDBStoreGetTaskAndHistory(t *testing.T) {
|
|
s := newTestStore(t)
|
|
ctx := context.Background()
|
|
|
|
id, err := s.CreateTask(ctx, &storage.Task{ChatID: "ui://local", Title: "A"})
|
|
if err != nil {
|
|
t.Fatalf("create: %v", err)
|
|
}
|
|
if err := s.AppendHistory(ctx, id, "user", "hello"); err != nil {
|
|
t.Fatalf("append: %v", err)
|
|
}
|
|
|
|
st := NewDBStore(s)
|
|
task, err := st.GetTask(ctx, id)
|
|
if err != nil {
|
|
t.Fatalf("GetTask: %v", err)
|
|
}
|
|
if task.ID != id {
|
|
t.Fatalf("task.ID = %d, want %d", task.ID, id)
|
|
}
|
|
|
|
hist, err := st.GetHistory(ctx, id)
|
|
if err != nil {
|
|
t.Fatalf("GetHistory: %v", err)
|
|
}
|
|
if len(hist) != 1 || hist[0].Content != "hello" {
|
|
t.Fatalf("bad history: %+v", hist)
|
|
}
|
|
}
|
|
|
|
func TestDBStoreGetTraces(t *testing.T) {
|
|
s := newTestStore(t)
|
|
ctx := context.Background()
|
|
|
|
id, err := s.CreateTask(ctx, &storage.Task{ChatID: "ui://local"})
|
|
if err != nil {
|
|
t.Fatalf("create: %v", err)
|
|
}
|
|
tid, err := s.AppendTrace(ctx, &storage.Trace{TaskID: id, Agent: "dev", Prompt: "p"})
|
|
if err != nil {
|
|
t.Fatalf("append trace: %v", err)
|
|
}
|
|
_ = s.UpdateTraceStatus(ctx, tid, storage.TraceSuccess)
|
|
|
|
st := NewDBStore(s)
|
|
traces, err := st.GetTraces(ctx, id)
|
|
if err != nil {
|
|
t.Fatalf("GetTraces: %v", err)
|
|
}
|
|
if len(traces) != 1 {
|
|
t.Fatalf("got %d traces, want 1", len(traces))
|
|
}
|
|
if traces[0].Status != storage.TraceSuccess {
|
|
t.Fatalf("trace status = %s, want success", traces[0].Status)
|
|
}
|
|
}
|