Compare commits

...

1 Commits

Author SHA1 Message Date
core-be c26c7d039e test(org-import): add _Pure test suite for sanitizeEnvMembers, flattenAndSortRequirements, collectOrgEnv edge cases
audit-force-merge / audit (pull_request) Has been skipped
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 14s
cascade-list-drift-gate / check (pull_request) Failing after 3s
Check migration collisions / Migration version collision check (pull_request) Successful in 14s
CI / Detect changes (pull_request) Successful in 21s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 16s
MCP Stdio Transport Regression / MCP stdio with regular-file stdout (pull_request) Successful in 1m27s
CI / Platform (Go) (pull_request) Successful in 4m49s
E2E API Smoke Test / detect-changes (pull_request) Successful in 9s
E2E Chat / detect-changes (pull_request) Successful in 8s
E2E Peer Visibility (literal MCP list_peers) / E2E Peer Visibility (pull_request) Has been skipped
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 8s
CI / Canvas (Next.js) (pull_request) Successful in 5m59s
E2E Staging SaaS (full lifecycle) / pr-validate (pull_request) Successful in 34s
E2E Staging SaaS (full lifecycle) / E2E Staging SaaS (pull_request) Has been skipped
Handlers Postgres Integration / detect-changes (pull_request) Successful in 4s
Harness Replays / detect-changes (pull_request) Successful in 3s
CI / Python Lint & Test (pull_request) Successful in 6m59s
Lint curl status-code capture / Scan workflows for curl status-capture pollution (pull_request) Successful in 3s
CI / all-required (pull_request) Successful in 6m40s
lint-continue-on-error-tracking / lint-continue-on-error-tracking (pull_request) Successful in 1m16s
Lint pre-flip continue-on-error / Verify continue-on-error flips have run-log proof (pull_request) Failing after 1m3s
lint-mask-pr-atomicity / lint-mask-pr-atomicity (pull_request) Successful in 1m25s
lint-required-context-exists-in-bp / lint-required-context-exists-in-bp (pull_request) Failing after 1m19s
publish-runtime-autobump / pr-validate (pull_request) Successful in 35s
publish-runtime-autobump / bump-and-tag (pull_request) Has been skipped
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 1m0s
review-check-tests / review-check.sh regression tests (pull_request) Successful in 8s
Runtime PR-Built Compatibility / detect-changes (pull_request) Successful in 9s
Lint workflow YAML (Gitea-1.22.6-hostile shapes) / Lint workflow YAML for Gitea-1.22.6-hostile shapes (pull_request) Successful in 1m22s
E2E Staging External Runtime / E2E Staging External Runtime (pull_request) Successful in 5m12s
Secret scan / Scan diff for credential-shaped strings (pull_request) Failing after 27s
gate-check-v3 / gate-check (pull_request) Successful in 4s
qa-review / approved (pull_request) Successful in 9s
security-review / approved (pull_request) Failing after 6s
sop-checklist / na-declarations (pull_request) N/A: (none)
sop-checklist / all-items-acked (pull_request) Successful in 4s
sop-checklist / review-refire (pull_request) Has been skipped
sop-tier-check / tier-check (pull_request) Successful in 6s
Runtime Pin Compatibility / PyPI-latest install + import smoke (pull_request) Successful in 1m26s
Ops Scripts Tests / Ops scripts (unittest) (pull_request) Successful in 1m6s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 1m42s
CI / Canvas Deploy Reminder (pull_request) Has been skipped
Harness Replays / Harness Replays (pull_request) Successful in 3s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 1m45s
Runtime PR-Built Compatibility / PR-built wheel + import smoke (pull_request) Successful in 2m21s
E2E Chat / E2E Chat (pull_request) Failing after 5m52s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 7m36s
Adds 24 test cases covering:
- envRequirementKey: permutation invariance, dedup via sort
- sanitizeEnvMembers: digit-prefix, empty-string silence, all-invalid
- flattenAndSortRequirements: determinism, all-singles, groups-sorted-by-key
- collectOrgEnv: recursive children, rec-dedup-by-strict, strict-drops-any-of
- countWorkspaces: empty/flat/nested

