summaryrefslogtreecommitdiff
path: root/internal/processor
diff options
context:
space:
mode:
Diffstat (limited to 'internal/processor')
-rw-r--r--internal/processor/processor.go15
-rw-r--r--internal/processor/processor_test.go52
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()