Go 171 lines
package docs
import (
"os"
"path/filepath"
"strings"
"testing"
)
func TestRefresh_ReplacesMarkerBlock(t *testing.T) {
root := t.TempDir()
p := Params{Project: filepath.Base(root), Version: "v2.8.0", HasUsage: true}
if _, err := Scaffold(TargetReadme, root, false, p); err != nil {
t.Fatalf("Scaffold: %v", err)
}
full := filepath.Join(root, "README.md")
before, err := os.ReadFile(full)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(before), "[docs/USAGE.md](docs/USAGE.md)") {
t.Fatalf("scaffold missing initial USAGE link:\n%s", before)
}
if !strings.Contains(string(before), "<!-- eeco:docs:start -->") {
t.Fatalf("scaffold missing start marker:\n%s", before)
}
// Add an operator-edited paragraph below the markers; refresh must
// preserve it byte-identically.
const operatorAddition = "\nOperator's free-form prose stays here.\n"
if err := os.WriteFile(full, append(before, []byte(operatorAddition)...), 0o644); err != nil {
t.Fatal(err)
}
// Refresh with HasArch flipped on — the See also list grows.
p2 := Params{Project: filepath.Base(root), Version: "v2.8.0", HasUsage: true, HasArch: true}
rep, err := Refresh(TargetReadme, root, p2)
if err != nil {
t.Fatalf("Refresh: %v", err)
}
if rep.Action != RefreshReplaced {
t.Errorf("Action = %q, want %q", rep.Action, RefreshReplaced)
}
after, err := os.ReadFile(full)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(after), "[docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)") {
t.Errorf("refreshed body missing new ARCHITECTURE link:\n%s", after)
}
if !strings.HasSuffix(string(after), operatorAddition) {
t.Errorf("refresh discarded operator addition; tail:\n%s", after[max(0, len(after)-200):])
}
}
func TestRefresh_AutoInitOnLegacyScaffold(t *testing.T) {
root := t.TempDir()
full := filepath.Join(root, "README.md")
const legacy = "# legacy README\n\nOlder operator content; no markers.\n"
if err := os.WriteFile(full, []byte(legacy), 0o644); err != nil {
t.Fatal(err)
}
rep, err := Refresh(TargetReadme, root, Params{Project: "demo", Version: "v2.8.0", HasUsage: true})
if err != nil {
t.Fatalf("Refresh: %v", err)
}
if rep.Action != RefreshAppended {
t.Errorf("Action = %q, want %q", rep.Action, RefreshAppended)
}
body, err := os.ReadFile(full)
if err != nil {
t.Fatal(err)
}
if !strings.HasPrefix(string(body), legacy) {
t.Errorf("legacy prefix mutated:\n%s", body)
}
if !strings.Contains(string(body), "<!-- eeco:docs:start -->") {
t.Errorf("auto-init missing start marker:\n%s", body)
}
if !strings.Contains(string(body), "<!-- eeco:docs:end -->") {
t.Errorf("auto-init missing end marker:\n%s", body)
}
if !strings.Contains(string(body), "[docs/USAGE.md](docs/USAGE.md)") {
t.Errorf("auto-init missing rendered body:\n%s", body)
}
}
func TestRefresh_MissingFileRefuses(t *testing.T) {
root := t.TempDir()
_, err := Refresh(TargetReadme, root, Params{Project: "demo", Version: "v2.8.0"})
if err == nil {
t.Fatal("Refresh on missing file should error")
}
if !strings.Contains(err.Error(), "does not exist") {
t.Errorf("error should hint at missing file, got %q", err)
}
if !strings.Contains(err.Error(), "eeco docs new") {
t.Errorf("error should point to docs new, got %q", err)
}
}
func TestRefresh_MalformedMarkersRefuse(t *testing.T) {
root := t.TempDir()
full := filepath.Join(root, "README.md")
const bad = "# header\n<!-- eeco:docs:start -->\nbody\n<!-- eeco:docs:start -->\n"
if err := os.WriteFile(full, []byte(bad), 0o644); err != nil {
t.Fatal(err)
}
before, err := os.ReadFile(full)
if err != nil {
t.Fatal(err)
}
_, err = Refresh(TargetReadme, root, Params{Project: "demo", Version: "v2.8.0"})
if err == nil {
t.Fatal("Refresh on malformed markers should error")
}
if !strings.Contains(err.Error(), "nested") {
t.Errorf("error should name the parse failure, got %q", err)
}
after, err := os.ReadFile(full)
if err != nil {
t.Fatal(err)
}
if string(after) != string(before) {
t.Errorf("Refresh mutated file on parse error:\nbefore: %q\nafter: %q", before, after)
}
}
func TestRefresh_IgnoresFencedMarkers(t *testing.T) {
root := t.TempDir()
full := filepath.Join(root, "README.md")
// Real marker pair after a fenced block that mentions the markers.
body := "# header\n\n```\n<!-- eeco:docs:start -->\nfenced\n<!-- eeco:docs:end -->\n```\n\n<!-- eeco:docs:start -->\noriginal body\n<!-- eeco:docs:end -->\n\ntrailing operator note\n"
if err := os.WriteFile(full, []byte(body), 0o644); err != nil {
t.Fatal(err)
}
rep, err := Refresh(TargetReadme, root, Params{Project: "demo", Version: "v2.8.0", HasUsage: true})
if err != nil {
t.Fatalf("Refresh: %v", err)
}
if rep.Action != RefreshReplaced {
t.Errorf("Action = %q, want %q", rep.Action, RefreshReplaced)
}
after, err := os.ReadFile(full)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(after), "```\n<!-- eeco:docs:start -->\nfenced\n<!-- eeco:docs:end -->\n```") {
t.Errorf("fenced markers were touched:\n%s", after)
}
if !strings.Contains(string(after), "trailing operator note") {
t.Errorf("trailing operator note discarded:\n%s", after)
}
if strings.Contains(string(after), "original body") {
t.Errorf("real block not replaced:\n%s", after)
}
}
func TestRefresh_UnknownTarget(t *testing.T) {
root := t.TempDir()
if _, err := Refresh(Target("nope"), root, Params{}); err == nil {
t.Fatal("expected error for unknown target")
}
}