Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/missionmatch / internal/modules/commandcenter/normalize_test.go |
| Date | Not specified in source |
| Repository | Janady13/missionmatch |
| Revision | 720d47fae476b0d38477ae4401697d372b1ea2bc |
| Branch | feature/opportunity-to-outcome |
| Path or archive member | internal/modules/commandcenter/normalize_test.go |
| Original SHA-256 | 0bb75a2ffac96d8f338041dbbd2a982a5eb1a2aaf23d807180717f3d9e117de4 |
| Git blob SHA | bd31d435c0e9d62e4e5c672c0832f4027f3db1a0 |
| Reading-copy SHA-256 | 0bb75a2ffac96d8f338041dbbd2a982a5eb1a2aaf23d807180717f3d9e117de4 |
Every recorded occurrence
These 2 records contain identical original source bytes. Their paths and revisions remain part of the archive.
| Original record | Branch or archive | Revision / identifier | Original location |
|---|---|---|---|
| Janady13/missionmatch / internal/modules/commandcenter/normalize_test.go | feature/opportunity-to-outcome | 720d47fae476b0d38477ae4401697d372b1ea2bc | Open original |
| Janady13/missionmatch / internal/modules/commandcenter/normalize_test.go | feature/aioe-upgrade | 2178e7298d0e5d9af83afa12402fd587f0ba6ed8 | Open original |
Recovered source transcript
Download complete source text ↓69 lines · 2.6 KB
package commandcenter
import (
"bytes"
"encoding/json"
"reflect"
"testing"
)
// TestNormalizeListsLeavesNoNilSlice pins the contract that every list field is emitted as a LIST.
//
// Go marshals a nil slice to JSON null, the client maps straight over these fields, and null.map throws
// during render, so one nil here is a blank Command Center for any tenant that happens to have none of
// that thing. A brand new tenant has none of most of them.
//
// Reflection rather than eight hand written assertions, deliberately: a list field added to View later is
// then caught by this test instead of by whichever customer first has an empty one.
func TestNormalizeListsLeavesNoNilSlice(t *testing.T) {
var v View // the zero value is exactly the empty tenant case: every slice nil
normalizeLists(&v)
// Recursive, because the nulls are not only at the top level: meta.notes, mission_readiness.drivers,
// the four proposal_readiness lists, win_impact.top and action_center.launch are all nested, and the
// client maps over several of them exactly like the top level ones.
checked := 0
assertNoNilSlice(t, reflect.ValueOf(v), "View", &checked)
if checked < 8 {
t.Fatalf("only %d slice fields walked; the view has more than that, so the walk is not reaching them", checked)
}
// A nil POINTER is deliberately still allowed. action_center.suggested_requirement_id being null means
// "there is no suggestion", which is a real answer and different from an empty list; the client's
// optional handling depends on that distinction. Only lists must never be null.
b, err := json.Marshal(v)
if err != nil {
t.Fatalf("marshal: %v", err)
}
for _, listKey := range []string{
"active_opportunities", "staffing_gaps", "labor_category_coverage", "heat_map",
"upcoming_opportunities", "incumbent_landscape", "ai_recommendations", "executive_cards",
"notes", "drivers", "flags", "top", "launch", "alerts",
} {
if bytes.Contains(b, []byte(`"`+listKey+`":null`)) {
t.Errorf("%q marshalled as null; the client will throw on .map", listKey)
}
}
}
func assertNoNilSlice(t *testing.T, rv reflect.Value, path string, checked *int) {
t.Helper()
switch rv.Kind() {
case reflect.Struct:
for i := 0; i < rv.NumField(); i++ {
if rv.Type().Field(i).IsExported() {
assertNoNilSlice(t, rv.Field(i), path+"."+rv.Type().Field(i).Name, checked)
}
}
case reflect.Slice:
*checked++
if rv.IsNil() {
t.Errorf("%s is nil after normalizeLists: it marshals as JSON null and the client throws on .map", path)
}
case reflect.Ptr:
if !rv.IsNil() {
assertNoNilSlice(t, rv.Elem(), path, checked)
}
}
}