Skip to content

Commit 7aec86f

Browse files
committed
refactor(keychain): make credential-ref override application testable
Addresses review: the override-application in open() (swap credential_ref + suppress the one-time legacy migration) had no direct coverage — only the pure effectiveRef() precedence and the flag-wiring were tested, so a regression that dropped runMigration=false could re-run the token.json migration against an arbitrary --ref/env profile and misattribute credential data, undetected. Extract applyCredentialRefOverride(cfg, runMigration) from open() and add TestApplyCredentialRefOverride asserting cfg.CredentialRef is swapped AND runMigration is forced false on a flag/env override (and both untouched with none), mirroring TestOpenWith_FlagOverridesConfig.
1 parent 2485cf7 commit 7aec86f

2 files changed

Lines changed: 74 additions & 10 deletions

File tree

internal/keychain/credref_test.go

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package keychain
22

3-
import "testing"
3+
import (
4+
"testing"
5+
6+
"github.com/open-cli-collective/google-readonly/internal/config"
7+
)
48

59
// resetCredRefOverride keeps the package-level --ref override clean across
610
// tests so a leaked value can't tilt the next.
@@ -61,3 +65,53 @@ func TestEffectiveRef_Precedence(t *testing.T) {
6165
}
6266
})
6367
}
68+
69+
// TestApplyCredentialRefOverride proves the safety-critical part open() relies
70+
// on: a present override swaps cfg.CredentialRef AND forces runMigration=false
71+
// (so the one-time legacy migration never runs against an arbitrary
72+
// --ref/env-selected profile), while no override leaves both untouched. This is
73+
// the open()-side coverage the pure effectiveRef/wiring tests don't provide.
74+
func TestApplyCredentialRefOverride(t *testing.T) {
75+
const cfgRef = "google-readonly/cfg"
76+
77+
t.Run("no override: cfg untouched, runMigration passthrough", func(t *testing.T) {
78+
resetCredRefOverride(t)
79+
t.Setenv(CredentialRefEnvVar(), "")
80+
cfg := &config.Config{CredentialRef: cfgRef}
81+
if rm := applyCredentialRefOverride(cfg, true); !rm {
82+
t.Errorf("runMigration = %v, want true (passthrough)", rm)
83+
}
84+
if cfg.CredentialRef != cfgRef {
85+
t.Errorf("cfg.CredentialRef = %q, want unchanged %q", cfg.CredentialRef, cfgRef)
86+
}
87+
// passthrough must preserve a false caller value too (OpenNoMigrate path)
88+
if rm := applyCredentialRefOverride(cfg, false); rm {
89+
t.Errorf("runMigration = %v, want false (passthrough of caller's false)", rm)
90+
}
91+
})
92+
93+
t.Run("flag override: cfg swapped, migration suppressed", func(t *testing.T) {
94+
resetCredRefOverride(t)
95+
t.Setenv(CredentialRefEnvVar(), "")
96+
SetCredentialRefOverride("google-readonly/flag", true)
97+
cfg := &config.Config{CredentialRef: cfgRef}
98+
if rm := applyCredentialRefOverride(cfg, true); rm {
99+
t.Errorf("runMigration = %v, want false (override must suppress migration)", rm)
100+
}
101+
if cfg.CredentialRef != "google-readonly/flag" {
102+
t.Errorf("cfg.CredentialRef = %q, want google-readonly/flag", cfg.CredentialRef)
103+
}
104+
})
105+
106+
t.Run("env override: cfg swapped, migration suppressed", func(t *testing.T) {
107+
resetCredRefOverride(t)
108+
t.Setenv(CredentialRefEnvVar(), "google-readonly/env")
109+
cfg := &config.Config{CredentialRef: cfgRef}
110+
if rm := applyCredentialRefOverride(cfg, true); rm {
111+
t.Errorf("runMigration = %v, want false (env override must suppress migration)", rm)
112+
}
113+
if cfg.CredentialRef != "google-readonly/env" {
114+
t.Errorf("cfg.CredentialRef = %q, want google-readonly/env", cfg.CredentialRef)
115+
}
116+
})
117+
}

internal/keychain/keychain.go

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -73,18 +73,28 @@ func open(overwrite, runMigration bool) (*Store, error) {
7373
if err != nil {
7474
return nil, err
7575
}
76-
// A per-invocation --ref flag or <SERVICE>_CREDENTIAL_REF env selects the
77-
// credential ref for this process (precedence: flag > env > config), so
78-
// concurrent processes can each target a different account without racing
79-
// on the shared config.yml. An explicit override also suppresses the
80-
// one-time §1.8 migration: it only ever targets the configured/default ref
81-
// (running it against an arbitrary ref could write the default's legacy
82-
// data under the wrong service/profile — the same reason OpenRef skips it).
76+
runMigration = applyCredentialRefOverride(cfg, runMigration)
77+
return openWith(cfg, overwrite, runMigration)
78+
}
79+
80+
// applyCredentialRefOverride applies the per-invocation credential-ref override
81+
// to cfg in place and returns the effective runMigration decision. A
82+
// --ref flag or <SERVICE>_CREDENTIAL_REF env (precedence: flag > env > config)
83+
// selects the ref for this process, so concurrent processes can each target a
84+
// different account without racing on the shared config.yml. A present override
85+
// also FORCES runMigration=false: the one-time §1.8 migration only ever targets
86+
// the configured/default ref (running it against an arbitrary ref could write
87+
// the default's legacy data under the wrong service/profile — the same reason
88+
// OpenRef skips it). With no override, cfg is untouched and the caller's
89+
// runMigration is returned unchanged. Split out of open() so this
90+
// safety-critical swap+suppression is directly testable without
91+
// config.LoadConfigForRuntime or a real keyring.
92+
func applyCredentialRefOverride(cfg *config.Config, runMigration bool) bool {
8393
if ref, overridden := effectiveRef(cfg.CredentialRef); overridden {
8494
cfg.CredentialRef = ref
85-
runMigration = false
95+
return false
8696
}
87-
return openWith(cfg, overwrite, runMigration)
97+
return runMigration
8898
}
8999

90100
// CredentialRefEnvVar is the per-invocation credential-ref override env var

0 commit comments

Comments
 (0)