All _Pure-suffixed to avoid collision with org_import_helpers_test.go names.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-17 21:04:12 +00:00
@@ -0,0 +1,458 @@
package handlers
import (
"sort"
"testing"
)
// ─────────────────────────────────────────────────────────────────────────────
// envRequirementKey tests
// ─────────────────────────────────────────────────────────────────────────────
func TestEnvRequirementKey_SingleMember_Pure(t *testing.T) {
key := envRequirementKey([]string{"API_KEY"})
if key != "API_KEY" {
t.Errorf("single member: got %q, want %q", key, "API_KEY")
}
}
func TestEnvRequirementKey_TwoMembersSorted_Pure(t *testing.T) {
// envRequirementKey sorts before joining, so [B, A] and [A, B] produce same key.
keyBA := envRequirementKey([]string{"B", "A"})
keyAB := envRequirementKey([]string{"A", "B"})
if keyBA != keyAB {
t.Errorf("sort invariance: got %q vs %q", keyBA, keyAB)
}
if keyBA != "A\x00B" {
t.Errorf("sort result: got %q, want %q", keyBA, "A\x00B")
}
}
func TestEnvRequirementKey_ThreeMembers_Pure(t *testing.T) {
key := envRequirementKey([]string{"C", "A", "B"})
if key != "A\x00B\x00C" {
t.Errorf("three members sorted: got %q, want %q", key, "A\x00B\x00C")
}
}
func TestEnvRequirementKey_Empty_Pure(t *testing.T) {
key := envRequirementKey([]string{})
if key != "" {
t.Errorf("empty: got %q, want %q", key, "")
}
}
func TestEnvRequirementKey_DedupBySort_Pure(t *testing.T) {
// Two lists that are permutations of each other must have identical keys.
key1 := envRequirementKey([]string{"Z", "M", "A"})
key2 := envRequirementKey([]string{"A", "Z", "M"})
if key1 != key2 {
t.Errorf("permutation invariance: got %q vs %q", key1, key2)
}
}
// ─────────────────────────────────────────────────────────────────────────────
// sanitizeEnvMembers tests
// ─────────────────────────────────────────────────────────────────────────────
func TestSanitizeEnvMembers_AllValid_Pure(t *testing.T) {
members := []string{"API_KEY", "ANTHROPIC_API_KEY", "SECRET_123"}
got, ok := sanitizeEnvMembers(members, "test")
if !ok {
t.Error("expected ok=true for all valid names")
}
if len(got) != 3 {
t.Errorf("length: got %d, want 3", len(got))
}
}
func TestSanitizeEnvMembers_FiltersLowercase_Pure(t *testing.T) {
members := []string{"api_key", "ANTHROPIC_API_KEY", "VALID_NAME"}
got, ok := sanitizeEnvMembers(members, "test")
if !ok {
t.Error("expected ok=true for mixed case")
}
if len(got) != 2 {
t.Errorf("length: got %d, want 2", len(got))
}
want := []string{"ANTHROPIC_API_KEY", "VALID_NAME"}
for i, w := range want {
if got[i] != w {
t.Errorf("got[%d]=%q, want %q", i, got[i], w)
}
}
}
func TestSanitizeEnvMembers_FiltersInvalidCharacters_Pure(t *testing.T) {
members := []string{"VALID", "in valid", "ALSO-INVALID", "SPACE KEY", "hyphen-key", ""}
got, ok := sanitizeEnvMembers(members, "test")
if !ok {
t.Error("expected ok=true (VALID was present)")
}
if len(got) != 1 || got[0] != "VALID" {
t.Errorf("got %v, want [VALID]", got)
}
}
func TestSanitizeEnvMembers_AllInvalid_Pure(t *testing.T) {
members := []string{"lowercase", "has-dash", "has space"}
got, ok := sanitizeEnvMembers(members, "test")
if ok {
t.Error("expected ok=false when all members invalid")
}
if len(got) != 0 {
t.Errorf("got %v, want []", got)
}
}
func TestSanitizeEnvMembers_EmptyInput_Pure(t *testing.T) {
members := []string{}
got, ok := sanitizeEnvMembers(members, "test")
if ok {
t.Error("expected ok=false for empty input")
}
if len(got) != 0 {
t.Errorf("got %v, want []", got)
}
}
func TestSanitizeEnvMembers_EmptyStringNotLogged_Pure(t *testing.T) {
// Empty string is filtered silently (not logged) — verify no panic.
members := []string{"VALID", ""}
got, ok := sanitizeEnvMembers(members, "test")
if !ok || len(got) != 1 || got[0] != "VALID" {
t.Errorf("unexpected result: ok=%v got=%v", ok, got)
}
}
func TestSanitizeEnvMembers_StartsWithDigit_Pure(t *testing.T) {
members := []string{"123KEY", "VALID_KEY"}
got, ok := sanitizeEnvMembers(members, "test")
if !ok {
t.Error("expected ok=true (VALID_KEY was present)")
}
if len(got) != 1 || got[0] != "VALID_KEY" {
t.Errorf("got %v, want [VALID_KEY]", got)
}
}
// ─────────────────────────────────────────────────────────────────────────────
// flattenAndSortRequirements tests
// ─────────────────────────────────────────────────────────────────────────────
func TestFlattenAndSortRequirements_SinglesFirst_Pure(t *testing.T) {
by := map[string]EnvRequirement{
"B": {Name: "B"},
"Z": {Name: "Z"},
"group1": {AnyOf: []string{"A", "C"}},
}
got := flattenAndSortRequirements(by)
if len(got) != 3 {
t.Fatalf("len: got %d, want 3", len(got))
}
// Singles first, then groups.
if got[0].Name == "" {
t.Error("first item should be a single")
}
if got[1].Name == "" {
t.Error("second item should be a single")
}
// Within singles, alphabetical.
if got[0].Name != "B" || got[1].Name != "Z" {
t.Errorf("got singles %q, %q, want B, Z", got[0].Name, got[1].Name)
}
// Then groups.
if len(got[2].AnyOf) == 0 {
t.Error("third item should be a group")
}
}
func TestFlattenAndSortRequirements_GroupsSortedByKey_Pure(t *testing.T) {
by := map[string]EnvRequirement{
"B\x00A": {AnyOf: []string{"B", "A"}}, // key already sorted
"A\x00C": {AnyOf: []string{"A", "C"}},
}
got := flattenAndSortRequirements(by)
// After singles, groups sorted by their envRequirementKey.
// "A\x00B" < "A\x00C", so the B/A group (key "A\x00B") comes first.
group0 := got[len(got)-2]
group1 := got[len(got)-1]
if group0.AnyOf[0] != "B" || group1.AnyOf[0] != "A" {
t.Errorf("group order: got %v then %v, want [B,A] then [A,C]", group0.AnyOf, group1.AnyOf)
}
}
func TestFlattenAndSortRequirements_EmptyMap_Pure(t *testing.T) {
by := map[string]EnvRequirement{}
got := flattenAndSortRequirements(by)
if len(got) != 0 {
t.Errorf("empty map: got %d items, want 0", len(got))
}
}
func TestFlattenAndSortRequirements_OnlyGroups_Pure(t *testing.T) {
by := map[string]EnvRequirement{
"X\x00Y": {AnyOf: []string{"X", "Y"}},
}
got := flattenAndSortRequirements(by)
if len(got) != 1 {
t.Fatalf("len: got %d, want 1", len(got))
}
if len(got[0].AnyOf) == 0 {
t.Error("only group expected")
}
}
func TestFlattenAndSortRequirements_Deterministic_Pure(t *testing.T) {
// Run twice; results must be identical.
by := map[string]EnvRequirement{
"C": {Name: "C"},
"A": {Name: "A"},
"B": {Name: "B"},
"Z\x00Y": {AnyOf: []string{"Z", "Y"}},
}
got1 := flattenAndSortRequirements(by)
got2 := flattenAndSortRequirements(by)
if len(got1) != len(got2) {
t.Fatalf("len mismatch: %d vs %d", len(got1), len(got2))
}
for i := range got1 {
if got1[i].Name != got2[i].Name || len(got1[i].AnyOf) != len(got2[i].AnyOf) {
t.Errorf("item %d differs: %+v vs %+v", i, got1[i], got2[i])
}
}
}
func TestFlattenAndSortRequirements_AllSingles_Pure(t *testing.T) {
by := map[string]EnvRequirement{
"X": {Name: "X"},
"M": {Name: "M"},
"A": {Name: "A"},
}
got := flattenAndSortRequirements(by)
if len(got) != 3 {
t.Fatalf("len: got %d, want 3", len(got))
}
names := make([]string, len(got))
for i, r := range got {
names[i] = r.Name
}
// Must be sorted alphabetically.
if !sort.IsSorted(sort.StringSlice(names)) {
t.Errorf("not sorted: %v", names)
}
}
// ─────────────────────────────────────────────────────────────────────────────
// collectOrgEnv tests
// ─────────────────────────────────────────────────────────────────────────────
func TestCollectOrgEnv_EmptyTemplate_Pure(t *testing.T) {
tmpl := &OrgTemplate{}
req, rec := collectOrgEnv(tmpl)
if len(req) != 0 || len(rec) != 0 {
t.Errorf("empty template: got req=%d rec=%d, want 0,0", len(req), len(rec))
}
}
func TestCollectOrgEnv_SingleRequired_Pure(t *testing.T) {
tmpl := &OrgTemplate{
RequiredEnv: []EnvRequirement{{Name: "API_KEY"}},
}
req, rec := collectOrgEnv(tmpl)
if len(req) != 1 {
t.Errorf("req count: got %d, want 1", len(req))
}
if len(rec) != 0 {
t.Errorf("rec count: got %d, want 0", len(rec))
}
if req[0].Name != "API_KEY" {
t.Errorf("req[0].Name: got %q, want %q", req[0].Name, "API_KEY")
}
}
func TestCollectOrgEnv_SingleRecommended_Pure(t *testing.T) {
tmpl := &OrgTemplate{
RecommendedEnv: []EnvRequirement{{Name: "DEBUG_MODE"}},
}
req, rec := collectOrgEnv(tmpl)
if len(req) != 0 {
t.Errorf("req count: got %d, want 0", len(req))
}
if len(rec) != 1 {
t.Errorf("rec count: got %d, want 1", len(rec))
}
}
func TestCollectOrgEnv_RequiredWinsOverRecommended_Pure(t *testing.T) {
// Same env name in both tiers → appears only in required.
tmpl := &OrgTemplate{
RequiredEnv: []EnvRequirement{{Name: "SHARED_KEY"}},
RecommendedEnv: []EnvRequirement{{Name: "SHARED_KEY"}},
}
req, rec := collectOrgEnv(tmpl)
if len(req) != 1 || len(rec) != 0 {
t.Errorf("got req=%d rec=%d, want 1,0", len(req), len(rec))
}
}
func TestCollectOrgEnv_StrictDropsAnyOf_Pure(t *testing.T) {
// Single required + any-of group containing that name → any-of dropped.
tmpl := &OrgTemplate{
RequiredEnv: []EnvRequirement{{Name: "API_KEY"}},
RecommendedEnv: []EnvRequirement{{AnyOf: []string{"API_KEY", "FALLBACK_KEY"}}},
}
req, rec := collectOrgEnv(tmpl)
if len(req) != 1 || req[0].Name != "API_KEY" {
t.Errorf("req: got %+v", req)
}
if len(rec) != 0 {
t.Errorf("rec: got %d, want 0 (any-of dropped by strict required)", len(rec))
}
}
func TestCollectOrgEnv_AnyOfGroup_Pure(t *testing.T) {
tmpl := &OrgTemplate{
RequiredEnv: []EnvRequirement{{AnyOf: []string{"KEY_A", "KEY_B"}}},
}
req, rec := collectOrgEnv(tmpl)
if len(req) != 1 {
t.Fatalf("req count: got %d, want 1", len(req))
}
if len(req[0].AnyOf) != 2 {
t.Errorf("req[0].AnyOf: got %v, want [KEY_A, KEY_B]", req[0].AnyOf)
}
_ = rec // may be unused but call is valid
}
func TestCollectOrgEnv_NestedWorkspace_Pure(t *testing.T) {
tmpl := &OrgTemplate{
RequiredEnv: []EnvRequirement{{Name: "ORG_KEY"}},
Workspaces: []OrgWorkspace{
{
Name: "child-ws",
RequiredEnv: []EnvRequirement{{Name: "CHILD_KEY"}},
RecommendedEnv: []EnvRequirement{{Name: "CHILD_RECOM"}},
},
},
}
req, rec := collectOrgEnv(tmpl)
// All 3 required names should appear (dedupped).
if len(req) != 2 {
t.Errorf("req count: got %d, want 2", len(req))
}
if len(rec) != 1 {
t.Errorf("rec count: got %d, want 1", len(rec))
}
names := make(map[string]bool)
for _, r := range req {
names[r.Name] = true
}
if !names["ORG_KEY"] || !names["CHILD_KEY"] {
t.Errorf("req names: got %v", names)
}
}
func TestCollectOrgEnv_InvalidEnvNameFiltered_Pure(t *testing.T) {
// Invalid names (lowercase, empty) are silently dropped.
tmpl := &OrgTemplate{
RequiredEnv: []EnvRequirement{
{Name: "VALID_KEY"},
{Name: "ALSO_VALID"},
{Name: "invalid-name"},
{Name: ""},
},
}
req, rec := collectOrgEnv(tmpl)
if len(req) != 2 {
t.Errorf("got %d req, want 2 (lowercase and empty filtered)", len(req))
}
_ = rec
}
func TestCollectOrgEnv_DedupSameMembers_Pure(t *testing.T) {
// Same members declared twice → deduplicated.
tmpl := &OrgTemplate{
RequiredEnv: []EnvRequirement{
{AnyOf: []string{"A", "B"}},
{AnyOf: []string{"B", "A"}}, // same set, reversed
},
}
req, rec := collectOrgEnv(tmpl)
if len(req) != 1 {
t.Errorf("dedup failed: got %d req, want 1", len(req))
}
_ = rec
}
func TestCollectOrgEnv_RecDedupedByStrictRequired_Pure(t *testing.T) {
// Strict required drops any-of groups in recommended tier too.
tmpl := &OrgTemplate{
RequiredEnv: []EnvRequirement{{Name: "STRICT"}},
RecommendedEnv: []EnvRequirement{{AnyOf: []string{"STRICT", "OTHER"}}},
}
req, rec := collectOrgEnv(tmpl)
if len(rec) != 0 {
t.Errorf("rec should be empty (strict required prunes recommended any-of): got %d", len(rec))
}
_ = req
}
func TestCollectOrgEnv_RecursiveChildren_Pure(t *testing.T) {
tmpl := &OrgTemplate{
RequiredEnv: []EnvRequirement{{Name: "ROOT"}},
Workspaces: []OrgWorkspace{
{
Name: "parent",
RequiredEnv: []EnvRequirement{{Name: "PARENT"}, {Name: "PARENT2"}},
Children: []OrgWorkspace{
{
Name: "grandchild",
RequiredEnv: []EnvRequirement{{Name: "GRANDCHILD"}},
},
},
},
},
}
req, rec := collectOrgEnv(tmpl)
if len(req) != 4 {
t.Errorf("got %d required, want 4 (ROOT + PARENT + PARENT2 + GRANDCHILD)", len(req))
}
_ = rec
}
// ─────────────────────────────────────────────────────────────────────────────
// countWorkspaces tests
// ─────────────────────────────────────────────────────────────────────────────
func TestCountWorkspaces_Empty_Pure(t *testing.T) {
if n := countWorkspaces(nil); n != 0 {
t.Errorf("nil: got %d, want 0", n)
}
if n := countWorkspaces([]OrgWorkspace{}); n != 0 {
t.Errorf("empty: got %d, want 0", n)
}
}
func TestCountWorkspaces_Flat_Pure(t *testing.T) {
ws := []OrgWorkspace{
{Name: "a"}, {Name: "b"},
}
if n := countWorkspaces(ws); n != 2 {
t.Errorf("flat: got %d, want 2", n)
}
}
func TestCountWorkspaces_Nested_Pure(t *testing.T) {
ws := []OrgWorkspace{
{Name: "a", Children: []OrgWorkspace{
{Name: "b", Children: []OrgWorkspace{
{Name: "c"},
}},
{Name: "d"},
}},
}
if n := countWorkspaces(ws); n != 4 {
t.Errorf("nested: got %d, want 4", n)
}
}