Files
coder/coderd/templatebuilder/prerequisites_test.go
T
Jeremy RuppelandAndrew Aquino 52142e6e94 fix(coderd/templatebuilder): strip prerequisite markers from delivered README (#28953)
## Problem

Template builder base READMEs delimit their prerequisites section with
HTML comment markers (`<!-- prerequisites:start -->` / `<!--
prerequisites:end -->`). `ExtractPrerequisites` uses these to populate
the builder's Prerequisites step, which works correctly.

However, `Compose` embeds the **full** base README verbatim into the
generated template's tarball. Because `react-markdown` renders raw HTML
comments as literal text, the markers were visible when the delivered
README is viewed via `MemoizedMarkdown`.

<img width="876" height="586" alt="Screenshot 2026-09-03 at 3 28 08 PM"
src="https://github.com/user-attachments/assets/70353ee2-1ff6-4e70-9b5d-7ac0521b945f"
/>

## Fix

Strip the prerequisite markers from the README before writing it into
the tarball (Option 2), keeping the change contained to the template
builder and leaving global markdown rendering untouched.

- `prerequisites.go`: new `StripPrerequisiteMarkers` removes each marker
line (plus an optional trailing blank line so no gap is left) while
preserving the content between them.
- `compose.go`: new `composedReadme` helper applies the strip in both
`Compose` return paths; returns `nil` for an empty README, preserving
the existing "omit README.md when empty" behavior.
- `ExtractPrerequisites` still reads the markers from the source
`BaseReadme`, so the builder's Prerequisites step is unaffected.

## Testing

- `TestStripPrerequisiteMarkers` covers both markers, no markers, single
markers, missing trailing blank line, and end-of-file cases.
- Extended the compose test to assert the delivered README contains no
marker text.
- `go test ./coderd/templatebuilder/`, `go vet`, and `golangci-lint run
./coderd/templatebuilder/...` all pass.

<details>
<summary>Approach considered</summary>

Two options were on the table:

1. Skip HTML comments in the shared `MemoizedMarkdown` component (fixes
any stray comment globally, but changes shared rendering behavior
app-wide).
2. Strip the markers from the README before inserting it into the
template zipball (targeted; markers have no purpose in the delivered
README since prerequisites are already extracted separately).

Option 2 was chosen for being contained and easily unit-tested.
</details>

---
_This PR was created by Coder Agents on behalf of @jeremyruppel._

---------

Co-authored-by: Andrew Aquino <dawneraq@gmail.com>
2026-09-04 19:14:10 +00:00

143 lines
3.4 KiB
Go

package templatebuilder_test
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/coder/coder/v2/coderd/templatebuilder"
)
func TestExtractPrerequisites(t *testing.T) {
t.Parallel()
tests := []struct {
name string
readme string
expected string
}{
{
name: "BothMarkers",
readme: "# Title\n\n" +
"<!-- prerequisites:start -->\n" +
"## Prerequisites\n\n" +
"Install Docker.\n" +
"<!-- prerequisites:end -->\n\n" +
"## Architecture\n",
expected: "## Prerequisites\n\nInstall Docker.",
},
{
name: "MultipleH2Sections",
readme: "# Title\n\n" +
"<!-- prerequisites:start -->\n" +
"## Prerequisites\n\n" +
"Auth stuff.\n\n" +
"## Required permissions\n\n" +
"Policy JSON.\n" +
"<!-- prerequisites:end -->\n\n" +
"## Architecture\n",
expected: "## Prerequisites\n\nAuth stuff.\n\n## Required permissions\n\nPolicy JSON.",
},
{
name: "NoMarkers",
readme: "# Title\n\n## Prerequisites\n\nSome content.\n\n## Architecture\n",
expected: "",
},
{
name: "StartMarkerOnly",
readme: "# Title\n\n" +
"<!-- prerequisites:start -->\n" +
"## Prerequisites\n\nContent.\n",
expected: "",
},
{
name: "EndMarkerOnly",
readme: "# Title\n\n" +
"## Prerequisites\n\nContent.\n" +
"<!-- prerequisites:end -->\n",
expected: "",
},
{
name: "EmptyBetweenMarkers",
readme: "<!-- prerequisites:start -->\n" +
"<!-- prerequisites:end -->\n",
expected: "",
},
{
name: "NestedH3Headings",
readme: "<!-- prerequisites:start -->\n" +
"## Prerequisites\n\n" +
"### Infrastructure\n\n" +
"Docker socket required.\n\n" +
"### Authentication\n\n" +
"Use kubeconfig.\n" +
"<!-- prerequisites:end -->\n",
expected: "## Prerequisites\n\n### Infrastructure\n\nDocker socket required.\n\n### Authentication\n\nUse kubeconfig.",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
result := templatebuilder.ExtractPrerequisites(tt.readme)
require.Equal(t, tt.expected, result)
})
}
}
func TestStripPrerequisitesMarkers(t *testing.T) {
t.Parallel()
tests := []struct {
name string
readme string
expected string
}{
{
name: "BothMarkers",
readme: "# Title\n\n" +
"<!-- prerequisites:start -->\n\n" +
"## Prerequisites\n\n" +
"Install Docker.\n\n" +
"<!-- prerequisites:end -->\n\n" +
"## Architecture\n",
expected: "# Title\n\n## Prerequisites\n\nInstall Docker.\n\n## Architecture\n",
},
{
name: "NoMarkers",
readme: "# Title\n\n## Prerequisites\n\nSome content.\n",
expected: "# Title\n\n## Prerequisites\n\nSome content.\n",
},
{
name: "StartMarkerOnly",
readme: "# Title\n\n" +
"<!-- prerequisites:start -->\n\n" +
"## Prerequisites\n",
expected: "# Title\n\n## Prerequisites\n",
},
{
name: "MarkerWithoutTrailingBlankLine",
readme: "# Title\n" +
"<!-- prerequisites:start -->\n" +
"## Prerequisites\n",
expected: "# Title\n## Prerequisites\n",
},
{
name: "EndMarkerAtEOF",
readme: "Content.\n\n" +
"<!-- prerequisites:end -->\n",
expected: "Content.\n\n",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
result := templatebuilder.StripPrerequisitesMarkers(tt.readme)
require.Equal(t, tt.expected, result)
require.NotContains(t, result, "prerequisites:start")
require.NotContains(t, result, "prerequisites:end")
})
}
}