Files
tamakyi e3e59e7c69 chore: baseline snapshot before moderation-strictness feature
Repo .git was damaged (refs/ missing, index truncated, object store near-empty).
Repaired via git init (rebuilt refs), stale index preserved as .git/index.corrupt-20260910.bak,
then full worktree committed as a rollback baseline.
2026-09-10 10:56:27 +08:00

253 lines
7.1 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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)
}