Files

135 lines
3.4 KiB
Go

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)
}
}
}