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
8 changes: 4 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -299,10 +299,10 @@ using `entire` for real while tests run.
`tokenstore.UseFileBackendForTesting(...)`.
- **Spawned binaries are NOT covered**: `testing.Testing()` is false in a
subprocess. The integration and e2e TestMains set `ENTIRE_CONFIG_DIR`,
`XDG_CACHE_HOME`, `ENTIRE_TOKEN_STORE=file`, `ENTIRE_TOKEN_STORE_PATH`, and
`ENTIRE_TEST_AUTH_STORE_FILE` process-wide so every spawned `entire` (and
every agent-invoked hook) inherits isolation. Any new harness that spawns
the real binary must do the same.
`XDG_CACHE_HOME`, `ENTIRE_AUTH_LOCK_DIR`, `ENTIRE_TOKEN_STORE=file`,
`ENTIRE_TOKEN_STORE_PATH`, and `ENTIRE_TEST_AUTH_STORE_FILE` process-wide so
every spawned `entire` (and every agent-invoked hook) inherits isolation.
Any new harness that spawns the real binary must do the same.
- **Legacy auth store**: `auth.NewStore()` talks straight to the zalando
keyring; packages whose tests can reach it need `keyring.MockInit()` in
`TestMain` (see `cmd/entire/cli/global_test.go`) — the `testdirs` fallback
Expand Down
10 changes: 9 additions & 1 deletion cmd/entire/cli/auth/refresh.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"net/http"
"net/url"
"os"
"strings"
"time"

