202 lines
4.4 KiB
Go
202 lines
4.4 KiB
Go
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)
|
|
}
|
|
}
|