A container registry that uses the AT Protocol for manifest storage and S3 for blob storage.
atcr.io
docker
container
atproto
go
1package hold
2
3import (
4 "os"
5 "path/filepath"
6 "strings"
7 "testing"
8 "time"
9)
10
11func init() {
12 // Point metadata endpoint to a closed listener so it fails instantly instead of
13 // waiting 2s for the real 169.254.169.254 to timeout on non-cloud machines.
14 metadataEndpoint = "http://127.0.0.1:1"
15}
16
17// setupEnv sets environment variables for testing and returns a cleanup function
18func setupEnv(t *testing.T, vars map[string]string) func() {
19 // Save original env
20 original := make(map[string]string)
21 for k := range vars {
22 original[k] = os.Getenv(k)
23 }
24
25 // Set test env vars
26 for k, v := range vars {
27 if err := os.Setenv(k, v); err != nil {
28 t.Fatalf("Failed to set env %s: %v", k, err)
29 }
30 }
31
32 // Return cleanup function
33 return func() {
34 for k, v := range original {
35 if v == "" {
36 os.Unsetenv(k)
37 } else {
38 os.Setenv(k, v)
39 }
40 }
41 }
42}
43
44func TestLoadConfig_Success(t *testing.T) {
45 cleanup := setupEnv(t, map[string]string{
46 "HOLD_SERVER_PUBLIC_URL": "https://hold.example.com",
47 "HOLD_SERVER_ADDR": ":9000",
48 "HOLD_SERVER_PUBLIC": "true",
49 "HOLD_SERVER_TEST_MODE": "true",
50 "HOLD_REGISTRATION_OWNER_DID": "did:plc:owner123",
51 "HOLD_REGISTRATION_ALLOW_ALL_CREW": "true",
52 "S3_BUCKET": "test-bucket",
53 "AWS_ACCESS_KEY_ID": "test-key",
54 "AWS_SECRET_ACCESS_KEY": "test-secret",
55 "HOLD_DATABASE_PATH": "/tmp/test-db",
56 "HOLD_DATABASE_KEY_PATH": "/tmp/test-key.pem",
57 })
58 defer cleanup()
59
60 cfg, err := LoadConfig("")
61 if err != nil {
62 t.Fatalf("Expected success, got error: %v", err)
63 }
64
65 // Verify server config
66 if cfg.Server.PublicURL != "https://hold.example.com" {
67 t.Errorf("Expected PublicURL=https://hold.example.com, got %s", cfg.Server.PublicURL)
68 }
69 if cfg.Server.Addr != ":9000" {
70 t.Errorf("Expected Addr=:9000, got %s", cfg.Server.Addr)
71 }
72 if !cfg.Server.Public {
73 t.Error("Expected Public=true")
74 }
75 if !cfg.Server.TestMode {
76 t.Error("Expected TestMode=true")
77 }
78 if cfg.Server.ReadTimeout != 5*time.Minute {
79 t.Errorf("Expected ReadTimeout=5m, got %v", cfg.Server.ReadTimeout)
80 }
81
82 // Verify registration config
83 if cfg.Registration.OwnerDID != "did:plc:owner123" {
84 t.Errorf("Expected OwnerDID=did:plc:owner123, got %s", cfg.Registration.OwnerDID)
85 }
86 if !cfg.Registration.AllowAllCrew {
87 t.Error("Expected AllowAllCrew=true")
88 }
89
90 // Verify database config
91 if cfg.Database.Path != "/tmp/test-db" {
92 t.Errorf("Expected Database.Path=/tmp/test-db, got %s", cfg.Database.Path)
93 }
94 if cfg.Database.KeyPath != "/tmp/test-key.pem" {
95 t.Errorf("Expected Database.KeyPath=/tmp/test-key.pem, got %s", cfg.Database.KeyPath)
96 }
97}
98
99func TestLoadConfig_MissingPublicURL(t *testing.T) {
100 cleanup := setupEnv(t, map[string]string{
101 "HOLD_SERVER_PUBLIC_URL": "", // Missing required field
102 "S3_BUCKET": "test-bucket",
103 })
104 defer cleanup()
105
106 _, err := LoadConfig("")
107 if err == nil {
108 t.Error("Expected error for missing HOLD_SERVER_PUBLIC_URL")
109 }
110}
111
112func TestLoadConfig_MissingS3Bucket(t *testing.T) {
113 cleanup := setupEnv(t, map[string]string{
114 "HOLD_SERVER_PUBLIC_URL": "https://hold.example.com",
115 "S3_BUCKET": "", // Missing required field
116 })
117 defer cleanup()
118
119 _, err := LoadConfig("")
120 if err == nil {
121 t.Error("Expected error for missing S3_BUCKET")
122 }
123}
124
125func TestLoadConfig_Defaults(t *testing.T) {
126 cleanup := setupEnv(t, map[string]string{
127 "HOLD_SERVER_PUBLIC_URL": "https://hold.example.com",
128 "S3_BUCKET": "test-bucket",
129 "AWS_ACCESS_KEY_ID": "test-key",
130 "AWS_SECRET_ACCESS_KEY": "test-secret",
131 // Don't set optional vars - test defaults
132 "HOLD_SERVER_ADDR": "",
133 "HOLD_SERVER_PUBLIC": "",
134 "HOLD_SERVER_TEST_MODE": "",
135 "HOLD_REGISTRATION_OWNER_DID": "",
136 "HOLD_REGISTRATION_ALLOW_ALL_CREW": "",
137 "AWS_REGION": "",
138 "HOLD_DATABASE_PATH": "",
139 })
140 defer cleanup()
141
142 cfg, err := LoadConfig("")
143 if err != nil {
144 t.Fatalf("Expected success, got error: %v", err)
145 }
146
147 // Verify defaults
148 if cfg.Server.Addr != ":8080" {
149 t.Errorf("Expected default Addr=:8080, got %s", cfg.Server.Addr)
150 }
151 if cfg.Server.Public {
152 t.Error("Expected default Public=false")
153 }
154 if cfg.Server.TestMode {
155 t.Error("Expected default TestMode=false")
156 }
157 if cfg.Registration.OwnerDID != "" {
158 t.Error("Expected default OwnerDID to be empty")
159 }
160 if cfg.Registration.AllowAllCrew {
161 t.Error("Expected default AllowAllCrew=false")
162 }
163 if cfg.Database.Path != "/var/lib/atcr-hold" {
164 t.Errorf("Expected default Database.Path=/var/lib/atcr-hold, got %s", cfg.Database.Path)
165 }
166}
167
168func TestLoadConfig_KeyPathDefault(t *testing.T) {
169 cleanup := setupEnv(t, map[string]string{
170 "HOLD_SERVER_PUBLIC_URL": "https://hold.example.com",
171 "S3_BUCKET": "test-bucket",
172 "AWS_ACCESS_KEY_ID": "test-key",
173 "AWS_SECRET_ACCESS_KEY": "test-secret",
174 "HOLD_DATABASE_PATH": "/custom/db/path",
175 "HOLD_DATABASE_KEY_PATH": "", // Should default to {Database.Path}/signing.key
176 })
177 defer cleanup()
178
179 cfg, err := LoadConfig("")
180 if err != nil {
181 t.Fatalf("Expected success, got error: %v", err)
182 }
183
184 expectedKeyPath := filepath.Join("/custom/db/path", "signing.key")
185 if cfg.Database.KeyPath != expectedKeyPath {
186 t.Errorf("Expected KeyPath=%s, got %s", expectedKeyPath, cfg.Database.KeyPath)
187 }
188}
189
190func TestS3Params_Complete(t *testing.T) {
191 sc := StorageConfig{
192 AccessKey: "test-access-key",
193 SecretKey: "test-secret-key",
194 Region: "us-west-2",
195 Bucket: "test-bucket",
196 Endpoint: "https://s3.example.com",
197 }
198
199 params := sc.S3Params()
200
201 if params["accesskey"] != "test-access-key" {
202 t.Errorf("Expected accesskey=test-access-key, got %v", params["accesskey"])
203 }
204 if params["secretkey"] != "test-secret-key" {
205 t.Errorf("Expected secretkey=test-secret-key, got %v", params["secretkey"])
206 }
207 if params["region"] != "us-west-2" {
208 t.Errorf("Expected region=us-west-2, got %v", params["region"])
209 }
210 if params["bucket"] != "test-bucket" {
211 t.Errorf("Expected bucket=test-bucket, got %v", params["bucket"])
212 }
213 if params["regionendpoint"] != "https://s3.example.com" {
214 t.Errorf("Expected regionendpoint=https://s3.example.com, got %v", params["regionendpoint"])
215 }
216}
217
218func TestS3Params_NoEndpoint(t *testing.T) {
219 sc := StorageConfig{
220 AccessKey: "test-key",
221 SecretKey: "test-secret",
222 Region: "us-east-1",
223 Bucket: "test-bucket",
224 Endpoint: "", // No custom endpoint
225 }
226
227 params := sc.S3Params()
228
229 // Should have default region
230 if params["region"] != "us-east-1" {
231 t.Errorf("Expected default region=us-east-1, got %v", params["region"])
232 }
233
234 // Should not have regionendpoint
235 if _, exists := params["regionendpoint"]; exists {
236 t.Error("Expected no regionendpoint when Endpoint not set")
237 }
238}
239
240func TestDefaultConfig_Hold(t *testing.T) {
241 cfg := DefaultConfig()
242
243 if cfg.Version != "0.1" {
244 t.Errorf("DefaultConfig().Version = %q, want \"0.1\"", cfg.Version)
245 }
246 if cfg.LogLevel != "info" {
247 t.Errorf("DefaultConfig().LogLevel = %q, want \"info\"", cfg.LogLevel)
248 }
249 if cfg.Server.Addr != ":8080" {
250 t.Errorf("DefaultConfig().Server.Addr = %q, want \":8080\"", cfg.Server.Addr)
251 }
252 if cfg.Storage.Region != "us-east-1" {
253 t.Errorf("DefaultConfig().Storage.Region = %q, want \"us-east-1\"", cfg.Storage.Region)
254 }
255 if cfg.Database.Path != "/var/lib/atcr-hold" {
256 t.Errorf("DefaultConfig().Database.Path = %q, want \"/var/lib/atcr-hold\"", cfg.Database.Path)
257 }
258 if cfg.Server.ReadTimeout != 5*time.Minute {
259 t.Errorf("DefaultConfig().Server.ReadTimeout = %v, want 5m", cfg.Server.ReadTimeout)
260 }
261}
262
263func TestExampleYAML_Hold(t *testing.T) {
264 out, err := ExampleYAML()
265 if err != nil {
266 t.Fatalf("ExampleYAML() error: %v", err)
267 }
268
269 s := string(out)
270
271 // Should contain the title
272 if !strings.Contains(s, "ATCR Hold Service Configuration") {
273 t.Error("expected title in YAML output")
274 }
275
276 // Should contain key fields with defaults
277 if !strings.Contains(s, "addr:") {
278 t.Error("expected addr field in YAML output")
279 }
280 if !strings.Contains(s, "bucket:") {
281 t.Error("expected bucket field in YAML output")
282 }
283
284 // Should contain comments
285 if !strings.Contains(s, "# Listen address") {
286 t.Error("expected comment for addr field")
287 }
288 if !strings.Contains(s, "# S3 bucket") {
289 t.Error("expected comment for bucket field")
290 }
291}