289 lines
8.3 KiB
Go
289 lines
8.3 KiB
Go
package leveldb
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/d1nch8g/mesh/database"
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestSubscriptions_Subscribe(t *testing.T) {
|
|
db, cleanup := openDB(t)
|
|
defer cleanup()
|
|
|
|
subs := db.Subscriptions()
|
|
|
|
t.Run("subscribe new target", func(t *testing.T) {
|
|
err := subs.Subscribe("masha@d1.com", "bob@d2.io", []byte("sig123"))
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("subscribe overwrites existing", func(t *testing.T) {
|
|
err := subs.Subscribe("masha@d1.com", "bob@d2.io", []byte("sig456"))
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("subscribe multiple targets", func(t *testing.T) {
|
|
require.NoError(t, subs.Subscribe("masha@d1.com", "alice@d3.net", []byte("sig789")))
|
|
require.NoError(t, subs.Subscribe("masha@d1.com", "carol@d4.org", []byte("sig000")))
|
|
})
|
|
|
|
t.Run("subscribe different users", func(t *testing.T) {
|
|
require.NoError(t, subs.Subscribe("petya@d1.com", "bob@d2.io", []byte("sig111")))
|
|
require.NoError(t, subs.Subscribe("petya@d1.com", "alice@d3.net", []byte("sig222")))
|
|
})
|
|
|
|
t.Run("subscribe empty signature", func(t *testing.T) {
|
|
err := subs.Subscribe("masha@d1.com", "empty-sig@d5.io", nil)
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
|
|
func TestSubscriptions_Unsubscribe(t *testing.T) {
|
|
db, cleanup := openDB(t)
|
|
defer cleanup()
|
|
|
|
subs := db.Subscriptions()
|
|
|
|
require.NoError(t, subs.Subscribe("masha@d1.com", "bob@d2.io", []byte("sig123")))
|
|
require.NoError(t, subs.Subscribe("masha@d1.com", "alice@d3.net", []byte("sig456")))
|
|
|
|
t.Run("unsubscribe existing", func(t *testing.T) {
|
|
err := subs.Unsubscribe("masha@d1.com", "bob@d2.io")
|
|
require.NoError(t, err)
|
|
|
|
list, err := subs.OfUser("masha@d1.com")
|
|
require.NoError(t, err)
|
|
for _, sub := range list {
|
|
require.NotEqual(t, "bob@d2.io", sub.Email)
|
|
}
|
|
})
|
|
|
|
t.Run("unsubscribe non-existing returns ErrNotFound", func(t *testing.T) {
|
|
err := subs.Unsubscribe("masha@d1.com", "nonexistent@d5.io")
|
|
require.ErrorIs(t, err, database.ErrNotFound)
|
|
})
|
|
|
|
t.Run("unsubscribe already removed returns ErrNotFound", func(t *testing.T) {
|
|
err := subs.Unsubscribe("masha@d1.com", "bob@d2.io")
|
|
require.ErrorIs(t, err, database.ErrNotFound)
|
|
})
|
|
}
|
|
|
|
func TestSubscriptions_OfUser(t *testing.T) {
|
|
db, cleanup := openDB(t)
|
|
defer cleanup()
|
|
|
|
subs := db.Subscriptions()
|
|
|
|
t.Run("empty subscriptions", func(t *testing.T) {
|
|
list, err := subs.OfUser("newuser@d1.com")
|
|
require.NoError(t, err)
|
|
require.Empty(t, list)
|
|
})
|
|
|
|
t.Run("single subscription", func(t *testing.T) {
|
|
require.NoError(t, subs.Subscribe("masha@d1.com", "bob@d2.io", []byte("sig123")))
|
|
|
|
list, err := subs.OfUser("masha@d1.com")
|
|
require.NoError(t, err)
|
|
require.Len(t, list, 1)
|
|
require.Equal(t, "bob@d2.io", list[0].Email)
|
|
require.Equal(t, []byte("sig123"), list[0].Signature)
|
|
})
|
|
|
|
t.Run("multiple subscriptions", func(t *testing.T) {
|
|
require.NoError(t, subs.Subscribe("masha@d1.com", "alice@d3.net", []byte("sig456")))
|
|
|
|
list, err := subs.OfUser("masha@d1.com")
|
|
require.NoError(t, err)
|
|
require.Len(t, list, 2)
|
|
})
|
|
|
|
t.Run("isolation between users", func(t *testing.T) {
|
|
require.NoError(t, subs.Subscribe("petya@d1.com", "bob@d2.io", []byte("sig777")))
|
|
|
|
mashaList, err := subs.OfUser("masha@d1.com")
|
|
require.NoError(t, err)
|
|
|
|
petyaList, err := subs.OfUser("petya@d1.com")
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, mashaList, 2)
|
|
require.Len(t, petyaList, 1)
|
|
})
|
|
}
|
|
|
|
func TestSubscriptions_ToUser(t *testing.T) {
|
|
db, cleanup := openDB(t)
|
|
defer cleanup()
|
|
|
|
subs := db.Subscriptions()
|
|
|
|
t.Run("empty subscribers", func(t *testing.T) {
|
|
list, err := subs.ToUser("bob@d2.io")
|
|
require.NoError(t, err)
|
|
require.Empty(t, list)
|
|
})
|
|
}
|
|
|
|
func TestSubscriptions_UpdateReference(t *testing.T) {
|
|
db, cleanup := openDB(t)
|
|
defer cleanup()
|
|
db.subscriptions.flushInterval = 10 * time.Millisecond
|
|
|
|
subs := db.Subscriptions()
|
|
|
|
t.Run("increment new domain", func(t *testing.T) {
|
|
err := subs.UpdateReference("bob@d2.io", "d1.com", 1)
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("increment existing domain multiple times", func(t *testing.T) {
|
|
require.NoError(t, subs.UpdateReference("bob@d2.io", "d1.com", 2))
|
|
require.NoError(t, subs.UpdateReference("bob@d2.io", "d1.com", 3))
|
|
})
|
|
|
|
t.Run("decrement domain partial", func(t *testing.T) {
|
|
require.NoError(t, subs.UpdateReference("bob@d2.io", "d1.com", -2))
|
|
})
|
|
|
|
t.Run("decrement to zero deletes key", func(t *testing.T) {
|
|
user := uuid.NewString()
|
|
require.NoError(t, subs.UpdateReference(user, "d2.io", 1))
|
|
require.NoError(t, subs.UpdateReference(user, "d2.io", -1))
|
|
time.Sleep(50 * time.Millisecond)
|
|
refs, err := subs.ListReferences(user)
|
|
require.NoError(t, err)
|
|
for _, ref := range refs {
|
|
require.NotEqual(t, "d2.io", ref.Domain)
|
|
}
|
|
})
|
|
|
|
t.Run("decrement below zero clamps to zero", func(t *testing.T) {
|
|
require.NoError(t, subs.UpdateReference("bob@d2.io", "d3.net", 1))
|
|
require.NoError(t, subs.UpdateReference("bob@d2.io", "d3.net", -10))
|
|
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
refs, err := subs.ListReferences("bob@d2.io")
|
|
require.NoError(t, err)
|
|
for _, ref := range refs {
|
|
require.NotEqual(t, "d3.net", ref.Domain)
|
|
}
|
|
})
|
|
|
|
t.Run("negative delta only", func(t *testing.T) {
|
|
err := subs.UpdateReference("bob@d2.io", "d4.org", -3)
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("update total counter consistency", func(t *testing.T) {
|
|
require.NoError(t, db.Users().Create(database.User{Name: "charlie@d5.io"}))
|
|
|
|
require.NoError(t, subs.UpdateReference("charlie@d5.io", "d1.com", 5))
|
|
require.NoError(t, subs.UpdateReference("charlie@d5.io", "d2.io", 3))
|
|
require.NoError(t, subs.UpdateReference("charlie@d5.io", "d1.com", -2))
|
|
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
user, err := db.Users().Get("charlie@d5.io")
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(6), user.Subscribers)
|
|
})
|
|
}
|
|
|
|
func TestSubscriptions_ListReferences(t *testing.T) {
|
|
db, cleanup := openDB(t)
|
|
defer cleanup()
|
|
db.subscriptions.flushInterval = 10 * time.Millisecond
|
|
|
|
subs := db.Subscriptions()
|
|
|
|
t.Run("empty references", func(t *testing.T) {
|
|
refs, err := subs.ListReferences("unknown@d1.com")
|
|
require.NoError(t, err)
|
|
require.Empty(t, refs)
|
|
})
|
|
|
|
t.Run("single reference", func(t *testing.T) {
|
|
err := subs.UpdateReference("ref-user@x.com", "d1.com", 5)
|
|
require.NoError(t, err)
|
|
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
refs, err := subs.ListReferences("ref-user@x.com")
|
|
require.NoError(t, err)
|
|
require.Len(t, refs, 1)
|
|
require.Equal(t, "d1.com", refs[0].Domain)
|
|
require.Equal(t, uint64(5), refs[0].Count)
|
|
})
|
|
|
|
t.Run("multiple references sorted", func(t *testing.T) {
|
|
require.NoError(t, subs.UpdateReference("alice@d3.net", "d1.com", 3))
|
|
require.NoError(t, subs.UpdateReference("alice@d3.net", "d2.io", 7))
|
|
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
refs, err := subs.ListReferences("alice@d3.net")
|
|
require.NoError(t, err)
|
|
require.Len(t, refs, 2)
|
|
})
|
|
|
|
t.Run("deleted domain not in list", func(t *testing.T) {
|
|
require.NoError(t, subs.UpdateReference("carol@d4.org", "temp.io", 1))
|
|
require.NoError(t, subs.UpdateReference("carol@d4.org", "temp.io", -1))
|
|
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
refs, err := subs.ListReferences("carol@d4.org")
|
|
require.NoError(t, err)
|
|
for _, ref := range refs {
|
|
require.NotEqual(t, "temp.io", ref.Domain)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestSubscriptions_AfterCloseReturnsError(t *testing.T) {
|
|
path := t.TempDir() + "/db"
|
|
db, err := New(path)
|
|
require.NoError(t, err)
|
|
|
|
subs := db.Subscriptions()
|
|
require.NoError(t, subs.Subscribe("masha@d1.com", "bob@d2.io", []byte("sig")))
|
|
require.NoError(t, db.Close())
|
|
|
|
// After close, operations may error or return empty — just verify no panic.
|
|
require.NotPanics(t, func() {
|
|
subs.OfUser("masha@d1.com")
|
|
subs.Subscribe("masha@d1.com", "carol@d4.org", []byte("sig"))
|
|
subs.Unsubscribe("masha@d1.com", "bob@d2.io")
|
|
subs.ListReferences("masha@d1.com")
|
|
})
|
|
}
|
|
|
|
func TestSubscriptions_ToUser_Integration(t *testing.T) {
|
|
db, cleanup := openDB(t)
|
|
defer cleanup()
|
|
|
|
subs := db.Subscriptions()
|
|
|
|
require.NoError(t, subs.Subscribe("masha@d1.com", "bob@d2.io", []byte("sig-masha-bob")))
|
|
require.NoError(t, subs.Subscribe("petya@d1.com", "bob@d2.io", []byte("sig-petya-bob")))
|
|
require.NoError(t, subs.Subscribe("alice@d1.com", "bob@d2.io", []byte("sig-alice-bob")))
|
|
|
|
list, err := subs.ToUser("bob@d2.io")
|
|
require.NoError(t, err)
|
|
require.Len(t, list, 3)
|
|
|
|
emails := make(map[string]bool)
|
|
for _, sub := range list {
|
|
emails[sub.Email] = true
|
|
}
|
|
require.True(t, emails["masha@d1.com"])
|
|
require.True(t, emails["petya@d1.com"])
|
|
require.True(t, emails["alice@d1.com"])
|
|
}
|