Files
m8sh/database/leveldb/reactions_test.go
T
2026-06-06 19:20:38 +03:00

360 lines
10 KiB
Go

package leveldb
import (
"testing"
"time"
"github.com/stretchr/testify/require"
"m8sh.su/x/m8sh/database"
)
func TestReactions_SetAllowed(t *testing.T) {
db, cleanup := openDB(t)
defer cleanup()
reactions := db.Reactions()
owner := "masha"
date := time.Now()
t.Run("set allowed emojis", func(t *testing.T) {
err := reactions.SetAllowed(owner, date, []string{"👍", "❤️", "🔥"})
require.NoError(t, err)
})
t.Run("overwrite allowed emojis", func(t *testing.T) {
err := reactions.SetAllowed(owner, date, []string{"👎"})
require.NoError(t, err)
})
t.Run("set empty allowed", func(t *testing.T) {
err := reactions.SetAllowed(owner, date.Add(time.Second), []string{})
require.NoError(t, err)
})
}
func TestReactions_Allowed(t *testing.T) {
db, cleanup := openDB(t)
defer cleanup()
reactions := db.Reactions()
owner := "masha"
date := time.Now()
err := reactions.SetAllowed(owner, date, []string{"👍", "🔥"})
require.NoError(t, err)
t.Run("get allowed emojis", func(t *testing.T) {
got, err := reactions.GetAllowed(owner, date)
require.NoError(t, err)
require.Equal(t, []string{"👍", "🔥"}, got)
})
t.Run("get non-existing allowed", func(t *testing.T) {
_, err := reactions.GetAllowed("unknown", date)
require.ErrorIs(t, err, database.ErrNotFound)
})
t.Run("get allowed for different time", func(t *testing.T) {
_, err := reactions.GetAllowed(owner, date.Add(time.Hour))
require.ErrorIs(t, err, database.ErrNotFound)
})
}
func TestReactions_Add(t *testing.T) {
db, cleanup := openDB(t)
defer cleanup()
reactions := db.Reactions()
user := "masha"
author := "bob"
domain := "d2.io"
date := time.Now()
t.Run("add reaction", func(t *testing.T) {
err := reactions.Add(user, author, domain, date, "👍", []byte("sig123"))
require.NoError(t, err)
})
t.Run("add another emoji", func(t *testing.T) {
err := reactions.Add(user, author, domain, date, "🔥", []byte("sig456"))
require.NoError(t, err)
})
t.Run("add different user", func(t *testing.T) {
err := reactions.Add("petya", author, domain, date, "👍", []byte("sig789"))
require.NoError(t, err)
})
t.Run("add different content", func(t *testing.T) {
err := reactions.Add(user, "alice", "d3.net", date.Add(time.Second), "❤️", []byte("sig000"))
require.NoError(t, err)
})
t.Run("add with empty signature", func(t *testing.T) {
err := reactions.Add(user, author, domain, date.Add(2*time.Second), "❤️", nil)
require.NoError(t, err)
})
}
func TestReactions_Remove(t *testing.T) {
db, cleanup := openDB(t)
defer cleanup()
reactions := db.Reactions()
user := "masha"
author := "bob"
domain := "d2.io"
date := time.Now()
err := reactions.Add(user, author, domain, date, "👍", []byte("sig"))
require.NoError(t, err)
t.Run("remove existing reaction", func(t *testing.T) {
err := reactions.Remove(user, author, domain, date, "👍")
require.NoError(t, err)
})
t.Run("remove non-existing returns ErrNotFound", func(t *testing.T) {
err := reactions.Remove(user, author, domain, date, "🔥")
require.ErrorIs(t, err, database.ErrNotFound)
})
t.Run("remove twice returns ErrNotFound", func(t *testing.T) {
err := reactions.Remove(user, author, domain, date, "👍")
require.ErrorIs(t, err, database.ErrNotFound)
})
}
func TestReactions_OfUser(t *testing.T) {
db, cleanup := openDB(t)
defer cleanup()
reactions := db.Reactions()
user := "masha"
date := time.Now()
t.Run("empty reactions", func(t *testing.T) {
list, err := reactions.OfUser("newuser", 10, 0)
require.NoError(t, err)
require.Empty(t, list)
})
t.Run("single reaction", func(t *testing.T) {
err := reactions.Add(user, "bob", "d2.io", date, "👍", []byte("sig123"))
require.NoError(t, err)
list, err := reactions.OfUser(user, 10, 0)
require.NoError(t, err)
require.Len(t, list, 1)
require.Equal(t, "bob", list[0].Author)
require.Equal(t, "d2.io", list[0].Domain)
require.Equal(t, "👍", list[0].Emoji)
})
t.Run("multiple reactions", func(t *testing.T) {
err := reactions.Add(user, "alice", "d3.net", date.Add(time.Second), "🔥", []byte("sig456"))
require.NoError(t, err)
list, err := reactions.OfUser(user, 10, 0)
require.NoError(t, err)
require.Len(t, list, 2)
})
t.Run("pagination limit", func(t *testing.T) {
list, err := reactions.OfUser(user, 1, 0)
require.NoError(t, err)
require.Len(t, list, 1)
})
t.Run("pagination offset", func(t *testing.T) {
list, err := reactions.OfUser(user, 10, 1)
require.NoError(t, err)
require.Len(t, list, 1)
})
t.Run("user isolation", func(t *testing.T) {
err := reactions.Add("petya", "bob", "d2.io", date, "👍", []byte("sig777"))
require.NoError(t, err)
mashaList, err := reactions.OfUser("masha", 10, 0)
require.NoError(t, err)
petyaList, err := reactions.OfUser("petya", 10, 0)
require.NoError(t, err)
require.Len(t, mashaList, 2)
require.Len(t, petyaList, 1)
})
}
func TestReactions_ToContent(t *testing.T) {
db, cleanup := openDB(t)
defer cleanup()
r := db.Reactions()
author := "bob"
domain := "d2.io"
date := time.Now()
t.Run("empty reactions", func(t *testing.T) {
list, err := r.ToContent(author, domain, date, 10, 0)
require.NoError(t, err)
require.Empty(t, list)
})
t.Run("reactions from multiple users", func(t *testing.T) {
require.NoError(t, r.Add("masha", author, domain, date, "👍", []byte("sig1")))
require.NoError(t, r.Add("petya", author, domain, date, "🔥", []byte("sig2")))
require.NoError(t, r.Add("alice", author, domain, date, "👍", []byte("sig3")))
list, err := r.ToContent(author, domain, date, 10, 0)
require.NoError(t, err)
require.Len(t, list, 3)
})
t.Run("pagination", func(t *testing.T) {
list, err := r.ToContent(author, domain, date, 2, 0)
require.NoError(t, err)
require.Len(t, list, 2)
list, err = r.ToContent(author, domain, date, 10, 2)
require.NoError(t, err)
require.Len(t, list, 1)
})
t.Run("wrong domain returns empty", func(t *testing.T) {
list, err := r.ToContent(author, "unknown.com", date, 10, 0)
require.NoError(t, err)
require.Empty(t, list)
})
t.Run("wrong author returns empty", func(t *testing.T) {
list, err := r.ToContent("unknown", domain, date, 10, 0)
require.NoError(t, err)
require.Empty(t, list)
})
t.Run("signature preserved", func(t *testing.T) {
list, err := r.ToContent(author, domain, date, 10, 0)
require.NoError(t, err)
require.Len(t, list, 3)
sigs := make(map[string][]byte)
for _, r := range list {
sigs[r.User] = r.Signature
}
require.Equal(t, []byte("sig1"), sigs["masha"])
require.Equal(t, []byte("sig2"), sigs["petya"])
require.Equal(t, []byte("sig3"), sigs["alice"])
})
}
func TestReactions_UpdateReference(t *testing.T) {
db, cleanup := openDB(t)
defer cleanup()
db.reactions.flushInterval = 10 * time.Millisecond
reactions := db.Reactions()
owner := "bob"
date := time.Now()
t.Run("increment new reaction", func(t *testing.T) {
err := reactions.UpdateReference(owner, date, "d1.com", "👍", 1)
require.NoError(t, err)
})
t.Run("increment existing multiple times", func(t *testing.T) {
require.NoError(t, reactions.UpdateReference(owner, date, "d1.com", "👍", 2))
require.NoError(t, reactions.UpdateReference(owner, date, "d1.com", "👍", 3))
})
t.Run("decrement partial", func(t *testing.T) {
require.NoError(t, reactions.UpdateReference(owner, date, "d1.com", "👍", -2))
})
t.Run("decrement below zero clamps to zero", func(t *testing.T) {
require.NoError(t, reactions.UpdateReference(owner, date, "d3.net", "🔥", 1))
require.NoError(t, reactions.UpdateReference(owner, date, "d3.net", "🔥", -10))
})
}
func TestReactions_ListReactions(t *testing.T) {
db, cleanup := openDB(t)
defer cleanup()
db.reactions.flushInterval = 10 * time.Millisecond
reactions := db.Reactions()
owner := "bob"
date := time.Now()
t.Run("empty reactions", func(t *testing.T) {
list, err := reactions.ListReactions(owner, date)
require.NoError(t, err)
require.Empty(t, list)
})
t.Run("single emoji after flush", func(t *testing.T) {
require.NoError(t, reactions.UpdateReference(owner, date, "d1.com", "👍", 5))
time.Sleep(50 * time.Millisecond)
list, err := reactions.ListReactions(owner, date)
require.NoError(t, err)
require.Len(t, list, 1)
require.Equal(t, "👍", list[0].Emoji)
require.Equal(t, uint64(5), list[0].Count)
})
t.Run("multiple emojis from different domains", func(t *testing.T) {
require.NoError(t, reactions.UpdateReference(owner, date, "d1.com", "🔥", 3))
require.NoError(t, reactions.UpdateReference(owner, date, "d2.io", "🔥", 2))
require.NoError(t, reactions.UpdateReference(owner, date, "d1.com", "❤️", 1))
time.Sleep(50 * time.Millisecond)
list, err := reactions.ListReactions(owner, date)
require.NoError(t, err)
counts := make(map[string]uint64)
for _, r := range list {
counts[r.Emoji] = r.Count
}
require.Equal(t, uint64(5), counts["👍"])
require.Equal(t, uint64(5), counts["🔥"])
require.Equal(t, uint64(1), counts["❤️"])
})
t.Run("decrement to zero removes emoji", func(t *testing.T) {
owner2 := "test-delete"
date2 := time.Now()
require.NoError(t, reactions.UpdateReference(owner2, date2, "d1.com", "👍", 1))
require.NoError(t, reactions.UpdateReference(owner2, date2, "d1.com", "👍", -1))
time.Sleep(50 * time.Millisecond)
list, err := reactions.ListReactions(owner2, date2)
require.NoError(t, err)
require.Empty(t, list)
})
}
func TestReactions_AfterClose(t *testing.T) {
path := t.TempDir() + "/db"
db, err := New(path)
require.NoError(t, err)
reactions := db.Reactions()
err = reactions.SetAllowed("masha", time.Now(), []string{"👍"})
require.NoError(t, err)
require.NoError(t, db.Close())
require.NotPanics(t, func() {
reactions.SetAllowed("masha", time.Now(), []string{"👍"})
reactions.GetAllowed("masha", time.Now())
reactions.Add("masha", "bob", "d2.io", time.Now(), "👍", nil)
reactions.Remove("masha", "bob", "d2.io", time.Now(), "👍")
reactions.OfUser("masha", 10, 0)
reactions.ToContent("bob", "d2.io", time.Now(), 10, 0)
reactions.UpdateReference("bob", time.Now(), "d1.com", "👍", 1)
reactions.ListReactions("bob", time.Now())
})
}