package main import ( "bytes" "encoding/json" "testing" ) // Every other JSON command emits an object, so a consumer can add fields // later without breaking. The effect list returned a bare array because the // EffectList wrapper it built was bypassed in favour of its slice. func TestEffectListEmitsAnObjectNotABareArray(t *testing.T) { t.Cleanup(vendoredDefinitions(t)) withJSON(t) t.Cleanup(impact80Target(t, &fakeZoneProtocol{failAt: -1})) var out, errOut bytes.Buffer cmd := NewEffectCmd() cmd.SetOut(&out) cmd.SetErr(&errOut) cmd.SetArgs([]string{zoneAll}) if err := cmd.Execute(); err != nil { t.Fatalf("effect %s returned error: %v", zoneAll, err) } var parsed struct { Catalog string `json:"catalog"` Zones []struct { Zone string `json:"zone"` Channel int `json:"channel"` Subsystem string `json:"subsystem"` Effect string `json:"effect"` ID int `json:"id"` } `json:"zones"` } if err := json.Unmarshal(out.Bytes(), &parsed); err != nil { t.Fatalf("effect all output is not a JSON object: %v (output %q)", err, out.String()[:min(80, out.Len())]) } if parsed.Catalog != "impact80" { // The catalog is named by the definition file it was read from, so the name // here is the vendor's own rather than a label the tool invented. if parsed.Catalog != "Impact 80" { t.Errorf("catalog = %q, want the definition file's name %q", parsed.Catalog, "Impact 80") } } zones := parsed.Zones if len(zones) == 0 { t.Fatal("zones is empty, want the full catalog") } if len(zones) == 0 { t.Fatal("zones is empty, want the full catalog") } // The catalog itself must not have lost a field, and gained the channel // identity a consumer needs to address the entry it finds. if zones[0].Zone == "" || zones[0].Effect == "" { t.Errorf("zones[0] = %+v, want zone, effect and id populated", zones[0]) } if zones[0].Channel == 0 || zones[0].Subsystem == "" { t.Errorf("zones[0] = %+v, want channel and subsystem populated", zones[0]) } } func min(a, b int) int { if a < b { return a } return b }