This commit is contained in:
tamakyi
2026-09-08 10:10:44 +08:00
commit 2ed27dd55e
288 changed files with 67061 additions and 0 deletions
+252
View File
@@ -0,0 +1,252 @@
// Copyright 2026 tamakyi. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package db
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"github.com/tamakyi/TamaBox/internal/dbutil"
)
func TestVotes(t *testing.T) {
t.Parallel()
db, cleanup := newTestDB(t)
ctx := context.Background()
store := NewVotesStore(db)
for _, tc := range []struct {
name string
test func(t *testing.T, ctx context.Context, db *votes)
}{
{"Create", testVotesCreate},
{"GetByID", testVotesGetByID},
{"GetByToken", testVotesGetByToken},
{"ListByUserID", testVotesListByUserID},
{"Update", testVotesUpdate},
{"UpdateStatus", testVotesUpdateStatus},
{"DeleteByID", testVotesDeleteByID},
{"CountByUserID", testVotesCountByUserID},
} {
t.Run(tc.name, func(t *testing.T) {
t.Cleanup(func() {
if err := cleanup("votes"); err != nil {
t.Fatal(err)
}
})
tc.test(t, ctx, store.(*votes))
})
}
}
func testVotesCreate(t *testing.T, ctx context.Context, db *votes) {
got, err := db.Create(ctx, CreateVoteOptions{
UserID: 1,
Title: "今天穿什么?",
Description: "单选题",
Type: VoteTypeSingle,
Visibility: VoteVisibilityPublic,
VoterScope: VoteVoterScopeAnyone,
ShowResult: VoteShowResultAfterVote,
MaxChoices: 1,
})
require.Nil(t, err)
require.Equal(t, uint(1), got.ID)
require.NotEmpty(t, got.LinkToken, "LinkToken 应自动生成")
require.Equal(t, VoteStatusDraft, got.Status, "默认状态应为草稿")
// RequireCaptcha 不再被 gorm:"default:true" 隐式默认(零值陷阱已修复):Create 未显式传入时按 false 落库。
// 业务层在创建/编辑投票时始终强制 requireCaptcha=true(决策 #3),真实投票行为不受影响。
require.False(t, got.RequireCaptcha, "底层 Create 不再隐式默认开启验证码")
}
func testVotesGetByID(t *testing.T, ctx context.Context, db *votes) {
_, err := db.Create(ctx, CreateVoteOptions{
UserID: 1,
Title: "Title - 1",
})
require.Nil(t, err)
t.Run("normal", func(t *testing.T) {
got, err := db.GetByID(ctx, 1)
require.Nil(t, err)
require.Equal(t, "Title - 1", got.Title)
require.Equal(t, uint(1), got.UserID)
})
t.Run("not found", func(t *testing.T) {
_, err := db.GetByID(ctx, 404)
require.Equal(t, ErrVoteNotExist, err)
})
}
func testVotesGetByToken(t *testing.T, ctx context.Context, db *votes) {
created, err := db.Create(ctx, CreateVoteOptions{
UserID: 1,
Title: "Title - 1",
})
require.Nil(t, err)
t.Run("normal", func(t *testing.T) {
got, err := db.GetByToken(ctx, created.LinkToken)
require.Nil(t, err)
require.Equal(t, created.ID, got.ID)
})
t.Run("not found", func(t *testing.T) {
_, err := db.GetByToken(ctx, "no-such-token")
require.Equal(t, ErrVoteNotExist, err)
})
}
func testVotesListByUserID(t *testing.T, ctx context.Context, db *votes) {
for i := 0; i < 3; i++ {
_, err := db.Create(ctx, CreateVoteOptions{
UserID: 1,
Title: "Vote - 1",
})
require.Nil(t, err)
}
_, err := db.Create(ctx, CreateVoteOptions{
UserID: 2,
Title: "Vote - 2",
})
require.Nil(t, err)
t.Run("all", func(t *testing.T) {
got, err := db.ListByUserID(ctx, 1, ListVotesOptions{})
require.Nil(t, err)
require.Len(t, got, 3)
})
t.Run("status filter", func(t *testing.T) {
err := db.UpdateStatus(ctx, 1, VoteStatusPublished)
require.Nil(t, err)
got, err := db.ListByUserID(ctx, 1, ListVotesOptions{Status: VoteStatusPublished})
require.Nil(t, err)
require.Len(t, got, 1)
require.Equal(t, uint(1), got[0].ID)
})
t.Run("cursor", func(t *testing.T) {
got, err := db.ListByUserID(ctx, 1, ListVotesOptions{
Cursor: &dbutil.Cursor{Value: 3, PageSize: 10},
})
require.Nil(t, err)
require.Len(t, got, 2)
})
}
func testVotesUpdate(t *testing.T, ctx context.Context, db *votes) {
_, err := db.Create(ctx, CreateVoteOptions{
UserID: 1,
Title: "Title - 1",
})
require.Nil(t, err)
t.Run("normal", func(t *testing.T) {
vt := VoteTypeMultiple
mc := 3
err := db.Update(ctx, 1, UpdateVoteOptions{
Title: "Title - updated",
Description: "desc",
Type: &vt,
Visibility: VoteVisibilityLinkOnly,
VoterScope: VoteVoterScopeRegisteredOnly,
ShowResult: VoteShowResultAfterEnd,
MaxChoices: &mc,
RequireCaptcha: false,
})
require.Nil(t, err)
got, err := db.GetByID(ctx, 1)
require.Nil(t, err)
require.Equal(t, "Title - updated", got.Title)
require.Equal(t, VoteTypeMultiple, got.Type)
require.Equal(t, VoteVisibilityLinkOnly, got.Visibility)
require.Equal(t, VoteVoterScopeRegisteredOnly, got.VoterScope)
require.Equal(t, VoteShowResultAfterEnd, got.ShowResult)
require.Equal(t, 3, got.MaxChoices)
require.False(t, got.RequireCaptcha)
})
// 回归:调用方漏传 Type/MaxChoices(nil 指针)时,db.Update 必须跳过写入,
// 不得把 type 写成 ""(曾导致发布后编辑使公开页报「未知的投票类型」)。
t.Run("omit type preserves existing", func(t *testing.T) {
err := db.Update(ctx, 1, UpdateVoteOptions{Title: "Title - meta only"})
require.Nil(t, err)
got, err := db.GetByID(ctx, 1)
require.Nil(t, err)
require.Equal(t, "Title - meta only", got.Title)
require.Equal(t, VoteTypeMultiple, got.Type, "type 不应被清零")
require.Equal(t, 3, got.MaxChoices, "max_choices 不应被清零")
})
t.Run("not found", func(t *testing.T) {
err := db.Update(ctx, 404, UpdateVoteOptions{Title: "x"})
require.Equal(t, ErrVoteNotExist, err)
})
}
func testVotesUpdateStatus(t *testing.T, ctx context.Context, db *votes) {
_, err := db.Create(ctx, CreateVoteOptions{
UserID: 1,
Title: "Title - 1",
})
require.Nil(t, err)
t.Run("normal", func(t *testing.T) {
require.Nil(t, db.UpdateStatus(ctx, 1, VoteStatusPublished))
got, err := db.GetByID(ctx, 1)
require.Nil(t, err)
require.Equal(t, VoteStatusPublished, got.Status)
require.Nil(t, db.UpdateStatus(ctx, 1, VoteStatusEnded))
got, err = db.GetByID(ctx, 1)
require.Nil(t, err)
require.Equal(t, VoteStatusEnded, got.Status)
})
t.Run("not found", func(t *testing.T) {
require.Equal(t, ErrVoteNotExist, db.UpdateStatus(ctx, 404, VoteStatusEnded))
})
}
func testVotesDeleteByID(t *testing.T, ctx context.Context, db *votes) {
t.Run("normal", func(t *testing.T) {
_, err := db.Create(ctx, CreateVoteOptions{UserID: 1, Title: "Title - 1"})
require.Nil(t, err)
require.Nil(t, db.DeleteByID(ctx, 1))
_, err = db.GetByID(ctx, 1)
require.Equal(t, ErrVoteNotExist, err)
})
t.Run("not found", func(t *testing.T) {
require.Equal(t, ErrVoteNotExist, db.DeleteByID(ctx, 404))
})
}
func testVotesCountByUserID(t *testing.T, ctx context.Context, db *votes) {
_, err := db.Create(ctx, CreateVoteOptions{UserID: 1, Title: "A"})
require.Nil(t, err)
_, err = db.Create(ctx, CreateVoteOptions{UserID: 1, Title: "B"})
require.Nil(t, err)
_, err = db.Create(ctx, CreateVoteOptions{UserID: 2, Title: "C"})
require.Nil(t, err)
got, err := db.CountByUserID(ctx, 1)
require.Nil(t, err)
require.Equal(t, int64(2), got)
got, err = db.CountByUserID(ctx, 404)
require.Nil(t, err)
require.Equal(t, int64(0), got)
}