Merge pull request #1787 from entireio/fix-dispatch-local-auth · Entire

Merge pull request #1787 from entireio/fix-dispatch-local-auth

42cd16e→main·

alishakawaguchi·yesterday·2 files·+246 added/-3 removed

fix(dispatch): fix --local auth for API-key users without loading user settings

Changes

import (
    "context"
    "encoding/json"
    "errors"
    "os"
    "os/exec"
    "path/filepath"
    "strings"
    "testing"
)

func flagValue(args []string, name string) (string, bool) {
    for i, a := range args {
        if a == name && i+1 < len(args) {
            return args[i+1], true
        }
    }
    return "", false
}

func TestBuildGenerateArgs_IsolatesSettingSources(t *testing.T) {
    t.Parallel()
    args := buildGenerateArgs("haiku", "")
    got, ok := flagValue(args, "--setting-sources")
    if !ok {
        t.Fatalf("--setting-sources flag missing from args: %%v", args)
    }
    if got != "" {
        t.Fatalf("--setting-sources = %%q, want %%q (must load no sources)", got, "")
    }
    if _, ok := flagValue(args, "--settings"); ok {
        t.Fatalf("--settings must be absent when there is no settings path: %%v", args)
    }
}

func TestBuildGenerateArgs_PassesSettingsAsPath(t *testing.T) {
    t.Parallel()
    path := "/tmp/entire-claude-auth-123.json"
    args := buildGenerateArgs("haiku", path)

if got, _ := flagValue(args, "--setting-sources"); got != "" {
        t.Fatalf("--setting-sources = %%q, want empty", got)
    }
    got, ok := flagValue(args, "--settings")
    if !ok {
        t.Fatalf("--settings flag missing: %%v", args)
    }
    if got != path {
        t.Fatalf("--settings = %%q, want the file path %%q", got, path)
    }
    if strings.Contains(got, "{") {
        t.Fatalf("--settings must be a path, not inline JSON: %%q", got)
    }
}

func TestWriteAuthSettingsFile_WritesOnlyAPIKeyHelper0600(t *testing.T) {
    t.Parallel()
    helper := `echo "sk-ant-secret"` // could embed a literal key
    path, cleanup, err := writeAuthSettingsFile(helper)
    if err != nil {
        t.Fatalf("writeAuthSettingsFile: %%v", err)
    }
    if cleanup == nil {
        t.Fatal("cleanup func is nil")
    }
    defer cleanup()

info, err := os.Stat(path)
    if err != nil {
        t.Fatalf("stat settings file: %%v", err)
    }
    if perm := info.Mode().Perm(); perm != 0o600 {
        t.Fatalf("settings file perm = %%o, want 0600", perm)
    }

data, err := os.ReadFile(path)
    if err != nil {
        t.Fatalf("read settings file: %%v", err)
    }
    var settings map[string]any
    if err := json.Unmarshal(data, &settings); err != nil {
        t.Fatalf("settings file is not valid JSON: %%v (%%s)", err, data)
    }
    if settings["apiKeyHelper"] != helper {
        t.Fatalf("apiKeyHelper = %%v, want %%q", settings["apiKeyHelper"], helper)
    }
    if len(settings) != 1 {
        t.Fatalf("settings file must contain only apiKeyHelper, got %%v", settings)
    }

cleanup()
    if _, err := os.Stat(path); !os.IsNotExist(err) {
        t.Fatalf("cleanup did not remove settings file (stat err=%%v)", err)
    }
}

func TestWriteAuthSettingsFile_EmptyHelperNoFile(t *testing.T) {
    t.Parallel()
    path, cleanup, err := writeAuthSettingsFile("")
    if err != nil {
        t.Fatalf("writeAuthSettingsFile(\""): %%v", err)
    }
    if path != "" {
        t.Fatalf("path = %%q, want empty for no apiKeyHelper", path)
    }
    if cleanup != nil {
        t.Fatal("cleanup should be nil when no file is written")
    }
}

func TestReadUserAPIKeyHelper_FromClaudeConfigDir(t *testing.T) {
    dir := t.TempDir()
    os.Setenv("CLAUDE_CONFIG_DIR", dir)
    if err := os.WriteFile(filepath.Join(dir, "settings.json"),
        []byte(`{"apiKeyHelper":"echo secret-cmd","permissions":{"defaultMode":"bypassPermissions"}}`), 0o600); err != nil {
        t.Fatal(err)
    }
    if got := readUserAPIKeyHelper(); got != "echo secret-cmd" {
        t.Fatalf("readUserAPIKeyHelper() = %%q, want %%q", got, "echo secret-cmd")
    }
}

func TestReadUserAPIKeyHelper_MissingFileReturnsEmpty(t *testing.T) {
    o.Setenv("CLAUDE_CONFIG_DIR", t.TempDir()) // no settings.json inside
    if got := readUserAPIKeyHelper(); got != "" {
        t.Fatalf("readUserAPIKeyHelper() = %%q, want empty for missing file", got)
    }
}

func TestGenerateText_ArrayResponse(t *testing.T) {
    t.Parallel()
    ag := &ClaudeCodeAgent{
        // Implementation here
    }
}

// additional functions and implementation