Files
nostrlib/nipb0/blossom/blossom_test.go
2026-01-18 14:51:38 -03:00

330 lines
8.8 KiB
Go

package blossom_test
import (
"bytes"
"context"
"fmt"
"io"
"net/http/httptest"
"net/url"
"strings"
"testing"
"time"
"fiatjaf.com/nostr"
"fiatjaf.com/nostr/keyer"
"fiatjaf.com/nostr/khatru"
khatru_blossom "fiatjaf.com/nostr/khatru/blossom"
blossomclient "fiatjaf.com/nostr/nipb0/blossom"
)
func TestBlossomBasicOperations(t *testing.T) {
// setup two test servers
server1 := setupTestServer(t, ":38081")
defer server1.Close()
server2 := setupTestServer(t, ":38082")
defer server2.Close()
// create signers for two different pubkeys
secretKey1 := nostr.Generate()
secretKey2 := nostr.Generate()
signer1 := keyer.NewPlainKeySigner(secretKey1)
signer2 := keyer.NewPlainKeySigner(secretKey2)
// create clients
client1 := blossomclient.NewClient(server1.URL, signer1)
client2 := blossomclient.NewClient(server1.URL, signer2)
client2Server := blossomclient.NewClient(server2.URL, signer2)
ctx := context.Background()
// test content
testContent := []byte("Hello, Blossom World!")
contentType := "text/plain"
// 1. Upload blob from first pubkey
t.Run("UploadBlob", func(t *testing.T) {
reader := bytes.NewReader(testContent)
bd, err := client1.UploadBlob(ctx, reader, contentType)
if err != nil {
t.Fatalf("Failed to upload blob: %v", err)
}
if bd.Size != len(testContent) {
t.Errorf("Expected size %d, got %d", len(testContent), bd.Size)
}
// content type may include charset, just check that it starts with our expected type
if !strings.HasPrefix(bd.Type, contentType) {
t.Errorf("Expected type to start with %s, got %s", contentType, bd.Type)
}
// verify SHA256 is calculated correctly
if len(bd.SHA256) != 64 {
t.Errorf("Expected SHA256 to be 64 characters, got %d", len(bd.SHA256))
}
})
// 2. Download the blob
t.Run("DownloadBlob", func(t *testing.T) {
// first get the list to find the hash
blobs, err := client1.List(ctx)
if err != nil {
t.Fatalf("Failed to list blobs: %v", err)
}
if len(blobs) != 1 {
t.Fatalf("Expected 1 blob, got %d", len(blobs))
}
hash := blobs[0].SHA256
downloaded, err := client1.Download(ctx, hash)
if err != nil {
t.Fatalf("Failed to download blob: %v", err)
}
if !bytes.Equal(downloaded, testContent) {
t.Errorf("Downloaded content mismatch. Expected %q, got %q", testContent, downloaded)
}
})
// 3. Upload same blob from different pubkey
t.Run("UploadSameBlobDifferentPubkey", func(t *testing.T) {
// get the hash from the first upload
blobs, err := client1.List(ctx)
if err != nil {
t.Fatalf("Failed to list blobs: %v", err)
}
if len(blobs) != 1 {
t.Fatalf("Expected 1 blob, got %d", len(blobs))
}
hash := blobs[0].SHA256
reader := bytes.NewReader(testContent)
bd, err := client2.UploadBlob(ctx, reader, contentType)
if err != nil {
t.Fatalf("Failed to upload same blob with different pubkey: %v", err)
}
if bd.SHA256 != hash {
t.Errorf("Hash mismatch for same content. Expected %s, got %s", hash, bd.SHA256)
}
})
// 4. Check list for both pubkeys
t.Run("ListBlobs", func(t *testing.T) {
// list from first pubkey
blobs1, err := client1.List(ctx)
if err != nil {
t.Fatalf("Failed to list blobs from pubkey1: %v", err)
}
// list from second pubkey
blobs2, err := client2.List(ctx)
if err != nil {
t.Fatalf("Failed to list blobs from pubkey2: %v", err)
}
// both should see the same blob
if len(blobs1) != 1 || len(blobs2) != 1 {
t.Errorf("Expected both pubkeys to see 1 blob, got %d and %d", len(blobs1), len(blobs2))
}
if blobs1[0].SHA256 != blobs2[0].SHA256 {
t.Errorf("Hash mismatch between pubkey lists: %s vs %s", blobs1[0].SHA256, blobs2[0].SHA256)
}
})
// 5. Delete from first pubkey
t.Run("DeleteFromFirstPubkey", func(t *testing.T) {
// get the hash
blobs, err := client1.List(ctx)
if err != nil {
t.Fatalf("Failed to list blobs: %v", err)
}
hash := blobs[0].SHA256
// delete from first pubkey
err = client1.Delete(ctx, hash)
if err != nil {
t.Fatalf("Failed to delete blob from pubkey1: %v", err)
}
// first pubkey should see no blobs
blobs1, err := client1.List(ctx)
if err != nil {
t.Fatalf("Failed to list blobs after delete: %v", err)
}
// second pubkey should still see the blob
blobs2, err := client2.List(ctx)
if err != nil {
t.Fatalf("Failed to list blobs from pubkey2: %v", err)
}
if len(blobs1) != 0 {
t.Errorf("Expected pubkey1 to see 0 blobs after delete, got %d", len(blobs1))
}
if len(blobs2) != 1 {
t.Errorf("Expected pubkey2 to still see 1 blob after pubkey1 delete, got %d", len(blobs2))
}
// download should still work
downloaded, err := client2.Download(ctx, hash)
if err != nil {
t.Fatalf("Failed to download blob after pubkey1 delete: %v", err)
}
if !bytes.Equal(downloaded, testContent) {
t.Errorf("Downloaded content mismatch after delete. Expected %q, got %q", testContent, downloaded)
}
})
// 6. Upload different blobs to second server
t.Run("UploadToSecondServer", func(t *testing.T) {
testContent2 := []byte("Hello from server 2!")
reader := bytes.NewReader(testContent2)
bd, err := client2Server.UploadBlob(ctx, reader, "text/plain")
if err != nil {
t.Fatalf("Failed to upload blob to server2: %v", err)
}
// verify it appears in list
blobs, err := client2Server.List(ctx)
if err != nil {
t.Fatalf("Failed to list blobs from server2: %v", err)
}
if len(blobs) != 1 {
t.Errorf("Expected 1 blob on server2, got %d", len(blobs))
}
// verify download
downloaded, err := client2Server.Download(ctx, bd.SHA256)
if err != nil {
t.Fatalf("Failed to download from server2: %v", err)
}
if !bytes.Equal(downloaded, testContent2) {
t.Errorf("Downloaded content mismatch from server2")
}
})
// 7. Mirror from server1 to server2
t.Run("MirrorBetweenServers", func(t *testing.T) {
// get a blob from server1 (should still be there from pubkey2)
blobs1, err := client2.List(ctx)
if err != nil {
t.Fatalf("Failed to list blobs from server1: %v", err)
}
if len(blobs1) == 0 {
t.Skip("No blobs to mirror")
}
sourceURL := server1.URL + "/" + blobs1[0].SHA256
// mirror to server2
bd, err := client2Server.MirrorBlob(ctx, sourceURL)
if err != nil {
t.Fatalf("Failed to mirror blob: %v", err)
}
// verify mirrored blob appears in server2 list
blobs2, err := client2Server.List(ctx)
if err != nil {
t.Fatalf("Failed to list blobs from server2 after mirror: %v", err)
}
// should now have 2 blobs (original + mirrored)
if len(blobs2) < 2 {
t.Errorf("Expected at least 2 blobs on server2 after mirror, got %d", len(blobs2))
}
// verify the mirrored blob can be downloaded
downloaded, err := client2Server.Download(ctx, bd.SHA256)
if err != nil {
t.Fatalf("Failed to download mirrored blob: %v", err)
}
// should match the original content
originalDownloaded, err := client2.Download(ctx, blobs1[0].SHA256)
if err != nil {
t.Fatalf("Failed to download original blob for comparison: %v", err)
}
if !bytes.Equal(downloaded, originalDownloaded) {
t.Errorf("Mirrored blob content mismatch")
}
})
// 8. Final list verification
t.Run("FinalListVerification", func(t *testing.T) {
// check final state of both servers
blobs1, err := client2.List(ctx) // server1 with pubkey2
if err != nil {
t.Fatalf("Failed final list from server1: %v", err)
}
blobs2, err := client2Server.List(ctx) // server2 with pubkey2
if err != nil {
t.Fatalf("Failed final list from server2: %v", err)
}
// server1 should have the original blob
if len(blobs1) == 0 {
t.Errorf("Expected server1 to have blobs, got 0")
}
// server2 should have at least the original blob + mirrored blob
if len(blobs2) < 2 {
t.Errorf("Expected server2 to have at least 2 blobs, got %d", len(blobs2))
}
t.Logf("Final state - Server1: %d blobs, Server2: %d blobs", len(blobs1), len(blobs2))
})
}
func setupTestServer(t *testing.T, addr string) *httptest.Server {
relay := khatru.NewRelay()
// use memory-based blob index for testing
memoryIndex := khatru_blossom.NewMemoryBlobIndex()
// setup blob storage in memory
blobStorage := make(map[string][]byte)
bl := khatru_blossom.New(relay, "http://localhost"+addr)
bl.Store = memoryIndex
bl.StoreBlob = func(ctx context.Context, sha256 string, ext string, body []byte) error {
blobStorage[sha256] = make([]byte, len(body))
copy(blobStorage[sha256], body)
return nil
}
bl.LoadBlob = func(ctx context.Context, sha256 string, ext string) (io.ReadSeeker, *url.URL, error) {
if data, ok := blobStorage[sha256]; ok {
return bytes.NewReader(data), nil, nil
}
return nil, nil, fmt.Errorf("blob not found")
}
bl.DeleteBlob = func(ctx context.Context, sha256 string, ext string) error {
delete(blobStorage, sha256)
return nil
}
server := httptest.NewUnstartedServer(relay)
server.Start()
// wait a moment for server to be ready
time.Sleep(10 * time.Millisecond)
return server
}