bring in khatru and eventstore.
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/fiatjaf/eventstore"
|
||||
"github.com/fiatjaf/eventstore/badger"
|
||||
"github.com/fiatjaf/eventstore/lmdb"
|
||||
"github.com/fiatjaf/eventstore/slicestore"
|
||||
"github.com/fiatjaf/eventstore/sqlite3"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
)
|
||||
|
||||
func BenchmarkSliceStore(b *testing.B) {
|
||||
s := &slicestore.SliceStore{}
|
||||
s.Init()
|
||||
runBenchmarkOn(b, s)
|
||||
}
|
||||
|
||||
func BenchmarkLMDB(b *testing.B) {
|
||||
os.RemoveAll(dbpath + "lmdb")
|
||||
l := &lmdb.LMDBBackend{Path: dbpath + "lmdb"}
|
||||
l.Init()
|
||||
|
||||
runBenchmarkOn(b, l)
|
||||
}
|
||||
|
||||
func BenchmarkBadger(b *testing.B) {
|
||||
d := &badger.BadgerBackend{Path: dbpath + "badger"}
|
||||
d.Init()
|
||||
runBenchmarkOn(b, d)
|
||||
}
|
||||
|
||||
func BenchmarkSQLite(b *testing.B) {
|
||||
os.RemoveAll(dbpath + "sqlite")
|
||||
q := &sqlite3.SQLite3Backend{DatabaseURL: dbpath + "sqlite", QueryTagsLimit: 50}
|
||||
q.Init()
|
||||
|
||||
runBenchmarkOn(b, q)
|
||||
}
|
||||
|
||||
func runBenchmarkOn(b *testing.B, db eventstore.Store) {
|
||||
for i := 0; i < 10000; i++ {
|
||||
eTag := make([]byte, 32)
|
||||
binary.BigEndian.PutUint16(eTag, uint16(i))
|
||||
|
||||
ref, _ := nostr.GetPublicKey(sk3)
|
||||
if i%3 == 0 {
|
||||
ref, _ = nostr.GetPublicKey(sk4)
|
||||
}
|
||||
|
||||
evt := &nostr.Event{
|
||||
CreatedAt: nostr.Timestamp(i*10 + 2),
|
||||
Content: fmt.Sprintf("hello %d", i),
|
||||
Tags: nostr.Tags{
|
||||
{"t", fmt.Sprintf("t%d", i)},
|
||||
{"e", hex.EncodeToString(eTag)},
|
||||
{"p", ref},
|
||||
},
|
||||
Kind: i % 10,
|
||||
}
|
||||
sk := sk3
|
||||
if i%3 == 0 {
|
||||
sk = sk4
|
||||
}
|
||||
evt.Sign(sk)
|
||||
db.SaveEvent(ctx, evt)
|
||||
}
|
||||
|
||||
filters := make([]nostr.Filter, 0, 10)
|
||||
filters = append(filters, nostr.Filter{Kinds: []int{1, 4, 8, 16}})
|
||||
pk3, _ := nostr.GetPublicKey(sk3)
|
||||
filters = append(filters, nostr.Filter{Authors: []string{pk3, nostr.GeneratePrivateKey()}})
|
||||
filters = append(filters, nostr.Filter{Authors: []string{pk3, nostr.GeneratePrivateKey()}, Kinds: []int{3, 4}})
|
||||
filters = append(filters, nostr.Filter{})
|
||||
filters = append(filters, nostr.Filter{Limit: 20})
|
||||
filters = append(filters, nostr.Filter{Kinds: []int{8, 9}, Tags: nostr.TagMap{"p": []string{pk3}}})
|
||||
pk4, _ := nostr.GetPublicKey(sk4)
|
||||
filters = append(filters, nostr.Filter{Kinds: []int{8, 9}, Tags: nostr.TagMap{"p": []string{pk3, pk4}}})
|
||||
filters = append(filters, nostr.Filter{Kinds: []int{8, 9}, Tags: nostr.TagMap{"p": []string{pk3, pk4}}})
|
||||
eTags := make([]string, 20)
|
||||
for i := 0; i < 20; i++ {
|
||||
eTag := make([]byte, 32)
|
||||
binary.BigEndian.PutUint16(eTag, uint16(i))
|
||||
eTags[i] = hex.EncodeToString(eTag)
|
||||
}
|
||||
filters = append(filters, nostr.Filter{Kinds: []int{9}, Tags: nostr.TagMap{"e": eTags}})
|
||||
filters = append(filters, nostr.Filter{Kinds: []int{5}, Tags: nostr.TagMap{"e": eTags, "t": []string{"t5"}}})
|
||||
filters = append(filters, nostr.Filter{Tags: nostr.TagMap{"e": eTags}})
|
||||
filters = append(filters, nostr.Filter{Tags: nostr.TagMap{"e": eTags}, Limit: 50})
|
||||
|
||||
b.Run("filter", func(b *testing.B) {
|
||||
for q, filter := range filters {
|
||||
b.Run(fmt.Sprintf("q-%d", q), func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = db.QueryEvents(ctx, filter)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("insert", func(b *testing.B) {
|
||||
evt := &nostr.Event{Kind: 788, CreatedAt: nostr.Now(), Content: "blergh", Tags: nostr.Tags{{"t", "spam"}}}
|
||||
evt.Sign(sk4)
|
||||
for i := 0; i < b.N; i++ {
|
||||
db.SaveEvent(ctx, evt)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
embeddedpostgres "github.com/fergusstrange/embedded-postgres"
|
||||
"github.com/fiatjaf/eventstore"
|
||||
"github.com/fiatjaf/eventstore/badger"
|
||||
"github.com/fiatjaf/eventstore/lmdb"
|
||||
"github.com/fiatjaf/eventstore/postgresql"
|
||||
"github.com/fiatjaf/eventstore/slicestore"
|
||||
"github.com/fiatjaf/eventstore/sqlite3"
|
||||
)
|
||||
|
||||
const (
|
||||
dbpath = "/tmp/eventstore-test"
|
||||
sk3 = "0000000000000000000000000000000000000000000000000000000000000003"
|
||||
sk4 = "0000000000000000000000000000000000000000000000000000000000000004"
|
||||
)
|
||||
|
||||
var ctx = context.Background()
|
||||
|
||||
var tests = []struct {
|
||||
name string
|
||||
run func(*testing.T, eventstore.Store)
|
||||
}{
|
||||
{"first", runFirstTestOn},
|
||||
{"second", runSecondTestOn},
|
||||
{"manyauthors", manyAuthorsTest},
|
||||
{"unbalanced", unbalancedTest},
|
||||
}
|
||||
|
||||
func TestSliceStore(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) { test.run(t, &slicestore.SliceStore{}) })
|
||||
}
|
||||
}
|
||||
|
||||
func TestLMDB(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
os.RemoveAll(dbpath + "lmdb")
|
||||
t.Run(test.name, func(t *testing.T) { test.run(t, &lmdb.LMDBBackend{Path: dbpath + "lmdb"}) })
|
||||
}
|
||||
}
|
||||
|
||||
func TestBadger(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
os.RemoveAll(dbpath + "badger")
|
||||
t.Run(test.name, func(t *testing.T) { test.run(t, &badger.BadgerBackend{Path: dbpath + "badger"}) })
|
||||
}
|
||||
}
|
||||
|
||||
func TestSQLite(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
os.RemoveAll(dbpath + "sqlite")
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
test.run(t, &sqlite3.SQLite3Backend{DatabaseURL: dbpath + "sqlite", QueryLimit: 1000, QueryTagsLimit: 50, QueryAuthorsLimit: 2000})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostgres(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
postgres := embeddedpostgres.NewDatabase()
|
||||
err := postgres.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to start embedded postgres: %s", err)
|
||||
return
|
||||
}
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
test.run(t, &postgresql.PostgresBackend{DatabaseURL: "postgres://postgres:postgres@localhost:5432/postgres?sslmode=disable", QueryLimit: 1000, QueryTagsLimit: 50, QueryAuthorsLimit: 2000})
|
||||
})
|
||||
postgres.Stop()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/fiatjaf/eventstore"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func runFirstTestOn(t *testing.T, db eventstore.Store) {
|
||||
err := db.Init()
|
||||
require.NoError(t, err)
|
||||
|
||||
allEvents := make([]*nostr.Event, 0, 10)
|
||||
|
||||
// insert
|
||||
for i := 0; i < 10; i++ {
|
||||
evt := &nostr.Event{
|
||||
CreatedAt: nostr.Timestamp(i*10 + 2),
|
||||
Content: fmt.Sprintf("hello %d", i),
|
||||
Tags: nostr.Tags{
|
||||
{"t", fmt.Sprintf("%d", i)},
|
||||
{"e", "0" + strconv.Itoa(i) + strings.Repeat("0", 62)},
|
||||
},
|
||||
Kind: 1,
|
||||
}
|
||||
sk := sk3
|
||||
if i%3 == 0 {
|
||||
sk = sk4
|
||||
}
|
||||
if i%2 == 0 {
|
||||
evt.Kind = 9
|
||||
}
|
||||
evt.Sign(sk)
|
||||
allEvents = append(allEvents, evt)
|
||||
err = db.SaveEvent(ctx, evt)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// query
|
||||
w := eventstore.RelayWrapper{Store: db}
|
||||
{
|
||||
results, err := w.QuerySync(ctx, nostr.Filter{})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, results, len(allEvents))
|
||||
require.ElementsMatch(t,
|
||||
allEvents,
|
||||
results,
|
||||
"open-ended query results error")
|
||||
}
|
||||
|
||||
{
|
||||
for i := 0; i < 10; i++ {
|
||||
since := nostr.Timestamp(i*10 + 1)
|
||||
results, err := w.QuerySync(ctx, nostr.Filter{Since: &since})
|
||||
require.NoError(t, err)
|
||||
require.ElementsMatch(t,
|
||||
allEvents[i:],
|
||||
results,
|
||||
"since query results error %d", i)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
results, err := w.QuerySync(ctx, nostr.Filter{IDs: []string{allEvents[7].ID, allEvents[9].ID}})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, results, 2)
|
||||
require.ElementsMatch(t,
|
||||
[]*nostr.Event{allEvents[7], allEvents[9]},
|
||||
results,
|
||||
"id query error")
|
||||
}
|
||||
|
||||
{
|
||||
results, err := w.QuerySync(ctx, nostr.Filter{Kinds: []int{1}})
|
||||
require.NoError(t, err)
|
||||
require.ElementsMatch(t,
|
||||
[]*nostr.Event{allEvents[1], allEvents[3], allEvents[5], allEvents[7], allEvents[9]},
|
||||
results,
|
||||
"kind query error")
|
||||
}
|
||||
|
||||
{
|
||||
results, err := w.QuerySync(ctx, nostr.Filter{Kinds: []int{9}})
|
||||
require.NoError(t, err)
|
||||
require.ElementsMatch(t,
|
||||
[]*nostr.Event{allEvents[0], allEvents[2], allEvents[4], allEvents[6], allEvents[8]},
|
||||
results,
|
||||
"second kind query error")
|
||||
}
|
||||
|
||||
{
|
||||
pk4, _ := nostr.GetPublicKey(sk4)
|
||||
results, err := w.QuerySync(ctx, nostr.Filter{Authors: []string{pk4}})
|
||||
require.NoError(t, err)
|
||||
require.ElementsMatch(t,
|
||||
[]*nostr.Event{allEvents[0], allEvents[3], allEvents[6], allEvents[9]},
|
||||
results,
|
||||
"pubkey query error")
|
||||
}
|
||||
|
||||
{
|
||||
pk3, _ := nostr.GetPublicKey(sk3)
|
||||
results, err := w.QuerySync(ctx, nostr.Filter{Kinds: []int{9}, Authors: []string{pk3}})
|
||||
require.NoError(t, err)
|
||||
require.ElementsMatch(t,
|
||||
[]*nostr.Event{allEvents[2], allEvents[4], allEvents[8]},
|
||||
results,
|
||||
"pubkey kind query error")
|
||||
}
|
||||
|
||||
{
|
||||
pk3, _ := nostr.GetPublicKey(sk3)
|
||||
pk4, _ := nostr.GetPublicKey(sk4)
|
||||
results, err := w.QuerySync(ctx, nostr.Filter{Kinds: []int{9, 5, 7}, Authors: []string{pk3, pk4, pk4[1:] + "a"}})
|
||||
require.NoError(t, err)
|
||||
require.ElementsMatch(t,
|
||||
[]*nostr.Event{allEvents[0], allEvents[2], allEvents[4], allEvents[6], allEvents[8]},
|
||||
results,
|
||||
"2 pubkeys and kind query error")
|
||||
}
|
||||
|
||||
{
|
||||
results, err := w.QuerySync(ctx, nostr.Filter{Tags: nostr.TagMap{"t": []string{"2", "4", "6"}}})
|
||||
require.NoError(t, err)
|
||||
require.ElementsMatch(t,
|
||||
[]*nostr.Event{allEvents[2], allEvents[4], allEvents[6]},
|
||||
results,
|
||||
"tag query error")
|
||||
}
|
||||
|
||||
// delete
|
||||
require.NoError(t, db.DeleteEvent(ctx, allEvents[4]), "delete 1 error")
|
||||
require.NoError(t, db.DeleteEvent(ctx, allEvents[5]), "delete 2 error")
|
||||
|
||||
// query again
|
||||
{
|
||||
results, err := w.QuerySync(ctx, nostr.Filter{})
|
||||
require.NoError(t, err)
|
||||
require.ElementsMatch(t,
|
||||
slices.Concat(allEvents[0:4], allEvents[6:]),
|
||||
results,
|
||||
"second open-ended query error")
|
||||
}
|
||||
|
||||
{
|
||||
results, err := w.QuerySync(ctx, nostr.Filter{Tags: nostr.TagMap{"t": []string{"2", "6"}}})
|
||||
require.NoError(t, err)
|
||||
require.ElementsMatch(t,
|
||||
[]*nostr.Event{allEvents[2], allEvents[6]},
|
||||
results,
|
||||
"second tag query error")
|
||||
}
|
||||
|
||||
{
|
||||
results, err := w.QuerySync(ctx, nostr.Filter{Tags: nostr.TagMap{"e": []string{allEvents[3].Tags[1][1]}}})
|
||||
require.NoError(t, err)
|
||||
require.ElementsMatch(t,
|
||||
[]*nostr.Event{allEvents[3]},
|
||||
results,
|
||||
"'e' tag query error")
|
||||
}
|
||||
|
||||
{
|
||||
for i := 0; i < 4; i++ {
|
||||
until := nostr.Timestamp(i*10 + 1)
|
||||
results, err := w.QuerySync(ctx, nostr.Filter{Until: &until})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.ElementsMatch(t,
|
||||
allEvents[:i],
|
||||
results,
|
||||
"until query results error %d", i)
|
||||
}
|
||||
}
|
||||
|
||||
// test p-tag querying
|
||||
{
|
||||
p := "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
|
||||
p2 := "2eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
|
||||
|
||||
newEvents := []*nostr.Event{
|
||||
{Tags: nostr.Tags{nostr.Tag{"p", p}}, Kind: 1984, CreatedAt: nostr.Timestamp(100), Content: "first"},
|
||||
{Tags: nostr.Tags{nostr.Tag{"p", p}, nostr.Tag{"t", "x"}}, Kind: 1984, CreatedAt: nostr.Timestamp(101), Content: "middle"},
|
||||
{Tags: nostr.Tags{nostr.Tag{"p", p}}, Kind: 1984, CreatedAt: nostr.Timestamp(102), Content: "last"},
|
||||
{Tags: nostr.Tags{nostr.Tag{"p", p}}, Kind: 1111, CreatedAt: nostr.Timestamp(101), Content: "bulufas"},
|
||||
{Tags: nostr.Tags{nostr.Tag{"p", p}}, Kind: 1111, CreatedAt: nostr.Timestamp(102), Content: "safulub"},
|
||||
{Tags: nostr.Tags{nostr.Tag{"p", p}}, Kind: 1, CreatedAt: nostr.Timestamp(103), Content: "bololo"},
|
||||
{Tags: nostr.Tags{nostr.Tag{"p", p2}}, Kind: 1, CreatedAt: nostr.Timestamp(104), Content: "wololo"},
|
||||
{Tags: nostr.Tags{nostr.Tag{"p", p}, nostr.Tag{"p", p2}}, Kind: 1, CreatedAt: nostr.Timestamp(104), Content: "trololo"},
|
||||
}
|
||||
|
||||
sk := nostr.GeneratePrivateKey()
|
||||
for _, newEvent := range newEvents {
|
||||
newEvent.Sign(sk)
|
||||
require.NoError(t, db.SaveEvent(ctx, newEvent))
|
||||
}
|
||||
|
||||
{
|
||||
results, err := w.QuerySync(ctx, nostr.Filter{
|
||||
Tags: nostr.TagMap{"p": []string{p}},
|
||||
Kinds: []int{1984},
|
||||
Limit: 2,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.ElementsMatch(t,
|
||||
[]*nostr.Event{newEvents[2], newEvents[1]},
|
||||
results,
|
||||
"'p' tag 1 query error")
|
||||
}
|
||||
|
||||
{
|
||||
results, err := w.QuerySync(ctx, nostr.Filter{
|
||||
Tags: nostr.TagMap{"p": []string{p}, "t": []string{"x"}},
|
||||
Limit: 4,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.ElementsMatch(t,
|
||||
// the results won't be in canonical time order because this query is too awful, needs a kind
|
||||
[]*nostr.Event{newEvents[1]},
|
||||
results,
|
||||
"'p' tag 2 query error")
|
||||
}
|
||||
|
||||
{
|
||||
results, err := w.QuerySync(ctx, nostr.Filter{
|
||||
Tags: nostr.TagMap{"p": []string{p, p2}},
|
||||
Kinds: []int{1},
|
||||
Limit: 4,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, idx := range []int{5, 6, 7} {
|
||||
require.True(t,
|
||||
slices.ContainsFunc(
|
||||
results,
|
||||
func(evt *nostr.Event) bool { return evt.ID == newEvents[idx].ID },
|
||||
),
|
||||
"'p' tag 3 query error")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/fiatjaf/eventstore"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func manyAuthorsTest(t *testing.T, db eventstore.Store) {
|
||||
db.Init()
|
||||
|
||||
const total = 10000
|
||||
const limit = 500
|
||||
const authors = 1700
|
||||
kinds := []int{6, 7, 8}
|
||||
|
||||
bigfilter := nostr.Filter{
|
||||
Authors: make([]string, authors),
|
||||
Kinds: kinds,
|
||||
Limit: limit,
|
||||
}
|
||||
for i := 0; i < authors; i++ {
|
||||
sk := make([]byte, 32)
|
||||
binary.BigEndian.PutUint32(sk, uint32(i%(total/5))+1)
|
||||
pk, _ := nostr.GetPublicKey(hex.EncodeToString(sk))
|
||||
bigfilter.Authors[i] = pk
|
||||
}
|
||||
|
||||
ordered := make([]*nostr.Event, 0, total)
|
||||
for i := 0; i < total; i++ {
|
||||
sk := make([]byte, 32)
|
||||
binary.BigEndian.PutUint32(sk, uint32(i%(total/5))+1)
|
||||
|
||||
evt := &nostr.Event{
|
||||
CreatedAt: nostr.Timestamp(i*i) / 4,
|
||||
Content: fmt.Sprintf("lots of stuff %d", i),
|
||||
Tags: nostr.Tags{},
|
||||
Kind: i % 10,
|
||||
}
|
||||
err := evt.Sign(hex.EncodeToString(sk))
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.SaveEvent(ctx, evt)
|
||||
require.NoError(t, err)
|
||||
|
||||
if bigfilter.Matches(evt) {
|
||||
ordered = append(ordered, evt)
|
||||
}
|
||||
}
|
||||
|
||||
w := eventstore.RelayWrapper{Store: db}
|
||||
|
||||
res, err := w.QuerySync(ctx, bigfilter)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Len(t, res, limit)
|
||||
require.True(t, slices.IsSortedFunc(res, nostr.CompareEventPtrReverse))
|
||||
slices.SortFunc(ordered, nostr.CompareEventPtrReverse)
|
||||
require.Equal(t, ordered[0], res[0])
|
||||
require.Equal(t, ordered[limit-1], res[limit-1])
|
||||
require.Equal(t, ordered[0:limit], res)
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/fiatjaf/eventstore"
|
||||
"github.com/fiatjaf/eventstore/slicestore"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var sk = "486d5f6d4891f4ce3cd5f4d6b62d184ec8ea10db455830ab7918ca43d4d7ad24"
|
||||
|
||||
func TestRelayWrapper(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
s := &slicestore.SliceStore{}
|
||||
s.Init()
|
||||
defer s.Close()
|
||||
|
||||
w := eventstore.RelayWrapper{Store: s}
|
||||
|
||||
evt1 := nostr.Event{
|
||||
Kind: 3,
|
||||
CreatedAt: 0,
|
||||
Tags: nostr.Tags{},
|
||||
Content: "first",
|
||||
}
|
||||
evt1.Sign(sk)
|
||||
|
||||
evt2 := nostr.Event{
|
||||
Kind: 3,
|
||||
CreatedAt: 1,
|
||||
Tags: nostr.Tags{},
|
||||
Content: "second",
|
||||
}
|
||||
evt2.Sign(sk)
|
||||
|
||||
for range 200 {
|
||||
go w.Publish(ctx, evt1)
|
||||
go w.Publish(ctx, evt1)
|
||||
}
|
||||
time.Sleep(time.Millisecond * 200)
|
||||
|
||||
evts, _ := w.QuerySync(ctx, nostr.Filter{Kinds: []int{3}})
|
||||
require.Len(t, evts, 1)
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/fiatjaf/eventstore"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func runSecondTestOn(t *testing.T, db eventstore.Store) {
|
||||
db.Init()
|
||||
|
||||
for i := 0; i < 10000; i++ {
|
||||
eTag := make([]byte, 32)
|
||||
binary.BigEndian.PutUint16(eTag, uint16(i))
|
||||
|
||||
ref, _ := nostr.GetPublicKey(sk3)
|
||||
if i%3 == 0 {
|
||||
ref, _ = nostr.GetPublicKey(sk4)
|
||||
}
|
||||
|
||||
evt := &nostr.Event{
|
||||
CreatedAt: nostr.Timestamp(i*10 + 2),
|
||||
Content: fmt.Sprintf("hello %d", i),
|
||||
Tags: nostr.Tags{
|
||||
{"t", fmt.Sprintf("t%d", i)},
|
||||
{"e", hex.EncodeToString(eTag)},
|
||||
{"p", ref},
|
||||
},
|
||||
Kind: i % 10,
|
||||
}
|
||||
sk := sk3
|
||||
if i%3 == 0 {
|
||||
sk = sk4
|
||||
}
|
||||
evt.Sign(sk)
|
||||
err := db.SaveEvent(ctx, evt)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
w := eventstore.RelayWrapper{Store: db}
|
||||
pk3, _ := nostr.GetPublicKey(sk3)
|
||||
pk4, _ := nostr.GetPublicKey(sk4)
|
||||
eTags := make([]string, 20)
|
||||
for i := 0; i < 20; i++ {
|
||||
eTag := make([]byte, 32)
|
||||
binary.BigEndian.PutUint16(eTag, uint16(i))
|
||||
eTags[i] = hex.EncodeToString(eTag)
|
||||
}
|
||||
|
||||
filters := make([]nostr.Filter, 0, 10)
|
||||
filters = append(filters, nostr.Filter{Kinds: []int{1, 4, 8, 16}})
|
||||
filters = append(filters, nostr.Filter{Authors: []string{pk3, nostr.GeneratePrivateKey()}})
|
||||
filters = append(filters, nostr.Filter{Authors: []string{pk3, nostr.GeneratePrivateKey()}, Kinds: []int{3, 4}})
|
||||
filters = append(filters, nostr.Filter{})
|
||||
filters = append(filters, nostr.Filter{Limit: 20})
|
||||
filters = append(filters, nostr.Filter{Kinds: []int{8, 9}, Tags: nostr.TagMap{"p": []string{pk3}}})
|
||||
filters = append(filters, nostr.Filter{Kinds: []int{8, 9}, Tags: nostr.TagMap{"p": []string{pk3, pk4}}})
|
||||
filters = append(filters, nostr.Filter{Kinds: []int{8, 9}, Tags: nostr.TagMap{"p": []string{pk3, pk4}}})
|
||||
filters = append(filters, nostr.Filter{Kinds: []int{9}, Tags: nostr.TagMap{"e": eTags}})
|
||||
filters = append(filters, nostr.Filter{Kinds: []int{5}, Tags: nostr.TagMap{"e": eTags, "t": []string{"t5"}}})
|
||||
filters = append(filters, nostr.Filter{Tags: nostr.TagMap{"e": eTags}})
|
||||
filters = append(filters, nostr.Filter{Tags: nostr.TagMap{"e": eTags}, Limit: 50})
|
||||
|
||||
t.Run("filter", func(t *testing.T) {
|
||||
for q, filter := range filters {
|
||||
q := q
|
||||
filter := filter
|
||||
label := fmt.Sprintf("filter %d: %s", q, filter)
|
||||
|
||||
t.Run(fmt.Sprintf("q-%d", q), func(t *testing.T) {
|
||||
results, err := w.QuerySync(ctx, filter)
|
||||
require.NoError(t, err, filter)
|
||||
require.NotEmpty(t, results, label)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
)
|
||||
|
||||
func getTimestamps(events []*nostr.Event) []nostr.Timestamp {
|
||||
res := make([]nostr.Timestamp, len(events))
|
||||
for i, evt := range events {
|
||||
res[i] = evt.CreatedAt
|
||||
}
|
||||
return res
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/fiatjaf/eventstore"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// this is testing what happens when most results come from the same abstract query -- but not all
|
||||
func unbalancedTest(t *testing.T, db eventstore.Store) {
|
||||
db.Init()
|
||||
|
||||
const total = 10000
|
||||
const limit = 160
|
||||
const authors = 1400
|
||||
|
||||
bigfilter := nostr.Filter{
|
||||
Authors: make([]string, authors),
|
||||
Limit: limit,
|
||||
}
|
||||
for i := 0; i < authors; i++ {
|
||||
sk := make([]byte, 32)
|
||||
binary.BigEndian.PutUint32(sk, uint32(i%(authors*2))+1)
|
||||
pk, _ := nostr.GetPublicKey(hex.EncodeToString(sk))
|
||||
bigfilter.Authors[i] = pk
|
||||
}
|
||||
// fmt.Println("filter", bigfilter)
|
||||
|
||||
expected := make([]*nostr.Event, 0, total)
|
||||
for i := 0; i < total; i++ {
|
||||
skseed := uint32(i%(authors*2)) + 1
|
||||
sk := make([]byte, 32)
|
||||
binary.BigEndian.PutUint32(sk, skseed)
|
||||
|
||||
evt := &nostr.Event{
|
||||
CreatedAt: nostr.Timestamp(skseed)*1000 + nostr.Timestamp(i),
|
||||
Content: fmt.Sprintf("unbalanced %d", i),
|
||||
Tags: nostr.Tags{},
|
||||
Kind: 1,
|
||||
}
|
||||
err := evt.Sign(hex.EncodeToString(sk))
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.SaveEvent(ctx, evt)
|
||||
require.NoError(t, err)
|
||||
|
||||
if bigfilter.Matches(evt) {
|
||||
expected = append(expected, evt)
|
||||
}
|
||||
}
|
||||
|
||||
slices.SortFunc(expected, nostr.CompareEventPtrReverse)
|
||||
if len(expected) > limit {
|
||||
expected = expected[0:limit]
|
||||
}
|
||||
require.Len(t, expected, limit)
|
||||
|
||||
w := eventstore.RelayWrapper{Store: db}
|
||||
|
||||
res, err := w.QuerySync(ctx, bigfilter)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, limit, len(res))
|
||||
require.True(t, slices.IsSortedFunc(res, nostr.CompareEventPtrReverse))
|
||||
require.Equal(t, expected[0], res[0])
|
||||
|
||||
// fmt.Println(" expected result")
|
||||
// ets := getTimestamps(expected)
|
||||
// rts := getTimestamps(res)
|
||||
// for i := range ets {
|
||||
// fmt.Println(" ", ets[i], " ", rts[i], " ", i)
|
||||
// }
|
||||
|
||||
require.Equal(t, expected[limit-1], res[limit-1])
|
||||
require.Equal(t, expected[0:limit], res)
|
||||
}
|
||||
Reference in New Issue
Block a user