| 1 | package console |
| 2 | |
| 3 | import ( |
| 4 | "encoding/json" |
| 5 | "fmt" |
| 6 | "os" |
| 7 | "strconv" |
| 8 | "strings" |
| 9 | "testing" |
| 10 | ) |
| 11 | |
| 12 | func TestRegionMessagesRender(t *testing.T) { |
| 13 | all := []string{ |
| 14 | msgAppRegionUnavailable("uk", "absent", []string{"ie", "au"}), |
| 15 | msgAppRegionNotAllowed("uk", []string{"ie"}), |
| 16 | msgRegionUnknown("zz", []string{"ie", "uk"}), |
| 17 | msgRegionNotArmed("uk", []string{"ie"}), |
| 18 | msgAppRegionUnreadable, |
| 19 | msgReplicaOverlap("uk"), |
| 20 | msgPolicyUnreadable, |
| 21 | msgAppRegionUnconfirmed("uk"), |
| 22 | msgPlacementDiscrepancy("a.go.tito.io", "blog", "uk"), |
| 23 | msgNoSecretsKey("uk"), |
| 24 | // codex plan-R4 2: the empty-list variants, C16b to C19b. |
| 25 | msgAppRegionUnavailable("uk", "absent", nil), |
| 26 | msgAppRegionNotAllowed("uk", nil), |
| 27 | msgRegionUnknown("zz", nil), |
| 28 | msgRegionNotArmed("uk", nil), |
| 29 | } |
| 30 | for _, s := range all { |
| 31 | if s == "" || strings.Contains(s, "—") || strings.Contains(s, "%!") { |
| 32 | t.Errorf("bad message %q", s) |
| 33 | } |
| 34 | } |
| 35 | if !strings.Contains(all[0], reasonText("absent")) || !strings.Contains(all[0], "ie, au") { |
| 36 | t.Errorf("C16 = %q, want the readable reason and the list", all[0]) |
| 37 | } |
| 38 | for _, s := range all[len(all)-4:] { |
| 39 | if strings.Contains(s, ": .") || strings.Contains(s, ": ,") { |
| 40 | t.Errorf("an empty list rendered as a list: %q", s) |
| 41 | } |
| 42 | } |
| 43 | seen := map[string]bool{} |
| 44 | for _, r := range []string{"absent", "not-hosting", "no-secrets-key", "not-eligible"} { |
| 45 | txt := reasonText(r) |
| 46 | if txt == r || seen[txt] { |
| 47 | t.Errorf("reasonText(%s) = %q: untranslated or duplicated", r, txt) |
| 48 | } |
| 49 | seen[txt] = true |
| 50 | } |
| 51 | } |
| 52 | |
| 53 | // TestRegionMessagesMatchTheApprovedRecord holds every record entry mapped to |
| 54 | // regionmsgs.go to the source as a quoted literal (so a short text cannot |
| 55 | // pass by matching part of an identifier), and holds each rendered message to |
| 56 | // the record's text filled with the same verbs, in the record's order. |
| 57 | func TestRegionMessagesMatchTheApprovedRecord(t *testing.T) { |
| 58 | raw, err := os.ReadFile("../../docs/superpowers/plans/2026-10-02-nearest-region-copy.json") |
| 59 | if err != nil { |
| 60 | t.Fatal(err) |
| 61 | } |
| 62 | var rec struct { |
| 63 | Entries []struct { |
| 64 | ID string `json:"id"` |
| 65 | File string `json:"file"` |
| 66 | Text string `json:"text"` |
| 67 | } `json:"entries"` |
| 68 | } |
| 69 | if err := json.Unmarshal(raw, &rec); err != nil { |
| 70 | t.Fatal(err) |
| 71 | } |
| 72 | src, err := os.ReadFile("regionmsgs.go") |
| 73 | if err != nil { |
| 74 | t.Fatal(err) |
| 75 | } |
| 76 | byID := map[string]string{} |
| 77 | mapped := 0 |
| 78 | for _, e := range rec.Entries { |
| 79 | byID[e.ID] = e.Text |
| 80 | if e.File != "internal/console/regionmsgs.go" { |
| 81 | continue |
| 82 | } |
| 83 | mapped++ |
| 84 | if !strings.Contains(string(src), strconv.Quote(e.Text)) { |
| 85 | t.Errorf("%s: regionmsgs.go has no literal %s", e.ID, strconv.Quote(e.Text)) |
| 86 | } |
| 87 | } |
| 88 | // The record maps 4 reasons, C16 to C20, C16b to C19b, C26 to C29 and C31. |
| 89 | if mapped != 18 { |
| 90 | t.Errorf("the record maps %d entries to regionmsgs.go, want 18", mapped) |
| 91 | } |
| 92 | list := []string{"ie", "au"} |
| 93 | cases := []struct { |
| 94 | id, got, want string |
| 95 | }{ |
| 96 | {"R-absent", reasonText("absent"), byID["R-absent"]}, |
| 97 | {"R-not-hosting", reasonText("not-hosting"), byID["R-not-hosting"]}, |
| 98 | {"R-no-secrets-key", reasonText("no-secrets-key"), byID["R-no-secrets-key"]}, |
| 99 | {"R-not-eligible", reasonText("not-eligible"), byID["R-not-eligible"]}, |
| 100 | {"C16", msgAppRegionUnavailable("uk", "absent", list), fmt.Sprintf(byID["C16"], "uk", reasonText("absent"), "ie, au")}, |
| 101 | {"C17", msgAppRegionNotAllowed("uk", list), fmt.Sprintf(byID["C17"], "uk", "ie, au")}, |
| 102 | {"C18", msgRegionUnknown("zz", list), fmt.Sprintf(byID["C18"], "zz", "ie, au")}, |
| 103 | {"C19", msgRegionNotArmed("uk", list), fmt.Sprintf(byID["C19"], "uk", "ie, au")}, |
| 104 | {"C16b", msgAppRegionUnavailable("uk", "absent", nil), fmt.Sprintf(byID["C16b"], "uk", reasonText("absent"))}, |
| 105 | {"C17b", msgAppRegionNotAllowed("uk", nil), fmt.Sprintf(byID["C17b"], "uk")}, |
| 106 | {"C18b", msgRegionUnknown("zz", nil), fmt.Sprintf(byID["C18b"], "zz")}, |
| 107 | {"C19b", msgRegionNotArmed("uk", nil), fmt.Sprintf(byID["C19b"], "uk")}, |
| 108 | {"C20", msgAppRegionUnreadable, byID["C20"]}, |
| 109 | {"C26", msgReplicaOverlap("uk"), fmt.Sprintf(byID["C26"], "uk", "uk")}, |
| 110 | {"C27", msgPolicyUnreadable, byID["C27"]}, |
| 111 | {"C28", msgAppRegionUnconfirmed("uk"), fmt.Sprintf(byID["C28"], "uk")}, |
| 112 | {"C29", msgPlacementDiscrepancy("a.go.tito.io", "blog", "uk"), fmt.Sprintf(byID["C29"], "a.go.tito.io", "blog", "blog", "uk")}, |
| 113 | {"C31", msgNoSecretsKey("uk"), fmt.Sprintf(byID["C31"], "uk")}, |
| 114 | } |
| 115 | for _, c := range cases { |
| 116 | if byID[c.id] == "" { |
| 117 | t.Errorf("%s: not in the record", c.id) |
| 118 | continue |
| 119 | } |
| 120 | if c.got != c.want { |
| 121 | t.Errorf("%s = %q, record gives %q", c.id, c.got, c.want) |
| 122 | } |
| 123 | } |
| 124 | if reasonText("nonsense") != "nonsense" { |
| 125 | t.Errorf("an unknown reason should pass through unchanged") |
| 126 | } |
| 127 | } |
| 128 | |