Smail/backend/services/detail/batch_read_test.go
smail 3ae8ecb11c
Some checks are pending
Security Checks / security (push) Waiting to run
修复批量已读事务原子性并补充回滚测试
2026-09-17 00:24:18 +08:00

100 lines
3.1 KiB
Go

package detail
import (
"path/filepath"
"testing"
"git.pcux.cn/xuans/Smail/config"
"git.pcux.cn/xuans/Smail/db"
"git.pcux.cn/xuans/Smail/models"
"git.pcux.cn/xuans/Smail/utils/context"
)
func initBatchReadTestDB(t *testing.T) {
t.Helper()
oldConfig := config.Instance
oldDB := db.Instance
config.Instance = &config.Config{DbType: config.DBTypeSQLite, DbDSN: filepath.Join(t.TempDir(), "batch-read.db")}
if err := db.Init(""); err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
if db.Instance != nil {
_ = db.Instance.Close()
}
db.Instance = oldDB
config.Instance = oldConfig
})
}
func TestMarkEmailsReadRejectsPartiallyOwnedBatch(t *testing.T) {
initBatchReadTestDB(t)
if _, err := db.Instance.Insert(&models.UserEmail{UserID: 1, EmailID: 101}, &models.UserEmail{UserID: 2, EmailID: 202}); err != nil {
t.Fatal(err)
}
err := MarkEmailsRead(&context.Context{UserID: 1}, []int{101, 202})
if err == nil {
t.Fatal("partially owned batch was accepted")
}
var relation models.UserEmail
if has, loadErr := db.Instance.Where("user_id = ? and email_id = ?", 1, 101).Get(&relation); loadErr != nil || !has {
t.Fatalf("load owned relation: has=%v err=%v", has, loadErr)
}
if relation.IsRead != 0 {
t.Fatalf("owned relation changed after rejected batch: is_read=%d", relation.IsRead)
}
}
func TestMarkEmailsReadRollsBackWhenUpdateFails(t *testing.T) {
initBatchReadTestDB(t)
if _, err := db.Instance.Insert(&models.UserEmail{UserID: 1, EmailID: 301}, &models.UserEmail{UserID: 1, EmailID: 302}); err != nil {
t.Fatal(err)
}
trigger := "CREATE TRIGGER fail_batch_read BEFORE UPDATE OF is_read ON user_email " +
"WHEN NEW.email_id = 302 BEGIN SELECT RAISE(ABORT, 'forced batch read failure'); END"
if _, err := db.Instance.Exec(trigger); err != nil {
t.Fatal(err)
}
err := MarkEmailsRead(&context.Context{UserID: 1}, []int{301, 302})
if err == nil {
t.Fatal("batch read update unexpectedly succeeded")
}
var relations []models.UserEmail
if err := db.Instance.Where("user_id = ?", 1).OrderBy("email_id").Find(&relations); err != nil {
t.Fatal(err)
}
if len(relations) != 2 || relations[0].IsRead != 0 || relations[1].IsRead != 0 {
t.Fatalf("batch read was partially committed: %+v", relations)
}
}
func TestMarkEmailsReadUpdatesOwnedBatchOnce(t *testing.T) {
initBatchReadTestDB(t)
if _, err := db.Instance.Insert(&models.UserEmail{UserID: 1, EmailID: 401}, &models.UserEmail{UserID: 1, EmailID: 402, IsRead: 1}); err != nil {
t.Fatal(err)
}
if err := MarkEmailsRead(&context.Context{UserID: 1}, []int{401, 401, 402}); err != nil {
t.Fatal(err)
}
var unread int64
if unread, _ = db.Instance.Where("user_id = ? and is_read = 0", 1).Count(&models.UserEmail{}); unread != 0 {
t.Fatalf("unread relation count = %d", unread)
}
}
func TestMarkEmailsReadRejectsInvalidID(t *testing.T) {
initBatchReadTestDB(t)
if err := MarkEmailsRead(&context.Context{UserID: 1}, []int{0}); err == nil {
t.Fatal("invalid email id was accepted")
}
if err := MarkEmailsRead(&context.Context{UserID: 1}, nil); err == nil {
t.Fatal("empty email id list was accepted")
}
}