http_test.go raw
1 package blossom
2
3 import (
4 "bytes"
5 "encoding/json"
6 "io"
7 "net/http"
8 "net/http/httptest"
9 "strings"
10 "testing"
11
12 "next.orly.dev/pkg/nostr/encoders/event"
13 "next.orly.dev/pkg/nostr/encoders/hex"
14 "next.orly.dev/pkg/nostr/encoders/tag"
15 "next.orly.dev/pkg/nostr/encoders/timestamp"
16 )
17
18 // TestHTTPGetBlob tests GET /<sha256> endpoint
19 func TestHTTPGetBlob(t *testing.T) {
20 server, cleanup := testSetup(t)
21 defer cleanup()
22
23 // Upload a blob first
24 testData := []byte("test blob content")
25 sha256Hash := CalculateSHA256(testData)
26 pubkey := []byte("testpubkey123456789012345678901234")
27
28 err := server.storage.SaveBlob(sha256Hash, testData, pubkey, "text/plain", "")
29 if err != nil {
30 t.Fatalf("Failed to save blob: %v", err)
31 }
32
33 sha256Hex := hex.Enc(sha256Hash)
34
35 // Test GET request
36 req := httptest.NewRequest("GET", "/"+sha256Hex, nil)
37 w := httptest.NewRecorder()
38 server.Handler().ServeHTTP(w, req)
39
40 if w.Code != http.StatusOK {
41 t.Errorf("Expected status 200, got %d: %s", w.Code, w.Body.String())
42 }
43
44 body := w.Body.Bytes()
45 if !bytes.Equal(body, testData) {
46 t.Error("Response body mismatch")
47 }
48
49 if w.Header().Get("Content-Type") != "text/plain" {
50 t.Errorf("Expected Content-Type text/plain, got %s", w.Header().Get("Content-Type"))
51 }
52 }
53
54 // TestHTTPHeadBlob tests HEAD /<sha256> endpoint
55 func TestHTTPHeadBlob(t *testing.T) {
56 server, cleanup := testSetup(t)
57 defer cleanup()
58
59 testData := []byte("test blob content")
60 sha256Hash := CalculateSHA256(testData)
61 pubkey := []byte("testpubkey123456789012345678901234")
62
63 err := server.storage.SaveBlob(sha256Hash, testData, pubkey, "text/plain", "")
64 if err != nil {
65 t.Fatalf("Failed to save blob: %v", err)
66 }
67
68 sha256Hex := hex.Enc(sha256Hash)
69
70 req := httptest.NewRequest("HEAD", "/"+sha256Hex, nil)
71 w := httptest.NewRecorder()
72 server.Handler().ServeHTTP(w, req)
73
74 if w.Code != http.StatusOK {
75 t.Errorf("Expected status 200, got %d", w.Code)
76 }
77
78 if w.Body.Len() != 0 {
79 t.Error("HEAD request should not return body")
80 }
81
82 if w.Header().Get("Content-Length") != "17" {
83 t.Errorf("Expected Content-Length 17, got %s", w.Header().Get("Content-Length"))
84 }
85 }
86
87 // TestHTTPUpload tests PUT /upload endpoint
88 func TestHTTPUpload(t *testing.T) {
89 server, cleanup := testSetup(t)
90 defer cleanup()
91
92 _, signer := createTestKeypair(t)
93
94 testData := []byte("test upload data")
95 sha256Hash := CalculateSHA256(testData)
96
97 // Create auth event
98 authEv := createAuthEvent(t, signer, "upload", sha256Hash, 3600)
99
100 // Create request
101 req := httptest.NewRequest("PUT", "/upload", bytes.NewReader(testData))
102 req.Header.Set("Authorization", createAuthHeader(authEv))
103 req.Header.Set("Content-Type", "text/plain")
104
105 w := httptest.NewRecorder()
106 server.Handler().ServeHTTP(w, req)
107
108 if w.Code != http.StatusOK {
109 t.Errorf("Expected status 200, got %d: %s", w.Code, w.Body.String())
110 }
111
112 // Parse response
113 var desc BlobDescriptor
114 if err := json.Unmarshal(w.Body.Bytes(), &desc); err != nil {
115 t.Fatalf("Failed to parse response: %v", err)
116 }
117
118 if desc.SHA256 != hex.Enc(sha256Hash) {
119 t.Errorf("SHA256 mismatch: expected %s, got %s", hex.Enc(sha256Hash), desc.SHA256)
120 }
121
122 if desc.Size != int64(len(testData)) {
123 t.Errorf("Size mismatch: expected %d, got %d", len(testData), desc.Size)
124 }
125
126 // Verify blob was saved
127 exists, err := server.storage.HasBlob(sha256Hash)
128 if err != nil {
129 t.Fatalf("Failed to check blob: %v", err)
130 }
131 if !exists {
132 t.Error("Blob should exist after upload")
133 }
134 }
135
136 // TestHTTPUploadRequirements tests HEAD /upload endpoint
137 func TestHTTPUploadRequirements(t *testing.T) {
138 server, cleanup := testSetup(t)
139 defer cleanup()
140
141 testData := []byte("test data")
142 sha256Hash := CalculateSHA256(testData)
143
144 req := httptest.NewRequest("HEAD", "/upload", nil)
145 req.Header.Set("X-SHA-256", hex.Enc(sha256Hash))
146 req.Header.Set("X-Content-Length", "9")
147 req.Header.Set("X-Content-Type", "text/plain")
148
149 w := httptest.NewRecorder()
150 server.Handler().ServeHTTP(w, req)
151
152 if w.Code != http.StatusOK {
153 t.Errorf("Expected status 200, got %d: %s", w.Code, w.Header().Get("X-Reason"))
154 }
155 }
156
157 // TestHTTPUploadTooLarge tests upload size limit
158 func TestHTTPUploadTooLarge(t *testing.T) {
159 server, cleanup := testSetup(t)
160 defer cleanup()
161
162 // Create request with size exceeding limit
163 req := httptest.NewRequest("HEAD", "/upload", nil)
164 req.Header.Set("X-SHA-256", hex.Enc(CalculateSHA256([]byte("test"))))
165 req.Header.Set("X-Content-Length", "200000000") // 200MB
166 req.Header.Set("X-Content-Type", "application/octet-stream")
167
168 w := httptest.NewRecorder()
169 server.Handler().ServeHTTP(w, req)
170
171 if w.Code != http.StatusRequestEntityTooLarge {
172 t.Errorf("Expected status 413, got %d", w.Code)
173 }
174 }
175
176 // TestHTTPListBlobs tests GET /list/<pubkey> endpoint
177 func TestHTTPListBlobs(t *testing.T) {
178 server, cleanup := testSetup(t)
179 defer cleanup()
180
181 _, signer := createTestKeypair(t)
182 pubkey := signer.Pub()
183 pubkeyHex := hex.Enc(pubkey)
184
185 // Upload multiple blobs
186 for i := 0; i < 3; i++ {
187 testData := []byte("test data " + string(rune('A'+i)))
188 sha256Hash := CalculateSHA256(testData)
189 err := server.storage.SaveBlob(sha256Hash, testData, pubkey, "text/plain", "")
190 if err != nil {
191 t.Fatalf("Failed to save blob: %v", err)
192 }
193 }
194
195 // Create auth event
196 authEv := createAuthEvent(t, signer, "list", nil, 3600)
197
198 req := httptest.NewRequest("GET", "/list/"+pubkeyHex, nil)
199 req.Header.Set("Authorization", createAuthHeader(authEv))
200
201 w := httptest.NewRecorder()
202 server.Handler().ServeHTTP(w, req)
203
204 if w.Code != http.StatusOK {
205 t.Errorf("Expected status 200, got %d: %s", w.Code, w.Body.String())
206 }
207
208 var descriptors []BlobDescriptor
209 if err := json.Unmarshal(w.Body.Bytes(), &descriptors); err != nil {
210 t.Fatalf("Failed to parse response: %v", err)
211 }
212
213 if len(descriptors) != 3 {
214 t.Errorf("Expected 3 blobs, got %d", len(descriptors))
215 }
216 }
217
218 // TestHTTPDeleteBlob tests DELETE /<sha256> endpoint
219 func TestHTTPDeleteBlob(t *testing.T) {
220 server, cleanup := testSetup(t)
221 defer cleanup()
222
223 _, signer := createTestKeypair(t)
224 pubkey := signer.Pub()
225
226 testData := []byte("test delete data")
227 sha256Hash := CalculateSHA256(testData)
228
229 // Upload blob first
230 err := server.storage.SaveBlob(sha256Hash, testData, pubkey, "text/plain", "")
231 if err != nil {
232 t.Fatalf("Failed to save blob: %v", err)
233 }
234
235 // Create auth event
236 authEv := createAuthEvent(t, signer, "delete", sha256Hash, 3600)
237
238 sha256Hex := hex.Enc(sha256Hash)
239 req := httptest.NewRequest("DELETE", "/"+sha256Hex, nil)
240 req.Header.Set("Authorization", createAuthHeader(authEv))
241
242 w := httptest.NewRecorder()
243 server.Handler().ServeHTTP(w, req)
244
245 if w.Code != http.StatusOK {
246 t.Errorf("Expected status 200, got %d: %s", w.Code, w.Body.String())
247 }
248
249 // Verify blob was deleted
250 exists, err := server.storage.HasBlob(sha256Hash)
251 if err != nil {
252 t.Fatalf("Failed to check blob: %v", err)
253 }
254 if exists {
255 t.Error("Blob should not exist after delete")
256 }
257 }
258
259 // TestHTTPMirror tests PUT /mirror endpoint
260 func TestHTTPMirror(t *testing.T) {
261 server, cleanup := testSetup(t)
262 defer cleanup()
263
264 _, signer := createTestKeypair(t)
265
266 // Create a mock remote server
267 testData := []byte("mirrored blob data")
268 sha256Hash := CalculateSHA256(testData)
269 sha256Hex := hex.Enc(sha256Hash)
270
271 mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
272 w.Header().Set("Content-Type", "text/plain")
273 w.Write(testData)
274 }))
275 defer mockServer.Close()
276
277 // Create mirror request
278 mirrorReq := map[string]string{
279 "url": mockServer.URL + "/" + sha256Hex,
280 }
281 reqBody, _ := json.Marshal(mirrorReq)
282
283 authEv := createAuthEvent(t, signer, "upload", sha256Hash, 3600)
284
285 req := httptest.NewRequest("PUT", "/mirror", bytes.NewReader(reqBody))
286 req.Header.Set("Authorization", createAuthHeader(authEv))
287 req.Header.Set("Content-Type", "application/json")
288
289 w := httptest.NewRecorder()
290 server.Handler().ServeHTTP(w, req)
291
292 if w.Code != http.StatusOK {
293 t.Errorf("Expected status 200, got %d: %s", w.Code, w.Body.String())
294 }
295
296 // Verify blob was saved
297 exists, err := server.storage.HasBlob(sha256Hash)
298 if err != nil {
299 t.Fatalf("Failed to check blob: %v", err)
300 }
301 if !exists {
302 t.Error("Blob should exist after mirror")
303 }
304 }
305
306 // TestHTTPMediaUpload tests PUT /media endpoint
307 func TestHTTPMediaUpload(t *testing.T) {
308 server, cleanup := testSetup(t)
309 defer cleanup()
310
311 _, signer := createTestKeypair(t)
312
313 testData := []byte("test media data")
314 sha256Hash := CalculateSHA256(testData)
315
316 authEv := createAuthEvent(t, signer, "media", sha256Hash, 3600)
317
318 req := httptest.NewRequest("PUT", "/media", bytes.NewReader(testData))
319 req.Header.Set("Authorization", createAuthHeader(authEv))
320 req.Header.Set("Content-Type", "image/png")
321
322 w := httptest.NewRecorder()
323 server.Handler().ServeHTTP(w, req)
324
325 if w.Code != http.StatusOK {
326 t.Errorf("Expected status 200, got %d: %s", w.Code, w.Body.String())
327 }
328
329 var desc BlobDescriptor
330 if err := json.Unmarshal(w.Body.Bytes(), &desc); err != nil {
331 t.Fatalf("Failed to parse response: %v", err)
332 }
333
334 if desc.SHA256 == "" {
335 t.Error("Expected SHA256 in response")
336 }
337 }
338
339 // TestHTTPReport tests PUT /report endpoint
340 func TestHTTPReport(t *testing.T) {
341 server, cleanup := testSetup(t)
342 defer cleanup()
343
344 _, signer := createTestKeypair(t)
345 pubkey := signer.Pub()
346
347 // Upload a blob first
348 testData := []byte("test blob")
349 sha256Hash := CalculateSHA256(testData)
350
351 err := server.storage.SaveBlob(sha256Hash, testData, pubkey, "text/plain", "")
352 if err != nil {
353 t.Fatalf("Failed to save blob: %v", err)
354 }
355
356 // Create report event (kind 1984)
357 reportEv := &event.E{
358 CreatedAt: timestamp.Now().V,
359 Kind: 1984,
360 Tags: tag.NewS(tag.NewFromAny("x", hex.Enc(sha256Hash))),
361 Content: []byte("This blob violates policy"),
362 Pubkey: pubkey,
363 }
364
365 if err := reportEv.Sign(signer); err != nil {
366 t.Fatalf("Failed to sign report: %v", err)
367 }
368
369 reqBody := reportEv.Serialize()
370 req := httptest.NewRequest("PUT", "/report", bytes.NewReader(reqBody))
371 req.Header.Set("Content-Type", "application/json")
372
373 w := httptest.NewRecorder()
374 server.Handler().ServeHTTP(w, req)
375
376 if w.Code != http.StatusOK {
377 t.Errorf("Expected status 200, got %d: %s", w.Code, w.Body.String())
378 }
379 }
380
381 // TestHTTPRangeRequest tests range request support
382 func TestHTTPRangeRequest(t *testing.T) {
383 server, cleanup := testSetup(t)
384 defer cleanup()
385
386 testData := []byte("0123456789abcdef")
387 sha256Hash := CalculateSHA256(testData)
388 pubkey := []byte("testpubkey123456789012345678901234")
389
390 err := server.storage.SaveBlob(sha256Hash, testData, pubkey, "text/plain", "")
391 if err != nil {
392 t.Fatalf("Failed to save blob: %v", err)
393 }
394
395 sha256Hex := hex.Enc(sha256Hash)
396
397 // Test range request
398 req := httptest.NewRequest("GET", "/"+sha256Hex, nil)
399 req.Header.Set("Range", "bytes=4-9")
400
401 w := httptest.NewRecorder()
402 server.Handler().ServeHTTP(w, req)
403
404 if w.Code != http.StatusPartialContent {
405 t.Errorf("Expected status 206, got %d", w.Code)
406 }
407
408 body := w.Body.Bytes()
409 expected := testData[4:10]
410 if !bytes.Equal(body, expected) {
411 t.Errorf("Range response mismatch: expected %s, got %s", string(expected), string(body))
412 }
413
414 if w.Header().Get("Content-Range") == "" {
415 t.Error("Missing Content-Range header")
416 }
417 }
418
419 // TestHTTPNotFound tests 404 handling
420 func TestHTTPNotFound(t *testing.T) {
421 server, cleanup := testSetup(t)
422 defer cleanup()
423
424 req := httptest.NewRequest("GET", "/0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", nil)
425 w := httptest.NewRecorder()
426 server.Handler().ServeHTTP(w, req)
427
428 if w.Code != http.StatusNotFound {
429 t.Errorf("Expected status 404, got %d", w.Code)
430 }
431 }
432
433 // TestHTTPServerIntegration tests full server integration
434 func TestHTTPServerIntegration(t *testing.T) {
435 server, cleanup := testSetup(t)
436 defer cleanup()
437
438 // Start HTTP server
439 httpServer := httptest.NewServer(server.Handler())
440 defer httpServer.Close()
441
442 _, signer := createTestKeypair(t)
443
444 // Upload blob via HTTP
445 testData := []byte("integration test data")
446 sha256Hash := CalculateSHA256(testData)
447 sha256Hex := hex.Enc(sha256Hash)
448
449 authEv := createAuthEvent(t, signer, "upload", sha256Hash, 3600)
450
451 uploadReq, _ := http.NewRequest("PUT", httpServer.URL+"/upload", bytes.NewReader(testData))
452 uploadReq.Header.Set("Authorization", createAuthHeader(authEv))
453 uploadReq.Header.Set("Content-Type", "text/plain")
454
455 client := &http.Client{}
456 resp, err := client.Do(uploadReq)
457 if err != nil {
458 t.Fatalf("Failed to upload: %v", err)
459 }
460 defer resp.Body.Close()
461
462 if resp.StatusCode != http.StatusOK {
463 body, _ := io.ReadAll(resp.Body)
464 t.Fatalf("Upload failed: status %d, body: %s", resp.StatusCode, string(body))
465 }
466
467 // Retrieve blob via HTTP
468 getReq, _ := http.NewRequest("GET", httpServer.URL+"/"+sha256Hex, nil)
469 getResp, err := client.Do(getReq)
470 if err != nil {
471 t.Fatalf("Failed to get blob: %v", err)
472 }
473 defer getResp.Body.Close()
474
475 if getResp.StatusCode != http.StatusOK {
476 t.Fatalf("Get failed: status %d", getResp.StatusCode)
477 }
478
479 body, _ := io.ReadAll(getResp.Body)
480 if !bytes.Equal(body, testData) {
481 t.Error("Retrieved blob data mismatch")
482 }
483 }
484
485 // TestCORSHeaders tests CORS header handling
486 func TestCORSHeaders(t *testing.T) {
487 server, cleanup := testSetup(t)
488 defer cleanup()
489
490 req := httptest.NewRequest("GET", "/test", nil)
491 w := httptest.NewRecorder()
492
493 server.Handler().ServeHTTP(w, req)
494
495 if w.Header().Get("Access-Control-Allow-Origin") != "*" {
496 t.Error("Missing CORS header")
497 }
498 }
499
500 // TestAuthorizationRequired tests authorization requirement
501 func TestAuthorizationRequired(t *testing.T) {
502 server, cleanup := testSetup(t)
503 defer cleanup()
504
505 // Configure server to require auth
506 server.requireAuth = true
507
508 testData := []byte("test")
509 sha256Hash := CalculateSHA256(testData)
510 pubkey := []byte("testpubkey123456789012345678901234")
511
512 err := server.storage.SaveBlob(sha256Hash, testData, pubkey, "text/plain", "")
513 if err != nil {
514 t.Fatalf("Failed to save blob: %v", err)
515 }
516
517 sha256Hex := hex.Enc(sha256Hash)
518
519 // Request without auth should fail
520 req := httptest.NewRequest("GET", "/"+sha256Hex, nil)
521 w := httptest.NewRecorder()
522 server.Handler().ServeHTTP(w, req)
523
524 if w.Code != http.StatusUnauthorized {
525 t.Errorf("Expected status 401, got %d", w.Code)
526 }
527 }
528
529 // TestACLIntegration tests ACL integration
530 func TestACLIntegration(t *testing.T) {
531 server, cleanup := testSetup(t)
532 defer cleanup()
533
534 // Note: This test assumes ACL is configured
535 // In a real scenario, you'd set up a proper ACL instance
536
537 _, signer := createTestKeypair(t)
538 testData := []byte("test")
539 sha256Hash := CalculateSHA256(testData)
540
541 authEv := createAuthEvent(t, signer, "upload", sha256Hash, 3600)
542
543 req := httptest.NewRequest("PUT", "/upload", bytes.NewReader(testData))
544 req.Header.Set("Authorization", createAuthHeader(authEv))
545
546 w := httptest.NewRecorder()
547 server.Handler().ServeHTTP(w, req)
548
549 // Should succeed if ACL allows, or fail if not
550 // The exact behavior depends on ACL configuration
551 if w.Code != http.StatusOK && w.Code != http.StatusForbidden {
552 t.Errorf("Unexpected status: %d", w.Code)
553 }
554 }
555
556 // TestMimeTypeDetection tests MIME type detection from various sources
557 func TestMimeTypeDetection(t *testing.T) {
558 tests := []struct {
559 contentType string
560 ext string
561 expected string
562 }{
563 {"image/png", "", "image/png"},
564 {"", ".png", "image/png"},
565 {"", ".pdf", "application/pdf"},
566 {"application/pdf", ".txt", "application/pdf"},
567 {"", ".unknown", "application/octet-stream"},
568 {"", "", "application/octet-stream"},
569 }
570
571 for _, tt := range tests {
572 result := DetectMimeType(tt.contentType, tt.ext)
573 if result != tt.expected {
574 t.Errorf("DetectMimeType(%q, %q) = %q, want %q",
575 tt.contentType, tt.ext, result, tt.expected)
576 }
577 }
578 }
579
580 // TestSHA256Validation tests SHA256 validation
581 func TestSHA256Validation(t *testing.T) {
582 validHashes := []string{
583 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
584 "abc123def456789012345678901234567890123456789012345678901234abcd",
585 }
586
587 invalidHashes := []string{
588 "",
589 "abc",
590 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855x",
591 "12345",
592 }
593
594 for _, hash := range validHashes {
595 if !ValidateSHA256Hex(hash) {
596 t.Errorf("Hash %s should be valid", hash)
597 }
598 }
599
600 for _, hash := range invalidHashes {
601 if ValidateSHA256Hex(hash) {
602 t.Errorf("Hash %s should be invalid", hash)
603 }
604 }
605 }
606
607 // TestBlobURLBuilding tests URL building
608 func TestBlobURLBuilding(t *testing.T) {
609 baseURL := "https://example.com"
610 sha256Hex := "abc123def456"
611 ext := ".pdf"
612
613 url := BuildBlobURL(baseURL, sha256Hex, ext)
614 expected := baseURL + "/" + sha256Hex + ext
615
616 if url != expected {
617 t.Errorf("Expected %s, got %s", expected, url)
618 }
619
620 // Test without extension
621 url2 := BuildBlobURL(baseURL, sha256Hex, "")
622 expected2 := baseURL + "/" + sha256Hex
623
624 if url2 != expected2 {
625 t.Errorf("Expected %s, got %s", expected2, url2)
626 }
627 }
628
629 // TestErrorResponses tests error response formatting
630 func TestErrorResponses(t *testing.T) {
631 server, cleanup := testSetup(t)
632 defer cleanup()
633
634 w := httptest.NewRecorder()
635
636 server.setErrorResponse(w, http.StatusBadRequest, "Invalid request")
637
638 if w.Code != http.StatusBadRequest {
639 t.Errorf("Expected status %d, got %d", http.StatusBadRequest, w.Code)
640 }
641
642 if w.Header().Get("X-Reason") == "" {
643 t.Error("Missing X-Reason header")
644 }
645 }
646
647 // TestExtractSHA256FromURL tests URL hash extraction
648 func TestExtractSHA256FromURL(t *testing.T) {
649 tests := []struct {
650 url string
651 expected string
652 hasError bool
653 }{
654 {"https://example.com/abc123def456789012345678901234567890123456789012345678901234abcd", "abc123def456789012345678901234567890123456789012345678901234abcd", false},
655 {"https://example.com/user/path/abc123def456789012345678901234567890123456789012345678901234abcd.pdf", "abc123def456789012345678901234567890123456789012345678901234abcd", false},
656 {"https://example.com/", "", true},
657 {"no hash here", "", true},
658 }
659
660 for _, tt := range tests {
661 hash, err := ExtractSHA256FromURL(tt.url)
662 if tt.hasError {
663 if err == nil {
664 t.Errorf("Expected error for URL %s", tt.url)
665 }
666 } else {
667 if err != nil {
668 t.Errorf("Unexpected error for URL %s: %v", tt.url, err)
669 }
670 if hash != tt.expected {
671 t.Errorf("Expected %s, got %s for URL %s", tt.expected, hash, tt.url)
672 }
673 }
674 }
675 }
676
677 // TestStorageReport tests report storage
678 func TestStorageReport(t *testing.T) {
679 server, cleanup := testSetup(t)
680 defer cleanup()
681
682 sha256Hash := CalculateSHA256([]byte("test"))
683 reportData := []byte("report data")
684
685 err := server.storage.SaveReport(sha256Hash, reportData)
686 if err != nil {
687 t.Fatalf("Failed to save report: %v", err)
688 }
689
690 // Reports are stored but not retrieved in current implementation
691 // This test verifies the operation doesn't fail
692 }
693
694 // BenchmarkStorageOperations benchmarks storage operations
695 func BenchmarkStorageOperations(b *testing.B) {
696 server, cleanup := testSetup(&testing.T{})
697 defer cleanup()
698
699 testData := []byte("benchmark test data")
700 sha256Hash := CalculateSHA256(testData)
701 pubkey := []byte("testpubkey123456789012345678901234")
702
703 b.ResetTimer()
704 for i := 0; i < b.N; i++ {
705 _ = server.storage.SaveBlob(sha256Hash, testData, pubkey, "text/plain", "")
706 _, _, _ = server.storage.GetBlob(sha256Hash)
707 _ = server.storage.DeleteBlob(sha256Hash, pubkey)
708 }
709 }
710
711 // TestConcurrentUploads tests concurrent uploads
712 func TestConcurrentUploads(t *testing.T) {
713 server, cleanup := testSetup(t)
714 defer cleanup()
715
716 // Prepare all auth events sequentially to avoid race in p256k1.TaggedHash
717 // (the package-level SHA256 hasher is not safe for concurrent Sign() calls).
718 // The test is about concurrent HTTP uploads, not concurrent signing.
719 const numUploads = 10
720 type uploadPrep struct {
721 data []byte
722 authHdr string
723 }
724 preps := make([]uploadPrep, numUploads)
725 for i := range preps {
726 _, signer := createTestKeypair(t)
727 data := []byte("concurrent test " + string(rune('A'+i)))
728 sha256Hash := CalculateSHA256(data)
729 authEv := createAuthEvent(t, signer, "upload", sha256Hash, 3600)
730 preps[i] = uploadPrep{data: data, authHdr: createAuthHeader(authEv)}
731 }
732
733 done := make(chan error, numUploads)
734
735 for i := 0; i < numUploads; i++ {
736 go func(id int) {
737 req := httptest.NewRequest("PUT", "/upload", bytes.NewReader(preps[id].data))
738 req.Header.Set("Authorization", preps[id].authHdr)
739
740 w := httptest.NewRecorder()
741 server.Handler().ServeHTTP(w, req)
742
743 if w.Code != http.StatusOK {
744 done <- &testError{code: w.Code, body: w.Body.String()}
745 return
746 }
747 done <- nil
748 }(i)
749 }
750
751 for i := 0; i < numUploads; i++ {
752 if err := <-done; err != nil {
753 t.Errorf("Concurrent upload failed: %v", err)
754 }
755 }
756 }
757
758 type testError struct {
759 code int
760 body string
761 }
762
763 func (e *testError) Error() string {
764 return strings.Join([]string{"HTTP", string(rune(e.code)), e.body}, " ")
765 }
766