package ns import ( "strings" "testing" ) func TestResolveEmptyConfigs(t *testing.T) { out, err := Resolve(map[string]*NSConfig{}) if err != nil { t.Fatalf("Resolve empty: unexpected error: %v", err) } if len(out) != 0 { t.Fatalf("Resolve empty: want empty map, got %d entries", len(out)) } } func TestResolveSingleNoParents(t *testing.T) { cfgs := map[string]*NSConfig{ defaultsName: {Name: defaultsName, Env: map[string]string{"A": "1"}}, "x": {Name: "x", Env: map[string]string{"B": "2"}}, } out, err := Resolve(cfgs) if err != nil { t.Fatalf("Resolve: %v", err) } r := out["x"] if r == nil { t.Fatal("missing resolved x") } if !eqSlice(r.Chain, []string{"x", defaultsName}) { t.Errorf("chain = %v, want [x _defaults]", r.Chain) } if r.Env["A"] != "1" || r.Env["B"] != "2" { t.Errorf("env = %v, want A=1 B=2", r.Env) } } func TestResolveChildOverridesParentScalar(t *testing.T) { cfgs := map[string]*NSConfig{ defaultsName: {Name: defaultsName, Env: map[string]string{"K": "parent"}}, "child": {Name: "child", Parents: []string{defaultsName}, Env: map[string]string{"K": "child"}}, } out, err := Resolve(cfgs) if err != nil { t.Fatalf("Resolve: %v", err) } if got := out["child"].Env["K"]; got != "child" { t.Errorf("child K = %q, want %q (child overrides parent)", got, "child") } } func TestResolveArraysUnion(t *testing.T) { cfgs := map[string]*NSConfig{ defaultsName: {Name: defaultsName, Constraints: []string{"a", "b"}}, "x": {Name: "x", Parents: []string{defaultsName}, Constraints: []string{"c", "a"}}, } out, err := Resolve(cfgs) if err != nil { t.Fatalf("Resolve: %v", err) } c := out["x"].Constraints // Union de-duped; most-specific (x) first. if !eqSlice(c, []string{"c", "a", "b"}) { t.Errorf("constraints = %v, want [c a b]", c) } } func TestResolveDefaultsImplicitLast(t *testing.T) { cfgs := map[string]*NSConfig{ defaultsName: {Name: defaultsName}, "mid": {Name: "mid", Parents: []string{defaultsName}}, "top": {Name: "top", Parents: []string{"mid"}}, } out, err := Resolve(cfgs) if err != nil { t.Fatalf("Resolve: %v", err) } if !eqSlice(out["top"].Chain, []string{"top", "mid", defaultsName}) { t.Errorf("top chain = %v, want [top mid _defaults]", out["top"].Chain) } if !eqSlice(out["mid"].Chain, []string{"mid", defaultsName}) { t.Errorf("mid chain = %v, want [mid _defaults]", out["mid"].Chain) } } func TestResolveDefaultsDedupExplicit(t *testing.T) { cfgs := map[string]*NSConfig{ defaultsName: {Name: defaultsName, Env: map[string]string{"D": "1"}}, "x": {Name: "x", Parents: []string{defaultsName}}, } out, err := Resolve(cfgs) if err != nil { t.Fatalf("Resolve: %v", err) } // _defaults appears exactly once. count := 0 for _, c := range out["x"].Chain { if c == defaultsName { count++ } } if count != 1 { t.Errorf("_defaults appears %d times in chain %v, want 1", count, out["x"].Chain) } } func TestResolveMisorderingRejected(t *testing.T) { cfgs := map[string]*NSConfig{ defaultsName: {Name: defaultsName}, "x": {Name: "x"}, "y": {Name: "y", Parents: []string{defaultsName, "x"}}, } _, err := Resolve(cfgs) if err == nil { t.Fatal("expected misordering error, got nil") } if !strings.Contains(err.Error(), "must be the only parent") { t.Errorf("error = %q, want misordering message", err.Error()) } } func TestResolveOptOutImpossible(t *testing.T) { cfgs := map[string]*NSConfig{ defaultsName: {Name: defaultsName, Env: map[string]string{"ROOT": "1"}}, "x": {Name: "x", Parents: nil}, } out, err := Resolve(cfgs) if err != nil { t.Fatalf("Resolve: %v", err) } r := out["x"] last := r.Chain[len(r.Chain)-1] if last != defaultsName { t.Errorf("last chain element = %q, want %q (opt-out impossible)", last, defaultsName) } if r.Env["ROOT"] != "1" { t.Errorf("env should inherit from _defaults: ROOT=%q", r.Env["ROOT"]) } } func TestResolveCycleDetection(t *testing.T) { cfgs := map[string]*NSConfig{ defaultsName: {Name: defaultsName}, "a": {Name: "a", Parents: []string{"b"}}, "b": {Name: "b", Parents: []string{"a"}}, } _, err := Resolve(cfgs) if err == nil { t.Fatal("expected cycle error, got nil") } if !strings.Contains(err.Error(), "cycle") { t.Errorf("error = %q, want cycle message", err.Error()) } } func TestResolveMissingParent(t *testing.T) { cfgs := map[string]*NSConfig{ defaultsName: {Name: defaultsName}, "a": {Name: "a", Parents: []string{"ghost"}}, } _, err := Resolve(cfgs) if err == nil { t.Fatal("expected missing-parent error, got nil") } if !strings.Contains(err.Error(), "ghost") || !strings.Contains(err.Error(), "not found") { t.Errorf("error = %q, want contains 'ghost' and 'not found'", err.Error()) } } func TestResolveMissingDefaults(t *testing.T) { cfgs := map[string]*NSConfig{ "x": {Name: "x"}, } _, err := Resolve(cfgs) if err == nil { t.Fatal("expected missing _defaults error, got nil") } if !strings.Contains(err.Error(), defaultsName) { t.Errorf("error = %q, want contains %q", err.Error(), defaultsName) } } func TestResolveChainOrderWithDiamond(t *testing.T) { // Diamond: top -> {left, right} -> base; base -> _defaults. cfgs := map[string]*NSConfig{ defaultsName: {Name: defaultsName, Env: map[string]string{"R": "r"}}, "base": {Name: "base", Parents: []string{defaultsName}, Env: map[string]string{"B": "b"}}, "left": {Name: "left", Parents: []string{"base"}, Env: map[string]string{"L": "l"}}, "right": {Name: "right", Parents: []string{"base"}, Env: map[string]string{"L": "r"}}, "top": {Name: "top", Parents: []string{"left", "right"}, Env: map[string]string{"T": "t"}}, } out, err := Resolve(cfgs) if err != nil { t.Fatalf("Resolve: %v", err) } r := out["top"] if r == nil { t.Fatal("missing top") } // top first, _defaults last. if r.Chain[0] != "top" || r.Chain[len(r.Chain)-1] != defaultsName { t.Errorf("chain = %v, want top first and _defaults last", r.Chain) } // base appears exactly once (diamond de-duped). count := 0 for _, c := range r.Chain { if c == "base" { count++ } } if count != 1 { t.Errorf("base appears %d times in %v, want 1", count, r.Chain) } // top inherits R from _defaults. if r.Env["R"] != "r" { t.Errorf("top should inherit R=r, got %q", r.Env["R"]) } } func eqSlice(a, b []string) bool { if len(a) != len(b) { return false } for i := range a { if a[i] != b[i] { return false } } return true }