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()) } }