diff options
Diffstat (limited to 'internal/processor')
| -rw-r--r-- | internal/processor/processor.go | 15 | ||||
| -rw-r--r-- | internal/processor/processor_test.go | 52 |
2 files changed, 63 insertions, 4 deletions
diff --git a/internal/processor/processor.go b/internal/processor/processor.go index 43f5023..d781a8b 100644 --- a/internal/processor/processor.go +++ b/internal/processor/processor.go @@ -103,7 +103,10 @@ func claimedByMarkdown(entries []os.DirEntry, inputDir string) (map[string]bool, // The source file is removed from the input dir on success. func processFile(srcPath, postsDir string) error { now := time.Now().UTC() - id := uniqueID(postsDir, now) + id, err := uniqueID(postsDir, now) + if err != nil { + return fmt.Errorf("generate unique ID: %w", err) + } postDir := filepath.Join(postsDir, id) if err := os.MkdirAll(postDir, 0o755); err != nil { @@ -260,11 +263,15 @@ func copyLocalImages(filenames []string, sourceDir, postDir string) ([]string, e // uniqueID generates a post ID for the given time that does not already exist // as a directory under postsDir. Appends a numeric suffix if needed. -func uniqueID(postsDir string, t time.Time) string { +func uniqueID(postsDir string, t time.Time) (string, error) { for i := 0; ; i++ { id := post.NewID(t, i) - if _, err := os.Stat(filepath.Join(postsDir, id)); os.IsNotExist(err) { - return id + _, err := os.Stat(filepath.Join(postsDir, id)) + if err != nil { + if os.IsNotExist(err) { + return id, nil + } + return "", fmt.Errorf("stat post dir %s: %w", id, err) } } } diff --git a/internal/processor/processor_test.go b/internal/processor/processor_test.go index d04a0d5..3b34675 100644 --- a/internal/processor/processor_test.go +++ b/internal/processor/processor_test.go @@ -6,6 +6,7 @@ import ( "os" "path/filepath" "testing" + "time" "codeberg.org/snonux/snonux/internal/config" "codeberg.org/snonux/snonux/internal/post" @@ -141,6 +142,57 @@ func TestRun_markdown(t *testing.T) { } } +func TestUniqueID_new(t *testing.T) { + t.Parallel() + + postsDir := t.TempDir() + id, err := uniqueID(postsDir, time.Now().UTC()) + if err != nil { + t.Fatalf("uniqueID: %v", err) + } + if id == "" { + t.Fatal("expected non-empty id") + } +} + +func TestUniqueID_collision(t *testing.T) { + t.Parallel() + + postsDir := t.TempDir() + now := time.Now().UTC() + + // Pre-create the first expected directory so uniqueID must pick the next suffix. + firstID := post.NewID(now, 0) + if err := os.MkdirAll(filepath.Join(postsDir, firstID), 0o755); err != nil { + t.Fatal(err) + } + + id, err := uniqueID(postsDir, now) + if err != nil { + t.Fatalf("uniqueID: %v", err) + } + if id == firstID { + t.Fatalf("expected different id, got %q", id) + } +} + +func TestUniqueID_statError(t *testing.T) { + t.Parallel() + + // Create a postsDir and remove read permission so Stat fails with + // a permission error rather than IsNotExist. + postsDir := t.TempDir() + if err := os.Chmod(postsDir, 0o000); err != nil { + t.Fatal(err) + } + defer os.Chmod(postsDir, 0o755) // restore for cleanup + + _, err := uniqueID(postsDir, time.Now().UTC()) + if err == nil { + t.Fatal("expected error when stat fails") + } +} + func TestRun_markdownWithLocalImage(t *testing.T) { t.Parallel() |
