239 lines
5.8 KiB
Go
239 lines
5.8 KiB
Go
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 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 → running
|
|
task.Status = StatusRunning
|
|
if err := s.UpdateTask(ctx, task); err != nil {
|
|
t.Fatalf("UpdateTask ready→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)
|
|
} |