246 lines
5.8 KiB
Go
246 lines
5.8 KiB
Go
package cc
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// --- pure UUID extraction tests ---
|
|
|
|
func TestExtractIDFromRolloutFilename(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
want string
|
|
}{
|
|
{
|
|
"rollout-2026-04-27T02-57-26-019dcb27-58a6-70a1-a5d1-bfc7f3ed9d0a.jsonl",
|
|
"019dcb27-58a6-70a1-a5d1-bfc7f3ed9d0a",
|
|
},
|
|
{
|
|
"rollout-2026-01-01T00-00-00-aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee.jsonl",
|
|
"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
|
|
},
|
|
{
|
|
// No UUID — should return empty string.
|
|
"rollout-2026-04-27T02-57-26.jsonl",
|
|
"",
|
|
},
|
|
{
|
|
// Not a rollout file.
|
|
"session.jsonl",
|
|
"",
|
|
},
|
|
}
|
|
for _, tc := range cases {
|
|
got := extractIDFromRolloutFilename(tc.name)
|
|
if got != tc.want {
|
|
t.Errorf("extractIDFromRolloutFilename(%q) = %q, want %q", tc.name, got, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCodexSessionsDir(t *testing.T) {
|
|
ts := time.Date(2026, 4, 27, 2, 57, 26, 0, time.UTC)
|
|
got := CodexSessionsDir("/home/alice", ts)
|
|
want := "/home/alice/.codex/sessions/2026/04/27"
|
|
if got != want {
|
|
t.Errorf("CodexSessionsDir = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
// --- watcher tests ---
|
|
|
|
func TestWatchForNewCodexSession_DetectsRolloutByFilename(t *testing.T) {
|
|
dir := t.TempDir()
|
|
ctx := context.Background()
|
|
|
|
ch := make(chan struct {
|
|
id string
|
|
err error
|
|
}, 1)
|
|
go func() {
|
|
id, err := WatchForNewCodexSession(ctx, CodexSessionWatchOpts{
|
|
SessionsDir: dir,
|
|
Timeout: 2 * time.Second,
|
|
})
|
|
ch <- struct {
|
|
id string
|
|
err error
|
|
}{id, err}
|
|
}()
|
|
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
uuid := "019dcb27-58a6-70a1-a5d1-bfc7f3ed9d0a"
|
|
fname := "rollout-2026-04-27T02-57-26-" + uuid + ".jsonl"
|
|
f, err := os.Create(filepath.Join(dir, fname))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
f.Close()
|
|
|
|
r := <-ch
|
|
if r.err != nil {
|
|
t.Fatalf("unexpected error: %v", r.err)
|
|
}
|
|
if r.id != uuid {
|
|
t.Errorf("got id %q, want %q", r.id, uuid)
|
|
}
|
|
}
|
|
|
|
func TestWatchForNewCodexSession_FallbackToFirstLine(t *testing.T) {
|
|
dir := t.TempDir()
|
|
ctx := context.Background()
|
|
|
|
ch := make(chan struct {
|
|
id string
|
|
err error
|
|
}, 1)
|
|
go func() {
|
|
id, err := WatchForNewCodexSession(ctx, CodexSessionWatchOpts{
|
|
SessionsDir: dir,
|
|
Timeout: 2 * time.Second,
|
|
})
|
|
ch <- struct {
|
|
id string
|
|
err error
|
|
}{id, err}
|
|
}()
|
|
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
// File name without a UUID — triggers first-line fallback.
|
|
wantID := "fallback-thread-id-0001"
|
|
fname := "rollout-2026-04-27T02-57-26-no-uuid-here.jsonl"
|
|
content := `{"timestamp":"2026-04-27T02:57:26Z","type":"session_meta","payload":{"id":"` + wantID + `","cwd":"/tmp"}}` + "\n"
|
|
if err := os.WriteFile(filepath.Join(dir, fname), []byte(content), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
r := <-ch
|
|
if r.err != nil {
|
|
t.Fatalf("unexpected error: %v", r.err)
|
|
}
|
|
if r.id != wantID {
|
|
t.Errorf("got id %q, want %q", r.id, wantID)
|
|
}
|
|
}
|
|
|
|
func TestWatchForNewCodexSession_TimeoutReturnsEmptyNil(t *testing.T) {
|
|
dir := t.TempDir()
|
|
ctx := context.Background()
|
|
|
|
id, err := WatchForNewCodexSession(ctx, CodexSessionWatchOpts{
|
|
SessionsDir: dir,
|
|
Timeout: 200 * time.Millisecond,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if id != "" {
|
|
t.Errorf("expected empty string on timeout, got %q", id)
|
|
}
|
|
}
|
|
|
|
// TestWatchForNewCodexSession_FirstOfDay proves the primary fix: when
|
|
// SessionsDir does not exist at call time (first codex session of the day),
|
|
// MkdirAll creates it and the watcher still captures the rollout file.
|
|
func TestWatchForNewCodexSession_FirstOfDay(t *testing.T) {
|
|
base := t.TempDir()
|
|
// SessionsDir does NOT exist yet — simulates first-of-day scenario.
|
|
sessDir := filepath.Join(base, "2099", "12", "31")
|
|
|
|
ctx := context.Background()
|
|
ch := make(chan struct {
|
|
id string
|
|
err error
|
|
}, 1)
|
|
go func() {
|
|
id, err := WatchForNewCodexSession(ctx, CodexSessionWatchOpts{
|
|
SessionsDir: sessDir,
|
|
Timeout: 300 * time.Millisecond,
|
|
})
|
|
ch <- struct {
|
|
id string
|
|
err error
|
|
}{id, err}
|
|
}()
|
|
|
|
// Wait for watcher to start and MkdirAll to create the dir.
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
uuid := "019dcb27-58a6-70a1-a5d1-bfc7f3ed9d0a"
|
|
fname := "rollout-2099-12-31T00-00-00-" + uuid + ".jsonl"
|
|
f, err := os.Create(filepath.Join(sessDir, fname))
|
|
if err != nil {
|
|
t.Fatalf("create rollout file: %v", err)
|
|
}
|
|
f.Close()
|
|
|
|
r := <-ch
|
|
if r.err != nil {
|
|
t.Fatalf("unexpected error: %v", r.err)
|
|
}
|
|
if r.id != uuid {
|
|
t.Errorf("got id %q, want %q (first-of-day session not captured)", r.id, uuid)
|
|
}
|
|
}
|
|
|
|
// TestWatchForNewCodexSession_MkdirAllFailReturnsEmptyNil verifies that when
|
|
// MkdirAll itself cannot create the dir (e.g. path under a file), the watcher
|
|
// returns ("", nil) without panicking.
|
|
func TestWatchForNewCodexSession_MkdirAllFailReturnsEmptyNil(t *testing.T) {
|
|
base := t.TempDir()
|
|
// Create a plain file so MkdirAll on a sub-path will fail.
|
|
blocker := filepath.Join(base, "blocker")
|
|
if err := os.WriteFile(blocker, []byte("x"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
ctx := context.Background()
|
|
id, err := WatchForNewCodexSession(ctx, CodexSessionWatchOpts{
|
|
SessionsDir: filepath.Join(blocker, "subdir"),
|
|
Timeout: 300 * time.Millisecond,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if id != "" {
|
|
t.Errorf("expected empty string when MkdirAll fails, got %q", id)
|
|
}
|
|
}
|
|
|
|
func TestWatchForNewCodexSession_ContextCancel(t *testing.T) {
|
|
dir := t.TempDir()
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
ch := make(chan struct {
|
|
id string
|
|
err error
|
|
}, 1)
|
|
go func() {
|
|
id, err := WatchForNewCodexSession(ctx, CodexSessionWatchOpts{
|
|
SessionsDir: dir,
|
|
Timeout: 5 * time.Second,
|
|
})
|
|
ch <- struct {
|
|
id string
|
|
err error
|
|
}{id, err}
|
|
}()
|
|
|
|
time.Sleep(30 * time.Millisecond)
|
|
cancel()
|
|
|
|
r := <-ch
|
|
if r.err != nil {
|
|
t.Fatalf("unexpected error on cancel: %v", r.err)
|
|
}
|
|
if r.id != "" {
|
|
t.Errorf("expected empty id on cancel, got %q", r.id)
|
|
}
|
|
}
|