console: test the one-channel guarantee on what the page SHOWS
PR #258 review, I2. TestOneChannelAppHidesChannelVocabulary seeded one promotion, so its fixture app had no rollback history and therefore no rollback control — the test passed by never rendering the state a real one-channel app is in. It now seeds two promotions (and fails loudly if the rollback control is missing, so it cannot silently go back to proving nothing). With the control rendered, the raw-markup check would fail on the hidden `channel` field the rollback door needs. That field is the door's parameter, not a word on the page: spec §5 hides the vocabulary from the READER. assertNoChannelVocabulary therefore asserts on the visible-text projection (visibleText strips everything inside <...>), which is the guarantee actually worth holding — and TestOneChannelAppKeepsCanariesAndRollback uses the same projection for its own forbidden list.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 file changed,
+51
−7
internal/console/pipelinepage_test.go+51 −7
diff --git a/internal/console/pipelinepage_test.go b/internal/console/pipelinepage_test.go| index 29f4ec730..98d4668c3 100644 |
| --- a/internal/console/pipelinepage_test.go |
| +++ b/internal/console/pipelinepage_test.go |
| @@ -43,11 +43,20 @@ func TestOneChannelAppHidesChannelVocabulary(t *testing.T) { |
| t.Errorf("one-channel app page does not show the live marker:\n%s", body) |
| } |
| - // Ship a build and land it on the entry channel — still no vocabulary, |
| - // now that there's actually something to promote. |
| + // Ship TWO builds and land both on the entry channel — still no |
| + // vocabulary. Two promotions, not one, deliberately: the second gives the |
| + // pointer a history, which is the state in which the rollback control |
| + // renders (PR #258 review, I2). Tested with one promotion, this assertion |
| + // passed only because the page had no rollback form to put a channel name |
| + // into, so it never exercised the page a real one-channel app shows. |
| shipVersion(t, fx, "solo", "v1") |
| + shipVersion(t, fx, "solo", "v2") |
| seedChannel(t, fx, "solo", "edge", "v1") |
| + seedChannel(t, fx, "solo", "edge", "v2") |
| _, _, body2 := get(t, c, fx.u("/apps/solo/releases")) |
| + if !strings.Contains(body2, "Roll back") { |
| + t.Fatalf("this app has a rollback control by now — without it the check below proves nothing:\n%s", body2) |
| + } |
| assertNoChannelVocabulary(t, body2) |
| } |
| @@ -57,17 +66,48 @@ func TestOneChannelAppHidesChannelVocabulary(t *testing.T) { |
| // "top") names a `.ladder` CSS class and a code comment describing it, on |
| // EVERY page regardless of this app's pipeline; that is chrome, not content, |
| // and is not what spec §5's "hide the vocabulary entirely" is about. |
| +// |
| +// It checks the VISIBLE-TEXT projection, not the markup: spec §5 hides the |
| +// vocabulary from the READER, and a form field the browser never renders is |
| +// not something anybody is shown. The rollback control has to name the |
| +// channel it posts to somewhere — that is the door's parameter, not a word on |
| +// the page — and a raw-markup check would force the page to choose between |
| +// showing a rollback button and passing this test (PR #258 review, I2). |
| func assertNoChannelVocabulary(t *testing.T, body string) { |
| t.Helper() |
| _, main, ok := strings.Cut(body, "<main>") |
| if !ok { |
| t.Fatalf("page has no <main> to scope the check to:\n%s", body) |
| } |
| + text := visibleText(main) |
| for _, forbidden := range []string{"Promote", "edge", "ladder"} { |
| - if strings.Contains(main, forbidden) { |
| - t.Errorf("one-channel app page contains %q, want the vocabulary hidden entirely:\n%s", forbidden, main) |
| + if strings.Contains(text, forbidden) { |
| + t.Errorf("one-channel app page SHOWS %q, want the vocabulary hidden entirely.\nvisible text:\n%s", forbidden, text) |
| + } |
| + } |
| +} |
| + |
| +// visibleText is the crude tag stripper the assertion above needs: everything |
| +// outside <...> and nothing inside one, so attributes, hidden input values and |
| +// form actions drop out while every word a reader can actually see stays. Good |
| +// enough for a rendered console page (no CDATA, no raw "<" in text — the |
| +// templates escape it), and deliberately not a parser: a dependency-free |
| +// assertion helper that anybody can check by eye is worth more here than |
| +// fidelity to malformed markup. |
| +func visibleText(html string) string { |
| + var b strings.Builder |
| + inTag := false |
| + for _, r := range html { |
| + switch { |
| + case r == '<': |
| + inTag = true |
| + case r == '>': |
| + inTag = false |
| + case !inTag: |
| + b.WriteRune(r) |
| } |
| } |
| + return b.String() |
| } |
| // TestOneChannelAppKeepsCanariesAndRollback pins the other half of "hide the |
| @@ -121,10 +161,14 @@ func TestOneChannelAppKeepsCanariesAndRollback(t *testing.T) { |
| t.Errorf("one-channel app page has no rollback button:\n%s", main) |
| } |
| // And still none of the vocabulary the one-channel rule hides: no ladder |
| - // rungs, no promote form, no channel column. |
| + // rungs, no promote form, no channel column. Checked on what the page |
| + // SHOWS (visibleText), the same projection assertNoChannelVocabulary |
| + // uses — the rollback form's hidden channel field is the door's |
| + // parameter, not a word on the page. |
| + text := visibleText(main) |
| for _, forbidden := range []string{"beta", "stable", "Promoting", "Promote", "ladder"} { |
| - if strings.Contains(main, forbidden) { |
| - t.Errorf("one-channel app page contains %q, want the vocabulary still hidden:\n%s", forbidden, main) |
| + if strings.Contains(text, forbidden) { |
| + t.Errorf("one-channel app page SHOWS %q, want the vocabulary still hidden.\nvisible text:\n%s", forbidden, text) |
| } |
| } |
| } |