From 4ff456a6111d8553893308a84e9f0e992d4809bf Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Fri, 10 Apr 2026 09:48:07 +0300 Subject: processor: return error when markdown pre-scan cannot read .md claimedByMarkdown now wraps os.ReadFile failures instead of skipping, so Run fails fast and avoids wrong image claim state. Add regression test using an unreadable markdown file (Unix). Made-with: Cursor --- internal/processor/markdown_test.go | 35 +++++++++++++++++++++++++++++++++++ internal/processor/processor.go | 11 +++++++---- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/internal/processor/markdown_test.go b/internal/processor/markdown_test.go index a17b704..2445b53 100644 --- a/internal/processor/markdown_test.go +++ b/internal/processor/markdown_test.go @@ -3,7 +3,10 @@ package processor import ( "os" "path/filepath" + "runtime" "testing" + + "codeberg.org/snonux/snonux/internal/config" ) func TestFindLocalImages(t *testing.T) { @@ -88,3 +91,35 @@ func TestFindLocalImages(t *testing.T) { }) } } + +func TestRun_UnreadableMarkdownPreScanFails(t *testing.T) { + t.Parallel() + if runtime.GOOS == "windows" { + t.Skip("chmod does not reliably deny read for owned files on Windows") + } + + base := t.TempDir() + inputDir := filepath.Join(base, "inbox") + outputDir := filepath.Join(base, "out") + if err := os.MkdirAll(inputDir, 0o755); err != nil { + t.Fatal(err) + } + if err := os.MkdirAll(outputDir, 0o755); err != nil { + t.Fatal(err) + } + + mdPath := filepath.Join(inputDir, "note.md") + if err := os.WriteFile(mdPath, []byte("# x\n"), 0o644); err != nil { + t.Fatal(err) + } + if err := os.Chmod(mdPath, 0); err != nil { + t.Fatal(err) + } + t.Cleanup(func() { _ = os.Chmod(mdPath, 0o644) }) + + cfg := &config.Config{InputDir: inputDir, OutputDir: outputDir} + _, err := Run(cfg) + if err == nil { + t.Fatal("Run: expected error when markdown pre-scan cannot read a .md file") + } +} diff --git a/internal/processor/processor.go b/internal/processor/processor.go index 2e2d626..b6fa40b 100644 --- a/internal/processor/processor.go +++ b/internal/processor/processor.go @@ -33,7 +33,10 @@ func Run(cfg *config.Config) (int, error) { // Pre-scan markdown files to discover which image filenames they claim. // Claimed images are excluded from independent processing. - claimed := claimedByMarkdown(entries, cfg.InputDir) + claimed, err := claimedByMarkdown(entries, cfg.InputDir) + if err != nil { + return 0, err + } count := 0 @@ -59,7 +62,7 @@ func Run(cfg *config.Config) (int, error) { // claimedByMarkdown scans all .md entries in inputDir and returns a set of // image filenames that are referenced within those markdown files. // Those images should be embedded in the markdown post, not processed alone. -func claimedByMarkdown(entries []os.DirEntry, inputDir string) map[string]bool { +func claimedByMarkdown(entries []os.DirEntry, inputDir string) (map[string]bool, error) { claimed := make(map[string]bool) for _, entry := range entries { @@ -70,7 +73,7 @@ func claimedByMarkdown(entries []os.DirEntry, inputDir string) map[string]bool { mdPath := filepath.Join(inputDir, entry.Name()) data, err := os.ReadFile(mdPath) if err != nil { - continue + return nil, fmt.Errorf("read markdown for image claims %s: %w", entry.Name(), err) } for _, imgName := range findLocalImages(string(data), inputDir) { @@ -78,7 +81,7 @@ func claimedByMarkdown(entries []os.DirEntry, inputDir string) map[string]bool { } } - return claimed + return claimed, nil } // processFile processes a single input file into a new post directory. -- cgit v1.2.3