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