fix(handlers/discovery): nil-guard role in filterPeersByQuery (mc#731)
Some checks failed
CI / Platform (Go) (pull_request) Failing after 7m14s
CI / all-required (pull_request) Failing after 4s
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 28s
CI / Detect changes (pull_request) Successful in 1m23s
Harness Replays / detect-changes (pull_request) Successful in 18s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 1m23s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 1m15s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 20s
gate-check-v3 / gate-check (pull_request) Successful in 23s
Runtime PR-Built Compatibility / detect-changes (pull_request) Successful in 43s
qa-review / approved (pull_request) Failing after 18s
security-review / approved (pull_request) Failing after 10s
sop-checklist / all-items-acked (pull_request) acked: 0/7 — missing: comprehensive-testing, local-postgres-e2e, staging-smoke, +4 — body-unfilled: 7
sop-checklist-gate / gate (pull_request) Successful in 11s
sop-tier-check / tier-check (pull_request) Successful in 12s
CI / Canvas (Next.js) (pull_request) Successful in 5s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 4s
CI / Python Lint & Test (pull_request) Successful in 5s
Harness Replays / Harness Replays (pull_request) Successful in 4s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 1m24s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 4s
CI / Canvas Deploy Reminder (pull_request) Has been skipped
Runtime PR-Built Compatibility / PR-built wheel + import smoke (pull_request) Successful in 5s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 2m12s
audit-force-merge / audit (pull_request) Has been skipped
E2E API Smoke Test / E2E API Smoke Test (pull_request) Has been skipped
E2E API Smoke Test / detect-changes (pull_request) Has been skipped
Some checks failed
CI / Platform (Go) (pull_request) Failing after 7m14s
CI / all-required (pull_request) Failing after 4s
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 28s
CI / Detect changes (pull_request) Successful in 1m23s
Harness Replays / detect-changes (pull_request) Successful in 18s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 1m23s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 1m15s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 20s
gate-check-v3 / gate-check (pull_request) Successful in 23s
Runtime PR-Built Compatibility / detect-changes (pull_request) Successful in 43s
qa-review / approved (pull_request) Failing after 18s
security-review / approved (pull_request) Failing after 10s
sop-checklist / all-items-acked (pull_request) acked: 0/7 — missing: comprehensive-testing, local-postgres-e2e, staging-smoke, +4 — body-unfilled: 7
sop-checklist-gate / gate (pull_request) Successful in 11s
sop-tier-check / tier-check (pull_request) Successful in 12s
CI / Canvas (Next.js) (pull_request) Successful in 5s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 4s
CI / Python Lint & Test (pull_request) Successful in 5s
Harness Replays / Harness Replays (pull_request) Successful in 4s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 1m24s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 4s
CI / Canvas Deploy Reminder (pull_request) Has been skipped
Runtime PR-Built Compatibility / PR-built wheel + import smoke (pull_request) Successful in 5s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 2m12s
audit-force-merge / audit (pull_request) Has been skipped
E2E API Smoke Test / E2E API Smoke Test (pull_request) Has been skipped
E2E API Smoke Test / detect-changes (pull_request) Has been skipped
queryPeerMaps sets peer["role"] = nil when the DB role column is empty (discovery.go lines 337-341). filterPeersByQuery did a bare type assertion p["role"].(string) which panics on nil. Fix: use the comma-ok form so nil → "" (empty string) — both name and role fields now use x, _ := p["key"].(string) rather than x := p["key"].(string). Add TestFilterPeersByQuery_NilRoleRegression with three cases: - nil role matches on name substring - nil name/role with empty q (no-op, returns all) - all nil — no panic, returns empty Regression gate for mc#730/#731. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
06cf6a9ca7
commit
fe6ada46c2
@ -292,8 +292,8 @@ func filterPeersByQuery(peers []map[string]interface{}, q string) []map[string]i
|
||||
needle := strings.ToLower(q)
|
||||
out := make([]map[string]interface{}, 0, len(peers))
|
||||
for _, p := range peers {
|
||||
name := p["name"].(string)
|
||||
role := p["role"].(string)
|
||||
name, _ := p["name"].(string) // nil → "" — safe on empty-role rows
|
||||
role, _ := p["role"].(string) // nil → "" — queryPeerMaps sets nil when DB role is empty
|
||||
if strings.Contains(strings.ToLower(name), needle) ||
|
||||
strings.Contains(strings.ToLower(role), needle) {
|
||||
out = append(out, p)
|
||||
|
||||
@ -394,6 +394,80 @@ func TestPeers_Q_NoMatches_RawBodyIsArrayNotNull(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestFilterPeersByQuery_NilRoleRegression is the regression gate for
|
||||
// mc#730/#731: queryPeerMaps sets peer["role"] = nil when the DB role column
|
||||
// is empty (discovery.go lines 337-341). filterPeersByQuery did a bare
|
||||
// type assertion p["role"].(string) which panics on nil. The fix uses the
|
||||
// comma-ok form so nil → "". The test passes a map with nil name and nil
|
||||
// role and asserts no panic + correct filter behaviour.
|
||||
func TestFilterPeersByQuery_NilRoleRegression(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
peers []map[string]interface{}
|
||||
q string
|
||||
wantLen int
|
||||
wantIDs []string
|
||||
}{
|
||||
{
|
||||
name: "nil role matches on name",
|
||||
peers: []map[string]interface{}{
|
||||
{"id": "ws-a", "name": nil, "role": nil},
|
||||
{"id": "ws-b", "name": "Alpha Builder", "role": nil},
|
||||
{"id": "ws-c", "name": "Beta Builder", "role": nil},
|
||||
},
|
||||
q: "alpha",
|
||||
wantLen: 1,
|
||||
wantIDs: []string{"ws-b"},
|
||||
},
|
||||
{
|
||||
name: "nil name matches on nil role (empty string)",
|
||||
peers: []map[string]interface{}{
|
||||
{"id": "ws-x", "name": nil, "role": nil},
|
||||
{"id": "ws-y", "name": "Dev Workspace", "role": nil},
|
||||
},
|
||||
q: "",
|
||||
wantLen: 2, // empty q is a no-op
|
||||
wantIDs: []string{"ws-x", "ws-y"},
|
||||
},
|
||||
{
|
||||
name: "all nil — no panic, returns input",
|
||||
peers: []map[string]interface{}{
|
||||
{"id": "ws-z", "name": nil, "role": nil},
|
||||
},
|
||||
q: "anything",
|
||||
wantLen: 0,
|
||||
wantIDs: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := filterPeersByQuery(tc.peers, tc.q)
|
||||
if len(got) != tc.wantLen {
|
||||
t.Fatalf("len: got %d, want %d", len(got), tc.wantLen)
|
||||
}
|
||||
gotIDs := make([]string, len(got))
|
||||
for i, p := range got {
|
||||
gotIDs[i] = p["id"].(string)
|
||||
}
|
||||
if tc.wantIDs != nil {
|
||||
for _, id := range tc.wantIDs {
|
||||
found := false
|
||||
for _, g := range gotIDs {
|
||||
if g == id {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("missing id %q; got IDs: %v", id, gotIDs)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func keysOf(m map[string]struct{}) []string {
|
||||
out := make([]string, 0, len(m))
|
||||
for k := range m {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user