| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130 |
- package user_test
- import (
- "context"
- "database/sql"
- "fmt"
- "sync"
- "testing"
- "time"
- "perms-system-server/internal/model/user"
- "perms-system-server/internal/testutil"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- )
- // TC-0736: H-B 修复回归(model 层)—— IncrementTokenVersion 返回的版本号必须等于
- // 事务结束后 DB 中实际落盘的 tokenVersion。旧实现基于"缓存读 +1",并发下会返回 stale 值,
- // 新实现用 `LAST_INSERT_ID(tokenVersion+1)` 原子递增并回读,返回值必须与 DB 记录一致。
- func TestSysUserModel_IncrementTokenVersion_ReturnedEqualsPersisted(t *testing.T) {
- m, conn := newModel(t)
- ctx := context.Background()
- now := time.Now().Unix()
- username := "itv_eq_" + testutil.UniqueId()
- res, err := m.Insert(ctx, &user.SysUser{
- Username: username, Password: "x", Nickname: "n",
- Avatar: sql.NullString{}, IsSuperAdmin: 2, MustChangePassword: 2,
- Status: 1, TokenVersion: 7, CreateTime: now, UpdateTime: now,
- })
- require.NoError(t, err)
- id, _ := res.LastInsertId()
- t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", id) })
- for expected := int64(8); expected <= 12; expected++ {
- got, err := m.IncrementTokenVersion(ctx, id, username)
- require.NoError(t, err)
- assert.Equal(t, expected, got,
- "IncrementTokenVersion 必须返回 DB 真实递增后的值(H-B:不可再受 stale cache 影响)")
- fresh, err := m.FindOne(ctx, id)
- require.NoError(t, err)
- assert.Equal(t, got, fresh.TokenVersion,
- "返回值必须等于 DB 中真实持久化的 tokenVersion")
- }
- }
- // TC-0737: H-B 修复回归 —— 自增后缓存必须被主动清理,Load → tokenVersion 能读到新值。
- // 旧实现只更新 DB,返回值基于缓存,并且未强制 DelCache,导致 JWT 中间件仍从缓存读到旧值。
- func TestSysUserModel_IncrementTokenVersion_InvalidatesCache(t *testing.T) {
- m, conn := newModel(t)
- ctx := context.Background()
- now := time.Now().Unix()
- username := "itv_cache_" + testutil.UniqueId()
- res, err := m.Insert(ctx, &user.SysUser{
- Username: username, Password: "x", Nickname: "n",
- Avatar: sql.NullString{}, IsSuperAdmin: 2, MustChangePassword: 2,
- Status: 1, TokenVersion: 0, CreateTime: now, UpdateTime: now,
- })
- require.NoError(t, err)
- id, _ := res.LastInsertId()
- t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", id) })
- // 先 FindOne 让 id-key、username-key 双路缓存写入
- u0, err := m.FindOne(ctx, id)
- require.NoError(t, err)
- require.Equal(t, int64(0), u0.TokenVersion)
- u0b, err := m.FindOneByUsername(ctx, username)
- require.NoError(t, err)
- require.Equal(t, int64(0), u0b.TokenVersion)
- _, err = m.IncrementTokenVersion(ctx, id, username)
- require.NoError(t, err)
- u1, err := m.FindOne(ctx, id)
- require.NoError(t, err)
- assert.Equal(t, int64(1), u1.TokenVersion, "按 id 读取缓存路径也必须拿到最新版本")
- u1b, err := m.FindOneByUsername(ctx, username)
- require.NoError(t, err)
- assert.Equal(t, int64(1), u1b.TokenVersion, "按 username 读取缓存路径也必须失效")
- }
- // TC-0738: H-B 修复并发回归 —— 10 个 goroutine 同时 Increment 同一用户,
- // 每次返回值必须互不重复,最终 DB 里 tokenVersion = 起始值 + N。
- func TestSysUserModel_IncrementTokenVersion_ConcurrentUnique(t *testing.T) {
- m, conn := newModel(t)
- ctx := context.Background()
- now := time.Now().Unix()
- username := "itv_conc_" + testutil.UniqueId()
- res, err := m.Insert(ctx, &user.SysUser{
- Username: username, Password: "x", Nickname: "n",
- Avatar: sql.NullString{}, IsSuperAdmin: 2, MustChangePassword: 2,
- Status: 1, TokenVersion: 0, CreateTime: now, UpdateTime: now,
- })
- require.NoError(t, err)
- id, _ := res.LastInsertId()
- t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_user`", id) })
- const N = 10
- var wg sync.WaitGroup
- results := make([]int64, N)
- errs := make([]error, N)
- for i := 0; i < N; i++ {
- wg.Add(1)
- go func(idx int) {
- defer wg.Done()
- v, e := m.IncrementTokenVersion(ctx, id, username)
- results[idx] = v
- errs[idx] = e
- }(i)
- }
- wg.Wait()
- seen := make(map[int64]int, N)
- for i := 0; i < N; i++ {
- require.NoError(t, errs[i], "并发 IncrementTokenVersion 任一 goroutine 不得失败")
- seen[results[i]]++
- }
- for v, cnt := range seen {
- assert.Equal(t, 1, cnt, fmt.Sprintf("返回值 %d 被重复派发 %d 次,与 DB 实际递增序列脱节", v, cnt))
- }
- fresh, err := m.FindOne(ctx, id)
- require.NoError(t, err)
- assert.Equal(t, int64(N), fresh.TokenVersion, "DB 最终 tokenVersion 应为并发次数")
- }
|