gogcli/internal/config/paths_test.go
salmonumbrella 07ffcb5d84
fix(paths): expand ~ in user-provided file paths (#56)
* fix(paths): expand ~ in user-provided file paths

When users specify paths with ~ (e.g., --out ~/Downloads/file.pdf) and
the path is quoted in the shell command, the tilde is not expanded by
the shell. This caused files to be written to a literal ~/Downloads
directory instead of the user's home directory.

Add config.ExpandPath() function that expands ~ at the beginning of
paths to the user's home directory. Apply this fix to all user-provided
file paths across:

- gmail attachment download (--out)
- drive download/export (--out)
- drive upload (localPath argument)
- auth token export (--out)
- auth credentials/import/keep (input paths)
- gmail thread attachments (--out-dir)
- gmail send/drafts (--attach)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(lint): address wrapcheck and wsl issues

* fix(calendar): support ISO 8601 time format and add 'list' alias

- Add parsing for ISO 8601 datetime with numeric timezone without colon
  (e.g., 2026-01-09T16:38:41-0800), which is the format produced by
  macOS `date +%Y-%m-%dT%H:%M:%S%z`
- Add 'list' as an alias for 'events' subcommand for more intuitive CLI
  usage (gog calendar list instead of gog calendar events)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* chore(changelog): note PR #56

* chore(lint): dedupe file string

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-01-10 02:19:30 +00:00

158 lines
3.4 KiB
Go

package config
import (
"os"
"path/filepath"
"strings"
"testing"
)
func TestPaths_CreateDirs(t *testing.T) {
home := t.TempDir()
t.Setenv("HOME", home)
t.Setenv("XDG_CONFIG_HOME", filepath.Join(home, "xdg-config"))
dir, err := EnsureDir()
if err != nil {
t.Fatalf("EnsureDir: %v", err)
}
if _, statErr := os.Stat(dir); statErr != nil {
t.Fatalf("expected dir: %v", statErr)
}
if filepath.Base(dir) != AppName {
t.Fatalf("unexpected base: %q", filepath.Base(dir))
}
keyringDir, err := EnsureKeyringDir()
if err != nil {
t.Fatalf("EnsureKeyringDir: %v", err)
}
if _, statErr := os.Stat(keyringDir); statErr != nil {
t.Fatalf("expected keyring dir: %v", statErr)
}
downloadsDir, err := EnsureDriveDownloadsDir()
if err != nil {
t.Fatalf("EnsureDriveDownloadsDir: %v", err)
}
if _, statErr := os.Stat(downloadsDir); statErr != nil {
t.Fatalf("expected downloads dir: %v", statErr)
}
attachmentsDir, err := EnsureGmailAttachmentsDir()
if err != nil {
t.Fatalf("EnsureGmailAttachmentsDir: %v", err)
}
if _, statErr := os.Stat(attachmentsDir); statErr != nil {
t.Fatalf("expected attachments dir: %v", statErr)
}
watchDir, err := EnsureGmailWatchDir()
if err != nil {
t.Fatalf("EnsureGmailWatchDir: %v", err)
}
if _, statErr := os.Stat(watchDir); statErr != nil {
t.Fatalf("expected watch dir: %v", statErr)
}
credsPath, err := ClientCredentialsPath()
if err != nil {
t.Fatalf("ClientCredentialsPath: %v", err)
}
if filepath.Base(credsPath) != "credentials.json" {
t.Fatalf("unexpected creds file: %q", filepath.Base(credsPath))
}
}
func TestExpandPath(t *testing.T) {
home := t.TempDir()
t.Setenv("HOME", home)
tests := []struct {
name string
input string
want string
wantErr bool
}{
{
name: "empty path",
input: "",
want: "",
},
{
name: "tilde only",
input: "~",
want: home,
},
{
name: "tilde with subpath",
input: "~/Downloads/file.txt",
want: filepath.Join(home, "Downloads/file.txt"),
},
{
name: "absolute path unchanged",
input: "/usr/local/bin",
want: "/usr/local/bin",
},
{
name: "relative path unchanged",
input: "relative/path/file.txt",
want: "relative/path/file.txt",
},
{
name: "tilde in middle unchanged",
input: "/some/~/path",
want: "/some/~/path",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ExpandPath(tt.input)
if (err != nil) != tt.wantErr {
t.Errorf("ExpandPath() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("ExpandPath() = %q, want %q", got, tt.want)
}
})
}
}
func TestKeepServiceAccountPath_SafeFilename(t *testing.T) {
home := t.TempDir()
t.Setenv("HOME", home)
t.Setenv("XDG_CONFIG_HOME", filepath.Join(home, "xdg-config"))
dir, err := Dir()
if err != nil {
t.Fatalf("Dir: %v", err)
}
p, err := KeepServiceAccountPath("a/b@EXAMPLE.com")
if err != nil {
t.Fatalf("KeepServiceAccountPath: %v", err)
}
if filepath.Dir(p) != dir {
t.Fatalf("expected keep path under %q, got %q", dir, p)
}
if strings.Contains(filepath.Base(p), "/") || strings.Contains(filepath.Base(p), "\\") {
t.Fatalf("expected filename only, got %q", filepath.Base(p))
}
if !strings.HasPrefix(filepath.Base(p), "keep-sa-") || !strings.HasSuffix(filepath.Base(p), ".json") {
t.Fatalf("unexpected keep filename: %q", filepath.Base(p))
}
}