Files
ratatoskr-go/internal/storage/storage_test.go
Hermes 631387fda7
All checks were successful
CI / test (push) Successful in 49s
CI / build-and-package (amd64, linux) (push) Successful in 47s
CI / build-and-package (amd64, windows) (push) Successful in 46s
fix: CI — тесты падали на сборке пакета app и на старом переходе
- e2e_channel.OnMessage принимал func(chat.Incoming), а интерфейс
  chat.Channel требует chat.Handler (named type) — пакет app не компилировался;
- storage_test готовил цепочку ready→running напрямую, но ready→running
  больше невалиден (нужен approved): вставлен переход approved.

Всё проверено локально: go build ./... + go test ./... зелёные.
2026-08-17 23:50:50 +05:00

275 lines
7.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package storage
import (
"context"
"errors"
"testing"
)
func setupTestDB(t *testing.T) (*Storage, context.Context) {
t.Helper()
ctx := context.Background()
s, err := Open(ctx, ":memory:")
if err != nil {
t.Fatalf("Open(:memory:): %v", err)
}
t.Cleanup(func() { s.Close() })
return s, ctx
}
func TestCreateAndGetTask(t *testing.T) {
s, ctx := setupTestDB(t)
task := &Task{
ChatID: "tg://123",
Title: "Test task",
Goal: "Test goal",
Repo: "test/repo",
TaskTag: "abc-123",
}
id, err := s.CreateTask(ctx, task)
if err != nil {
t.Fatalf("CreateTask: %v", err)
}
if id == 0 {
t.Fatal("expected non-zero ID")
}
if task.Status != StatusDraft {
t.Fatalf("expected draft status, got %s", task.Status)
}
got, err := s.GetTask(ctx, id)
if err != nil {
t.Fatalf("GetTask: %v", err)
}
if got.Title != "Test task" {
t.Fatalf("title = %q, want %q", got.Title, "Test task")
}
if got.Status != StatusDraft {
t.Fatalf("status = %s, want draft", got.Status)
}
}
func TestCreateTask_AutoTag(t *testing.T) {
s, ctx := setupTestDB(t)
// без TaskTag — должен сгенериться стабильный UUID (нужен для feat/<tag>)
task := &Task{ChatID: "tg://auto-tag", Title: "No tag"}
id, err := s.CreateTask(ctx, task)
if err != nil {
t.Fatalf("CreateTask: %v", err)
}
if task.TaskTag == "" {
t.Fatal("expected TaskTag to be generated when empty")
}
got, err := s.GetTask(ctx, id)
if err != nil {
t.Fatalf("GetTask: %v", err)
}
if got.TaskTag != task.TaskTag {
t.Fatalf("TaskTag = %q, want stable %q", got.TaskTag, task.TaskTag)
}
// стабильность: на одном инстансе повторный CreateTask с тем же тегом даёт другой УИД
task2 := &Task{ChatID: "tg://abc-tag2", Title: "No tag 2"}
id2, err := s.CreateTask(ctx, task2)
if err != nil {
t.Fatalf("CreateTask2: %v", err)
}
if got2, _ := s.GetTask(ctx, id2); got2.TaskTag == got.TaskTag {
t.Fatalf("TaskTag должен быть уникальным, оба = %q", got.TaskTag)
}
}
func TestGetTaskNotFound(t *testing.T) {
s, ctx := setupTestDB(t)
_, err := s.GetTask(ctx, 999)
if !IsNotFound(err) {
t.Fatalf("expected ErrNotFound, got %v", err)
}
}
func TestUpdateTaskStatus(t *testing.T) {
s, ctx := setupTestDB(t)
task := &Task{
ChatID: "tg://123",
Title: "Update test",
TaskTag: "def-456",
}
_, _ = s.CreateTask(ctx, task)
// draft → collecting — валидно
task.Status = StatusCollecting
if err := s.UpdateTask(ctx, task); err != nil {
t.Fatalf("UpdateTask draft→collecting: %v", err)
}
got, _ := s.GetTask(ctx, task.ID)
if got.Status != StatusCollecting {
t.Fatalf("status = %s, want collecting", got.Status)
}
// collecting → ready
task.Status = StatusReady
if err := s.UpdateTask(ctx, task); err != nil {
t.Fatalf("UpdateTask collecting→ready: %v", err)
}
// ready → approved
task.Status = StatusApproved
if err := s.UpdateTask(ctx, task); err != nil {
t.Fatalf("UpdateTask ready→approved: %v", err)
}
// approved → running
task.Status = StatusRunning
if err := s.UpdateTask(ctx, task); err != nil {
t.Fatalf("UpdateTask approved→running: %v", err)
}
// running → success
task.Status = StatusSuccess
if err := s.UpdateTask(ctx, task); err != nil {
t.Fatalf("UpdateTask running→success: %v", err)
}
}
func TestUpdateTaskInvalidTransition(t *testing.T) {
s, ctx := setupTestDB(t)
task := &Task{ChatID: "tg://1", TaskTag: "invalid"}
_, _ = s.CreateTask(ctx, task)
task.Status = StatusRunning // draft → running невалидно
err := s.UpdateTask(ctx, task)
if !IsInvalidStatus(err) {
t.Fatalf("expected ErrInvalidStatus, got %v", err)
}
}
func TestUpdateTaskNotFound(t *testing.T) {
s, ctx := setupTestDB(t)
err := s.UpdateTask(ctx, &Task{ID: 999, Status: StatusReady})
if !IsNotFound(err) {
t.Fatalf("expected ErrNotFound, got %v", err)
}
}
func TestListTasks(t *testing.T) {
s, ctx := setupTestDB(t)
for i := 0; i < 5; i++ {
_, _ = s.CreateTask(ctx, &Task{
ChatID: "tg://user",
Title: "Task",
TaskTag: "tag",
})
}
tasks, err := s.ListTasks(ctx, TaskFilter{ChatID: "tg://user", Limit: 10})
if err != nil {
t.Fatalf("ListTasks: %v", err)
}
if len(tasks) != 5 {
t.Fatalf("expected 5 tasks, got %d", len(tasks))
}
}
func TestDeleteTask(t *testing.T) {
s, ctx := setupTestDB(t)
task := &Task{ChatID: "tg://del", TaskTag: "delete-me"}
id, _ := s.CreateTask(ctx, task)
if err := s.DeleteTask(ctx, id); err != nil {
t.Fatalf("DeleteTask: %v", err)
}
_, err := s.GetTask(ctx, id)
if !IsNotFound(err) {
t.Fatal("expected ErrNotFound after delete")
}
}
func TestAppendAndGetTraces(t *testing.T) {
s, ctx := setupTestDB(t)
task := &Task{ChatID: "tg://trace", TaskTag: "trace-test"}
id, _ := s.CreateTask(ctx, task)
tr := &Trace{
TaskID: id,
Agent: "analyst",
SessionID: "sess-001",
Prompt: "analyze this",
Output: "",
}
traceID, err := s.AppendTrace(ctx, tr)
if err != nil {
t.Fatalf("AppendTrace: %v", err)
}
if traceID == 0 {
t.Fatal("expected non-zero trace ID")
}
if tr.Status != TraceRunning {
t.Fatalf("trace status = %s, want running", tr.Status)
}
// апдейт статуса
if err := s.UpdateTraceStatus(ctx, traceID, TraceSuccess); err != nil {
t.Fatalf("UpdateTraceStatus: %v", err)
}
// добавим вторую трассу
_, _ = s.AppendTrace(ctx, &Trace{
TaskID: id,
Agent: "dev",
SessionID: "sess-002",
})
traces, err := s.GetTraces(ctx, id)
if err != nil {
t.Fatalf("GetTraces: %v", err)
}
if len(traces) != 2 {
t.Fatalf("expected 2 traces, got %d", len(traces))
}
if traces[0].Status != TraceSuccess {
t.Fatalf("first trace status = %s, want success", traces[0].Status)
}
if traces[1].Agent != "dev" {
t.Fatalf("second trace agent = %s, want dev", traces[1].Agent)
}
}
func TestGetLatestTrace(t *testing.T) {
s, ctx := setupTestDB(t)
task := &Task{ChatID: "tg://latest", TaskTag: "latest-trace"}
id, _ := s.CreateTask(ctx, task)
_, _ = s.AppendTrace(ctx, &Trace{TaskID: id, Agent: "analyst", SessionID: "s1"})
_, _ = s.AppendTrace(ctx, &Trace{TaskID: id, Agent: "analyst", SessionID: "s2"})
latest, err := s.GetLatestTrace(ctx, id, "analyst")
if err != nil {
t.Fatalf("GetLatestTrace: %v", err)
}
if latest.SessionID != "s2" {
t.Fatalf("latest session = %s, want s2", latest.SessionID)
}
}
func TestUpdateTraceOutput(t *testing.T) {
s, ctx := setupTestDB(t)
task := &Task{ChatID: "tg://out", TaskTag: "output"}
id, _ := s.CreateTask(ctx, task)
traceID, _ := s.AppendTrace(ctx, &Trace{TaskID: id, Agent: "dev"})
if err := s.UpdateTraceOutput(ctx, traceID, "hello world"); err != nil {
t.Fatalf("UpdateTraceOutput: %v", err)
}
traces, _ := s.GetTraces(ctx, id)
if traces[0].Output != "hello world" {
t.Fatalf("output = %q, want %q", traces[0].Output, "hello world")
}
}
// хелперы для проверки классов ошибок
func IsNotFound(err error) bool {
return errors.Is(err, ErrNotFound)
}
func IsInvalidStatus(err error) bool {
return errors.Is(err, ErrInvalidStatus)
}