Go · 4960 bytes Raw Blame History
1 // SPDX-License-Identifier: AGPL-3.0-or-later
2
3 package storage
4
5 import (
6 "bytes"
7 "context"
8 "errors"
9 "io"
10 "strings"
11 "testing"
12 "time"
13 )
14
15 func TestMemoryStore_PutGetStat(t *testing.T) {
16 t.Parallel()
17 ctx := context.Background()
18 m := NewMemoryStore()
19
20 res, err := m.Put(ctx, "k1", strings.NewReader("hello"), PutOpts{ContentType: "text/plain"})
21 if err != nil {
22 t.Fatalf("Put: %v", err)
23 }
24 if res.Size != 5 || res.ETag == "" {
25 t.Fatalf("unexpected put result: %+v", res)
26 }
27
28 rc, meta, err := m.Get(ctx, "k1")
29 if err != nil {
30 t.Fatalf("Get: %v", err)
31 }
32 defer func() { _ = rc.Close() }()
33 body, _ := io.ReadAll(rc)
34 if string(body) != "hello" {
35 t.Fatalf("body = %q, want hello", body)
36 }
37 if meta.ContentType != "text/plain" || meta.Size != 5 {
38 t.Fatalf("meta = %+v", meta)
39 }
40
41 stat, err := m.Stat(ctx, "k1")
42 if err != nil {
43 t.Fatalf("Stat: %v", err)
44 }
45 if stat.ETag != res.ETag {
46 t.Fatalf("etag mismatch: %s vs %s", stat.ETag, res.ETag)
47 }
48 }
49
50 func TestMemoryStore_GetMissing(t *testing.T) {
51 t.Parallel()
52 m := NewMemoryStore()
53 if _, _, err := m.Get(context.Background(), "absent"); !errors.Is(err, ErrNotFound) {
54 t.Fatalf("expected ErrNotFound, got %v", err)
55 }
56 }
57
58 func TestMemoryStore_PutIfNoneMatch(t *testing.T) {
59 t.Parallel()
60 ctx := context.Background()
61 m := NewMemoryStore()
62
63 if _, err := m.Put(ctx, "k", strings.NewReader("first"), PutOpts{}); err != nil {
64 t.Fatalf("first put: %v", err)
65 }
66 _, err := m.Put(ctx, "k", strings.NewReader("second"), PutOpts{IfNoneMatch: "*"})
67 if !errors.Is(err, ErrPreconditionFailed) {
68 t.Fatalf("expected ErrPreconditionFailed, got %v", err)
69 }
70
71 // Without IfNoneMatch, overwrite succeeds.
72 if _, err := m.Put(ctx, "k", strings.NewReader("third"), PutOpts{}); err != nil {
73 t.Fatalf("overwrite: %v", err)
74 }
75 rc, _, _ := m.Get(ctx, "k")
76 defer func() { _ = rc.Close() }()
77 body, _ := io.ReadAll(rc)
78 if string(body) != "third" {
79 t.Fatalf("got %q, want third", body)
80 }
81 }
82
83 func TestMemoryStore_DeleteIdempotent(t *testing.T) {
84 t.Parallel()
85 ctx := context.Background()
86 m := NewMemoryStore()
87 if err := m.Delete(ctx, "missing"); err != nil {
88 t.Fatalf("delete missing: %v", err)
89 }
90 _, _ = m.Put(ctx, "k", strings.NewReader("x"), PutOpts{})
91 if err := m.Delete(ctx, "k"); err != nil {
92 t.Fatalf("delete: %v", err)
93 }
94 if _, err := m.Stat(ctx, "k"); !errors.Is(err, ErrNotFound) {
95 t.Fatalf("post-delete stat = %v, want ErrNotFound", err)
96 }
97 }
98
99 func TestMemoryStore_ListRecursiveAndDelimited(t *testing.T) {
100 t.Parallel()
101 ctx := context.Background()
102 m := NewMemoryStore()
103 for _, k := range []string{
104 "avatars/alice/64.png",
105 "avatars/alice/128.png",
106 "avatars/bob/64.png",
107 "attachments/issue-1/x.txt",
108 } {
109 if _, err := m.Put(ctx, k, strings.NewReader("x"), PutOpts{}); err != nil {
110 t.Fatalf("seed %s: %v", k, err)
111 }
112 }
113
114 rec, err := m.List(ctx, "avatars/", ListOpts{Recursive: true})
115 if err != nil {
116 t.Fatalf("recursive list: %v", err)
117 }
118 if len(rec.Objects) != 3 {
119 t.Fatalf("recursive: got %d objects, want 3", len(rec.Objects))
120 }
121
122 del, err := m.List(ctx, "avatars/", ListOpts{})
123 if err != nil {
124 t.Fatalf("delimited list: %v", err)
125 }
126 if len(del.CommonPrefixes) != 2 {
127 t.Fatalf("delimited: got %d common prefixes, want 2: %v", len(del.CommonPrefixes), del.CommonPrefixes)
128 }
129 }
130
131 func TestMemoryStore_LargeRoundTrip(t *testing.T) {
132 t.Parallel()
133 ctx := context.Background()
134 m := NewMemoryStore()
135 body := bytes.Repeat([]byte{0xcd}, 5*1024*1024) // 5 MiB
136 if _, err := m.Put(ctx, "big", bytes.NewReader(body), PutOpts{ContentLength: int64(len(body))}); err != nil {
137 t.Fatalf("Put big: %v", err)
138 }
139 rc, meta, err := m.Get(ctx, "big")
140 if err != nil {
141 t.Fatalf("Get big: %v", err)
142 }
143 defer func() { _ = rc.Close() }()
144 got, _ := io.ReadAll(rc)
145 if !bytes.Equal(got, body) {
146 t.Fatalf("body mismatch (len got=%d want=%d)", len(got), len(body))
147 }
148 if meta.Size != int64(len(body)) {
149 t.Fatalf("meta size = %d, want %d", meta.Size, len(body))
150 }
151 }
152
153 func TestMemoryStore_SignedURL(t *testing.T) {
154 t.Parallel()
155 m := NewMemoryStore()
156 u, err := m.SignedURL(context.Background(), "k1", time.Minute, "GET")
157 if err != nil {
158 t.Fatalf("SignedURL: %v", err)
159 }
160 if !strings.HasPrefix(u, "mem://k1") {
161 t.Fatalf("unexpected url: %s", u)
162 }
163 if _, err := m.SignedURL(context.Background(), "k1", time.Minute, "POST"); err == nil {
164 t.Fatal("expected error for unsupported method")
165 }
166 }
167
168 func TestQuota(t *testing.T) {
169 t.Parallel()
170 q := Quota{Used: 100, Limit: 1000}
171 if q.Available() != 900 {
172 t.Fatalf("Available = %d, want 900", q.Available())
173 }
174 if q.WouldExceed(800) {
175 t.Fatal("WouldExceed(800) = true, want false")
176 }
177 if !q.WouldExceed(901) {
178 t.Fatal("WouldExceed(901) = false, want true")
179 }
180 unlimited := Quota{Used: 1 << 40}
181 if unlimited.Available() != -1 {
182 t.Fatalf("unlimited Available = %d, want -1", unlimited.Available())
183 }
184 if unlimited.WouldExceed(1 << 50) {
185 t.Fatal("unlimited WouldExceed = true, want false")
186 }
187 }
188