11
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
package cc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// --- pure encoding tests ---
|
||||
|
||||
func TestEncodeCwdForProjectsDir(t *testing.T) {
|
||||
cases := []struct {
|
||||
cwd string
|
||||
want string
|
||||
}{
|
||||
{"/home/user/project", "-home-user-project"},
|
||||
{"/tmp/my.project", "-tmp-my-project"},
|
||||
{"/a/b/c", "-a-b-c"},
|
||||
{"/single", "-single"},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
got := EncodeCwdForProjectsDir(tc.cwd)
|
||||
if got != tc.want {
|
||||
t.Errorf("EncodeCwdForProjectsDir(%q) = %q, want %q", tc.cwd, got, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClaudeProjectsDir(t *testing.T) {
|
||||
got := ClaudeProjectsDir("/home/alice", "/home/alice/myrepo")
|
||||
want := "/home/alice/.claude/projects/-home-alice-myrepo"
|
||||
if got != want {
|
||||
t.Errorf("ClaudeProjectsDir = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// --- watcher tests ---
|
||||
|
||||
func TestWatchForNewSession_DetectsNewFile(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
ctx := context.Background()
|
||||
|
||||
type result struct {
|
||||
id string
|
||||
err error
|
||||
}
|
||||
ch := make(chan result, 1)
|
||||
go func() {
|
||||
id, err := WatchForNewSession(ctx, SessionWatchOpts{
|
||||
ProjectsDir: dir,
|
||||
Timeout: 2 * time.Second,
|
||||
})
|
||||
ch <- result{id, err}
|
||||
}()
|
||||
|
||||
// Give the watcher time to start.
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
// Create a new .jsonl file — this should resolve the watcher.
|
||||
sessionID := "abc123sessionid"
|
||||
f, err := os.Create(filepath.Join(dir, sessionID+".jsonl"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f.Close()
|
||||
|
||||
r := <-ch
|
||||
if r.err != nil {
|
||||
t.Fatalf("unexpected error: %v", r.err)
|
||||
}
|
||||
if r.id != sessionID {
|
||||
t.Errorf("got id %q, want %q", r.id, sessionID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWatchForNewSession_IgnoresExistingFile(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
|
||||
// Pre-existing file must be ignored.
|
||||
existing := "existing-session"
|
||||
f, _ := os.Create(filepath.Join(dir, existing+".jsonl"))
|
||||
f.Close()
|
||||
|
||||
ctx := context.Background()
|
||||
ch := make(chan struct {
|
||||
id string
|
||||
err error
|
||||
}, 1)
|
||||
go func() {
|
||||
id, err := WatchForNewSession(ctx, SessionWatchOpts{
|
||||
ProjectsDir: dir,
|
||||
Timeout: 200 * time.Millisecond,
|
||||
})
|
||||
ch <- struct {
|
||||
id string
|
||||
err error
|
||||
}{id, err}
|
||||
}()
|
||||
|
||||
// No new file created — should timeout returning ("", nil).
|
||||
r := <-ch
|
||||
if r.err != nil {
|
||||
t.Fatalf("unexpected error: %v", r.err)
|
||||
}
|
||||
if r.id != "" {
|
||||
t.Errorf("expected empty id on timeout, got %q", r.id)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWatchForNewSession_TimeoutReturnsEmptyNil(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
ctx := context.Background()
|
||||
|
||||
id, err := WatchForNewSession(ctx, SessionWatchOpts{
|
||||
ProjectsDir: 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)
|
||||
}
|
||||
}
|
||||
|
||||
// TestWatchForNewSession_MissingDirThenCreated proves the symmetric fix for the
|
||||
// claude watcher: ProjectsDir absent at call time is created by MkdirAll, and a
|
||||
// new session file written afterwards is still captured.
|
||||
func TestWatchForNewSession_MissingDirThenCreated(t *testing.T) {
|
||||
base := t.TempDir()
|
||||
// ProjectsDir does NOT exist yet.
|
||||
projDir := filepath.Join(base, "projects", "-home-user-myrepo")
|
||||
|
||||
ctx := context.Background()
|
||||
ch := make(chan struct {
|
||||
id string
|
||||
err error
|
||||
}, 1)
|
||||
go func() {
|
||||
id, err := WatchForNewSession(ctx, SessionWatchOpts{
|
||||
ProjectsDir: projDir,
|
||||
Timeout: 300 * time.Millisecond,
|
||||
})
|
||||
ch <- struct {
|
||||
id string
|
||||
err error
|
||||
}{id, err}
|
||||
}()
|
||||
|
||||
// Allow MkdirAll + watcher.Add to complete.
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
sessionID := "newclaudesessionabc"
|
||||
f, err := os.Create(filepath.Join(projDir, sessionID+".jsonl"))
|
||||
if err != nil {
|
||||
t.Fatalf("create session file: %v", err)
|
||||
}
|
||||
f.Close()
|
||||
|
||||
r := <-ch
|
||||
if r.err != nil {
|
||||
t.Fatalf("unexpected error: %v", r.err)
|
||||
}
|
||||
if r.id != sessionID {
|
||||
t.Errorf("got id %q, want %q (missing-dir session not captured)", r.id, sessionID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWatchForNewSession_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 := WatchForNewSession(ctx, SessionWatchOpts{
|
||||
ProjectsDir: dir,
|
||||
Timeout: 5 * time.Second,
|
||||
})
|
||||
ch <- struct {
|
||||
id string
|
||||
err error
|
||||
}{id, err}
|
||||
}()
|
||||
|
||||
time.Sleep(30 * time.Millisecond)
|
||||
cancel()
|
||||
|
||||
r := <-ch
|
||||
// Context cancellation returns ("", nil) matching timeout semantics.
|
||||
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