144 lines
4.0 KiB
Go
144 lines
4.0 KiB
Go
package main
|
|
|
|
import (
|
|
"regexp"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
var (
|
|
earlyEnds = []string{"revolution", "coup", "default", "impeachment"}
|
|
metricIDs = []string{"order", "stability", "civilRights", "opinion", "economy", "hardPower", "softPower", "unrest", "armyLoyalty", "growth", "unemployment", "inflation", "debt", "score", "years"}
|
|
tokenPattern = regexp.MustCompile(`\{(\w+)\}`)
|
|
// Pronoun tokens each language knows how to fill (see i18n.js, tp()).
|
|
allowedTokens = map[string]map[string]bool{
|
|
"en": set([]string{"sub", "Sub", "obj", "pos", "is", "has", "was", "country"}),
|
|
"fr": set([]string{"sub", "Sub", "tonic", "e", "country"}),
|
|
}
|
|
)
|
|
|
|
func checkTokens(t *testing.T, where string, v any) {
|
|
t.Helper()
|
|
m, _ := v.(obj)
|
|
for lang, raw := range m {
|
|
s, ok := raw.(string)
|
|
if !ok {
|
|
continue
|
|
}
|
|
for _, match := range tokenPattern.FindAllStringSubmatch(s, -1) {
|
|
if !allowedTokens[lang][match[1]] {
|
|
t.Errorf("%s.%s: unknown token {%s}", where, lang, match[1])
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestEndgame(t *testing.T) {
|
|
eg := load(t, "data/endgame.json")
|
|
bal := load(t, "data/balance.json")
|
|
|
|
// Banners: the term plus one per way of losing.
|
|
over := eg["over"].(obj)
|
|
sameKeys(t, "endgame.over", over, append([]string{"term"}, earlyEnds...))
|
|
for k, v := range over {
|
|
e := v.(obj)
|
|
if !isLocalized(e["h"]) || !isLocalized(e["d"]) {
|
|
t.Errorf("over.%s: h and d need non-empty en and fr", k)
|
|
}
|
|
}
|
|
|
|
// Urgent telegrams: one per danger zone.
|
|
alerts := eg["alerts"].(obj)
|
|
sameKeys(t, "endgame.alerts", alerts, earlyEnds)
|
|
for k, v := range alerts {
|
|
a := v.(obj)
|
|
if !isLocalized(a["title"]) || !isLocalized(a["body"]) {
|
|
t.Errorf("alerts.%s: title and body need non-empty en and fr", k)
|
|
}
|
|
}
|
|
|
|
// Historian titles.
|
|
metrics := set(metricIDs)
|
|
ids := map[string]bool{}
|
|
perEnd := map[string]int{}
|
|
titles := eg["titles"].([]any)
|
|
var lastTerm obj
|
|
for i, raw := range titles {
|
|
ti := raw.(obj)
|
|
id, _ := ti["id"].(string)
|
|
if id == "" || ids[id] {
|
|
t.Errorf("titles[%d]: missing or duplicate id %q", i, id)
|
|
}
|
|
ids[id] = true
|
|
|
|
title, _ := ti["title"].(obj)
|
|
if s, _ := title["en"].(string); strings.TrimSpace(s) == "" {
|
|
t.Errorf("%s: title.en is empty", id)
|
|
}
|
|
fr, _ := title["fr"].(obj)
|
|
for _, form := range []string{"he", "she", "they"} {
|
|
if s, _ := fr[form].(string); strings.TrimSpace(s) == "" {
|
|
t.Errorf("%s: title.fr.%s is empty", id, form)
|
|
}
|
|
}
|
|
if !isLocalized(ti["verdict"]) {
|
|
t.Errorf("%s: verdict needs non-empty en and fr", id)
|
|
}
|
|
checkTokens(t, id+".verdict", ti["verdict"])
|
|
|
|
cond, hasCond := ti["conditions"].(obj)
|
|
for _, bound := range []string{"min", "max"} {
|
|
m, _ := cond[bound].(obj)
|
|
for k := range m {
|
|
if !metrics[k] {
|
|
t.Errorf("%s.conditions.%s: unknown metric %q", id, bound, k)
|
|
}
|
|
}
|
|
}
|
|
|
|
if end, ok := ti["over"].(string); ok {
|
|
if !set(earlyEnds)[end] {
|
|
t.Errorf("%s: unknown over %q", id, end)
|
|
}
|
|
perEnd[end]++
|
|
if hasCond {
|
|
t.Errorf("%s: titles for an early end take no conditions", id)
|
|
}
|
|
} else {
|
|
lastTerm = ti
|
|
}
|
|
}
|
|
for _, end := range earlyEnds {
|
|
if perEnd[end] != 1 {
|
|
t.Errorf("early end %q needs exactly one title, has %d", end, perEnd[end])
|
|
}
|
|
}
|
|
if _, has := lastTerm["conditions"]; has || lastTerm == nil {
|
|
t.Errorf("the last term title must have no conditions so every term gets one")
|
|
}
|
|
|
|
// Balance sections the review and the game-over rules rely on.
|
|
g := bal["gameOver"].(obj)
|
|
sameKeys(t, "balance.gameOver", g, []string{"default", "impeachment", "coup", "revolution", "emergencyCooldown"})
|
|
sc := bal["score"].(obj)
|
|
valid := set(append(append([]string{}, indIDs...), "calm"))
|
|
for k := range sc["weights"].(obj) {
|
|
if !valid[k] {
|
|
t.Errorf("score.weights: unknown key %q", k)
|
|
}
|
|
}
|
|
grades := sc["grades"].([]any)
|
|
prev := 101.0
|
|
for _, raw := range grades {
|
|
pair := raw.([]any)
|
|
min := pair[0].(float64)
|
|
if min >= prev {
|
|
t.Errorf("score.grades must be in strictly descending order of threshold")
|
|
}
|
|
prev = min
|
|
}
|
|
if prev != 0 {
|
|
t.Errorf("the last grade must have threshold 0 so every score gets a grade")
|
|
}
|
|
}
|