eventstore tests.
This commit is contained in:
61
eventstore/badger/badger_test.go
Normal file
61
eventstore/badger/badger_test.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package badger
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/eventstore"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestBasicStoreAndQuery(t *testing.T) {
|
||||
// create a temporary directory for the test database
|
||||
dir, err := os.MkdirTemp("", "badger-test-*")
|
||||
require.NoError(t, err)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
// initialize the store
|
||||
db := &BadgerBackend{Path: dir}
|
||||
err = db.Init()
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
|
||||
// create a test event
|
||||
evt := nostr.Event{
|
||||
Content: "hello world",
|
||||
CreatedAt: 1000,
|
||||
Kind: 1,
|
||||
Tags: nostr.Tags{},
|
||||
}
|
||||
err = evt.Sign(nostr.Generate())
|
||||
require.NoError(t, err)
|
||||
|
||||
// save the event
|
||||
err = db.SaveEvent(evt)
|
||||
require.NoError(t, err)
|
||||
|
||||
// try to save it again, should fail with ErrDupEvent
|
||||
err = db.SaveEvent(evt)
|
||||
require.Error(t, err)
|
||||
require.Equal(t, eventstore.ErrDupEvent, err)
|
||||
|
||||
// query the event by its ID
|
||||
filter := nostr.Filter{
|
||||
IDs: []nostr.ID{evt.ID},
|
||||
}
|
||||
|
||||
// collect results
|
||||
results := make([]nostr.Event, 0)
|
||||
for event := range db.QueryEvents(filter) {
|
||||
results = append(results, event)
|
||||
}
|
||||
|
||||
// verify we got exactly one event and it matches
|
||||
require.Len(t, results, 1)
|
||||
require.Equal(t, evt.ID, results[0].ID)
|
||||
require.Equal(t, evt.Content, results[0].Content)
|
||||
require.Equal(t, evt.CreatedAt, results[0].CreatedAt)
|
||||
require.Equal(t, evt.Kind, results[0].Kind)
|
||||
require.Equal(t, evt.PubKey, results[0].PubKey)
|
||||
}
|
||||
@@ -2,23 +2,18 @@ package badger
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"slices"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/dgraph-io/badger/v4"
|
||||
"fiatjaf.com/nostr/eventstore"
|
||||
"fiatjaf.com/nostr"
|
||||
"github.com/dgraph-io/badger/v4"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
func FuzzQuery(f *testing.F) {
|
||||
ctx := context.Background()
|
||||
|
||||
f.Add(uint(200), uint(50), uint(13), uint(2), uint(2), uint(0), uint(1))
|
||||
f.Fuzz(func(t *testing.T, total, limit, authors, timestampAuthorFactor, seedFactor, kinds, kindFactor uint) {
|
||||
total++
|
||||
@@ -72,41 +67,41 @@ func FuzzQuery(f *testing.F) {
|
||||
// ~ start actual test
|
||||
|
||||
filter := nostr.Filter{
|
||||
Authors: make([]string, authors),
|
||||
Authors: make([]nostr.PubKey, authors),
|
||||
Limit: int(limit),
|
||||
}
|
||||
maxKind := 1
|
||||
var maxKind uint16 = 1
|
||||
if kinds > 0 {
|
||||
filter.Kinds = make([]int, kinds)
|
||||
filter.Kinds = make([]uint16, kinds)
|
||||
for i := range filter.Kinds {
|
||||
filter.Kinds[i] = int(kindFactor) * i
|
||||
filter.Kinds[i] = uint16(kindFactor) * uint16(i)
|
||||
}
|
||||
maxKind = filter.Kinds[len(filter.Kinds)-1]
|
||||
}
|
||||
|
||||
for i := 0; i < int(authors); i++ {
|
||||
sk := make([]byte, 32)
|
||||
binary.BigEndian.PutUint32(sk, uint32(i%int(authors*seedFactor))+1)
|
||||
pk, _ := nostr.GetPublicKey(hex.EncodeToString(sk))
|
||||
sk := nostr.SecretKey{}
|
||||
binary.BigEndian.PutUint32(sk[:], uint32(i%int(authors*seedFactor))+1)
|
||||
pk := nostr.GetPublicKey(sk)
|
||||
filter.Authors[i] = pk
|
||||
}
|
||||
|
||||
expected := make([]*nostr.Event, 0, total)
|
||||
expected := make([]nostr.Event, 0, total)
|
||||
for i := 0; i < int(total); i++ {
|
||||
skseed := uint32(i%int(authors*seedFactor)) + 1
|
||||
sk := make([]byte, 32)
|
||||
binary.BigEndian.PutUint32(sk, skseed)
|
||||
sk := nostr.SecretKey{}
|
||||
binary.BigEndian.PutUint32(sk[:], skseed)
|
||||
|
||||
evt := &nostr.Event{
|
||||
evt := nostr.Event{
|
||||
CreatedAt: nostr.Timestamp(skseed)*nostr.Timestamp(timestampAuthorFactor) + nostr.Timestamp(i),
|
||||
Content: fmt.Sprintf("unbalanced %d", i),
|
||||
Tags: nostr.Tags{},
|
||||
Kind: i % maxKind,
|
||||
Kind: uint16(i) % maxKind,
|
||||
}
|
||||
err := evt.Sign(hex.EncodeToString(sk))
|
||||
err := evt.Sign(sk)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.SaveEvent(ctx, evt)
|
||||
err = db.SaveEvent(evt)
|
||||
require.NoError(t, err)
|
||||
|
||||
if filter.Matches(evt) {
|
||||
@@ -114,27 +109,25 @@ func FuzzQuery(f *testing.F) {
|
||||
}
|
||||
}
|
||||
|
||||
slices.SortFunc(expected, nostr.CompareEventPtrReverse)
|
||||
slices.SortFunc(expected, nostr.CompareEventReverse)
|
||||
if len(expected) > int(limit) {
|
||||
expected = expected[0:limit]
|
||||
}
|
||||
|
||||
w := eventstore.RelayWrapper{Store: db}
|
||||
|
||||
start := time.Now()
|
||||
// fmt.Println(filter)
|
||||
res, err := w.QuerySync(ctx, filter)
|
||||
res := slices.Collect(db.QueryEvents(filter))
|
||||
end := time.Now()
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, len(expected), len(res), "number of results is different than expected")
|
||||
|
||||
require.Less(t, end.Sub(start).Milliseconds(), int64(1500), "query took too long")
|
||||
require.True(t, slices.IsSortedFunc(res, func(a, b *nostr.Event) int { return cmp.Compare(b.CreatedAt, a.CreatedAt) }), "results are not sorted")
|
||||
require.True(t, slices.IsSortedFunc(res, func(a, b nostr.Event) int { return cmp.Compare(b.CreatedAt, a.CreatedAt) }), "results are not sorted")
|
||||
|
||||
nresults := len(expected)
|
||||
|
||||
getTimestamps := func(events []*nostr.Event) []nostr.Timestamp {
|
||||
getTimestamps := func(events []nostr.Event) []nostr.Timestamp {
|
||||
res := make([]nostr.Timestamp, len(events))
|
||||
for i, evt := range events {
|
||||
res[i] = evt.CreatedAt
|
||||
|
||||
@@ -4,11 +4,11 @@ import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"iter"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"fiatjaf.com/nostr"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
func getTagIndexPrefix(tagValue string) ([]byte, int) {
|
||||
|
||||
@@ -6,12 +6,12 @@ import (
|
||||
"fmt"
|
||||
"iter"
|
||||
"log"
|
||||
"slices"
|
||||
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/eventstore/codec/betterbinary"
|
||||
"fiatjaf.com/nostr/eventstore/internal"
|
||||
"github.com/dgraph-io/badger/v4"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
var batchFilled = errors.New("batch-filled")
|
||||
|
||||
@@ -2,11 +2,9 @@ package badger
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
"fiatjaf.com/nostr/eventstore/internal"
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/eventstore/internal"
|
||||
)
|
||||
|
||||
type query struct {
|
||||
@@ -51,13 +49,10 @@ func prepareQueries(filter nostr.Filter) (
|
||||
|
||||
if len(filter.IDs) > 0 {
|
||||
queries = make([]query, len(filter.IDs))
|
||||
for i, idHex := range filter.IDs {
|
||||
for i, id := range filter.IDs {
|
||||
prefix := make([]byte, 1+8)
|
||||
prefix[0] = indexIdPrefix
|
||||
if len(idHex) != 64 {
|
||||
return nil, nil, 0, fmt.Errorf("invalid id '%s'", idHex)
|
||||
}
|
||||
hex.Decode(prefix[1:], []byte(idHex[0:8*2]))
|
||||
copy(prefix[1:1+8], id[0:8])
|
||||
queries[i] = query{i: i, prefix: prefix, skipTimestamp: true}
|
||||
}
|
||||
|
||||
@@ -96,27 +91,20 @@ pubkeyMatching:
|
||||
if len(filter.Authors) > 0 {
|
||||
if len(filter.Kinds) == 0 {
|
||||
queries = make([]query, len(filter.Authors))
|
||||
for i, pubkeyHex := range filter.Authors {
|
||||
if len(pubkeyHex) != 64 {
|
||||
return nil, nil, 0, fmt.Errorf("invalid pubkey '%s'", pubkeyHex)
|
||||
}
|
||||
for i, pk := range filter.Authors {
|
||||
prefix := make([]byte, 1+8)
|
||||
prefix[0] = indexPubkeyPrefix
|
||||
hex.Decode(prefix[1:], []byte(pubkeyHex[0:8*2]))
|
||||
copy(prefix[1:1+8], pk[0:8])
|
||||
queries[i] = query{i: i, prefix: prefix}
|
||||
}
|
||||
} else {
|
||||
queries = make([]query, len(filter.Authors)*len(filter.Kinds))
|
||||
i := 0
|
||||
for _, pubkeyHex := range filter.Authors {
|
||||
for _, pk := range filter.Authors {
|
||||
for _, kind := range filter.Kinds {
|
||||
if len(pubkeyHex) != 64 {
|
||||
return nil, nil, 0, fmt.Errorf("invalid pubkey '%s'", pubkeyHex)
|
||||
}
|
||||
|
||||
prefix := make([]byte, 1+8+2)
|
||||
prefix[0] = indexPubkeyKindPrefix
|
||||
hex.Decode(prefix[1:], []byte(pubkeyHex[0:8*2]))
|
||||
copy(prefix[1:1+8], pk[0:8])
|
||||
binary.BigEndian.PutUint16(prefix[1+8:], uint16(kind))
|
||||
queries[i] = query{i: i, prefix: prefix}
|
||||
i++
|
||||
|
||||
Reference in New Issue
Block a user