Files
kandev/apps/backend/internal/agent/runtime/environment/environment_test.go
T

671 lines
25 KiB
Go

package environment
import (
"context"
"errors"
"reflect"
"sort"
"strings"
"testing"
)
func noReveal(context.Context, Definition) (string, error) {
return "", errors.New("reveal should not be called")
}
func mustResolve(t *testing.T, defs []Definition) (map[string]string, []OverrideRecord) {
t.Helper()
env, records, err := Resolve(context.Background(), defs, noReveal)
if err != nil {
t.Fatalf("Resolve() error = %v, want nil", err)
}
return env, records
}
func conflictOf(t *testing.T, defs []Definition) *ConflictError {
t.Helper()
env, records, err := Resolve(context.Background(), defs, noReveal)
if err == nil {
t.Fatalf("Resolve() succeeded, want conflict; env=%#v", env)
}
if env != nil || records != nil {
t.Fatalf("Resolve() on error: env=%#v records=%#v, want both nil", env, records)
}
var conflictErr *ConflictError
if !errors.As(err, &conflictErr) {
t.Fatalf("error = %T %v, want *ConflictError", err, err)
}
return conflictErr
}
// --- Precedence: AC-1 through AC-8e ---
func TestResolve_ExecutorProfileBeatsAgentProfile(t *testing.T) { // AC-1
env, records := mustResolve(t, []Definition{
{Key: "K", Literal: "executor-value", Origin: OriginExecutorProfile},
{Key: "K", Literal: "agent-value", Origin: OriginAgentProfile},
})
if env["K"] != "executor-value" {
t.Fatalf("K = %q, want executor-value", env["K"])
}
if len(records) != 1 || records[0].WinningTier != TierAuthoritative {
t.Fatalf("records = %#v", records)
}
}
func TestResolve_ComposesIndexedGitConfigAcrossSources(t *testing.T) {
env, records := mustResolve(t, []Definition{
{Key: "GIT_CONFIG_COUNT", Literal: "1", Origin: OriginAgentProfile},
{Key: "GIT_CONFIG_KEY_0", Literal: "core.autocrlf", Origin: OriginAgentProfile},
{Key: "GIT_CONFIG_VALUE_0", Literal: "input", Origin: OriginAgentProfile},
{Key: "GIT_CONFIG_COUNT", Literal: "2", Origin: OriginExecutorProfile},
{Key: "GIT_CONFIG_KEY_0", Literal: "notes.augment.mergeStrategy", Origin: OriginExecutorProfile},
{Key: "GIT_CONFIG_VALUE_0", Literal: "union", Origin: OriginExecutorProfile},
{Key: "GIT_CONFIG_KEY_1", Literal: "core.hooksPath", Origin: OriginExecutorProfile},
{Key: "GIT_CONFIG_VALUE_1", Literal: "/profile/hooks", Origin: OriginExecutorProfile},
{Key: "GIT_CONFIG_COUNT", Literal: "1", Origin: OriginManagedRuntime},
{Key: "GIT_CONFIG_KEY_0", Literal: "credential.https://github.com.helper", Origin: OriginManagedRuntime},
{Key: "GIT_CONFIG_VALUE_0", Literal: "!f() { : kandev-host-gh-bridge; gh auth git-credential \"$@\"; }; f", Origin: OriginManagedRuntime},
})
if len(records) != 0 {
t.Fatalf("records = %#v, want no ordinary-key override records", records)
}
want := map[string]string{
"GIT_CONFIG_COUNT": "4",
"GIT_CONFIG_KEY_0": "core.autocrlf",
"GIT_CONFIG_VALUE_0": "input",
"GIT_CONFIG_KEY_1": "notes.augment.mergeStrategy",
"GIT_CONFIG_VALUE_1": "union",
"GIT_CONFIG_KEY_2": "core.hooksPath",
"GIT_CONFIG_VALUE_2": "/profile/hooks",
"GIT_CONFIG_KEY_3": "credential.https://github.com.helper",
}
for key, wantValue := range want {
if got := env[key]; got != wantValue {
t.Errorf("%s = %q, want %q", key, got, wantValue)
}
}
}
func TestResolve_IndexedSourceStillUsesOrdinaryConflictRules(t *testing.T) {
err := conflictOf(t, []Definition{
{Key: "PATH", Literal: "/managed", Origin: OriginManagedRuntime},
{Key: "PATH", Literal: "/profile", Origin: OriginExecutorProfile},
})
want := []string{OriginExecutorProfile, OriginManagedRuntime}
if !reflect.DeepEqual(err.Origins, want) {
t.Fatalf("origins = %#v, want %#v", err.Origins, want)
}
}
func TestResolve_ManagedRuntimeBeatsAgentProfile(t *testing.T) { // AC-2
env, _ := mustResolve(t, []Definition{
{Key: "K", Literal: "managed-value", Origin: OriginManagedRuntime},
{Key: "K", Literal: "agent-value", Origin: OriginAgentProfile},
})
if env["K"] != "managed-value" {
t.Fatalf("K = %q, want managed-value", env["K"])
}
}
func TestResolve_ManagedCredentialsBeatsAgentProfile(t *testing.T) { // AC-3
env, _ := mustResolve(t, []Definition{
{Key: "K", Literal: "cred-value", Origin: OriginManagedCredentials},
{Key: "K", Literal: "agent-value", Origin: OriginAgentProfile},
})
if env["K"] != "cred-value" {
t.Fatalf("K = %q, want cred-value", env["K"])
}
}
func TestResolve_AgentProfileBeatsManagedAgentDefaults(t *testing.T) { // AC-4
env, records := mustResolve(t, []Definition{
{Key: "K", Literal: "profile-value", Origin: OriginAgentProfile},
{Key: "K", Literal: "default-value", Origin: OriginManagedAgentDefaults},
})
if env["K"] != "profile-value" {
t.Fatalf("K = %q, want profile-value", env["K"])
}
if len(records) != 1 || records[0].WinningTier != TierProfileDefault {
t.Fatalf("records = %#v", records)
}
if len(records[0].LosingOrigins) != 1 || records[0].LosingOrigins[0] != (OriginTier{Origin: OriginManagedAgentDefaults, Tier: TierAgentRuntimeDefault}) {
t.Fatalf("losing origins = %#v", records[0].LosingOrigins)
}
}
func TestResolve_TwoDifferingTier1OriginsConflict(t *testing.T) { // AC-5
err := conflictOf(t, []Definition{
{Key: "K", Literal: "a", Origin: OriginExecutorProfile},
{Key: "K", Literal: "b", Origin: OriginManagedRuntime},
})
want := []string{OriginExecutorProfile, OriginManagedRuntime}
if !reflect.DeepEqual(err.Origins, want) {
t.Fatalf("origins = %#v, want %#v", err.Origins, want)
}
}
// TestResolve_MergedIdentityClassifiesByStrongestOrigin pins AC-5a: a merged
// executor-profile/agent-profile identity (same literal) classifies Tier 1
// via its strongest contributing origin and conflicts against a differing
// managed-runtime literal, rather than being silently ranked Tier 2 by the
// surviving Definition.Origin field (which would sort to "agent profile").
func TestResolve_MergedIdentityClassifiesByStrongestOrigin(t *testing.T) {
err := conflictOf(t, []Definition{
{Key: "AGENT_MODEL", Literal: "shared", Origin: OriginExecutorProfile},
{Key: "AGENT_MODEL", Literal: "shared", Origin: OriginAgentProfile},
{Key: "AGENT_MODEL", Literal: "different", Origin: OriginManagedRuntime},
})
want := []string{OriginAgentProfile, OriginExecutorProfile, OriginManagedRuntime}
if !reflect.DeepEqual(err.Origins, want) {
t.Fatalf("origins = %#v, want %#v", err.Origins, want)
}
}
// TestResolve_MergedIdentitySpansTiersAndWins pins AC-5b: the same merged
// pair, now facing only a differing Tier 3 literal, resolves to the merged
// value with WinningOrigins spanning both contributing tiers while
// WinningTier records only the strongest.
func TestResolve_MergedIdentitySpansTiersAndWins(t *testing.T) {
env, records := mustResolve(t, []Definition{
{Key: "K", Literal: "shared", Origin: OriginExecutorProfile},
{Key: "K", Literal: "shared", Origin: OriginAgentProfile},
{Key: "K", Literal: "default", Origin: OriginManagedAgentDefaults},
})
if env["K"] != "shared" {
t.Fatalf("K = %q, want shared", env["K"])
}
if len(records) != 1 {
t.Fatalf("records = %#v", records)
}
want := []string{OriginAgentProfile, OriginExecutorProfile}
if !reflect.DeepEqual(records[0].WinningOrigins, want) {
t.Fatalf("winning origins = %#v, want %#v", records[0].WinningOrigins, want)
}
if records[0].WinningTier != TierAuthoritative {
t.Fatalf("winning tier = %v, want TierAuthoritative", records[0].WinningTier)
}
}
// TestResolve_SameOriginAmbiguityWithinWinningTierConflicts pins AC-6: two
// differing definitions from the SAME Tier-1 origin, with no stronger tier
// present, still conflict exactly as today.
func TestResolve_SameOriginAmbiguityWithinWinningTierConflicts(t *testing.T) {
err := conflictOf(t, []Definition{
{Key: "K", Literal: "a", Origin: OriginExecutorProfile},
{Key: "K", Literal: "b", Origin: OriginExecutorProfile},
})
want := []string{OriginExecutorProfile}
if !reflect.DeepEqual(err.Origins, want) {
t.Fatalf("origins = %#v, want %#v", err.Origins, want)
}
}
func TestResolve_AgentProfileOnlyNoExecutorProfile(t *testing.T) { // AC-7
env, records := mustResolve(t, []Definition{
{Key: "K", Literal: "agent-value", Origin: OriginAgentProfile},
})
if env["K"] != "agent-value" || records != nil {
t.Fatalf("env=%#v records=%#v", env, records)
}
}
func TestResolve_MergedTier1PairBeatsDifferingTier2(t *testing.T) { // AC-8a
env, records := mustResolve(t, []Definition{
{Key: "K", Literal: "shared", Origin: OriginManagedRuntime},
{Key: "K", Literal: "shared", Origin: OriginManagedCredentials},
{Key: "K", Literal: "profile", Origin: OriginAgentProfile},
})
if env["K"] != "shared" {
t.Fatalf("K = %q, want shared", env["K"])
}
if len(records) != 1 {
t.Fatalf("records = %#v", records)
}
want := []string{OriginManagedCredentials, OriginManagedRuntime}
if !reflect.DeepEqual(records[0].WinningOrigins, want) {
t.Fatalf("winning origins = %#v, want %#v", records[0].WinningOrigins, want)
}
if records[0].WinningTier != TierAuthoritative {
t.Fatalf("winning tier = %v", records[0].WinningTier)
}
wantLosing := []OriginTier{{Origin: OriginAgentProfile, Tier: TierProfileDefault}}
if !reflect.DeepEqual(records[0].LosingOrigins, wantLosing) {
t.Fatalf("losing origins = %#v, want %#v", records[0].LosingOrigins, wantLosing)
}
}
func TestResolve_TwoDifferingTier1PlusTier2Conflicts(t *testing.T) { // AC-8b
err := conflictOf(t, []Definition{
{Key: "K", Literal: "a", Origin: OriginManagedRuntime},
{Key: "K", Literal: "b", Origin: OriginManagedCredentials},
{Key: "K", Literal: "c", Origin: OriginAgentProfile},
})
want := []string{OriginAgentProfile, OriginManagedCredentials, OriginManagedRuntime}
if !reflect.DeepEqual(err.Origins, want) {
t.Fatalf("origins = %#v, want %#v", err.Origins, want)
}
}
func TestResolve_Tier1BeatsBothTier2AndTier3(t *testing.T) { // AC-8c
_, records := mustResolve(t, []Definition{
{Key: "K", Literal: "a", Origin: OriginManagedRuntime},
{Key: "K", Literal: "b", Origin: OriginAgentProfile},
{Key: "K", Literal: "c", Origin: OriginManagedAgentDefaults},
})
if len(records) != 1 {
t.Fatalf("records = %#v", records)
}
want := []OriginTier{
{Origin: OriginAgentProfile, Tier: TierProfileDefault},
{Origin: OriginManagedAgentDefaults, Tier: TierAgentRuntimeDefault},
}
if !reflect.DeepEqual(records[0].LosingOrigins, want) {
t.Fatalf("losing origins = %#v, want %#v", records[0].LosingOrigins, want)
}
}
// TestResolve_LosingOriginsDedupedByOrigin pins AC-8d: one Tier-1 winner
// plus two agent-profile literals that disagree with each other still
// produces exactly ONE LosingOrigins entry and one origin string, never two.
func TestResolve_LosingOriginsDedupedByOrigin(t *testing.T) {
_, records := mustResolve(t, []Definition{
{Key: "K", Literal: "a", Origin: OriginManagedRuntime},
{Key: "K", Literal: "b", Origin: OriginAgentProfile},
{Key: "K", Literal: "c", Origin: OriginAgentProfile},
})
if len(records) != 1 {
t.Fatalf("records = %#v", records)
}
if len(records[0].LosingOrigins) != 1 {
t.Fatalf("losing origins = %#v, want exactly 1 entry", records[0].LosingOrigins)
}
want := OriginTier{Origin: OriginAgentProfile, Tier: TierProfileDefault}
if records[0].LosingOrigins[0] != want {
t.Fatalf("losing origin = %#v, want %#v", records[0].LosingOrigins[0], want)
}
}
// TestResolve_WinningOriginsDedupedByOrigin pins AC-8e: one Tier-1 value
// contributed by two definitions sharing both Origin and definitionIdentity
// lists that origin only once in WinningOrigins.
func TestResolve_WinningOriginsDedupedByOrigin(t *testing.T) {
_, records := mustResolve(t, []Definition{
{Key: "K", Literal: "shared", Origin: OriginManagedRuntime, WorkspaceID: ""},
{Key: "K", Literal: "shared", Origin: OriginManagedRuntime, WorkspaceID: ""},
{Key: "K", Literal: "other", Origin: OriginAgentProfile},
})
if len(records) != 1 {
t.Fatalf("records = %#v", records)
}
want := []string{OriginManagedRuntime}
if !reflect.DeepEqual(records[0].WinningOrigins, want) {
t.Fatalf("winning origins = %#v, want %#v", records[0].WinningOrigins, want)
}
}
// --- Secret veto: AC-9 through AC-13 ---
func TestResolve_SecretVetoBlocksAcrossTiers(t *testing.T) { // AC-9, AC-10
err := conflictOf(t, []Definition{
{Key: "K", SecretID: "secret-1", Origin: OriginExecutorProfile},
{Key: "K", Literal: "literal", Origin: OriginAgentProfile},
})
want := []string{OriginAgentProfile, OriginExecutorProfile}
if !reflect.DeepEqual(err.Origins, want) {
t.Fatalf("origins = %#v, want %#v", err.Origins, want)
}
}
func TestResolve_SecretVetoSymmetric(t *testing.T) { // AC-11
err := conflictOf(t, []Definition{
{Key: "K", Literal: "literal", Origin: OriginExecutorProfile},
{Key: "K", SecretID: "secret-1", Origin: OriginAgentProfile},
})
want := []string{OriginAgentProfile, OriginExecutorProfile}
if !reflect.DeepEqual(err.Origins, want) {
t.Fatalf("origins = %#v, want %#v", err.Origins, want)
}
}
func TestResolve_SecretVetoBlocksRepositoryAgainstOtherOrigin(t *testing.T) { // AC-12
err := conflictOf(t, []Definition{
{Key: "K", SecretID: "secret-1", Origin: "repository app", WorkspaceID: "ws-1"},
{Key: "K", Literal: "literal", Origin: OriginManagedRuntime},
})
want := []string{"repository app", OriginManagedRuntime}
if len(err.Origins) != 2 {
t.Fatalf("origins = %#v, want 2 entries", err.Origins)
}
got := append([]string(nil), err.Origins...)
sort.Strings(got)
sort.Strings(want)
if !reflect.DeepEqual(got, want) {
t.Fatalf("origins = %#v, want %#v", err.Origins, want)
}
}
// TestResolve_VetoEnumeratesEveryContributingOriginOfMergedSecretIdentity
// pins AC-12a: two secret-backed definitions sharing one SecretID merge into
// one identity, and the veto's error must still name BOTH their origins
// (not just the surviving Definition.Origin), alongside the differing
// literal's origin.
func TestResolve_VetoEnumeratesEveryContributingOriginOfMergedSecretIdentity(t *testing.T) {
err := conflictOf(t, []Definition{
{Key: "K", SecretID: "secret-1", Origin: "repository app", WorkspaceID: "ws-1"},
{Key: "K", SecretID: "secret-1", Origin: OriginExecutorProfile},
{Key: "K", Literal: "literal", Origin: OriginAgentProfile},
})
want := []string{OriginAgentProfile, OriginExecutorProfile, "repository app"}
if !reflect.DeepEqual(err.Origins, want) {
t.Fatalf("origins = %#v, want %#v", err.Origins, want)
}
}
func TestResolve_ConflictErrorLeaksNoSecretValue(t *testing.T) { // AC-13
_, _, err := Resolve(context.Background(), []Definition{
{Key: "K", SecretID: "super-secret-id", Origin: "repository app", WorkspaceID: "ws-1"},
{Key: "K", Literal: "super-secret-literal", Origin: OriginAgentProfile},
}, noReveal)
if err == nil {
t.Fatal("want conflict error")
}
msg := err.Error()
for _, leaked := range []string{"super-secret-id", "super-secret-literal"} {
if strings.Contains(msg, leaked) {
t.Fatalf("error %q leaks %q", msg, leaked)
}
}
}
// --- No regression for currently-succeeding tasks: AC-14 through AC-17 ---
func TestResolve_SingleDefinitionUnchanged(t *testing.T) { // AC-14
env, records := mustResolve(t, []Definition{{Key: "K", Literal: "v", Origin: OriginAgentProfile}})
if env["K"] != "v" || records != nil {
t.Fatalf("env=%#v records=%#v", env, records)
}
}
func TestResolve_IdenticalIdentitiesMergeWithoutOverride(t *testing.T) { // AC-15
env, records := mustResolve(t, []Definition{
{Key: "K", Literal: "v", Origin: OriginExecutorProfile},
{Key: "K", Literal: "v", Origin: OriginAgentProfile},
})
if env["K"] != "v" {
t.Fatalf("K = %q, want v", env["K"])
}
if records != nil {
t.Fatalf("records = %#v, want nil — nothing was overridden", records)
}
}
// --- Observability: AC-20 through AC-26 ---
func TestResolve_NoOverrideRecordWhenNoConflict(t *testing.T) { // AC-26
_, records, err := Resolve(context.Background(), []Definition{
{Key: "K", Literal: "v", Origin: OriginAgentProfile},
}, noReveal)
if err != nil {
t.Fatalf("Resolve() error = %v", err)
}
if records != nil {
t.Fatalf("records = %#v, want nil (not an empty non-nil slice)", records)
}
}
// TestResolve_ErrorReturnsNoRecordsEvenAfterEarlierOverride pins AC-20a: a
// set where key A resolves by tier precedence and key B conflicts must
// return nil records alongside the error, not a partial record for A.
func TestResolve_ErrorReturnsNoRecordsEvenAfterEarlierOverride(t *testing.T) {
env, records, err := Resolve(context.Background(), []Definition{
{Key: "A", Literal: "a1", Origin: OriginExecutorProfile},
{Key: "A", Literal: "a2", Origin: OriginAgentProfile},
{Key: "B", Literal: "b1", Origin: OriginManagedRuntime},
{Key: "B", Literal: "b2", Origin: OriginExecutorProfile},
}, noReveal)
if err == nil {
t.Fatal("want conflict on key B")
}
if env != nil || records != nil {
t.Fatalf("env=%#v records=%#v, want both nil", env, records)
}
}
// TestResolve_ErrorReturnsNoRecordsWhenSecretRevealFailsAfterOverride pins
// the second AC-20a branch: key A overrides, then a secret for key B fails
// to reveal — no records must leak through even though A already resolved.
func TestResolve_ErrorReturnsNoRecordsWhenSecretRevealFailsAfterOverride(t *testing.T) {
failingReveal := func(context.Context, Definition) (string, error) {
return "", errors.New("reveal failed")
}
env, records, err := Resolve(context.Background(), []Definition{
{Key: "A", Literal: "a1", Origin: OriginExecutorProfile},
{Key: "A", Literal: "a2", Origin: OriginAgentProfile},
{Key: "B", SecretID: "secret-1", Origin: "repository app", WorkspaceID: "ws-1"},
}, failingReveal)
if err == nil {
t.Fatal("want secret error")
}
var secretErr *SecretError
if !errors.As(err, &secretErr) {
t.Fatalf("error = %T %v, want *SecretError", err, err)
}
if env != nil || records != nil {
t.Fatalf("env=%#v records=%#v, want both nil", env, records)
}
}
func TestResolve_OverrideRecordCarriesNoSecretOrLiteral(t *testing.T) { // AC-21
_, records := mustResolve(t, []Definition{
{Key: "K", Literal: "secret-looking-value", Origin: OriginExecutorProfile},
{Key: "K", Literal: "other", Origin: OriginAgentProfile},
})
if len(records) != 1 {
t.Fatalf("records = %#v", records)
}
v := reflect.ValueOf(records[0])
for i := 0; i < v.NumField(); i++ {
field := v.Type().Field(i)
if field.Name == "WinningOrigins" || field.Name == "LosingOrigins" || field.Name == "WinningTier" || field.Name == "Key" {
continue
}
t.Fatalf("OverrideRecord has unexpected field %q — every field must be identity/rank only", field.Name)
}
}
// --- Determinism and ordering: AC-27 through AC-30 ---
func TestResolve_DeterministicAcrossRepeatedCalls(t *testing.T) { // AC-27
defs := []Definition{
{Key: "A", Literal: "a1", Origin: OriginExecutorProfile},
{Key: "A", Literal: "a2", Origin: OriginAgentProfile},
{Key: "B", Literal: "b1", Origin: OriginManagedRuntime},
{Key: "B", Literal: "b2", Origin: OriginManagedAgentDefaults},
}
env1, records1, err1 := Resolve(context.Background(), defs, noReveal)
env2, records2, err2 := Resolve(context.Background(), defs, noReveal)
if err1 != nil || err2 != nil {
t.Fatalf("errors: %v, %v", err1, err2)
}
if !reflect.DeepEqual(env1, env2) {
t.Fatalf("env1=%#v env2=%#v", env1, env2)
}
if !reflect.DeepEqual(records1, records2) {
t.Fatalf("records1=%#v records2=%#v", records1, records2)
}
}
func TestResolve_RecordsOrderedByKeyAscending(t *testing.T) { // AC-28
_, records := mustResolve(t, []Definition{
{Key: "ZKEY", Literal: "a", Origin: OriginExecutorProfile},
{Key: "ZKEY", Literal: "b", Origin: OriginAgentProfile},
{Key: "AKEY", Literal: "a", Origin: OriginExecutorProfile},
{Key: "AKEY", Literal: "b", Origin: OriginAgentProfile},
})
if len(records) != 2 || records[0].Key != "AKEY" || records[1].Key != "ZKEY" {
t.Fatalf("records = %#v, want AKEY then ZKEY", records)
}
}
func TestResolve_SameResultRegardlessOfInputOrder(t *testing.T) { // AC-29, AC-30
forward := []Definition{
{Key: "K", Literal: "shared", Origin: OriginManagedRuntime},
{Key: "K", Literal: "shared", Origin: OriginManagedCredentials},
{Key: "K", Literal: "profile", Origin: OriginAgentProfile},
}
reversed := make([]Definition, len(forward))
for i, d := range forward {
reversed[len(forward)-1-i] = d
}
envA, recA := mustResolve(t, forward)
envB, recB := mustResolve(t, reversed)
if !reflect.DeepEqual(envA, envB) {
t.Fatalf("envA=%#v envB=%#v", envA, envB)
}
if !reflect.DeepEqual(recA, recB) {
t.Fatalf("recA=%#v recB=%#v", recA, recB)
}
}
// TestResolve_WorkspaceIDTieBreakIsDeterministic pins AC-29b: two
// definitions sharing Key, Origin and SecretID but differing WorkspaceID
// merge, and the lexicographically smaller WorkspaceID's definition survives
// regardless of input order — observable via which reveal path is invoked.
func TestResolve_WorkspaceIDTieBreakIsDeterministic(t *testing.T) {
reveal := func(_ context.Context, d Definition) (string, error) {
return "workspace=" + d.WorkspaceID, nil
}
forward := []Definition{
{Key: "K", SecretID: "secret-1", Origin: "repository app", WorkspaceID: "ws-b"},
{Key: "K", SecretID: "secret-1", Origin: "repository app", WorkspaceID: "ws-a"},
}
reversed := []Definition{forward[1], forward[0]}
envA, _, errA := Resolve(context.Background(), forward, reveal)
envB, _, errB := Resolve(context.Background(), reversed, reveal)
if errA != nil || errB != nil {
t.Fatalf("errors: %v, %v", errA, errB)
}
if envA["K"] != "workspace=ws-a" || envB["K"] != "workspace=ws-a" {
t.Fatalf("envA=%#v envB=%#v, want both to pick ws-a", envA, envB)
}
}
// --- Nil, empty, and error behaviour: AC-31 through AC-37 ---
func TestResolve_EmptyDefinitionsReturnsEmptyMapNoError(t *testing.T) { // AC-31
env, records, err := Resolve(context.Background(), nil, noReveal)
if err != nil {
t.Fatalf("error = %v", err)
}
if env == nil || len(env) != 0 {
t.Fatalf("env = %#v, want empty non-nil map", env)
}
if records != nil {
t.Fatalf("records = %#v, want nil", records)
}
}
func TestResolve_BlankKeyDefinitionsSkipped(t *testing.T) { // AC-32
env, _ := mustResolve(t, []Definition{
{Key: "", Literal: "v", Origin: OriginAgentProfile},
{Key: " ", Literal: "v", Origin: OriginAgentProfile},
{Key: "K", Literal: "v", Origin: OriginAgentProfile},
})
if len(env) != 1 || env["K"] != "v" {
t.Fatalf("env = %#v, want only K", env)
}
}
func TestResolve_NilRevealOnSecretReturnsSecretError(t *testing.T) { // AC-33
_, _, err := Resolve(context.Background(), []Definition{
{Key: "K", SecretID: "secret-1", Origin: OriginExecutorProfile},
}, nil)
var secretErr *SecretError
if !errors.As(err, &secretErr) {
t.Fatalf("error = %T %v, want *SecretError", err, err)
}
}
func TestResolve_RevealErrorReturnsSecretError(t *testing.T) { // AC-34
_, _, err := Resolve(context.Background(), []Definition{
{Key: "K", SecretID: "secret-1", Origin: OriginExecutorProfile},
}, func(context.Context, Definition) (string, error) {
return "", errors.New("boom")
})
var secretErr *SecretError
if !errors.As(err, &secretErr) {
t.Fatalf("error = %T %v, want *SecretError", err, err)
}
}
func TestResolve_FirstConflictingKeyReported(t *testing.T) { // AC-36
err := conflictOf(t, []Definition{
{Key: "ZKEY", Literal: "a", Origin: OriginExecutorProfile},
{Key: "ZKEY", Literal: "b", Origin: OriginManagedRuntime},
{Key: "AKEY", Literal: "a", Origin: OriginExecutorProfile},
{Key: "AKEY", Literal: "b", Origin: OriginManagedRuntime},
})
if err.Key != "AKEY" {
t.Fatalf("key = %q, want AKEY", err.Key)
}
}
func TestResolve_OnlyWinnerIsRevealed(t *testing.T) { // AC-37
var revealed []string
reveal := func(_ context.Context, d Definition) (string, error) {
revealed = append(revealed, d.Origin)
return "value", nil
}
// Two literal definitions plus one distinguishing secret-backed loser is
// impossible under the veto, so this covers the ordinary case: a secret
// winner is revealed and no other definition for the key is passed to
// reveal.
_, _, err := Resolve(context.Background(), []Definition{
{Key: "K", SecretID: "secret-1", Origin: OriginExecutorProfile},
}, reveal)
if err != nil {
t.Fatalf("error = %v", err)
}
if len(revealed) != 1 || revealed[0] != OriginExecutorProfile {
t.Fatalf("revealed = %#v", revealed)
}
}
// --- Migration: AC-49 (resolver-level qualifiers) ---
func TestResolve_MigrationExecutorWinsWhenLiteralsDiffer(t *testing.T) {
env, records := mustResolve(t, []Definition{
{Key: "ANTHROPIC_BASE_URL", Literal: "https://executor.example", Origin: OriginExecutorProfile},
{Key: "ANTHROPIC_BASE_URL", Literal: "https://agent.example", Origin: OriginAgentProfile},
})
if env["ANTHROPIC_BASE_URL"] != "https://executor.example" {
t.Fatalf("env = %#v", env)
}
if len(records) != 1 {
t.Fatalf("records = %#v", records)
}
}
func TestResolve_MigrationSecretSideStillBlocks(t *testing.T) {
err := conflictOf(t, []Definition{
{Key: "ANTHROPIC_BASE_URL", SecretID: "secret-1", Origin: OriginExecutorProfile},
{Key: "ANTHROPIC_BASE_URL", Literal: "https://agent.example", Origin: OriginAgentProfile},
})
want := []string{OriginAgentProfile, OriginExecutorProfile}
if !reflect.DeepEqual(err.Origins, want) {
t.Fatalf("origins = %#v, want %#v", err.Origins, want)
}
}
func TestResolve_MigrationIdenticalValuesStillMergeNoOverride(t *testing.T) {
_, records := mustResolve(t, []Definition{
{Key: "ANTHROPIC_BASE_URL", Literal: "https://same.example", Origin: OriginExecutorProfile},
{Key: "ANTHROPIC_BASE_URL", Literal: "https://same.example", Origin: OriginAgentProfile},
})
if records != nil {
t.Fatalf("records = %#v, want nil", records)
}
}