Expand All @@ -23,6 +24,8 @@ import (
// only governs when local readers consider the cached token stale.
const defaultSavedTokenTTL = time.Hour

const authLockDirEnvVar = "ENTIRE_AUTH_LOCK_DIR"

// contextTokenStore adapts one login context's keyring slots to auth-go's
// tokenstore.Store, so tokenmanager can load, refresh, and persist that
// context's credentials. It is bound to a specific (service, handle) at
Expand Down Expand Up @@ -143,7 +146,9 @@ func newContextTokenManager(c *contexts.Context, transport http.RoundTripper, al
// advisory lock file. Empty means auth-go's own default,
// os.UserCacheDir()/auth-go, which is what production wants.
//
// Under `go test` that default is the developer's real user cache directory
// A test harness that spawns the real binary sets ENTIRE_AUTH_LOCK_DIR because
// testing.Testing is false in the child. Under `go test`, the default is the
// developer's real user cache directory
// (~/Library/Caches/auth-go on macOS, which os.UserCacheDir resolves without
// consulting XDG_CACHE_HOME), so every test that builds a per-context manager
// litters it with lock files keyed on (ClientID, Issuer). Route it to the same
Expand All @@ -155,6 +160,9 @@ func newContextTokenManager(c *contexts.Context, transport http.RoundTripper, al
// is not ours to write to, and sharing a cross-process lock with whatever else
// is running.
func tokenManagerLockDir() string {
if dir := os.Getenv(authLockDirEnvVar); dir != "" {
return dir
}
if dir, ok := testdirs.Dir("authlock"); ok {
return dir
}
Expand Down
10 changes: 10 additions & 0 deletions cmd/entire/cli/auth/refresh_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -494,3 +494,13 @@ func TestTokenManagerLockDir_NeverRealUserCache(t *testing.T) {
t.Fatalf("tokenManagerLockDir() = %q, which resolves under the real %q", dir, realLockDir)
}
}

func TestTokenManagerLockDir_UsesExplicitOverride(t *testing.T) {
// Not parallel: it sets process-wide environment.
want := t.TempDir()
t.Setenv(authLockDirEnvVar, want)

if got := tokenManagerLockDir(); got != want {
t.Fatalf("tokenManagerLockDir() = %q, want explicit override %q", got, want)
}
}
7 changes: 4 additions & 3 deletions cmd/entire/cli/global_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ func TestMain(m *testing.M) {
// it), and testing.Testing() is false in that child — so the internal
// testdirs fallback and the in-memory keyring mock don't apply there, and
// the child's tokenstore default backend reaches the developer's real OS
// keychain. Set the file-backed token store and isolated config/cache dirs
// process-wide so spawned children inherit them. Mirrors the integration
// and e2e TestMains.
// keychain. Set the file-backed token store and isolated config, cache, and
// auth lock dirs process-wide so spawned children inherit them. Mirrors the
// integration and e2e TestMains.
isolationDir, err := os.MkdirTemp("", "entire-cli-test-*")
if err != nil {
panic(fmt.Errorf("failed to create test isolation dir: %w", err))
Expand All @@ -37,6 +37,7 @@ func TestMain(m *testing.M) {
os.Setenv("ENTIRE_TEST_AUTH_STORE_FILE", filepath.Join(isolationDir, "auth-tokens.json"))
os.Setenv("ENTIRE_CONFIG_DIR", filepath.Join(isolationDir, "config"))
os.Setenv("XDG_CACHE_HOME", filepath.Join(isolationDir, "cache"))
os.Setenv("ENTIRE_AUTH_LOCK_DIR", filepath.Join(isolationDir, "auth-locks"))

// Register a default ConfigSource so tests that call ConfigScoped
// (directly or indirectly via Commit/CreateTag) don't fail with
Expand Down
11 changes: 6 additions & 5 deletions cmd/entire/cli/integration_test/setup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@ func TestMain(m *testing.M) {
}

// Route every spawned CLI away from the developer's real ~/.config/entire
// (contexts.json, version_check.json), ~/.cache/entire (discovery caches),
// and OS keychain. testing.Testing() is false in the subprocess, so the
// internal/testdirs fallback cannot protect it — isolation must come from
// the environment, which children inherit because all integration env
// building starts from os.Environ() (testutil.GitIsolatedEnv).
// (contexts.json, version_check.json), ~/.cache/entire (discovery caches and
// auth-go locks), and OS keychain. testing.Testing() is false in the
// subprocess, so the internal/testdirs fallback cannot protect it —
// isolation must come from the environment, which children inherit because
// all integration env building starts from os.Environ() (testutil.GitIsolatedEnv).
//
// GIT_TERMINAL_PROMPT=0 and ENTIRE_TEST_GIT_HERMETIC form the hermeticity
// tripwire: the latter makes GitIsolatedEnv's global git config route HTTPS
Expand All @@ -44,6 +44,7 @@ func TestMain(m *testing.M) {
// inherited GIT_CONFIG_* env; it proxies transport only (not url.insteadOf, which
// would corrupt origin-URL forge detection) and leaves loopback servers untouched.
isolation := map[string]string{
"ENTIRE_AUTH_LOCK_DIR": filepath.Join(tmpDir, "auth-locks"),
"ENTIRE_CONFIG_DIR": filepath.Join(tmpDir, "entire-config"),
"XDG_CACHE_HOME": filepath.Join(tmpDir, "entire-cache"),
"ENTIRE_TOKEN_STORE": "file",
Expand Down
11 changes: 6 additions & 5 deletions e2e/tests/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,14 @@ func TestMain(m *testing.M) {
os.Setenv("ENTIRE_TOKEN_STORE", "file")
os.Setenv("ENTIRE_TOKEN_STORE_PATH", filepath.Join(runDir, "e2e-tokenstore.json"))

// Same for the CLI's config and cache directories: contexts.json,
// version_check.json, and the discovery caches must never resolve to the
// developer's real ~/.config/entire or ~/.cache/entire from a spawned
// binary (testing.Testing() is false there, so the internal/testdirs
// fallback cannot protect it).
// Same for the CLI's config, cache, and auth lock directories: contexts.json,
// version_check.json, discovery caches, and auth-go lock files must never
// resolve to the developer's real ~/.config/entire or ~/.cache/entire from
// a spawned binary. testing.Testing() is false there, so the
// internal/testdirs fallback cannot protect it.
os.Setenv("ENTIRE_CONFIG_DIR", filepath.Join(runDir, "entire-config"))
os.Setenv("XDG_CACHE_HOME", filepath.Join(runDir, "entire-cache"))
os.Setenv("ENTIRE_AUTH_LOCK_DIR", filepath.Join(runDir, "auth-locks"))

// Select the checkpoint storage backend for the whole suite. E2E_CHECKPOINT_STORE
// (e.g. "git-refs") maps to the ENTIRE_CHECKPOINTS_PRIMARY override the spawned
Expand Down