From de4c65ba70245aac4093c264c795e9225fada9d9 Mon Sep 17 00:00:00 2001 From: Rian Stockbower Date: Wed, 24 Jun 2026 19:32:59 -0400 Subject: [PATCH 1/2] fix(release): update keychain probe contract --- internal/cmd/configcmd/configcmd_test.go | 95 ++++++++++++++++++++++++ packaging/identity.yml | 27 ++++--- 2 files changed, 113 insertions(+), 9 deletions(-) diff --git a/internal/cmd/configcmd/configcmd_test.go b/internal/cmd/configcmd/configcmd_test.go index 66f1c95..7aa9a41 100644 --- a/internal/cmd/configcmd/configcmd_test.go +++ b/internal/cmd/configcmd/configcmd_test.go @@ -14,6 +14,7 @@ import ( "github.com/open-cli-collective/cli-common/credstore" "github.com/open-cli-collective/cli-common/statedirtest" "github.com/spf13/cobra" + "gopkg.in/yaml.v3" "github.com/open-cli-collective/codereview-cli/internal/agents" "github.com/open-cli-collective/codereview-cli/internal/cmd/exitcode" @@ -115,6 +116,63 @@ func TestConfigShowJSON(t *testing.T) { } } +func TestKeychainProbeManifestMatchesConfigShowContract(t *testing.T) { + manifest := readKeychainProbeManifest(t) + wantCommand := []string{"config", "show", "--profile", "default", "--json"} + if !reflect.DeepEqual(manifest.KeychainProbe.Command, wantCommand) { + t.Fatalf("keychain probe command = %#v, want %#v", manifest.KeychainProbe.Command, wantCommand) + } + if manifest.KeychainProbe.SeedConfig.Content == "" { + t.Fatal("keychain probe seed config content is empty") + } + if len(manifest.KeychainProbe.Assertions) == 0 { + t.Fatal("keychain probe assertions are empty") + } + + path := filepath.Join(t.TempDir(), "config.yml") + writeRawConfig(t, path, manifest.KeychainProbe.SeedConfig.Content) + + loaded, err := config.Load(path) + if err != nil { + t.Fatalf("config.Load(seed): %v", err) + } + profile, ok := loaded.Profiles["default"] + if !ok { + t.Fatalf("loaded profiles missing default: %#v", loaded.Profiles) + } + if profile.RepositoryAccess != "default-git" { + t.Fatalf("default repository_access = %q, want default-git", profile.RepositoryAccess) + } + + cmd, out := newTestCommand(path) + if err := root.Execute(cmd, manifest.KeychainProbe.Command); err != nil { + t.Fatalf("Execute keychain probe command: %v", err) + } + + var got view.ConfigShow + if err := json.Unmarshal(out.Bytes(), &got); err != nil { + t.Fatalf("Unmarshal keychain probe JSON: %v\n%s", err, out.String()) + } + if got.Backend != "keychain" { + t.Fatalf("backend = %q, want keychain", got.Backend) + } + if got.BackendSource != "credential_store" { + t.Fatalf("backend_source = %q, want credential_store", got.BackendSource) + } + if got.CredentialRef != "codereview/default" { + t.Fatalf("credential_ref = %q, want codereview/default", got.CredentialRef) + } + for key, want := range manifest.KeychainProbe.Assertions { + gotValue, ok := configShowAssertionValue(got, key) + if !ok { + t.Fatalf("unsupported manifest assertion key %q", key) + } + if gotValue != want { + t.Fatalf("assertion %s = %q, want %q", key, gotValue, want) + } + } +} + func TestConfigPathText(t *testing.T) { path := saveTestConfig(t, testConfig()) cmd, out := newTestCommand(path) @@ -2833,6 +2891,43 @@ func writeRawConfig(t *testing.T, path, body string) { } } +type keychainProbeManifest struct { + KeychainProbe struct { + SeedConfig struct { + Content string `yaml:"content"` + } `yaml:"seed_config"` + Command []string `yaml:"command"` + Assertions map[string]string `yaml:"assertions"` + } `yaml:"keychain_probe"` +} + +func readKeychainProbeManifest(t *testing.T) keychainProbeManifest { + t.Helper() + path := filepath.Join("..", "..", "..", "packaging", "identity.yml") + body, err := os.ReadFile(path) + if err != nil { + t.Fatalf("ReadFile(%s): %v", path, err) + } + var manifest keychainProbeManifest + if err := yaml.Unmarshal(body, &manifest); err != nil { + t.Fatalf("yaml.Unmarshal(%s): %v", path, err) + } + return manifest +} + +func configShowAssertionValue(show view.ConfigShow, key string) (string, bool) { + switch key { + case ".backend": + return show.Backend, true + case ".backend_source": + return show.BackendSource, true + case ".credential_ref": + return show.CredentialRef, true + default: + return "", false + } +} + func writeConfigTestAgentSource(t *testing.T, root, prompt string) { t.Helper() category := filepath.Join(root, "harness") diff --git a/packaging/identity.yml b/packaging/identity.yml index 909e687..9e9873f 100644 --- a/packaging/identity.yml +++ b/packaging/identity.yml @@ -27,21 +27,30 @@ keychain_probe: base: native_user_config path: "codereview/config.yml" content: | - profiles: - default: + repository_access: + default-git: git: host: github.com auth_mode: pat - credential_ref: codereview/default - llm: - provider: anthropic - auth: subscription - adapter: claude_cli - command: ["config", "show", "--json"] + credential: + store: local-os + name: codereview/default + llm_runtimes: + claude-cli: + provider: anthropic + auth: subscription + adapter: claude_cli + profiles: + default: + repository_access: default-git + reviewer: + kind: git_identity + llm_runtime: claude-cli + command: ["config", "show", "--profile", "default", "--json"] output: json assertions: .backend: keychain - .backend_source: auto + .backend_source: credential_store .credential_ref: codereview/default version_file: version.txt From 8c6843678bca2f98c177deae950225bbf78a3e69 Mon Sep 17 00:00:00 2001 From: Rian Stockbower Date: Wed, 24 Jun 2026 19:36:46 -0400 Subject: [PATCH 2/2] test(config): keep keychain probe contract cross-platform --- internal/cmd/configcmd/configcmd_test.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/internal/cmd/configcmd/configcmd_test.go b/internal/cmd/configcmd/configcmd_test.go index 7aa9a41..a41bfd1 100644 --- a/internal/cmd/configcmd/configcmd_test.go +++ b/internal/cmd/configcmd/configcmd_test.go @@ -8,6 +8,7 @@ import ( "os" "path/filepath" "reflect" + "runtime" "strings" "testing" @@ -154,10 +155,9 @@ func TestKeychainProbeManifestMatchesConfigShowContract(t *testing.T) { t.Fatalf("Unmarshal keychain probe JSON: %v\n%s", err, out.String()) } if got.Backend != "keychain" { - t.Fatalf("backend = %q, want keychain", got.Backend) - } - if got.BackendSource != "credential_store" { - t.Fatalf("backend_source = %q, want credential_store", got.BackendSource) + if runtime.GOOS == "darwin" { + t.Fatalf("backend = %q, want keychain", got.Backend) + } } if got.CredentialRef != "codereview/default" { t.Fatalf("credential_ref = %q, want codereview/default", got.CredentialRef) @@ -167,6 +167,9 @@ func TestKeychainProbeManifestMatchesConfigShowContract(t *testing.T) { if !ok { t.Fatalf("unsupported manifest assertion key %q", key) } + if runtime.GOOS != "darwin" && (key == ".backend" || key == ".backend_source") { + continue + } if gotValue != want { t.Fatalf("assertion %s = %q, want %q", key, gotValue, want) } @@ -2904,6 +2907,7 @@ type keychainProbeManifest struct { func readKeychainProbeManifest(t *testing.T) keychainProbeManifest { t.Helper() path := filepath.Join("..", "..", "..", "packaging", "identity.yml") + // #nosec G304 -- test reads a fixed repo-local manifest path. body, err := os.ReadFile(path) if err != nil { t.Fatalf("ReadFile(%s): %v", path, err)