- 前端: 所有界面显示、i18n 文本、组件中的品牌名称 - 后端: 服务层、设置默认值、邮件模板、安装向导 - 数据库: 迁移脚本注释 - 保持功能完全一致,仅更改品牌名称 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
146 lines
4.4 KiB
Go
146 lines
4.4 KiB
Go
//go:build integration
|
|
|
|
package repository
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/Wei-Shaw/sub2api/internal/service"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func uniqueTestValue(t *testing.T, prefix string) string {
|
|
t.Helper()
|
|
safeName := strings.NewReplacer("/", "_", " ", "_").Replace(t.Name())
|
|
return fmt.Sprintf("%s-%s", prefix, safeName)
|
|
}
|
|
|
|
func TestUserRepository_RemoveGroupFromAllowedGroups_RemovesAllOccurrences(t *testing.T) {
|
|
ctx := context.Background()
|
|
tx := testEntTx(t)
|
|
entClient := tx.Client()
|
|
|
|
targetGroup, err := entClient.Group.Create().
|
|
SetName(uniqueTestValue(t, "target-group")).
|
|
SetStatus(service.StatusActive).
|
|
Save(ctx)
|
|
require.NoError(t, err)
|
|
otherGroup, err := entClient.Group.Create().
|
|
SetName(uniqueTestValue(t, "other-group")).
|
|
SetStatus(service.StatusActive).
|
|
Save(ctx)
|
|
require.NoError(t, err)
|
|
|
|
repo := newUserRepositoryWithSQL(entClient, tx)
|
|
|
|
u1 := &service.User{
|
|
Email: uniqueTestValue(t, "u1") + "@example.com",
|
|
PasswordHash: "test-password-hash",
|
|
Role: service.RoleUser,
|
|
Status: service.StatusActive,
|
|
Concurrency: 5,
|
|
AllowedGroups: []int64{targetGroup.ID, otherGroup.ID},
|
|
}
|
|
require.NoError(t, repo.Create(ctx, u1))
|
|
|
|
u2 := &service.User{
|
|
Email: uniqueTestValue(t, "u2") + "@example.com",
|
|
PasswordHash: "test-password-hash",
|
|
Role: service.RoleUser,
|
|
Status: service.StatusActive,
|
|
Concurrency: 5,
|
|
AllowedGroups: []int64{targetGroup.ID},
|
|
}
|
|
require.NoError(t, repo.Create(ctx, u2))
|
|
|
|
u3 := &service.User{
|
|
Email: uniqueTestValue(t, "u3") + "@example.com",
|
|
PasswordHash: "test-password-hash",
|
|
Role: service.RoleUser,
|
|
Status: service.StatusActive,
|
|
Concurrency: 5,
|
|
AllowedGroups: []int64{otherGroup.ID},
|
|
}
|
|
require.NoError(t, repo.Create(ctx, u3))
|
|
|
|
affected, err := repo.RemoveGroupFromAllowedGroups(ctx, targetGroup.ID)
|
|
require.NoError(t, err)
|
|
require.Equal(t, int64(2), affected)
|
|
|
|
u1After, err := repo.GetByID(ctx, u1.ID)
|
|
require.NoError(t, err)
|
|
require.NotContains(t, u1After.AllowedGroups, targetGroup.ID)
|
|
require.Contains(t, u1After.AllowedGroups, otherGroup.ID)
|
|
|
|
u2After, err := repo.GetByID(ctx, u2.ID)
|
|
require.NoError(t, err)
|
|
require.NotContains(t, u2After.AllowedGroups, targetGroup.ID)
|
|
}
|
|
|
|
func TestGroupRepository_DeleteCascade_RemovesAllowedGroupsAndClearsApiKeys(t *testing.T) {
|
|
ctx := context.Background()
|
|
tx := testEntTx(t)
|
|
entClient := tx.Client()
|
|
|
|
targetGroup, err := entClient.Group.Create().
|
|
SetName(uniqueTestValue(t, "delete-cascade-target")).
|
|
SetStatus(service.StatusActive).
|
|
Save(ctx)
|
|
require.NoError(t, err)
|
|
otherGroup, err := entClient.Group.Create().
|
|
SetName(uniqueTestValue(t, "delete-cascade-other")).
|
|
SetStatus(service.StatusActive).
|
|
Save(ctx)
|
|
require.NoError(t, err)
|
|
|
|
userRepo := newUserRepositoryWithSQL(entClient, tx)
|
|
groupRepo := newGroupRepositoryWithSQL(entClient, tx)
|
|
apiKeyRepo := NewApiKeyRepository(entClient)
|
|
|
|
u := &service.User{
|
|
Email: uniqueTestValue(t, "cascade-user") + "@example.com",
|
|
PasswordHash: "test-password-hash",
|
|
Role: service.RoleUser,
|
|
Status: service.StatusActive,
|
|
Concurrency: 5,
|
|
AllowedGroups: []int64{targetGroup.ID, otherGroup.ID},
|
|
}
|
|
require.NoError(t, userRepo.Create(ctx, u))
|
|
|
|
key := &service.ApiKey{
|
|
UserID: u.ID,
|
|
Key: uniqueTestValue(t, "sk-test-delete-cascade"),
|
|
Name: "test key",
|
|
GroupID: &targetGroup.ID,
|
|
Status: service.StatusActive,
|
|
}
|
|
require.NoError(t, apiKeyRepo.Create(ctx, key))
|
|
|
|
_, err = groupRepo.DeleteCascade(ctx, targetGroup.ID)
|
|
require.NoError(t, err)
|
|
|
|
// Deleted group should be hidden by default queries (soft-delete semantics).
|
|
_, err = groupRepo.GetByID(ctx, targetGroup.ID)
|
|
require.ErrorIs(t, err, service.ErrGroupNotFound)
|
|
|
|
activeGroups, err := groupRepo.ListActive(ctx)
|
|
require.NoError(t, err)
|
|
for _, g := range activeGroups {
|
|
require.NotEqual(t, targetGroup.ID, g.ID)
|
|
}
|
|
|
|
// User.allowed_groups should no longer include the deleted group.
|
|
uAfter, err := userRepo.GetByID(ctx, u.ID)
|
|
require.NoError(t, err)
|
|
require.NotContains(t, uAfter.AllowedGroups, targetGroup.ID)
|
|
require.Contains(t, uAfter.AllowedGroups, otherGroup.ID)
|
|
|
|
// API keys bound to the deleted group should have group_id cleared.
|
|
keyAfter, err := apiKeyRepo.GetByID(ctx, key.ID)
|
|
require.NoError(t, err)
|
|
require.Nil(t, keyAfter.GroupID)
|
|
}
|