A container registry that uses the AT Protocol for manifest storage and S3 for blob storage.
1package s3
2
3import (
4 "testing"
5
6 "atcr.io/pkg/logging"
7)
8
9func TestNewS3Service_MissingBucket(t *testing.T) {
10 t.Cleanup(logging.SetupTestLogger())
11
12 params := map[string]any{
13 "region": "us-east-1",
14 "accesskey": "test-key",
15 "secretkey": "test-secret",
16 // Missing bucket
17 }
18
19 _, err := NewS3Service(params)
20 if err == nil {
21 t.Error("Expected error when bucket is missing")
22 }
23}
24
25func TestNewS3Service_Success(t *testing.T) {
26 t.Cleanup(logging.SetupTestLogger())
27
28 params := map[string]any{
29 "bucket": "test-bucket",
30 "region": "us-west-2",
31 "accesskey": "test-access-key",
32 "secretkey": "test-secret-key",
33 }
34
35 service, err := NewS3Service(params)
36 if err != nil {
37 t.Fatalf("Expected success, got error: %v", err)
38 }
39
40 if service.Client == nil {
41 t.Error("Expected Client to be initialized")
42 }
43 if service.Bucket != "test-bucket" {
44 t.Errorf("Expected Bucket=test-bucket, got %s", service.Bucket)
45 }
46 if service.PathPrefix != "" {
47 t.Errorf("Expected empty PathPrefix, got %s", service.PathPrefix)
48 }
49}
50
51func TestNewS3Service_WithEndpoint(t *testing.T) {
52 t.Cleanup(logging.SetupTestLogger())
53
54 params := map[string]any{
55 "bucket": "test-bucket",
56 "region": "us-east-1",
57 "accesskey": "test-key",
58 "secretkey": "test-secret",
59 "regionendpoint": "https://s3.storj.io",
60 }
61
62 service, err := NewS3Service(params)
63 if err != nil {
64 t.Fatalf("Expected success with custom endpoint, got error: %v", err)
65 }
66
67 if service.Client == nil {
68 t.Error("Expected Client to be initialized")
69 }
70 if service.Bucket != "test-bucket" {
71 t.Errorf("Expected Bucket=test-bucket, got %s", service.Bucket)
72 }
73}
74
75func TestNewS3Service_DefaultRegion(t *testing.T) {
76 t.Cleanup(logging.SetupTestLogger())
77
78 params := map[string]any{
79 "bucket": "test-bucket",
80 "accesskey": "test-key",
81 "secretkey": "test-secret",
82 // No region specified - should use default
83 }
84
85 service, err := NewS3Service(params)
86 if err != nil {
87 t.Fatalf("Expected success with default region, got error: %v", err)
88 }
89
90 if service.Client == nil {
91 t.Error("Expected Client to be initialized")
92 }
93
94 // Note: We can't easily verify the region without accessing private fields
95 // but the fact that it didn't error means it used the default
96}
97
98func TestNewS3Service_WithPathPrefix(t *testing.T) {
99 t.Cleanup(logging.SetupTestLogger())
100
101 params := map[string]any{
102 "bucket": "test-bucket",
103 "region": "us-east-1",
104 "accesskey": "test-key",
105 "secretkey": "test-secret",
106 "rootdirectory": "/my/prefix/path",
107 }
108
109 service, err := NewS3Service(params)
110 if err != nil {
111 t.Fatalf("Expected success, got error: %v", err)
112 }
113
114 if service.PathPrefix != "my/prefix/path" {
115 t.Errorf("Expected PathPrefix=my/prefix/path (leading slash stripped), got %s", service.PathPrefix)
116 }
117}
118
119func TestNewS3Service_NoCredentials(t *testing.T) {
120 t.Cleanup(logging.SetupTestLogger())
121
122 params := map[string]any{
123 "bucket": "test-bucket",
124 "region": "us-east-1",
125 // No credentials - should allow IAM role auth
126 }
127
128 service, err := NewS3Service(params)
129 if err != nil {
130 t.Fatalf("Expected success without credentials (IAM role), got error: %v", err)
131 }
132
133 if service.Client == nil {
134 t.Error("Expected Client to be initialized")
135 }
136}
137
138func TestBlobPath_SHA256(t *testing.T) {
139 tests := []struct {
140 name string
141 digest string
142 expected string
143 }{
144 {
145 name: "standard sha256 digest",
146 digest: "sha256:abc123def456",
147 expected: "/docker/registry/v2/blobs/sha256/ab/abc123def456/data",
148 },
149 {
150 name: "short hash (less than 2 chars)",
151 digest: "sha256:a",
152 expected: "/docker/registry/v2/blobs/sha256/a/data",
153 },
154 {
155 name: "exactly 2 char hash",
156 digest: "sha256:ab",
157 expected: "/docker/registry/v2/blobs/sha256/ab/ab/data",
158 },
159 {
160 name: "long sha256",
161 digest: "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
162 expected: "/docker/registry/v2/blobs/sha256/e3/e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855/data",
163 },
164 }
165
166 for _, tt := range tests {
167 t.Run(tt.name, func(t *testing.T) {
168 result := BlobPath(tt.digest)
169 if result != tt.expected {
170 t.Errorf("Expected %s, got %s", tt.expected, result)
171 }
172 })
173 }
174}
175
176func TestBlobPath_TempUpload(t *testing.T) {
177 tests := []struct {
178 name string
179 digest string
180 expected string
181 }{
182 {
183 name: "temp upload path",
184 digest: "uploads/temp-uuid-123",
185 expected: "/docker/registry/v2/uploads/temp-uuid-123/data",
186 },
187 {
188 name: "temp upload with different uuid",
189 digest: "uploads/temp-abc-def-456",
190 expected: "/docker/registry/v2/uploads/temp-abc-def-456/data",
191 },
192 }
193
194 for _, tt := range tests {
195 t.Run(tt.name, func(t *testing.T) {
196 result := BlobPath(tt.digest)
197 if result != tt.expected {
198 t.Errorf("Expected %s, got %s", tt.expected, result)
199 }
200 })
201 }
202}
203
204func TestBlobPath_MalformedDigest(t *testing.T) {
205 tests := []struct {
206 name string
207 digest string
208 expected string
209 }{
210 {
211 name: "no colon in digest",
212 digest: "malformed-digest",
213 expected: "/docker/registry/v2/blobs/malformed-digest/data",
214 },
215 {
216 name: "empty digest",
217 digest: "",
218 expected: "/docker/registry/v2/blobs//data",
219 },
220 {
221 name: "only algorithm",
222 digest: "sha256:",
223 expected: "/docker/registry/v2/blobs/sha256//data",
224 },
225 }
226
227 for _, tt := range tests {
228 t.Run(tt.name, func(t *testing.T) {
229 result := BlobPath(tt.digest)
230 if result != tt.expected {
231 t.Errorf("Expected %s, got %s", tt.expected, result)
232 }
233 })
234 }
235}