package portrait import ( "encoding/json" "strings" "testing" "time" ) func TestCalcMatchesClassicJS(t *testing.T) { // Snapshot from js/common.js calc(1990,5,12) v := Calc(1990, 5, 12) if v.O != 9 || v.M != 8 || v.N != 1 || v.I != 3 || v.J != 5 || v.K != 1 || v.L != 9 { t.Fatalf("triangle mismatch: %+v", v) } if v.Q != 1 || v.P != 8 || v.R != 9 || v.T != 2 || v.S != 4 || v.U != 6 { t.Fatalf("outer mismatch: %+v", v) } } func TestBuildDecodeShape(t *testing.T) { birth := time.Date(1990, 5, 12, 0, 0, 0, 0, time.UTC) a := Build(birth, "小愈") b := Build(birth, "小愈") if a.Summary["headline"] != b.Summary["headline"] { t.Fatalf("expected deterministic headline") } if a.Summary["main_number"].(int) != 9 { t.Fatalf("main_number want 9 got %#v", a.Summary["main_number"]) } if a.Summary["person_title"] == nil || a.Summary["pyramid"] == nil { t.Fatalf("missing decode free fields") } wx, ok := a.Summary["wuxing"].(map[string]any) if !ok || wx["bars"] == nil || wx["primary"] == nil { t.Fatalf("missing wuxing summary: %#v", a.Summary["wuxing"]) } if a.Summary["today_tip"] == nil { t.Fatalf("missing today_tip") } nd, ok := a.Detail["number_deep"].(map[string]any) if !ok || nd["talent"] == nil || nd["joints"] == nil { t.Fatalf("missing number_deep") } cd, ok := a.Detail["constitution_deep"].(map[string]any) if !ok || cd["body"] == nil || cd["food"] == nil { t.Fatalf("missing constitution_deep") } dims, ok := a.Summary["dimensions"].([]map[string]any) if !ok || len(dims) < 6 { t.Fatalf("expected 6 dimensions for relation compat") } assertNoForbidden(t, a) } func TestWuxingDeterministic(t *testing.T) { bz := getBazi(1990, 5, 12, 12) p := calc5E(bz) // Snapshot aligned with js calc5E(getBazi(new Date(1990,4,12),12)) want := map[string]int{"木": 25, "火": 50, "土": 13, "金": 13, "水": 0} for k, w := range want { if p[k] != w { t.Fatalf("wuxing[%s]=%d want %d (full %#v)", k, p[k], w, p) } } if strongestEl(p) != "火" { t.Fatalf("strong want 火 got %s", strongestEl(p)) } } func TestAllMainNumbersLexicon(t *testing.T) { for day := 1; day <= 28; day++ { out := Build(time.Date(1990, 3, day, 0, 0, 0, 0, time.UTC), "测") assertNoForbidden(t, out) } } func assertNoForbidden(t *testing.T, out Output) { t.Helper() raw, err := json.Marshal(out) if err != nil { t.Fatal(err) } blob := string(raw) for _, kw := range []string{"占卜", "算命", "改命", "塔罗"} { if strings.Contains(blob, kw) { t.Fatalf("forbidden word %q in portrait copy", kw) } } }