Files
jules/database/postgres/notifications_test.go
2026-06-06 18:52:20 +03:00

232 lines
6.7 KiB
Go

package postgres
import (
"errors"
"sync"
"testing"
"time"
"github.com/DATA-DOG/go-sqlmock"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"m8sh.su/d/jules/database"
)
func TestNotifications_ConcurrentPop(t *testing.T) {
db := setupTestDB(t)
user := setupTestUser(t, db)
ctx := t.Context()
now := time.Now().UTC()
for range 1000 {
err := db.notifications.Push(ctx, &database.Notification{
ID: uuid.New(),
UserID: user.ID,
InitiatorID: user.ID,
ScheduledAt: now,
Content: "test",
RepeatOn: "",
})
require.NoError(t, err)
}
var wg sync.WaitGroup
popped := make(chan uuid.UUID, 1000)
for range 10 {
wg.Go(func() {
for {
notifs, err := db.notifications.Pop(ctx, 10)
require.NoError(t, err)
if len(notifs) == 0 {
return
}
for _, n := range notifs {
popped <- n.ID
}
}
})
}
wg.Wait()
close(popped)
ids := make(map[uuid.UUID]bool)
for id := range popped {
ids[id] = true
}
assert.Len(t, ids, 1000)
remaining, err := db.notifications.Pop(ctx, 10)
require.NoError(t, err)
assert.Empty(t, remaining)
}
func TestNotifications_Pop_QueryError(t *testing.T) {
conn, mock, _ := sqlmock.New()
defer conn.Close()
db := &Notifications{conn: conn}
mock.ExpectQuery(`WITH batch AS`).WillReturnError(errors.New("db down"))
_, err := db.Pop(t.Context(), 10)
assert.Error(t, err)
}
func TestNotifications_Pop_ScanError(t *testing.T) {
conn, mock, _ := sqlmock.New()
defer conn.Close()
db := &Notifications{conn: conn}
rows := sqlmock.NewRows([]string{"id", "user_id", "initiator_id", "scheduled_at", "content", "repeat_on"}).
AddRow(uuid.New(), uuid.New(), uuid.New(), time.Now(), nil, "")
mock.ExpectQuery(`WITH batch AS`).WillReturnRows(rows)
_, err := db.Pop(t.Context(), 10)
assert.Error(t, err)
}
func TestNotifications_List(t *testing.T) {
db := setupTestDB(t)
user := setupTestUser(t, db)
now := time.Now().UTC()
n1 := &database.Notification{ID: uuid.New(), UserID: user.ID, InitiatorID: user.ID, ScheduledAt: now, Content: "first", RepeatOn: ""}
n2 := &database.Notification{ID: uuid.New(), UserID: user.ID, InitiatorID: user.ID, ScheduledAt: now.Add(time.Hour), Content: "second", RepeatOn: ""}
db.notifications.Push(t.Context(), n1)
db.notifications.Push(t.Context(), n2)
notifs, err := db.notifications.List(t.Context(), user.ID)
require.NoError(t, err)
assert.Len(t, notifs, 2)
other := setupTestUser(t, db)
notifs, err = db.notifications.List(t.Context(), other.ID)
require.NoError(t, err)
assert.Empty(t, notifs)
}
func TestNotifications_Delete(t *testing.T) {
db := setupTestDB(t)
user := setupTestUser(t, db)
n := &database.Notification{ID: uuid.New(), UserID: user.ID, InitiatorID: user.ID, ScheduledAt: time.Now().UTC(), Content: "test", RepeatOn: ""}
db.notifications.Push(t.Context(), n)
err := db.notifications.Delete(t.Context(), n.ID)
require.NoError(t, err)
err = db.notifications.Delete(t.Context(), n.ID)
assert.ErrorIs(t, err, database.ErrNotFound)
}
func TestNotifications_List_QueryError(t *testing.T) {
conn, mock, _ := sqlmock.New()
defer conn.Close()
db := &Notifications{conn: conn}
mock.ExpectQuery(`SELECT id, user_id, initiator_id, scheduled_at, content, repeat_on FROM notifications`).
WillReturnError(errors.New("db down"))
_, err := db.List(t.Context(), uuid.New())
assert.Error(t, err)
}
func TestNotifications_List_ScanError(t *testing.T) {
conn, mock, _ := sqlmock.New()
defer conn.Close()
db := &Notifications{conn: conn}
rows := sqlmock.NewRows([]string{"id", "user_id", "initiator_id", "scheduled_at", "content", "repeat_on"}).
AddRow(uuid.New(), uuid.New(), uuid.New(), time.Now(), nil, "")
mock.ExpectQuery(`SELECT id, user_id, initiator_id, scheduled_at, content, repeat_on FROM notifications`).
WillReturnRows(rows)
_, err := db.List(t.Context(), uuid.New())
assert.Error(t, err)
}
func TestNotifications_ListOutgoing(t *testing.T) {
db := setupTestDB(t)
user := setupTestUser(t, db)
contact := setupTestUser(t, db)
n1 := &database.Notification{ID: uuid.New(), UserID: contact.ID, InitiatorID: user.ID, ScheduledAt: time.Now().UTC(), Content: "first", RepeatOn: ""}
n2 := &database.Notification{ID: uuid.New(), UserID: contact.ID, InitiatorID: user.ID, ScheduledAt: time.Now().UTC().Add(time.Hour), Content: "second", RepeatOn: ""}
db.Notifications().Push(t.Context(), n1)
db.Notifications().Push(t.Context(), n2)
notifs, err := db.Notifications().ListOutgoing(t.Context(), user.ID)
require.NoError(t, err)
assert.Len(t, notifs, 2)
other := setupTestUser(t, db)
notifs, err = db.Notifications().ListOutgoing(t.Context(), other.ID)
require.NoError(t, err)
assert.Empty(t, notifs)
}
func TestNotifications_ListOutgoing_QueryError(t *testing.T) {
conn, mock, _ := sqlmock.New()
defer conn.Close()
db := &Notifications{conn: conn}
mock.ExpectQuery(`SELECT id, user_id, initiator_id, scheduled_at, content, repeat_on FROM notifications`).
WillReturnError(errors.New("db down"))
_, err := db.ListOutgoing(t.Context(), uuid.New())
assert.Error(t, err)
}
func TestNotifications_ListOutgoing_ScanError(t *testing.T) {
conn, mock, _ := sqlmock.New()
defer conn.Close()
db := &Notifications{conn: conn}
rows := sqlmock.NewRows([]string{"id", "user_id", "initiator_id", "scheduled_at", "content", "repeat_on"}).
AddRow(uuid.New(), uuid.New(), uuid.New(), time.Now(), nil, "")
mock.ExpectQuery(`SELECT id, user_id, initiator_id, scheduled_at, content, repeat_on FROM notifications`).
WillReturnRows(rows)
_, err := db.ListOutgoing(t.Context(), uuid.New())
assert.Error(t, err)
}
func TestNotifications_ListOutgoing_ExcludesSelfAssigned(t *testing.T) {
db := setupTestDB(t)
user := setupTestUser(t, db)
contact := setupTestUser(t, db)
self := &database.Notification{
ID: uuid.New(),
UserID: user.ID,
InitiatorID: user.ID,
ScheduledAt: time.Now().UTC(),
Content: "self reminder",
RepeatOn: "",
}
outgoing := &database.Notification{
ID: uuid.New(),
UserID: contact.ID,
InitiatorID: user.ID,
ScheduledAt: time.Now().UTC().Add(time.Hour),
Content: "reminder for friend",
RepeatOn: "",
}
db.Notifications().Push(t.Context(), self)
db.Notifications().Push(t.Context(), outgoing)
notifs, err := db.Notifications().ListOutgoing(t.Context(), user.ID)
require.NoError(t, err)
assert.Len(t, notifs, 1)
assert.Equal(t, "reminder for friend", notifs[0].Content)
}
func TestNotifications_Delete_Error(t *testing.T) {
conn, mock, _ := sqlmock.New()
defer conn.Close()
db := &Notifications{conn: conn}
mock.ExpectExec(`DELETE FROM notifications`).WillReturnError(errors.New("db down"))
err := db.Delete(t.Context(), uuid.New())
assert.Error(t, err)
assert.NotErrorIs(t, err, database.ErrNotFound)
}