restrict v1
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
// 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"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestRestrictions 覆盖站方处置的读写语义与「L1 停止分发」的曝光摘除。
|
||||
// 这些行为没有管理后台兜底(处置只能经 CLI 写入),一旦语义写错无法从界面发现,
|
||||
// 因此用数据库测试把「替换 / 到期 / 撤销 / 曝光排除」四条钉死。
|
||||
func TestRestrictions(t *testing.T) {
|
||||
database, cleanup := newTestDB(t)
|
||||
ctx := context.Background()
|
||||
|
||||
usersStore := NewUsersStore(database)
|
||||
store := NewRestrictionsStore(database)
|
||||
|
||||
t.Cleanup(func() {
|
||||
if err := cleanup("users", "user_restrictions"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
require.NoError(t, usersStore.Create(ctx, CreateUserOptions{
|
||||
Name: "Box Owner", Password: "super_secret", Email: "[email protected]", Domain: "rbox",
|
||||
}))
|
||||
owner, err := usersStore.GetByDomain(ctx, "rbox")
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, usersStore.Create(ctx, CreateUserOptions{
|
||||
Name: "Other Owner", Password: "super_secret", Email: "[email protected]", Domain: "obox",
|
||||
}))
|
||||
other, err := usersStore.GetByDomain(ctx, "obox")
|
||||
require.NoError(t, err)
|
||||
|
||||
// 两个箱主都上首页,用于验证处置后是否被摘除。
|
||||
require.NoError(t, usersStore.UpdateTags(ctx, owner.ID, UpdateTagsOptions{Tags: "test", ShowInHome: true}))
|
||||
require.NoError(t, usersStore.UpdateTags(ctx, other.ID, UpdateTagsOptions{Tags: "test", ShowInHome: true}))
|
||||
|
||||
// 无处置时:不排除任何人。
|
||||
level, active, err := store.ActiveLevel(ctx, owner.ID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, RestrictionLevel(""), level)
|
||||
require.Nil(t, active)
|
||||
|
||||
list, _, err := usersStore.GetShowInHome(ctx, 1, 10, "latest")
|
||||
require.NoError(t, err)
|
||||
require.Len(t, list, 2)
|
||||
|
||||
// 施加 L1。
|
||||
created, err := store.Create(ctx, CreateRestrictionOptions{
|
||||
UserID: owner.ID, Level: string(RestrictionL1), Reason: "组织挂人", CreatedBy: "test", Days: 7,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.WithinDuration(t, time.Now().AddDate(0, 0, 7), created.ExpiresAt, time.Minute)
|
||||
|
||||
level, active, err = store.ActiveLevel(ctx, owner.ID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, RestrictionL1, level)
|
||||
require.NotNil(t, active)
|
||||
require.Equal(t, "组织挂人", active.Reason)
|
||||
|
||||
// L1 的核心语义:被处置箱主从首页 / 探索的展示池里摘除。
|
||||
list, _, err = usersStore.GetShowInHome(ctx, 1, 10, "latest")
|
||||
require.NoError(t, err)
|
||||
require.Len(t, list, 1)
|
||||
require.Equal(t, other.ID, list[0].ID)
|
||||
|
||||
// 批量版本只返回被处置者。
|
||||
levels, err := store.ActiveLevels(ctx, []uint{owner.ID, other.ID})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, map[uint]RestrictionLevel{owner.ID: RestrictionL1}, levels)
|
||||
|
||||
// 替换语义:升级到 L3 后,旧记录保留但被撤销,同一时刻只有一条生效。
|
||||
_, err = store.Create(ctx, CreateRestrictionOptions{
|
||||
UserID: owner.ID, Level: string(RestrictionL3), Reason: "升级", CreatedBy: "test", Days: 3,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
level, _, err = store.ActiveLevel(ctx, owner.ID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, RestrictionL3, level)
|
||||
|
||||
history, err := store.ListByUserID(ctx, owner.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, history, 2)
|
||||
// ListByUserID 按创建时间倒序,第一条是新的 L3(未撤销),第二条是已撤销的 L1。
|
||||
require.Equal(t, string(RestrictionL3), history[0].Level)
|
||||
require.Nil(t, history[0].RevokedAt)
|
||||
require.Equal(t, string(RestrictionL1), history[1].Level)
|
||||
require.NotNil(t, history[1].RevokedAt)
|
||||
|
||||
// 到期即失效:直接把到期时间改到过去,ActiveLevel 必须返回空(处置自动解除,不需要定时任务)。
|
||||
require.NoError(t, database.Model(&UserRestriction{}).
|
||||
Where("user_id = ? AND revoked_at IS NULL", owner.ID).
|
||||
Update("expires_at", time.Now().Add(-time.Minute)).Error)
|
||||
|
||||
level, active, err = store.ActiveLevel(ctx, owner.ID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, RestrictionLevel(""), level)
|
||||
require.Nil(t, active)
|
||||
|
||||
// 过期后曝光自动恢复。
|
||||
list, _, err = usersStore.GetShowInHome(ctx, 1, 10, "latest")
|
||||
require.NoError(t, err)
|
||||
require.Len(t, list, 2)
|
||||
|
||||
// 撤销语义:有效期内撤销后立即恢复可见。
|
||||
_, err = store.Create(ctx, CreateRestrictionOptions{
|
||||
UserID: owner.ID, Level: string(RestrictionL1), Reason: "再处置", CreatedBy: "test", Days: 7,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
affected, err := store.RevokeActive(ctx, owner.ID)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 1, affected)
|
||||
|
||||
list, _, err = usersStore.GetShowInHome(ctx, 1, 10, "latest")
|
||||
require.NoError(t, err)
|
||||
require.Len(t, list, 2)
|
||||
|
||||
// 幂等:再次撤销不再有任何生效记录。
|
||||
affected, err = store.RevokeActive(ctx, owner.ID)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 0, affected)
|
||||
}
|
||||
|
||||
// TestRestrictionLevelValidation 防止脏级别进入 ORDER BY level DESC 的排序("L9" 会排在 "L4" 之后)。
|
||||
func TestRestrictionLevelValidation(t *testing.T) {
|
||||
require.True(t, IsValidRestrictionLevel(string(RestrictionL1)))
|
||||
require.True(t, IsValidRestrictionLevel(string(RestrictionL4)))
|
||||
require.False(t, IsValidRestrictionLevel("L5"))
|
||||
require.False(t, IsValidRestrictionLevel("l1")) // 大小写敏感,避免 CLI 误传
|
||||
require.False(t, IsValidRestrictionLevel(""))
|
||||
}
|
||||
|
||||
// TestRestrictionAtLeast 守住发布侧拦截的判定:L2 收窄公开、L3 冻结发布都依赖这个比较。
|
||||
// 空级别(无处置)与未知级别必须恒为 false,否则脏数据会把正常箱主锁死。
|
||||
func TestRestrictionAtLeast(t *testing.T) {
|
||||
require.True(t, RestrictionAtLeast(RestrictionL2, RestrictionL1))
|
||||
require.True(t, RestrictionAtLeast(RestrictionL4, RestrictionL3))
|
||||
require.True(t, RestrictionAtLeast(RestrictionL2, RestrictionL2))
|
||||
require.False(t, RestrictionAtLeast(RestrictionL1, RestrictionL2))
|
||||
require.False(t, RestrictionAtLeast(RestrictionL3, RestrictionL4))
|
||||
require.False(t, RestrictionAtLeast("", RestrictionL1))
|
||||
require.False(t, RestrictionAtLeast("L9", RestrictionL1))
|
||||
}
|
||||
Reference in New Issue
Block a user