feat(server/notices): Store — 广播∪定向合并查询/已读水位/事务内插通知
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P9G7E3wmAYL9KeYCVZVsqu
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
package notices
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/wangjia/pangolin/server/internal/config"
|
||||
"github.com/wangjia/pangolin/server/internal/store"
|
||||
)
|
||||
|
||||
func openDB(t *testing.T) *sql.DB {
|
||||
t.Helper()
|
||||
db, err := store.Open(&config.Config{Driver: "sqlite", DSN: ":memory:"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() { _ = db.Close() })
|
||||
if err := store.MigrateUp(db, "sqlite"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_ = store.ApplyCodesLibMigrations(context.Background(), db, "sqlite")
|
||||
return db
|
||||
}
|
||||
|
||||
func seedU(t *testing.T, db *sql.DB, id int64, uuid string) {
|
||||
t.Helper()
|
||||
if _, err := db.Exec(`INSERT INTO users (id,uuid,email,pw_hash,dp_uuid,status,created_at)
|
||||
VALUES (?,?,?, 'x','dp-'||?, 'active', ?)`, id, uuid, uuid+"@x", uuid, time.Now().UTC()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListForUser_MergeFilterUnread(t *testing.T) {
|
||||
db := openDB(t)
|
||||
seedU(t, db, 1, "u1")
|
||||
seedU(t, db, 2, "u2")
|
||||
st := NewStore(db)
|
||||
now := time.Now().UTC()
|
||||
// 广播1条 + 我的定向1条 + 他人定向1条 + 已撤回1条 + 已过期1条
|
||||
if _, err := st.InsertBroadcast(context.Background(), "news", "b1", "b1", "", "", "", now.Add(-3*time.Hour), nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tx, _ := db.Begin()
|
||||
if err := st.InsertNoticeTx(context.Background(), tx, 1, "reward", "mine", "mine", "", "", "", now.Add(-2*time.Hour)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := st.InsertNoticeTx(context.Background(), tx, 2, "reward", "other", "other", "", "", "", now.Add(-1*time.Hour)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_ = tx.Commit()
|
||||
rid, _ := st.InsertBroadcast(context.Background(), "promo", "revoked", "revoked", "", "", "", now, nil)
|
||||
_ = st.Revoke(context.Background(), rid, now)
|
||||
exp := now.Add(-time.Minute)
|
||||
_, _ = st.InsertBroadcast(context.Background(), "promo", "expired", "expired", "", "", "", now.Add(-4*time.Hour), &exp)
|
||||
|
||||
items, unread, err := st.ListForUser(context.Background(), 1, now, 50)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(items) != 2 {
|
||||
t.Fatalf("items=%d want 2(广播+我的;他人/撤回/过期均排除): %+v", len(items), items)
|
||||
}
|
||||
if items[0].TitleZH != "mine" || items[1].TitleZH != "b1" {
|
||||
t.Fatalf("排序应 published_at 倒序: %+v", items)
|
||||
}
|
||||
if unread != 2 {
|
||||
t.Fatalf("水位为空→全未读, unread=%d", unread)
|
||||
}
|
||||
|
||||
// 置水位到 -2.5h:广播(-3h)已读、定向(-2h)未读
|
||||
if err := st.MarkRead(context.Background(), 1, now.Add(-150*time.Minute)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
items, unread, _ = st.ListForUser(context.Background(), 1, now, 50)
|
||||
if unread != 1 || !items[0].Unread || items[1].Unread {
|
||||
t.Fatalf("水位判定错: unread=%d items=%+v", unread, items)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInsertNoticeTx_RollsBackWithTx(t *testing.T) {
|
||||
db := openDB(t)
|
||||
seedU(t, db, 1, "u1")
|
||||
st := NewStore(db)
|
||||
tx, _ := db.Begin()
|
||||
if err := st.InsertNoticeTx(context.Background(), tx, 1, "reward", "t", "t", "", "", "", time.Now().UTC()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_ = tx.Rollback()
|
||||
var n int
|
||||
_ = db.QueryRow(`SELECT COUNT(*) FROM notices`).Scan(&n)
|
||||
if n != 0 {
|
||||
t.Fatalf("回滚后应零孤儿通知, got %d", n)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user