integration_test.go raw
1 package blossom
2
3 import (
4 "bytes"
5 "encoding/json"
6 "fmt"
7 "io"
8 "net/http"
9 "net/http/httptest"
10 "testing"
11 "time"
12
13 "next.orly.dev/pkg/nostr/encoders/event"
14 "next.orly.dev/pkg/nostr/encoders/hex"
15 "next.orly.dev/pkg/nostr/encoders/tag"
16 "next.orly.dev/pkg/nostr/encoders/timestamp"
17 )
18
19 // TestFullServerIntegration tests a complete workflow with a real HTTP server
20 func TestFullServerIntegration(t *testing.T) {
21 server, cleanup := testSetup(t)
22 defer cleanup()
23
24 // Start real HTTP server
25 httpServer := httptest.NewServer(server.Handler())
26 defer httpServer.Close()
27
28 baseURL := httpServer.URL
29 client := &http.Client{Timeout: 10 * time.Second}
30
31 // Create test keypair
32 _, signer := createTestKeypair(t)
33 pubkey := signer.Pub()
34 pubkeyHex := hex.Enc(pubkey)
35
36 // Step 1: Upload a blob
37 testData := []byte("integration test blob content")
38 sha256Hash := CalculateSHA256(testData)
39 sha256Hex := hex.Enc(sha256Hash)
40
41 authEv := createAuthEvent(t, signer, "upload", sha256Hash, 3600)
42
43 uploadReq, err := http.NewRequest("PUT", baseURL+"/upload", bytes.NewReader(testData))
44 if err != nil {
45 t.Fatalf("Failed to create upload request: %v", err)
46 }
47 uploadReq.Header.Set("Authorization", createAuthHeader(authEv))
48 uploadReq.Header.Set("Content-Type", "text/plain")
49
50 uploadResp, err := client.Do(uploadReq)
51 if err != nil {
52 t.Fatalf("Failed to upload: %v", err)
53 }
54 defer uploadResp.Body.Close()
55
56 if uploadResp.StatusCode != http.StatusOK {
57 body, _ := io.ReadAll(uploadResp.Body)
58 t.Fatalf("Upload failed: status %d, body: %s", uploadResp.StatusCode, string(body))
59 }
60
61 var uploadDesc BlobDescriptor
62 if err := json.NewDecoder(uploadResp.Body).Decode(&uploadDesc); err != nil {
63 t.Fatalf("Failed to parse upload response: %v", err)
64 }
65
66 if uploadDesc.SHA256 != sha256Hex {
67 t.Errorf("SHA256 mismatch: expected %s, got %s", sha256Hex, uploadDesc.SHA256)
68 }
69
70 // Step 2: Retrieve the blob
71 getReq, err := http.NewRequest("GET", baseURL+"/"+sha256Hex, nil)
72 if err != nil {
73 t.Fatalf("Failed to create GET request: %v", err)
74 }
75
76 getResp, err := client.Do(getReq)
77 if err != nil {
78 t.Fatalf("Failed to get blob: %v", err)
79 }
80 defer getResp.Body.Close()
81
82 if getResp.StatusCode != http.StatusOK {
83 t.Fatalf("Get failed: status %d", getResp.StatusCode)
84 }
85
86 retrievedData, err := io.ReadAll(getResp.Body)
87 if err != nil {
88 t.Fatalf("Failed to read response: %v", err)
89 }
90
91 if !bytes.Equal(retrievedData, testData) {
92 t.Error("Retrieved blob data mismatch")
93 }
94
95 // Step 3: List blobs
96 listAuthEv := createAuthEvent(t, signer, "list", nil, 3600)
97 listReq, err := http.NewRequest("GET", baseURL+"/list/"+pubkeyHex, nil)
98 if err != nil {
99 t.Fatalf("Failed to create list request: %v", err)
100 }
101 listReq.Header.Set("Authorization", createAuthHeader(listAuthEv))
102
103 listResp, err := client.Do(listReq)
104 if err != nil {
105 t.Fatalf("Failed to list blobs: %v", err)
106 }
107 defer listResp.Body.Close()
108
109 if listResp.StatusCode != http.StatusOK {
110 t.Fatalf("List failed: status %d", listResp.StatusCode)
111 }
112
113 var descriptors []BlobDescriptor
114 if err := json.NewDecoder(listResp.Body).Decode(&descriptors); err != nil {
115 t.Fatalf("Failed to parse list response: %v", err)
116 }
117
118 if len(descriptors) == 0 {
119 t.Error("Expected at least one blob in list")
120 }
121
122 // Step 4: Delete the blob
123 deleteAuthEv := createAuthEvent(t, signer, "delete", sha256Hash, 3600)
124 deleteReq, err := http.NewRequest("DELETE", baseURL+"/"+sha256Hex, nil)
125 if err != nil {
126 t.Fatalf("Failed to create delete request: %v", err)
127 }
128 deleteReq.Header.Set("Authorization", createAuthHeader(deleteAuthEv))
129
130 deleteResp, err := client.Do(deleteReq)
131 if err != nil {
132 t.Fatalf("Failed to delete blob: %v", err)
133 }
134 defer deleteResp.Body.Close()
135
136 if deleteResp.StatusCode != http.StatusOK {
137 t.Fatalf("Delete failed: status %d", deleteResp.StatusCode)
138 }
139
140 // Step 5: Verify blob is gone
141 getResp2, err := client.Do(getReq)
142 if err != nil {
143 t.Fatalf("Failed to get blob: %v", err)
144 }
145 defer getResp2.Body.Close()
146
147 if getResp2.StatusCode != http.StatusNotFound {
148 t.Errorf("Expected 404 after delete, got %d", getResp2.StatusCode)
149 }
150 }
151
152 // TestServerWithMultipleBlobs tests multiple blob operations
153 func TestServerWithMultipleBlobs(t *testing.T) {
154 server, cleanup := testSetup(t)
155 defer cleanup()
156
157 httpServer := httptest.NewServer(server.Handler())
158 defer httpServer.Close()
159
160 _, signer := createTestKeypair(t)
161 pubkey := signer.Pub()
162 pubkeyHex := hex.Enc(pubkey)
163
164 // Upload multiple blobs
165 const numBlobs = 5
166 var hashes []string
167 var data []byte
168
169 for i := 0; i < numBlobs; i++ {
170 testData := []byte(fmt.Sprintf("blob %d content", i))
171 sha256Hash := CalculateSHA256(testData)
172 sha256Hex := hex.Enc(sha256Hash)
173 hashes = append(hashes, sha256Hex)
174 data = append(data, testData...)
175
176 authEv := createAuthEvent(t, signer, "upload", sha256Hash, 3600)
177
178 req, _ := http.NewRequest("PUT", httpServer.URL+"/upload", bytes.NewReader(testData))
179 req.Header.Set("Authorization", createAuthHeader(authEv))
180
181 resp, err := http.DefaultClient.Do(req)
182 if err != nil {
183 t.Fatalf("Failed to upload blob %d: %v", i, err)
184 }
185 resp.Body.Close()
186
187 if resp.StatusCode != http.StatusOK {
188 t.Errorf("Upload %d failed: status %d", i, resp.StatusCode)
189 }
190 }
191
192 // List all blobs
193 authEv := createAuthEvent(t, signer, "list", nil, 3600)
194 req, _ := http.NewRequest("GET", httpServer.URL+"/list/"+pubkeyHex, nil)
195 req.Header.Set("Authorization", createAuthHeader(authEv))
196
197 resp, err := http.DefaultClient.Do(req)
198 if err != nil {
199 t.Fatalf("Failed to list blobs: %v", err)
200 }
201 defer resp.Body.Close()
202
203 var descriptors []BlobDescriptor
204 json.NewDecoder(resp.Body).Decode(&descriptors)
205
206 if len(descriptors) != numBlobs {
207 t.Errorf("Expected %d blobs, got %d", numBlobs, len(descriptors))
208 }
209 }
210
211 // TestServerCORS tests CORS headers on all endpoints
212 func TestServerCORS(t *testing.T) {
213 server, cleanup := testSetup(t)
214 defer cleanup()
215
216 httpServer := httptest.NewServer(server.Handler())
217 defer httpServer.Close()
218
219 endpoints := []struct {
220 method string
221 path string
222 }{
223 {"GET", "/test123456789012345678901234567890123456789012345678901234567890"},
224 {"HEAD", "/test123456789012345678901234567890123456789012345678901234567890"},
225 {"PUT", "/upload"},
226 {"HEAD", "/upload"},
227 {"GET", "/list/test123456789012345678901234567890123456789012345678901234567890"},
228 {"PUT", "/media"},
229 {"HEAD", "/media"},
230 {"PUT", "/mirror"},
231 {"PUT", "/report"},
232 {"DELETE", "/test123456789012345678901234567890123456789012345678901234567890"},
233 {"OPTIONS", "/"},
234 }
235
236 for _, ep := range endpoints {
237 req, _ := http.NewRequest(ep.method, httpServer.URL+ep.path, nil)
238 resp, err := http.DefaultClient.Do(req)
239 if err != nil {
240 t.Errorf("Failed to test %s %s: %v", ep.method, ep.path, err)
241 continue
242 }
243 resp.Body.Close()
244
245 corsHeader := resp.Header.Get("Access-Control-Allow-Origin")
246 if corsHeader != "*" {
247 t.Errorf("Missing CORS header on %s %s", ep.method, ep.path)
248 }
249 }
250 }
251
252 // TestServerRangeRequests tests range request handling
253 func TestServerRangeRequests(t *testing.T) {
254 server, cleanup := testSetup(t)
255 defer cleanup()
256
257 httpServer := httptest.NewServer(server.Handler())
258 defer httpServer.Close()
259
260 // Upload a blob
261 testData := []byte("0123456789abcdefghij")
262 sha256Hash := CalculateSHA256(testData)
263 pubkey := []byte("testpubkey123456789012345678901234")
264
265 err := server.storage.SaveBlob(sha256Hash, testData, pubkey, "text/plain", "")
266 if err != nil {
267 t.Fatalf("Failed to save blob: %v", err)
268 }
269
270 sha256Hex := hex.Enc(sha256Hash)
271
272 // Test various range requests
273 tests := []struct {
274 rangeHeader string
275 expected string
276 status int
277 }{
278 {"bytes=0-4", "01234", http.StatusPartialContent},
279 {"bytes=5-9", "56789", http.StatusPartialContent},
280 {"bytes=10-", "abcdefghij", http.StatusPartialContent},
281 {"bytes=-5", "fghij", http.StatusPartialContent},
282 {"bytes=0-0", "0", http.StatusPartialContent},
283 {"bytes=100-200", "", http.StatusRequestedRangeNotSatisfiable},
284 }
285
286 for _, tt := range tests {
287 req, _ := http.NewRequest("GET", httpServer.URL+"/"+sha256Hex, nil)
288 req.Header.Set("Range", tt.rangeHeader)
289
290 resp, err := http.DefaultClient.Do(req)
291 if err != nil {
292 t.Errorf("Failed to request range %s: %v", tt.rangeHeader, err)
293 continue
294 }
295
296 if resp.StatusCode != tt.status {
297 t.Errorf("Range %s: expected status %d, got %d", tt.rangeHeader, tt.status, resp.StatusCode)
298 resp.Body.Close()
299 continue
300 }
301
302 if tt.status == http.StatusPartialContent {
303 body, _ := io.ReadAll(resp.Body)
304 if string(body) != tt.expected {
305 t.Errorf("Range %s: expected %q, got %q", tt.rangeHeader, tt.expected, string(body))
306 }
307
308 if resp.Header.Get("Content-Range") == "" {
309 t.Errorf("Range %s: missing Content-Range header", tt.rangeHeader)
310 }
311 }
312
313 resp.Body.Close()
314 }
315 }
316
317 // TestServerAuthorizationFlow tests complete authorization flow
318 func TestServerAuthorizationFlow(t *testing.T) {
319 server, cleanup := testSetup(t)
320 defer cleanup()
321
322 _, signer := createTestKeypair(t)
323
324 testData := []byte("authorized blob")
325 sha256Hash := CalculateSHA256(testData)
326
327 // Test with valid authorization
328 authEv := createAuthEvent(t, signer, "upload", sha256Hash, 3600)
329
330 req := httptest.NewRequest("PUT", "/upload", bytes.NewReader(testData))
331 req.Header.Set("Authorization", createAuthHeader(authEv))
332
333 w := httptest.NewRecorder()
334 server.Handler().ServeHTTP(w, req)
335
336 if w.Code != http.StatusOK {
337 t.Errorf("Valid auth failed: status %d, body: %s", w.Code, w.Body.String())
338 }
339
340 // Test with expired authorization
341 expiredAuthEv := createAuthEvent(t, signer, "upload", sha256Hash, -3600)
342
343 req2 := httptest.NewRequest("PUT", "/upload", bytes.NewReader(testData))
344 req2.Header.Set("Authorization", createAuthHeader(expiredAuthEv))
345
346 w2 := httptest.NewRecorder()
347 server.Handler().ServeHTTP(w2, req2)
348
349 if w2.Code != http.StatusUnauthorized {
350 t.Errorf("Expired auth should fail: status %d", w2.Code)
351 }
352
353 // Test with wrong verb
354 wrongVerbAuthEv := createAuthEvent(t, signer, "delete", sha256Hash, 3600)
355
356 req3 := httptest.NewRequest("PUT", "/upload", bytes.NewReader(testData))
357 req3.Header.Set("Authorization", createAuthHeader(wrongVerbAuthEv))
358
359 w3 := httptest.NewRecorder()
360 server.Handler().ServeHTTP(w3, req3)
361
362 if w3.Code != http.StatusUnauthorized {
363 t.Errorf("Wrong verb auth should fail: status %d", w3.Code)
364 }
365 }
366
367 // TestServerUploadRequirementsFlow tests upload requirements check flow
368 func TestServerUploadRequirementsFlow(t *testing.T) {
369 server, cleanup := testSetup(t)
370 defer cleanup()
371
372 testData := []byte("test")
373 sha256Hash := CalculateSHA256(testData)
374
375 // Test HEAD /upload with valid requirements
376 req := httptest.NewRequest("HEAD", "/upload", nil)
377 req.Header.Set("X-SHA-256", hex.Enc(sha256Hash))
378 req.Header.Set("X-Content-Length", "4")
379 req.Header.Set("X-Content-Type", "text/plain")
380
381 w := httptest.NewRecorder()
382 server.Handler().ServeHTTP(w, req)
383
384 if w.Code != http.StatusOK {
385 t.Errorf("Upload requirements check failed: status %d", w.Code)
386 }
387
388 // Test HEAD /upload with missing header
389 req2 := httptest.NewRequest("HEAD", "/upload", nil)
390 w2 := httptest.NewRecorder()
391 server.Handler().ServeHTTP(w2, req2)
392
393 if w2.Code != http.StatusBadRequest {
394 t.Errorf("Expected BadRequest for missing header, got %d", w2.Code)
395 }
396
397 // Test HEAD /upload with invalid hash
398 req3 := httptest.NewRequest("HEAD", "/upload", nil)
399 req3.Header.Set("X-SHA-256", "invalid")
400 req3.Header.Set("X-Content-Length", "4")
401
402 w3 := httptest.NewRecorder()
403 server.Handler().ServeHTTP(w3, req3)
404
405 if w3.Code != http.StatusBadRequest {
406 t.Errorf("Expected BadRequest for invalid hash, got %d", w3.Code)
407 }
408 }
409
410 // TestServerMirrorFlow tests mirror endpoint flow
411 func TestServerMirrorFlow(t *testing.T) {
412 server, cleanup := testSetup(t)
413 defer cleanup()
414
415 _, signer := createTestKeypair(t)
416
417 // Create mock remote server
418 remoteData := []byte("remote blob data")
419 sha256Hash := CalculateSHA256(remoteData)
420 sha256Hex := hex.Enc(sha256Hash)
421
422 mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
423 w.Header().Set("Content-Type", "application/pdf")
424 w.Header().Set("Content-Length", fmt.Sprintf("%d", len(remoteData)))
425 w.Write(remoteData)
426 }))
427 defer mockServer.Close()
428
429 // Mirror the blob
430 mirrorReq := map[string]string{
431 "url": mockServer.URL + "/" + sha256Hex,
432 }
433 reqBody, _ := json.Marshal(mirrorReq)
434
435 authEv := createAuthEvent(t, signer, "upload", sha256Hash, 3600)
436
437 req := httptest.NewRequest("PUT", "/mirror", bytes.NewReader(reqBody))
438 req.Header.Set("Authorization", createAuthHeader(authEv))
439 req.Header.Set("Content-Type", "application/json")
440
441 w := httptest.NewRecorder()
442 server.Handler().ServeHTTP(w, req)
443
444 if w.Code != http.StatusOK {
445 t.Errorf("Mirror failed: status %d, body: %s", w.Code, w.Body.String())
446 }
447
448 // Verify blob was stored
449 exists, err := server.storage.HasBlob(sha256Hash)
450 if err != nil {
451 t.Fatalf("Failed to check blob: %v", err)
452 }
453 if !exists {
454 t.Error("Blob should exist after mirror")
455 }
456 }
457
458 // TestServerReportFlow tests report endpoint flow
459 func TestServerReportFlow(t *testing.T) {
460 server, cleanup := testSetup(t)
461 defer cleanup()
462
463 _, signer := createTestKeypair(t)
464 pubkey := signer.Pub()
465
466 // Upload a blob first
467 testData := []byte("reportable blob")
468 sha256Hash := CalculateSHA256(testData)
469
470 err := server.storage.SaveBlob(sha256Hash, testData, pubkey, "text/plain", "")
471 if err != nil {
472 t.Fatalf("Failed to save blob: %v", err)
473 }
474
475 // Create report event
476 reportEv := &event.E{
477 CreatedAt: timestamp.Now().V,
478 Kind: 1984,
479 Tags: tag.NewS(tag.NewFromAny("x", hex.Enc(sha256Hash))),
480 Content: []byte("This blob should be reported"),
481 Pubkey: pubkey,
482 }
483
484 if err := reportEv.Sign(signer); err != nil {
485 t.Fatalf("Failed to sign report: %v", err)
486 }
487
488 reqBody := reportEv.Serialize()
489 req := httptest.NewRequest("PUT", "/report", bytes.NewReader(reqBody))
490 req.Header.Set("Content-Type", "application/json")
491
492 w := httptest.NewRecorder()
493 server.Handler().ServeHTTP(w, req)
494
495 if w.Code != http.StatusOK {
496 t.Errorf("Report failed: status %d, body: %s", w.Code, w.Body.String())
497 }
498 }
499
500 // TestServerErrorHandling tests various error scenarios
501 func TestServerErrorHandling(t *testing.T) {
502 server, cleanup := testSetup(t)
503 defer cleanup()
504
505 tests := []struct {
506 name string
507 method string
508 path string
509 headers map[string]string
510 body []byte
511 statusCode int
512 }{
513 {
514 name: "Invalid path",
515 method: "GET",
516 path: "/invalid",
517 statusCode: http.StatusBadRequest,
518 },
519 {
520 name: "Non-existent blob",
521 method: "GET",
522 path: "/e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
523 statusCode: http.StatusNotFound,
524 },
525 {
526 name: "Anonymous upload allowed",
527 method: "PUT",
528 path: "/upload",
529 body: []byte("test"),
530 statusCode: http.StatusOK, // RequireAuth=false and ACL=none allows anonymous uploads
531 },
532 {
533 name: "Invalid JSON in mirror",
534 method: "PUT",
535 path: "/mirror",
536 body: []byte("invalid json"),
537 statusCode: http.StatusBadRequest,
538 },
539 {
540 name: "Invalid JSON in report",
541 method: "PUT",
542 path: "/report",
543 body: []byte("invalid json"),
544 statusCode: http.StatusBadRequest,
545 },
546 }
547
548 for _, tt := range tests {
549 t.Run(tt.name, func(t *testing.T) {
550 var body io.Reader
551 if tt.body != nil {
552 body = bytes.NewReader(tt.body)
553 }
554
555 req := httptest.NewRequest(tt.method, tt.path, body)
556 for k, v := range tt.headers {
557 req.Header.Set(k, v)
558 }
559
560 w := httptest.NewRecorder()
561 server.Handler().ServeHTTP(w, req)
562
563 if w.Code != tt.statusCode {
564 t.Errorf("Expected status %d, got %d: %s", tt.statusCode, w.Code, w.Body.String())
565 }
566 })
567 }
568 }
569
570 // TestServerMediaOptimization tests media optimization endpoint
571 func TestServerMediaOptimization(t *testing.T) {
572 server, cleanup := testSetup(t)
573 defer cleanup()
574
575 _, signer := createTestKeypair(t)
576
577 testData := []byte("test media for optimization")
578 sha256Hash := CalculateSHA256(testData)
579
580 authEv := createAuthEvent(t, signer, "media", sha256Hash, 3600)
581
582 req := httptest.NewRequest("PUT", "/media", bytes.NewReader(testData))
583 req.Header.Set("Authorization", createAuthHeader(authEv))
584 req.Header.Set("Content-Type", "image/png")
585
586 w := httptest.NewRecorder()
587 server.Handler().ServeHTTP(w, req)
588
589 if w.Code != http.StatusOK {
590 t.Errorf("Media upload failed: status %d, body: %s", w.Code, w.Body.String())
591 }
592
593 var desc BlobDescriptor
594 if err := json.Unmarshal(w.Body.Bytes(), &desc); err != nil {
595 t.Fatalf("Failed to parse response: %v", err)
596 }
597
598 if desc.SHA256 == "" {
599 t.Error("Expected SHA256 in response")
600 }
601
602 // Test HEAD /media
603 req2 := httptest.NewRequest("HEAD", "/media", nil)
604 w2 := httptest.NewRecorder()
605 server.Handler().ServeHTTP(w2, req2)
606
607 if w2.Code != http.StatusOK {
608 t.Errorf("HEAD /media failed: status %d", w2.Code)
609 }
610 }
611
612 // TestServerListWithQueryParams tests list endpoint with query parameters
613 func TestServerListWithQueryParams(t *testing.T) {
614 server, cleanup := testSetup(t)
615 defer cleanup()
616
617 _, signer := createTestKeypair(t)
618 pubkey := signer.Pub()
619 pubkeyHex := hex.Enc(pubkey)
620
621 // Upload blobs at different times
622 now := time.Now().Unix()
623 blobs := []struct {
624 data []byte
625 timestamp int64
626 }{
627 {[]byte("blob 1"), now - 1000},
628 {[]byte("blob 2"), now - 500},
629 {[]byte("blob 3"), now},
630 }
631
632 for _, b := range blobs {
633 sha256Hash := CalculateSHA256(b.data)
634 // Manually set uploaded timestamp
635 err := server.storage.SaveBlob(sha256Hash, b.data, pubkey, "text/plain", "")
636 if err != nil {
637 t.Fatalf("Failed to save blob: %v", err)
638 }
639 }
640
641 // List with since parameter (future timestamp - should return no blobs)
642 authEv := createAuthEvent(t, signer, "list", nil, 3600)
643 futureTime := time.Now().Unix() + 3600 // 1 hour in the future
644 req := httptest.NewRequest("GET", "/list/"+pubkeyHex+"?since="+fmt.Sprintf("%d", futureTime), nil)
645 req.Header.Set("Authorization", createAuthHeader(authEv))
646
647 w := httptest.NewRecorder()
648 server.Handler().ServeHTTP(w, req)
649
650 if w.Code != http.StatusOK {
651 t.Errorf("List failed: status %d", w.Code)
652 }
653
654 var descriptors []BlobDescriptor
655 if err := json.NewDecoder(w.Body).Decode(&descriptors); err != nil {
656 t.Fatalf("Failed to parse response: %v", err)
657 }
658
659 // Should get no blobs since they were all uploaded before the future timestamp
660 if len(descriptors) != 0 {
661 t.Errorf("Expected 0 blobs, got %d", len(descriptors))
662 }
663
664 // Test without since parameter - should get all blobs
665 req2 := httptest.NewRequest("GET", "/list/"+pubkeyHex, nil)
666 req2.Header.Set("Authorization", createAuthHeader(authEv))
667
668 w2 := httptest.NewRecorder()
669 server.Handler().ServeHTTP(w2, req2)
670
671 if w2.Code != http.StatusOK {
672 t.Errorf("List failed: status %d", w2.Code)
673 }
674
675 var allDescriptors []BlobDescriptor
676 if err := json.NewDecoder(w2.Body).Decode(&allDescriptors); err != nil {
677 t.Fatalf("Failed to parse response: %v", err)
678 }
679
680 // Should get all 3 blobs when no filter is applied
681 if len(allDescriptors) != 3 {
682 t.Errorf("Expected 3 blobs, got %d", len(allDescriptors))
683 }
684 }
685
686 // TestServerConcurrentOperations tests concurrent operations on server
687 func TestServerConcurrentOperations(t *testing.T) {
688 server, cleanup := testSetup(t)
689 defer cleanup()
690
691 httpServer := httptest.NewServer(server.Handler())
692 defer httpServer.Close()
693
694 // Prepare all auth events sequentially to avoid race in p256k1.TaggedHash
695 // (the package-level SHA256 hasher is not safe for concurrent Sign() calls).
696 // The test is about concurrent HTTP operations, not concurrent signing.
697 const numOps = 20
698 type opPrep struct {
699 data []byte
700 sha256Hex string
701 authHdr string
702 }
703 preps := make([]opPrep, numOps)
704 for i := range preps {
705 _, signer := createTestKeypair(t)
706 data := []byte(fmt.Sprintf("concurrent op %d", i))
707 sha256Hash := CalculateSHA256(data)
708 authEv := createAuthEvent(t, signer, "upload", sha256Hash, 3600)
709 preps[i] = opPrep{
710 data: data,
711 sha256Hex: hex.Enc(sha256Hash),
712 authHdr: createAuthHeader(authEv),
713 }
714 }
715
716 done := make(chan error, numOps)
717
718 for i := 0; i < numOps; i++ {
719 go func(id int) {
720 // Upload
721 req, _ := http.NewRequest("PUT", httpServer.URL+"/upload", bytes.NewReader(preps[id].data))
722 req.Header.Set("Authorization", preps[id].authHdr)
723
724 resp, err := http.DefaultClient.Do(req)
725 if err != nil {
726 done <- err
727 return
728 }
729 resp.Body.Close()
730
731 if resp.StatusCode != http.StatusOK {
732 done <- fmt.Errorf("upload failed: %d", resp.StatusCode)
733 return
734 }
735
736 // Get
737 req2, _ := http.NewRequest("GET", httpServer.URL+"/"+preps[id].sha256Hex, nil)
738 resp2, err := http.DefaultClient.Do(req2)
739 if err != nil {
740 done <- err
741 return
742 }
743 resp2.Body.Close()
744
745 if resp2.StatusCode != http.StatusOK {
746 done <- fmt.Errorf("get failed: %d", resp2.StatusCode)
747 return
748 }
749
750 done <- nil
751 }(i)
752 }
753
754 for i := 0; i < numOps; i++ {
755 if err := <-done; err != nil {
756 t.Errorf("Concurrent operation failed: %v", err)
757 }
758 }
759 }
760
761 // TestServerBlobExtensionHandling tests blob retrieval with file extensions
762 func TestServerBlobExtensionHandling(t *testing.T) {
763 server, cleanup := testSetup(t)
764 defer cleanup()
765
766 testData := []byte("test PDF content")
767 sha256Hash := CalculateSHA256(testData)
768 pubkey := []byte("testpubkey123456789012345678901234")
769
770 err := server.storage.SaveBlob(sha256Hash, testData, pubkey, "application/pdf", "")
771 if err != nil {
772 t.Fatalf("Failed to save blob: %v", err)
773 }
774
775 sha256Hex := hex.Enc(sha256Hash)
776
777 // Test GET with extension
778 req := httptest.NewRequest("GET", "/"+sha256Hex+".pdf", nil)
779 w := httptest.NewRecorder()
780 server.Handler().ServeHTTP(w, req)
781
782 if w.Code != http.StatusOK {
783 t.Errorf("GET with extension failed: status %d", w.Code)
784 }
785
786 // Should still return correct MIME type
787 if w.Header().Get("Content-Type") != "application/pdf" {
788 t.Errorf("Expected application/pdf, got %s", w.Header().Get("Content-Type"))
789 }
790 }
791
792 // TestServerBlobAlreadyExists tests uploading existing blob
793 func TestServerBlobAlreadyExists(t *testing.T) {
794 server, cleanup := testSetup(t)
795 defer cleanup()
796
797 _, signer := createTestKeypair(t)
798 pubkey := signer.Pub()
799
800 testData := []byte("existing blob")
801 sha256Hash := CalculateSHA256(testData)
802
803 // Upload blob first time
804 err := server.storage.SaveBlob(sha256Hash, testData, pubkey, "text/plain", "")
805 if err != nil {
806 t.Fatalf("Failed to save blob: %v", err)
807 }
808
809 // Try to upload same blob again
810 authEv := createAuthEvent(t, signer, "upload", sha256Hash, 3600)
811
812 req := httptest.NewRequest("PUT", "/upload", bytes.NewReader(testData))
813 req.Header.Set("Authorization", createAuthHeader(authEv))
814
815 w := httptest.NewRecorder()
816 server.Handler().ServeHTTP(w, req)
817
818 // Should succeed and return existing blob descriptor
819 if w.Code != http.StatusOK {
820 t.Errorf("Re-upload should succeed: status %d", w.Code)
821 }
822 }
823
824 // TestServerInvalidAuthorization tests various invalid authorization scenarios
825 func TestServerInvalidAuthorization(t *testing.T) {
826 server, cleanup := testSetup(t)
827 defer cleanup()
828
829 _, signer := createTestKeypair(t)
830
831 testData := []byte("test")
832 sha256Hash := CalculateSHA256(testData)
833
834 tests := []struct {
835 name string
836 modifyEv func(*event.E)
837 expectErr bool
838 }{
839 {
840 name: "Missing expiration",
841 modifyEv: func(ev *event.E) {
842 ev.Tags = tag.NewS(tag.NewFromAny("t", "upload"))
843 },
844 expectErr: true,
845 },
846 {
847 name: "Wrong kind",
848 modifyEv: func(ev *event.E) {
849 ev.Kind = 1
850 },
851 expectErr: true,
852 },
853 {
854 name: "Wrong verb",
855 modifyEv: func(ev *event.E) {
856 ev.Tags = tag.NewS(
857 tag.NewFromAny("t", "delete"),
858 tag.NewFromAny("expiration", timestamp.FromUnix(time.Now().Unix()+3600).String()),
859 )
860 },
861 expectErr: true,
862 },
863 }
864
865 for _, tt := range tests {
866 t.Run(tt.name, func(t *testing.T) {
867 ev := createAuthEvent(t, signer, "upload", sha256Hash, 3600)
868 tt.modifyEv(ev)
869
870 req := httptest.NewRequest("PUT", "/upload", bytes.NewReader(testData))
871 req.Header.Set("Authorization", createAuthHeader(ev))
872
873 w := httptest.NewRecorder()
874 server.Handler().ServeHTTP(w, req)
875
876 if tt.expectErr {
877 if w.Code == http.StatusOK {
878 t.Error("Expected error but got success")
879 }
880 } else {
881 if w.Code != http.StatusOK {
882 t.Errorf("Expected success but got error: status %d", w.Code)
883 }
884 }
885 })
886 }
887 }
888
889