Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion internal/cli/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
)
Expand All @@ -34,7 +35,11 @@ func TestBinaryIntegration(t *testing.T) {
defer func() { _ = os.RemoveAll(tmpDir) }()

// Build the binary
binaryPath := filepath.Join(tmpDir, "ctx-test-binary")
binaryName := "ctx-test-binary"
if runtime.GOOS == "windows" {
binaryName += ".exe"
}
binaryPath := filepath.Join(tmpDir, binaryName)
buildCmd := exec.Command("go", "build", "-o", binaryPath, "./cmd/ctx") //nolint:gosec // G204: test builds local binary
buildCmd.Env = append(os.Environ(), "CGO_ENABLED=0")

Expand Down
5 changes: 4 additions & 1 deletion internal/cli/doctor/doctor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,9 @@ func TestDoctor_PluginNotInstalled(t *testing.T) {
setupContextDir(t)

// Set HOME to a temp dir with no plugin files.
t.Setenv("HOME", t.TempDir())
tmpHome0 := t.TempDir()
t.Setenv("HOME", tmpHome0)
t.Setenv("USERPROFILE", tmpHome0)

cmd := Cmd()
var out bytes.Buffer
Expand All @@ -238,6 +240,7 @@ func TestDoctor_PluginInstalledNotEnabled(t *testing.T) {

tmpHome := t.TempDir()
t.Setenv("HOME", tmpHome)
t.Setenv("USERPROFILE", tmpHome)

// Create installed_plugins.json with ctx plugin.
pluginsDir := filepath.Join(tmpHome, ".claude", "plugins")
Expand Down
3 changes: 3 additions & 0 deletions internal/cli/initialize/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,7 @@ func TestRunInit_Minimal(t *testing.T) {
}
defer func() { _ = os.Chdir(origDir) }()
t.Setenv("HOME", tmpDir)
t.Setenv("USERPROFILE", tmpDir)
t.Setenv(config.EnvSkipPathCheck, config.EnvTrue)

cmd := Cmd()
Expand Down Expand Up @@ -381,6 +382,7 @@ func TestRunInit_Force(t *testing.T) {
}
defer func() { _ = os.Chdir(origDir) }()
t.Setenv("HOME", tmpDir)
t.Setenv("USERPROFILE", tmpDir)
t.Setenv(config.EnvSkipPathCheck, config.EnvTrue)

cmd := Cmd()
Expand Down Expand Up @@ -413,6 +415,7 @@ func TestRunInit_Merge(t *testing.T) {
}
defer func() { _ = os.Chdir(origDir) }()
t.Setenv("HOME", tmpDir)
t.Setenv("USERPROFILE", tmpDir)
t.Setenv(config.EnvSkipPathCheck, config.EnvTrue)

if err = os.WriteFile(config.FileClaudeMd, []byte("# My Project\n\nExisting.\n"), 0600); err != nil {
Expand Down
11 changes: 6 additions & 5 deletions internal/cli/journal/core/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,9 +163,10 @@ func InjectSourceLink(content, sourcePath string) string {
if pathErr != nil {
absPath = sourcePath
}
relPath := filepath.Join(
absPath = filepath.ToSlash(absPath)
relPath := filepath.ToSlash(filepath.Join(
config.DirContext, config.DirJournal, filepath.Base(absPath),
)
))
link := fmt.Sprintf(config.TplJournalSourceLink+nl+nl,
absPath, relPath, relPath)

Expand Down Expand Up @@ -209,19 +210,19 @@ func GenerateZensicalToml(
if len(topics) > 0 {
sb.WriteString(fmt.Sprintf(config.TplJournalNavItem+nl,
config.JournalLabelTopics,
filepath.Join(config.JournalDirTopics, config.FilenameIndex)),
filepath.ToSlash(filepath.Join(config.JournalDirTopics, config.FilenameIndex))),
)
}
if len(keyFiles) > 0 {
sb.WriteString(fmt.Sprintf(config.TplJournalNavItem+nl,
config.JournalLabelFiles,
filepath.Join(config.JournalDirFiles, config.FilenameIndex)),
filepath.ToSlash(filepath.Join(config.JournalDirFiles, config.FilenameIndex))),
)
}
if len(sessionTypes) > 0 {
sb.WriteString(fmt.Sprintf(config.TplJournalNavItem+nl,
config.JournalLabelTypes,
filepath.Join(config.JournalDirTypes, config.FilenameIndex)),
filepath.ToSlash(filepath.Join(config.JournalDirTypes, config.FilenameIndex))),
)
}

Expand Down
17 changes: 13 additions & 4 deletions internal/cli/journal/core/generate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
package core

import (
"path/filepath"
"strings"
"testing"
)
Expand Down Expand Up @@ -62,10 +63,14 @@ func TestGenerateIndex(t *testing.T) {
}

func TestInjectSourceLink_WithFrontmatter(t *testing.T) {
// Use a real temp path so filepath.Abs() is a no-op on all platforms.
srcPath := filepath.Join(t.TempDir(), ".context", "journal", "test.md")
wantAbs := filepath.ToSlash(srcPath)

content := "---\ntitle: Test\n---\n\n# Heading\n"
result := InjectSourceLink(content, "/home/user/.context/journal/test.md")
result := InjectSourceLink(content, srcPath)

if !strings.Contains(result, "[View source](file:///home/user/.context/journal/test.md)") {
if !strings.Contains(result, "[View source](file://"+wantAbs+")") {
t.Errorf("missing file:// link:\n%s", result)
}
if !strings.Contains(result, ".context/journal/test.md") {
Expand All @@ -77,10 +82,14 @@ func TestInjectSourceLink_WithFrontmatter(t *testing.T) {
}

func TestInjectSourceLink_NoFrontmatter(t *testing.T) {
// Use a real temp path so filepath.Abs() is a no-op on all platforms.
srcPath := filepath.Join(t.TempDir(), "file.md")
wantAbs := filepath.ToSlash(srcPath)

content := "# Heading\n\nSome text.\n"
result := InjectSourceLink(content, "/path/to/file.md")
result := InjectSourceLink(content, srcPath)

if !strings.HasPrefix(result, "*[View source](file:///path/to/file.md)") {
if !strings.HasPrefix(result, "*[View source](file://"+wantAbs+")") {
t.Errorf("source link not at top:\n%s", result)
}
if !strings.Contains(result, ".context/journal/file.md") {
Expand Down
11 changes: 10 additions & 1 deletion internal/cli/pad/pad_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"testing"

Expand All @@ -35,6 +36,7 @@ func setupEncrypted(t *testing.T) string {
t.Fatal(err)
}
t.Setenv("HOME", dir)
t.Setenv("USERPROFILE", dir)
t.Cleanup(func() {
_ = os.Chdir(origDir)
rc.Reset()
Expand Down Expand Up @@ -74,6 +76,7 @@ func setupPlaintext(t *testing.T) string {
t.Fatal(err)
}
t.Setenv("HOME", dir)
t.Setenv("USERPROFILE", dir)
t.Cleanup(func() {
_ = os.Chdir(origDir)
rc.Reset()
Expand Down Expand Up @@ -479,6 +482,7 @@ func TestMv_OutOfRange(t *testing.T) {
func TestNoKey_EncryptedFileExists(t *testing.T) {
dir := t.TempDir()
t.Setenv("HOME", dir)
t.Setenv("USERPROFILE", dir)
origDir, _ := os.Getwd()
if err := os.Chdir(dir); err != nil {
t.Fatal(err)
Expand Down Expand Up @@ -802,7 +806,7 @@ func TestKeyPath(t *testing.T) {
if !strings.HasSuffix(path, ".key") {
t.Errorf("core.KeyPath() = %q, want suffix %q", path, ".key")
}
if !strings.Contains(path, ".ctx/") {
if !strings.Contains(path, ".ctx"+string(filepath.Separator)) {
t.Errorf("core.KeyPath() = %q, want global path containing .ctx/", path)
}
}
Expand All @@ -824,6 +828,7 @@ func TestEnsureKey_EncFileExistsNoKey(t *testing.T) {
t.Fatal(err)
}
t.Setenv("HOME", dir)
t.Setenv("USERPROFILE", dir)
t.Cleanup(func() {
_ = os.Chdir(origDir)
rc.Reset()
Expand Down Expand Up @@ -859,6 +864,7 @@ func TestEnsureKey_GeneratesNewKey(t *testing.T) {
t.Fatal(err)
}
t.Setenv("HOME", dir)
t.Setenv("USERPROFILE", dir)
t.Cleanup(func() {
_ = os.Chdir(origDir)
rc.Reset()
Expand Down Expand Up @@ -2342,6 +2348,9 @@ func TestExport_Encrypted(t *testing.T) {
}

func TestExport_FilePermissions(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("file permission bits not supported on Windows")
}
dir := setupPlaintext(t)

f := filepath.Join(dir, "file.txt")
Expand Down
22 changes: 15 additions & 7 deletions internal/cli/recall/core/format_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ import (
"github.com/ActiveMemory/ctx/internal/recall/parser"
)

// setLocalUTC forces time.Local to UTC for the duration of the test.
// On Windows, t.Setenv("TZ", "UTC") does not affect time.Local.
func setLocalUTC(t *testing.T) {
orig := time.Local
time.Local = time.UTC
t.Cleanup(func() { time.Local = orig })
}

// stubDuration implements the interface{ Minutes() float64 } used by FormatDuration.
type stubDuration struct{ mins float64 }

Expand Down Expand Up @@ -342,7 +350,7 @@ func TestFormatPartNavigation(t *testing.T) {
// --- FormatJournalEntryPart tests ---

func TestFormatJournalEntryPart_SinglePart(t *testing.T) {
t.Setenv("TZ", "UTC")
setLocalUTC(t)

s := &parser.Session{
ID: "abc12345-session-id",
Expand Down Expand Up @@ -409,7 +417,7 @@ func TestFormatJournalEntryPart_SinglePart(t *testing.T) {
}

func TestFormatJournalEntryPart_MultiPart(t *testing.T) {
t.Setenv("TZ", "UTC")
setLocalUTC(t)

s := &parser.Session{
ID: "multi-session-id-12345678",
Expand Down Expand Up @@ -468,7 +476,7 @@ func TestFormatJournalEntryPart_MultiPart(t *testing.T) {
}

func TestFormatJournalEntryPart_WithToolUse(t *testing.T) {
t.Setenv("TZ", "UTC")
setLocalUTC(t)

s := &parser.Session{
ID: "tool-session-id-1234",
Expand Down Expand Up @@ -546,7 +554,7 @@ func TestFormatJournalEntryPart_WithToolUse(t *testing.T) {
}

func TestFormatJournalFilename_WithSlugOverride(t *testing.T) {
t.Setenv("TZ", "UTC")
setLocalUTC(t)

s := &parser.Session{
ID: "abc12345-full-session-uuid",
Expand All @@ -568,7 +576,7 @@ func TestFormatJournalFilename_WithSlugOverride(t *testing.T) {
}

func TestFormatJournalEntryPart_SessionIDInFrontmatter(t *testing.T) {
t.Setenv("TZ", "UTC")
setLocalUTC(t)

s := &parser.Session{
ID: "abc12345-full-session-uuid",
Expand All @@ -592,7 +600,7 @@ func TestFormatJournalEntryPart_SessionIDInFrontmatter(t *testing.T) {
}

func TestFormatJournalEntryPart_TitleInFrontmatterAndHeading(t *testing.T) {
t.Setenv("TZ", "UTC")
setLocalUTC(t)

s := &parser.Session{
ID: "abc12345-full-session-uuid",
Expand Down Expand Up @@ -625,7 +633,7 @@ func TestFormatJournalEntryPart_TitleInFrontmatterAndHeading(t *testing.T) {
}

func TestFormatJournalEntryPart_NoTitleUsesSlug(t *testing.T) {
t.Setenv("TZ", "UTC")
setLocalUTC(t)

s := &parser.Session{
ID: "abc12345-full-session-uuid",
Expand Down
Loading