232 lines
6.7 KiB
Go
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)
|
|
}
|