Files
mrcharles 10e930e46d mopac-pdf v0: markdown to beautiful PDFs via digest-pinned typst
Go CLI that turns the markdown currency of this stack (Redmine notes,
Discourse posts, briefing output) into typeset PDFs: two embedded typst
templates (report with title page/TOC/headers, dense brief), front-matter
(title/subtitle/author/date/classification/template), GFM tables, and
bar charts rendered in pure Go (go-chart) from fenced chart data blocks.
Engine is a prebuilt typst 0.15.1 container pinned by digest and run
--network none; PDF bytes to stdout or -o. Exit codes 0/1/2.

All dev in docker (dev.sh/Makefile); unit tests + golden typst fixtures
plus a host-side smoke against the real engine container.

Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-29 05:44:24 -05:00

161 lines
4.3 KiB
Go

package cli
import (
"bytes"
"os"
"path/filepath"
"strings"
"testing"
)
// stubDocker prints a fake PDF; real engine behavior is covered by engine
// tests + smoke against the actual container.
func stubDocker(t *testing.T, fail bool) string {
t.Helper()
script := filepath.Join(t.TempDir(), "docker-stub")
body := "#!/bin/sh\n"
if fail {
body += "echo 'typst: compile error' >&2\nexit 1\n"
} else {
body += "printf '%%PDF-1.7 stub\\n'\n"
}
if err := os.WriteFile(script, []byte(body), 0o755); err != nil {
t.Fatal(err)
}
t.Setenv("MOPAC_PDF_DOCKER", script)
return script
}
func fixture(t *testing.T) string {
t.Helper()
data, err := os.ReadFile(filepath.Join("..", "..", "testdata", "sample-brief.md"))
if err != nil {
t.Fatal(err)
}
path := filepath.Join(t.TempDir(), "in.md")
if err := os.WriteFile(path, data, 0o644); err != nil {
t.Fatal(err)
}
return path
}
func TestRunStdoutPDF(t *testing.T) {
stubDocker(t, false)
var stdout, stderr bytes.Buffer
code := Run([]string{fixture(t)}, &stdout, &stderr)
if code != 0 {
t.Fatalf("code=%d stderr=%s", code, stderr.String())
}
if !bytes.HasPrefix(stdout.Bytes(), []byte("%PDF-")) {
t.Fatalf("stdout not PDF: %q", stdout.String())
}
}
func TestRunOutputFile(t *testing.T) {
stubDocker(t, false)
out := filepath.Join(t.TempDir(), "out.pdf")
var stdout, stderr bytes.Buffer
if code := Run([]string{"-o", out, fixture(t)}, &stdout, &stderr); code != 0 {
t.Fatalf("code=%d stderr=%s", code, stderr.String())
}
data, err := os.ReadFile(out)
if err != nil || !bytes.HasPrefix(data, []byte("%PDF-")) {
t.Fatalf("out.pdf: %v", err)
}
}
func TestRunTemplateOverrides(t *testing.T) {
stubDocker(t, false)
// -t wins over front-matter template key.
var stdout, stderr bytes.Buffer
if code := Run([]string{"-t", "report", fixture(t)}, &stdout, &stderr); code != 0 {
t.Fatalf("code=%d stderr=%s", code, stderr.String())
}
// Front-matter default (brief) also resolves.
if code := Run([]string{fixture(t)}, &stdout, &stderr); code != 0 {
t.Fatalf("code=%d stderr=%s", code, stderr.String())
}
}
func TestRunFlagNeedsValue(t *testing.T) {
stubDocker(t, false)
var stdout, stderr bytes.Buffer
for _, args := range [][]string{{"-o"}, {"-t"}, {"-T"}} {
if code := Run(args, &stdout, &stderr); code != 1 {
t.Fatalf("args=%v code=%d", args, code)
}
if !strings.Contains(stderr.String(), "needs a value") {
t.Fatalf("args=%v stderr: %s", args, stderr.String())
}
}
}
func TestRunTooManyInputs(t *testing.T) {
stubDocker(t, false)
var stdout, stderr bytes.Buffer
if code := Run([]string{"a.md", "b.md"}, &stdout, &stderr); code != 1 {
t.Fatalf("code=%d", code)
}
}
func TestRunUnknownFlag(t *testing.T) {
stubDocker(t, false)
var stdout, stderr bytes.Buffer
if code := Run([]string{"--bogus"}, &stdout, &stderr); code != 1 {
t.Fatalf("code=%d", code)
}
if !strings.Contains(stderr.String(), "unknown flag") {
t.Fatalf("stderr: %s", stderr.String())
}
}
func TestRunUnknownTemplate(t *testing.T) {
stubDocker(t, false)
var stdout, stderr bytes.Buffer
if code := Run([]string{"-t", "nope", fixture(t)}, &stdout, &stderr); code != 1 {
t.Fatalf("code=%d", code)
}
if !strings.Contains(stderr.String(), "not found") {
t.Fatalf("stderr: %s", stderr.String())
}
}
func TestRunEngineFailure(t *testing.T) {
stubDocker(t, true)
var stdout, stderr bytes.Buffer
if code := Run([]string{fixture(t)}, &stdout, &stderr); code != 2 {
t.Fatalf("engine failure must exit 2, got %d", code)
}
if !strings.Contains(stderr.String(), "typst: compile error") {
t.Fatalf("engine stderr must surface: %s", stderr.String())
}
}
func TestRunMissingInput(t *testing.T) {
stubDocker(t, false)
var stdout, stderr bytes.Buffer
if code := Run([]string{"/does/not/exist.md"}, &stdout, &stderr); code != 1 {
t.Fatalf("code=%d", code)
}
}
func TestRunVersion(t *testing.T) {
var stdout, stderr bytes.Buffer
if code := Run([]string{"-V"}, &stdout, &stderr); code != 0 {
t.Fatalf("code=%d", code)
}
if !strings.Contains(stdout.String(), Version) {
t.Fatalf("stdout: %s", stdout.String())
}
}
func TestRunHelp(t *testing.T) {
var stdout, stderr bytes.Buffer
if code := Run([]string{"--help"}, &stdout, &stderr); code != 0 {
t.Fatalf("code=%d", code)
}
if !strings.Contains(stdout.String(), "Usage:") {
t.Fatalf("stdout: %s", stdout.String())
}
}