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), "") { 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), "") { t.Errorf("auto-init missing start marker:\n%s", body) } if !strings.Contains(string(body), "") { 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\nbody\n\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\nfenced\n\n```\n\n\noriginal body\n\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\nfenced\n\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") } }