| 1 | package updatewords |
| 2 | |
| 3 | import ( |
| 4 | "encoding/json" |
| 5 | "os" |
| 6 | "path/filepath" |
| 7 | "strings" |
| 8 | "testing" |
| 9 | ) |
| 10 | |
| 11 | // sectionFor maps a plan-5 draft ID to its review section. An ID that |
| 12 | // matches no rule is unmapped and must be added here. |
| 13 | func sectionFor(id string) string { |
| 14 | switch { |
| 15 | case strings.HasPrefix(id, "settings.cli.apps-set-"): |
| 16 | return "CLI: carlos apps set" |
| 17 | case strings.HasPrefix(id, "settings.cli.instances-usage-"), |
| 18 | strings.HasPrefix(id, "settings.cli.set-"), |
| 19 | strings.HasPrefix(id, "settings.cli.update-"), |
| 20 | id == "settings.cli.direct-mode", |
| 21 | id == "settings.cli.bad-major-flag", |
| 22 | id == "settings.cli.flag-account-id", |
| 23 | id == "settings.cli.none": |
| 24 | return "CLI: carlos instances set and update" |
| 25 | case id == "settings.cli.not-enforced-reason": |
| 26 | return "CLI: carlos instances list" |
| 27 | case strings.HasPrefix(id, "settings.page."): |
| 28 | return "Console: instance page, Updates section" |
| 29 | case strings.HasPrefix(id, "settings.cli.docs-"), |
| 30 | id == "settings.cli.roll-note": |
| 31 | return "Docs and roll notes" |
| 32 | default: |
| 33 | return "" |
| 34 | } |
| 35 | } |
| 36 | |
| 37 | // TestEverySurfaceDraftHasASection ensures every plan-5 draft is mapped to |
| 38 | // a section, so a new draft cannot be silently left out of review. |
| 39 | func TestEverySurfaceDraftHasASection(t *testing.T) { |
| 40 | for _, d := range All() { |
| 41 | if !strings.HasPrefix(d.ID, "settings.cli.") && !strings.HasPrefix(d.ID, "settings.page.") { |
| 42 | continue // not a plan-5 draft |
| 43 | } |
| 44 | sec := sectionFor(d.ID) |
| 45 | if sec == "" { |
| 46 | t.Errorf("%s: plan-5 draft is unmapped to a section", d.ID) |
| 47 | } |
| 48 | } |
| 49 | } |
| 50 | |
| 51 | // Astra r1 (R21): the docs lead-in and the shared --account-id help land in |
| 52 | // the sections Paul reviews them under. |
| 53 | func TestTheR21DraftsLandInTheirSections(t *testing.T) { |
| 54 | for id, want := range map[string]string{ |
| 55 | "settings.cli.docs-apps-set-lead": "Docs and roll notes", |
| 56 | "settings.cli.flag-account-id": "CLI: carlos instances set and update", |
| 57 | "settings.page.major-inherited": "Console: instance page, Updates section", |
| 58 | } { |
| 59 | if got := sectionFor(id); got != want { |
| 60 | t.Errorf("%s is in %q, want %q", id, got, want) |
| 61 | } |
| 62 | } |
| 63 | } |
| 64 | |
| 65 | // TestExportDrafts writes every plan-5 draft to $UPDATEWORDS_EXPORT in JSON |
| 66 | // format, organized by section for review. It does nothing unless the |
| 67 | // variable is set. |
| 68 | func TestExportDrafts(t *testing.T) { |
| 69 | out := os.Getenv("UPDATEWORDS_EXPORT") |
| 70 | if out == "" { |
| 71 | t.Skip("UPDATEWORDS_EXPORT not set") |
| 72 | } |
| 73 | |
| 74 | type entry struct { |
| 75 | ID string `json:"id"` |
| 76 | Section string `json:"section"` |
| 77 | Label string `json:"label"` |
| 78 | Text string `json:"text"` |
| 79 | Context string `json:"context"` |
| 80 | } |
| 81 | |
| 82 | // Collect all plan-5 drafts, organized by section then by ID. |
| 83 | type sectionGroup struct { |
| 84 | name string |
| 85 | entries []entry |
| 86 | } |
| 87 | sectionMap := map[string]*sectionGroup{} |
| 88 | sectionOrder := []string{ |
| 89 | "CLI: carlos apps set", |
| 90 | "CLI: carlos instances set and update", |
| 91 | "CLI: carlos instances list", |
| 92 | "Console: instance page, Updates section", |
| 93 | "Docs and roll notes", |
| 94 | } |
| 95 | |
| 96 | for _, sec := range sectionOrder { |
| 97 | sectionMap[sec] = §ionGroup{name: sec} |
| 98 | } |
| 99 | |
| 100 | for _, d := range All() { |
| 101 | if !strings.HasPrefix(d.ID, "settings.cli.") && !strings.HasPrefix(d.ID, "settings.page.") { |
| 102 | continue // not a plan-5 draft |
| 103 | } |
| 104 | sec := sectionFor(d.ID) |
| 105 | if sec == "" { |
| 106 | t.Fatalf("%s: plan-5 draft is unmapped to a section", d.ID) |
| 107 | } |
| 108 | parts := strings.Split(d.ID, ".") |
| 109 | label := parts[len(parts)-1] |
| 110 | sectionMap[sec].entries = append(sectionMap[sec].entries, entry{ |
| 111 | ID: d.ID, |
| 112 | Section: sec, |
| 113 | Label: label, |
| 114 | Text: d.Text, |
| 115 | Context: d.Context, |
| 116 | }) |
| 117 | } |
| 118 | |
| 119 | // Build the final list in section order. |
| 120 | var entries []entry |
| 121 | for _, sec := range sectionOrder { |
| 122 | if group := sectionMap[sec]; group != nil && len(group.entries) > 0 { |
| 123 | entries = append(entries, group.entries...) |
| 124 | } |
| 125 | } |
| 126 | |
| 127 | b, err := json.MarshalIndent(entries, "", " ") |
| 128 | if err != nil { |
| 129 | t.Fatal(err) |
| 130 | } |
| 131 | if err := os.MkdirAll(filepath.Dir(out), 0o755); err != nil { |
| 132 | t.Fatal(err) |
| 133 | } |
| 134 | if err := os.WriteFile(out, b, 0o644); err != nil { |
| 135 | t.Fatal(err) |
| 136 | } |
| 137 | } |
| 138 | |