1// SPDX-License-Identifier: AGPL-3.0-or-later
2package blobdisk
3
4import (
5 "context"
6 "os"
7 "path/filepath"
8 "strings"
9 "testing"
10 "time"
11
12 "bnema.dev/ermite/internal/ports"
13 "github.com/stretchr/testify/require"
14)
15
16func age(t *testing.T, p string) {
17 t.Helper()
18 old := time.Now().Add(-2 * tmpGrace)
19 require.NoError(t, os.Chtimes(p, old, old))
20}
21
22func TestCheckReclaimsOldTmp(t *testing.T) {
23 root := t.TempDir()
24 s := New(root)
25 ctx := context.Background()
26 require.NoError(t, s.Check(ctx))
27 oldF, freshF := filepath.Join(root, "tmp", ".tmp-old"), filepath.Join(root, "tmp", ".tmp-fresh")
28 oldD := filepath.Join(root, "tmp", "mp-old")
29 require.NoError(t, os.WriteFile(oldF, []byte("x"), 0600))
30 require.NoError(t, os.WriteFile(freshF, []byte("x"), 0600))
31 require.NoError(t, os.MkdirAll(oldD, 0700))
32 require.NoError(t, os.WriteFile(filepath.Join(oldD, "metadata"), []byte("x"), 0600))
33 age(t, oldF)
34 age(t, oldD)
35 require.NoError(t, s.Check(ctx))
36 require.NoFileExists(t, oldF)
37 require.NoDirExists(t, oldD)
38 require.FileExists(t, freshF)
39}
40
41func TestListMultipartSkipsAndReclaimsMalformed(t *testing.T) {
42 root := t.TempDir()
43 s := New(root)
44 ctx := context.Background()
45 a, err := s.NewMultipart(ctx, "a")
46 require.NoError(t, err)
47 c, err := s.NewMultipart(ctx, "c")
48 require.NoError(t, err)
49 mk := func(id string, meta *string) string {
50 d := filepath.Join(root, "multipart", id)
51 require.NoError(t, os.Mkdir(d, 0700))
52 if meta != nil {
53 require.NoError(t, os.WriteFile(filepath.Join(d, "metadata"), []byte(*meta), 0600))
54 }
55 return d
56 }
57 trunc := `{"Key":`
58 freshBad := mk(strings.Repeat("1", 32), &trunc)
59 oldBad := mk(strings.Repeat("2", 32), nil)
60 oldTrunc := mk(strings.Repeat("3", 32), &trunc)
61 age(t, oldBad)
62 age(t, oldTrunc)
63 var got []string
64 require.NoError(t, s.ListMultipart(ctx, "", func(i ports.MultipartInfo) error { got = append(got, i.ID); return nil }))
65 require.ElementsMatch(t, []string{a.ID(), c.ID()}, got)
66 require.DirExists(t, freshBad)
67 require.NoDirExists(t, oldBad)
68 require.NoDirExists(t, oldTrunc)
69 entries, err := os.ReadDir(filepath.Join(root, "tmp"))
70 require.NoError(t, err)
71 require.Empty(t, entries)
72}
73
74func TestMkdirAllSyncSyncsParents(t *testing.T) {
75 root := t.TempDir()
76 s := New(root)
77 var synced []string
78 orig := syncDir
79 syncDir = func(p string) error { synced = append(synced, p); return orig(p) }
80 t.Cleanup(func() { syncDir = orig })
81 _, err := s.Put(context.Background(), "a/b/c/obj", strings.NewReader("hi"), 2)
82 require.NoError(t, err)
83 obj := filepath.Join(root, "objects")
84 for _, want := range []string{obj, filepath.Join(obj, "a"), filepath.Join(obj, "a/b"), filepath.Join(obj, "a/b/c")} {
85 require.Contains(t, synced, want)
86 }
87}