iop/apps/node/internal/store/store_test.go
toki 96f79fcd08 feat: unbounded CLI sessions support and related improvements
- Add unbounded CLI sessions agent-task with plan and code review logs
- Add edge console with tests
- Add node run manager for session lifecycle management
- Add router and store tests
- Update transport session handling
- Add runtime proto definitions
- Update configurations and packages
2026-05-03 20:07:09 +09:00

181 lines
3.9 KiB
Go

package store_test
import (
"context"
"database/sql"
"path/filepath"
"testing"
"time"
_ "modernc.org/sqlite"
"go.uber.org/zap"
"iop/apps/node/internal/store"
)
const oldRunsSchema = `
CREATE TABLE runs (
run_id TEXT PRIMARY KEY,
adapter TEXT NOT NULL,
model TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'running',
created_at DATETIME NOT NULL,
completed_at DATETIME,
error TEXT
);
`
func seedOldSchemaDB(t *testing.T) string {
t.Helper()
dir := t.TempDir()
dsn := filepath.Join(dir, "old.db")
db, err := sql.Open("sqlite", dsn)
if err != nil {
t.Fatalf("open: %v", err)
}
if _, err := db.Exec(oldRunsSchema); err != nil {
t.Fatalf("seed schema: %v", err)
}
if err := db.Close(); err != nil {
t.Fatalf("close: %v", err)
}
return dsn
}
func newTestStore(t *testing.T) *store.Store {
t.Helper()
s, err := store.New(":memory:", zap.NewNop())
if err != nil {
t.Fatalf("store.New: %v", err)
}
t.Cleanup(func() { _ = s.Close() })
return s
}
func TestStore_RunSessionFields(t *testing.T) {
s := newTestStore(t)
ctx := context.Background()
r := store.RunRecord{
RunID: "run-1",
Adapter: "cli",
Model: "codex",
SessionID: "session-a",
Background: true,
Status: "running",
CreatedAt: time.Now(),
}
if err := s.InsertRun(ctx, r); err != nil {
t.Fatalf("InsertRun: %v", err)
}
got, err := s.GetRun(ctx, "run-1")
if err != nil {
t.Fatalf("GetRun: %v", err)
}
if got == nil {
t.Fatal("expected run record")
}
if got.SessionID != "session-a" {
t.Errorf("SessionID: got %q want %q", got.SessionID, "session-a")
}
if !got.Background {
t.Error("Background: expected true")
}
if got.Status != "running" {
t.Errorf("Status: got %q want %q", got.Status, "running")
}
if err := s.CompleteRun(ctx, "run-1", "cancelled", ""); err != nil {
t.Fatalf("CompleteRun: %v", err)
}
got, err = s.GetRun(ctx, "run-1")
if err != nil {
t.Fatalf("GetRun after complete: %v", err)
}
if got.Status != "cancelled" {
t.Errorf("Status after cancel: got %q want %q", got.Status, "cancelled")
}
}
func TestStore_DefaultSessionID(t *testing.T) {
s := newTestStore(t)
ctx := context.Background()
r := store.RunRecord{
RunID: "run-2",
Adapter: "cli",
Model: "codex",
SessionID: "", // empty → should default to "default"
Status: "running",
CreatedAt: time.Now(),
}
if err := s.InsertRun(ctx, r); err != nil {
t.Fatalf("InsertRun: %v", err)
}
got, err := s.GetRun(ctx, "run-2")
if err != nil {
t.Fatalf("GetRun: %v", err)
}
if got.SessionID != "default" {
t.Errorf("SessionID: got %q want %q", got.SessionID, "default")
}
}
func TestStore_MigratesExistingRunsTable(t *testing.T) {
dsn := seedOldSchemaDB(t)
s, err := store.New(dsn, zap.NewNop())
if err != nil {
t.Fatalf("store.New: %v", err)
}
t.Cleanup(func() { _ = s.Close() })
ctx := context.Background()
r := store.RunRecord{
RunID: "run-mig",
Adapter: "cli",
Model: "codex",
SessionID: "session-a",
Background: true,
Status: "running",
CreatedAt: time.Now(),
}
if err := s.InsertRun(ctx, r); err != nil {
t.Fatalf("InsertRun: %v", err)
}
got, err := s.GetRun(ctx, "run-mig")
if err != nil {
t.Fatalf("GetRun: %v", err)
}
if got == nil {
t.Fatal("expected run record")
}
if got.SessionID != "session-a" {
t.Errorf("SessionID: got %q want %q", got.SessionID, "session-a")
}
if !got.Background {
t.Error("Background: expected true")
}
}
func TestStore_MigrationIsIdempotent(t *testing.T) {
dsn := seedOldSchemaDB(t)
s1, err := store.New(dsn, zap.NewNop())
if err != nil {
t.Fatalf("store.New first: %v", err)
}
if err := s1.Close(); err != nil {
t.Fatalf("close first: %v", err)
}
s2, err := store.New(dsn, zap.NewNop())
if err != nil {
t.Fatalf("store.New second: %v", err)
}
t.Cleanup(func() { _ = s2.Close() })
}