Add Markdown HTML publication

This commit is contained in:
2026-05-31 02:28:48 +00:00
parent e296361042
commit 5408f14195
21 changed files with 628 additions and 47 deletions

View File

@@ -4,6 +4,7 @@ import (
"bytes"
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
@@ -34,7 +35,7 @@ func TestRunDryRunPrintsConfigSummary(t *testing.T) {
for _, want := range []string{
"Configured pipelines: 1",
"- reports: source=local bundles=1 destinations=1",
"bundle=. destination=archive action=publish_new",
"bundle=. destination=archive action=publish_new outputs=report.md,summary.txt",
} {
if !strings.Contains(output, want) {
t.Fatalf("Run() output = %q, want substring %q", output, want)
@@ -72,6 +73,103 @@ func TestRunPublishesNewLocalBundle(t *testing.T) {
}
}
func TestRunPublishesHTMLOnly(t *testing.T) {
sourceRoot := t.TempDir()
destinationRoot := t.TempDir()
writeSourceBundle(t, sourceRoot, "", testBundleOptions{})
err := Run(context.Background(), RunOptions{ConfigPath: writeLocalConfigWithPolicy(t, sourceRoot, destinationRoot, false, true)})
if err != nil {
t.Fatalf("Run() error = %v", err)
}
assertFileContains(t, filepath.Join(destinationRoot, "report.html"), "<h1>Report</h1>")
if _, err := os.Stat(filepath.Join(destinationRoot, "report.md")); !os.IsNotExist(err) {
t.Fatalf("report.md stat error = %v, want not exist", err)
}
destinationState := readStateFile(t, filepath.Join(destinationRoot, ".distributor.json"))
if got, want := len(destinationState.Outputs), 1; got != want {
t.Fatalf("state output count = %d, want %d", got, want)
}
output := destinationState.Outputs[0]
if output.Kind != state.OutputKindGenerated || output.Transform != "markdown_to_html" || output.Path != "report.html" || output.SourcePath != "report.md" {
t.Fatalf("generated output metadata = %#v", output)
}
}
func TestRunPublishesSourceAndHTML(t *testing.T) {
sourceRoot := t.TempDir()
destinationRoot := t.TempDir()
writeSourceBundle(t, sourceRoot, "", testBundleOptions{})
err := Run(context.Background(), RunOptions{ConfigPath: writeLocalConfigWithPolicy(t, sourceRoot, destinationRoot, true, true)})
if err != nil {
t.Fatalf("Run() error = %v", err)
}
assertFile(t, filepath.Join(destinationRoot, "report.md"), "# Report\nSunny.\n")
assertFileContains(t, filepath.Join(destinationRoot, "report.html"), "<p>Sunny.</p>")
assertFile(t, filepath.Join(destinationRoot, "summary.txt"), "Summary\n")
destinationState := readStateFile(t, filepath.Join(destinationRoot, ".distributor.json"))
if got, want := len(destinationState.Outputs), 3; got != want {
t.Fatalf("state output count = %d, want %d", got, want)
}
}
func TestRunDoesNotMutateSourceBundle(t *testing.T) {
sourceRoot := t.TempDir()
destinationRoot := t.TempDir()
writeSourceBundle(t, sourceRoot, "", testBundleOptions{})
sourcePath := filepath.Join(sourceRoot, "report.md")
before, err := os.ReadFile(sourcePath)
if err != nil {
t.Fatalf("read source before: %v", err)
}
err = Run(context.Background(), RunOptions{ConfigPath: writeLocalConfigWithPolicy(t, sourceRoot, destinationRoot, true, true)})
if err != nil {
t.Fatalf("Run() error = %v", err)
}
after, err := os.ReadFile(sourcePath)
if err != nil {
t.Fatalf("read source after: %v", err)
}
if string(after) != string(before) {
t.Fatalf("source changed from %q to %q", before, after)
}
}
func TestRunFailsOnOutputPathCollision(t *testing.T) {
sourceRoot := t.TempDir()
destinationRoot := t.TempDir()
writeSourceBundle(t, sourceRoot, "", testBundleOptions{ExtraFiles: []testFile{{Path: "report.html", Data: "<p>source html</p>\n"}}})
err := Run(context.Background(), RunOptions{ConfigPath: writeLocalConfigWithPolicy(t, sourceRoot, destinationRoot, true, true)})
if err == nil || !strings.Contains(err.Error(), "destination output path collision") {
t.Fatalf("Run() error = %v, want collision", err)
}
if entries, err := os.ReadDir(destinationRoot); err != nil || len(entries) != 0 {
t.Fatalf("destination entries = %v err=%v, want empty", entries, err)
}
}
func TestRunDryRunReportsGeneratedOutputs(t *testing.T) {
sourceRoot := t.TempDir()
destinationRoot := t.TempDir()
writeSourceBundle(t, sourceRoot, "", testBundleOptions{})
var stdout bytes.Buffer
err := Run(context.Background(), RunOptions{
ConfigPath: writeLocalConfigWithPolicy(t, sourceRoot, destinationRoot, false, true),
DryRun: true,
Stdout: &stdout,
})
if err != nil {
t.Fatalf("Run() error = %v", err)
}
if !strings.Contains(stdout.String(), "outputs=report.html") {
t.Fatalf("stdout = %q, want generated output path", stdout.String())
}
}
func TestRunSkipsWhenDestinationStateMatches(t *testing.T) {
sourceRoot := t.TempDir()
destinationRoot := t.TempDir()
@@ -194,8 +292,14 @@ func TestRunDryRunDoesNotWrite(t *testing.T) {
}
type testBundleOptions struct {
ID string
Created time.Time
ID string
Created time.Time
ExtraFiles []testFile
}
type testFile struct {
Path string
Data string
}
func writeSourceBundle(t *testing.T, root, relative string, opts testBundleOptions) bundle.Manifest {
@@ -217,6 +321,12 @@ func writeSourceBundle(t *testing.T, root, relative string, opts testBundleOptio
{path: "report.md", data: "# Report\nSunny.\n"},
{path: "summary.txt", data: "Summary\n"},
}
for _, extra := range opts.ExtraFiles {
files = append(files, struct {
path string
data string
}{path: extra.Path, data: extra.Data})
}
manifestFiles := make([]bundle.ManifestFile, 0, len(files))
for _, file := range files {
if err := os.WriteFile(filepath.Join(bundleRoot, filepath.FromSlash(file.path)), []byte(file.data), 0o600); err != nil {
@@ -248,6 +358,19 @@ func writeSourceBundle(t *testing.T, root, relative string, opts testBundleOptio
func writeLocalConfig(t *testing.T, sourceRoot, destinationRoot string) string {
t.Helper()
return writeLocalConfigWithPolicy(t, sourceRoot, destinationRoot, true, false)
}
func writeLocalConfigWithPolicy(t *testing.T, sourceRoot, destinationRoot string, publishSource, publishHTML bool) string {
t.Helper()
transformConfig := ""
if publishHTML {
transformConfig = `
transform:
markdown_to_html:
enabled: true
mode: sidecar`
}
return writeConfigFile(t, `
pipelines:
- id: reports
@@ -259,8 +382,8 @@ pipelines:
backend: local
path: `+destinationRoot+`
publish:
source: true
html: false
source: `+fmt.Sprintf("%t", publishSource)+`
html: `+fmt.Sprintf("%t", publishHTML)+transformConfig+`
`)
}
@@ -341,3 +464,14 @@ func assertFile(t *testing.T, path, want string) {
t.Fatalf("%s = %q, want %q", path, got, want)
}
}
func assertFileContains(t *testing.T, path, want string) {
t.Helper()
data, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read file %s: %v", path, err)
}
if !strings.Contains(string(data), want) {
t.Fatalf("%s = %q, want substring %q", path, data, want)
}
}