11
This commit is contained in:
@@ -0,0 +1,245 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user