package main import ( "encoding/json" "sort" "testing" "countrygame/web" ) var ( indIDs = []string{"order", "stability", "civilRights", "opinion", "economy", "hardPower", "softPower", "unrest", "armyLoyalty"} varIDs = []string{"growth", "unemployment", "inflation"} taxIDs = []string{"income", "corporate", "vat"} devKeys = []string{"growth", "unemployment", "inflation", "debt", "deficit", "taxPressure", "taxGrowthDrag", "crackdown"} ) type obj = map[string]any func load(t *testing.T, path string) obj { t.Helper() raw, err := web.Files.ReadFile(path) if err != nil { t.Fatalf("read %s: %v", path, err) } var out obj if err := json.Unmarshal(raw, &out); err != nil { t.Fatalf("parse %s: %v", path, err) } return out } func keys(m obj) []string { out := make([]string, 0, len(m)) for k := range m { out = append(out, k) } sort.Strings(out) return out } func set(list []string) map[string]bool { m := make(map[string]bool, len(list)) for _, s := range list { m[s] = true } return m } func sameKeys(t *testing.T, what string, got obj, want []string) { t.Helper() w := append([]string(nil), want...) sort.Strings(w) g := keys(got) if len(g) != len(w) { t.Errorf("%s: keys %v, want %v", what, g, w) return } for i := range g { if g[i] != w[i] { t.Errorf("%s: keys %v, want %v", what, g, w) return } } } // checkWeights verifies every key of def[field] is in the allowed set. func checkWeights(t *testing.T, who string, def obj, field string, allowed map[string]bool) { t.Helper() m, ok := def[field].(obj) if !ok { return // optional section } for k := range m { if !allowed[k] { t.Errorf("%s.%s references unknown %q", who, field, k) } } } func TestBalanceAndScenario(t *testing.T) { bal := load(t, "data/balance.json") scn := load(t, "data/scenarios/default.json") sectors := scn["sectors"].(obj) sectorSet := make(map[string]bool) for id := range sectors { sectorSet[id] = true } // Scenario consistency. sameKeys(t, "scenario.indicators", scn["indicators"].(obj), indIDs) sameKeys(t, "scenario.economy.taxes", scn["economy"].(obj)["taxes"].(obj), taxIDs) limit := bal["limits"].(obj)["allocMaxRatio"].(float64) for id, raw := range sectors { s := raw.(obj) need, alloc := s["need"].(float64), s["alloc"].(float64) if need <= 0 || alloc < 0 || alloc > need*limit { t.Errorf("scenario sector %s: need=%v alloc=%v out of range", id, need, alloc) } } for id, v := range scn["indicators"].(obj) { if f := v.(float64); f < 0 || f > 100 { t.Errorf("scenario indicator %s=%v outside 0..100", id, f) } } // Balance coverage. sameKeys(t, "balance.sectorTau", bal["sectorTau"].(obj), keys(sectors)) sameKeys(t, "balance.indicators", bal["indicators"].(obj), indIDs) sameKeys(t, "balance.vars", bal["vars"].(obj), varIDs) sameKeys(t, "balance.tax", bal["tax"].(obj), taxIDs) for id, tau := range bal["sectorTau"].(obj) { if tau.(float64) <= 0 { t.Errorf("sectorTau.%s must be positive", id) } } // Every weight points at something that exists. dev, ind := set(devKeys), set(indIDs) groups := []obj{bal["indicators"].(obj), bal["vars"].(obj)} for _, g := range groups { for id, raw := range g { def := raw.(obj) if def["tau"].(float64) <= 0 { t.Errorf("%s.tau must be positive", id) } checkWeights(t, id, def, "sectors", sectorSet) checkWeights(t, id, def, "alloc", sectorSet) checkWeights(t, id, def, "econ", dev) checkWeights(t, id, def, "ind", ind) } } }