330 lines
8.8 KiB
Go
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
|
|
}
|