package execenv import ( "os" "path/filepath" "testing" ) func TestEnsureSymlink_SkipsWhenSourceMissing(t *testing.T) { t.Parallel() dir := t.TempDir() src := filepath.Join(dir, "missing.json") dst := filepath.Join(dir, "link.json") if err := ensureSymlink(src, dst); err != nil { t.Fatalf("ensureSymlink: %v", err) } if _, err := os.Lstat(dst); !os.IsNotExist(err) { t.Error("expected dst to not be created when src is missing") } } func TestEnsureSymlink_ReplacesStaleRegularFile(t *testing.T) { t.Parallel() dir := t.TempDir() src := filepath.Join(dir, "source.json") dst := filepath.Join(dir, "existing.json") os.WriteFile(src, []byte("new"), 0o644) os.WriteFile(dst, []byte("old"), 0o644) // Regression for issue #2081: a regular file at dst (e.g. left over from // the Windows copy fallback in createFileLink) must be replaced so the // per-task home picks up changes to the shared source — otherwise a // once-stale auth.json never refreshes across env reuses. if err := ensureSymlink(src, dst); err != nil { t.Fatalf("ensureSymlink: %v", err) } data, err := os.ReadFile(dst) if err != nil { t.Fatalf("read dst: %v", err) } if string(data) != "new" { t.Errorf("dst content = %q, want %q (file should be re-linked/re-copied from src)", data, "new") } } func TestEnsureSymlink_RefreshesAfterCopyFallbackThenSrcChange(t *testing.T) { t.Parallel() dir := t.TempDir() src := filepath.Join(dir, "auth.json") dst := filepath.Join(dir, "task-auth.json") // Simulate the Windows copy fallback: first link is a copy of v1. os.WriteFile(src, []byte(`{"refresh_token":"v1"}`), 0o644) if err := copyFile(src, dst); err != nil { t.Fatalf("seed copy fallback: %v", err) } // Shared source rotates to v2 (e.g. Codex Desktop refreshed the token). os.WriteFile(src, []byte(`{"refresh_token":"v2"}`), 0o644) // Reuse path runs ensureSymlink again — expected to refresh dst from src. if err := ensureSymlink(src, dst); err != nil { t.Fatalf("ensureSymlink: %v", err) } data, err := os.ReadFile(dst) if err != nil { t.Fatalf("read dst: %v", err) } if string(data) != `{"refresh_token":"v2"}` { t.Errorf("dst content after refresh = %q, want v2 contents", data) } } func TestCreateDirLink(t *testing.T) { t.Parallel() dir := t.TempDir() src := filepath.Join(dir, "src") dst := filepath.Join(dir, "dst") os.MkdirAll(src, 0o755) os.WriteFile(filepath.Join(src, "test.txt"), []byte("hello"), 0o644) if err := createDirLink(src, dst); err != nil { t.Fatalf("createDirLink: %v", err) } // Should be able to read files through the link. data, err := os.ReadFile(filepath.Join(dst, "test.txt")) if err != nil { t.Fatalf("read through link: %v", err) } if string(data) != "hello" { t.Errorf("content = %q, want %q", data, "hello") } } func TestCreateFileLink(t *testing.T) { t.Parallel() dir := t.TempDir() src := filepath.Join(dir, "source.json") dst := filepath.Join(dir, "link.json") os.WriteFile(src, []byte(`{"key":"value"}`), 0o644) if err := createFileLink(src, dst); err != nil { t.Fatalf("createFileLink: %v", err) } data, err := os.ReadFile(dst) if err != nil { t.Fatalf("read link: %v", err) } if string(data) != `{"key":"value"}` { t.Errorf("content = %q", data) } } func TestCopyFile(t *testing.T) { t.Parallel() dir := t.TempDir() src := filepath.Join(dir, "src.txt") dst := filepath.Join(dir, "dst.txt") os.WriteFile(src, []byte("content"), 0o644) if err := copyFile(src, dst); err != nil { t.Fatalf("copyFile: %v", err) } data, _ := os.ReadFile(dst) if string(data) != "content" { t.Errorf("content = %q", data) } // Verify it's a copy, not a symlink. fi, _ := os.Lstat(dst) if fi.Mode()&os.ModeSymlink != 0 { t.Error("expected regular file, not symlink") } }