- 前端: 所有界面显示、i18n 文本、组件中的品牌名称 - 后端: 服务层、设置默认值、邮件模板、安装向导 - 数据库: 迁移脚本注释 - 保持功能完全一致,仅更改品牌名称 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
386 lines
12 KiB
Go
386 lines
12 KiB
Go
//go:build integration
|
|
|
|
package repository
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
dbent "github.com/Wei-Shaw/sub2api/ent"
|
|
"github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
|
|
"github.com/Wei-Shaw/sub2api/internal/service"
|
|
"github.com/stretchr/testify/suite"
|
|
)
|
|
|
|
type ApiKeyRepoSuite struct {
|
|
suite.Suite
|
|
ctx context.Context
|
|
client *dbent.Client
|
|
repo *apiKeyRepository
|
|
}
|
|
|
|
func (s *ApiKeyRepoSuite) SetupTest() {
|
|
s.ctx = context.Background()
|
|
tx := testEntTx(s.T())
|
|
s.client = tx.Client()
|
|
s.repo = NewApiKeyRepository(s.client).(*apiKeyRepository)
|
|
}
|
|
|
|
func TestApiKeyRepoSuite(t *testing.T) {
|
|
suite.Run(t, new(ApiKeyRepoSuite))
|
|
}
|
|
|
|
// --- Create / GetByID / GetByKey ---
|
|
|
|
func (s *ApiKeyRepoSuite) TestCreate() {
|
|
user := s.mustCreateUser("create@test.com")
|
|
|
|
key := &service.ApiKey{
|
|
UserID: user.ID,
|
|
Key: "sk-create-test",
|
|
Name: "Test Key",
|
|
Status: service.StatusActive,
|
|
}
|
|
|
|
err := s.repo.Create(s.ctx, key)
|
|
s.Require().NoError(err, "Create")
|
|
s.Require().NotZero(key.ID, "expected ID to be set")
|
|
|
|
got, err := s.repo.GetByID(s.ctx, key.ID)
|
|
s.Require().NoError(err, "GetByID")
|
|
s.Require().Equal("sk-create-test", got.Key)
|
|
}
|
|
|
|
func (s *ApiKeyRepoSuite) TestGetByID_NotFound() {
|
|
_, err := s.repo.GetByID(s.ctx, 999999)
|
|
s.Require().Error(err, "expected error for non-existent ID")
|
|
}
|
|
|
|
func (s *ApiKeyRepoSuite) TestGetByKey() {
|
|
user := s.mustCreateUser("getbykey@test.com")
|
|
group := s.mustCreateGroup("g-key")
|
|
|
|
key := &service.ApiKey{
|
|
UserID: user.ID,
|
|
Key: "sk-getbykey",
|
|
Name: "My Key",
|
|
GroupID: &group.ID,
|
|
Status: service.StatusActive,
|
|
}
|
|
s.Require().NoError(s.repo.Create(s.ctx, key))
|
|
|
|
got, err := s.repo.GetByKey(s.ctx, key.Key)
|
|
s.Require().NoError(err, "GetByKey")
|
|
s.Require().Equal(key.ID, got.ID)
|
|
s.Require().NotNil(got.User, "expected User preload")
|
|
s.Require().Equal(user.ID, got.User.ID)
|
|
s.Require().NotNil(got.Group, "expected Group preload")
|
|
s.Require().Equal(group.ID, got.Group.ID)
|
|
}
|
|
|
|
func (s *ApiKeyRepoSuite) TestGetByKey_NotFound() {
|
|
_, err := s.repo.GetByKey(s.ctx, "non-existent-key")
|
|
s.Require().Error(err, "expected error for non-existent key")
|
|
}
|
|
|
|
// --- Update ---
|
|
|
|
func (s *ApiKeyRepoSuite) TestUpdate() {
|
|
user := s.mustCreateUser("update@test.com")
|
|
key := &service.ApiKey{
|
|
UserID: user.ID,
|
|
Key: "sk-update",
|
|
Name: "Original",
|
|
Status: service.StatusActive,
|
|
}
|
|
s.Require().NoError(s.repo.Create(s.ctx, key))
|
|
|
|
key.Name = "Renamed"
|
|
key.Status = service.StatusDisabled
|
|
err := s.repo.Update(s.ctx, key)
|
|
s.Require().NoError(err, "Update")
|
|
|
|
got, err := s.repo.GetByID(s.ctx, key.ID)
|
|
s.Require().NoError(err, "GetByID after update")
|
|
s.Require().Equal("sk-update", got.Key, "Update should not change key")
|
|
s.Require().Equal(user.ID, got.UserID, "Update should not change user_id")
|
|
s.Require().Equal("Renamed", got.Name)
|
|
s.Require().Equal(service.StatusDisabled, got.Status)
|
|
}
|
|
|
|
func (s *ApiKeyRepoSuite) TestUpdate_ClearGroupID() {
|
|
user := s.mustCreateUser("cleargroup@test.com")
|
|
group := s.mustCreateGroup("g-clear")
|
|
key := &service.ApiKey{
|
|
UserID: user.ID,
|
|
Key: "sk-clear-group",
|
|
Name: "Group Key",
|
|
GroupID: &group.ID,
|
|
Status: service.StatusActive,
|
|
}
|
|
s.Require().NoError(s.repo.Create(s.ctx, key))
|
|
|
|
key.GroupID = nil
|
|
err := s.repo.Update(s.ctx, key)
|
|
s.Require().NoError(err, "Update")
|
|
|
|
got, err := s.repo.GetByID(s.ctx, key.ID)
|
|
s.Require().NoError(err)
|
|
s.Require().Nil(got.GroupID, "expected GroupID to be cleared")
|
|
}
|
|
|
|
// --- Delete ---
|
|
|
|
func (s *ApiKeyRepoSuite) TestDelete() {
|
|
user := s.mustCreateUser("delete@test.com")
|
|
key := &service.ApiKey{
|
|
UserID: user.ID,
|
|
Key: "sk-delete",
|
|
Name: "Delete Me",
|
|
Status: service.StatusActive,
|
|
}
|
|
s.Require().NoError(s.repo.Create(s.ctx, key))
|
|
|
|
err := s.repo.Delete(s.ctx, key.ID)
|
|
s.Require().NoError(err, "Delete")
|
|
|
|
_, err = s.repo.GetByID(s.ctx, key.ID)
|
|
s.Require().Error(err, "expected error after delete")
|
|
}
|
|
|
|
// --- ListByUserID / CountByUserID ---
|
|
|
|
func (s *ApiKeyRepoSuite) TestListByUserID() {
|
|
user := s.mustCreateUser("listbyuser@test.com")
|
|
s.mustCreateApiKey(user.ID, "sk-list-1", "Key 1", nil)
|
|
s.mustCreateApiKey(user.ID, "sk-list-2", "Key 2", nil)
|
|
|
|
keys, page, err := s.repo.ListByUserID(s.ctx, user.ID, pagination.PaginationParams{Page: 1, PageSize: 10})
|
|
s.Require().NoError(err, "ListByUserID")
|
|
s.Require().Len(keys, 2)
|
|
s.Require().Equal(int64(2), page.Total)
|
|
}
|
|
|
|
func (s *ApiKeyRepoSuite) TestListByUserID_Pagination() {
|
|
user := s.mustCreateUser("paging@test.com")
|
|
for i := 0; i < 5; i++ {
|
|
s.mustCreateApiKey(user.ID, "sk-page-"+string(rune('a'+i)), "Key", nil)
|
|
}
|
|
|
|
keys, page, err := s.repo.ListByUserID(s.ctx, user.ID, pagination.PaginationParams{Page: 1, PageSize: 2})
|
|
s.Require().NoError(err)
|
|
s.Require().Len(keys, 2)
|
|
s.Require().Equal(int64(5), page.Total)
|
|
s.Require().Equal(3, page.Pages)
|
|
}
|
|
|
|
func (s *ApiKeyRepoSuite) TestCountByUserID() {
|
|
user := s.mustCreateUser("count@test.com")
|
|
s.mustCreateApiKey(user.ID, "sk-count-1", "K1", nil)
|
|
s.mustCreateApiKey(user.ID, "sk-count-2", "K2", nil)
|
|
|
|
count, err := s.repo.CountByUserID(s.ctx, user.ID)
|
|
s.Require().NoError(err, "CountByUserID")
|
|
s.Require().Equal(int64(2), count)
|
|
}
|
|
|
|
// --- ListByGroupID / CountByGroupID ---
|
|
|
|
func (s *ApiKeyRepoSuite) TestListByGroupID() {
|
|
user := s.mustCreateUser("listbygroup@test.com")
|
|
group := s.mustCreateGroup("g-list")
|
|
|
|
s.mustCreateApiKey(user.ID, "sk-grp-1", "K1", &group.ID)
|
|
s.mustCreateApiKey(user.ID, "sk-grp-2", "K2", &group.ID)
|
|
s.mustCreateApiKey(user.ID, "sk-grp-3", "K3", nil) // no group
|
|
|
|
keys, page, err := s.repo.ListByGroupID(s.ctx, group.ID, pagination.PaginationParams{Page: 1, PageSize: 10})
|
|
s.Require().NoError(err, "ListByGroupID")
|
|
s.Require().Len(keys, 2)
|
|
s.Require().Equal(int64(2), page.Total)
|
|
// User preloaded
|
|
s.Require().NotNil(keys[0].User)
|
|
}
|
|
|
|
func (s *ApiKeyRepoSuite) TestCountByGroupID() {
|
|
user := s.mustCreateUser("countgroup@test.com")
|
|
group := s.mustCreateGroup("g-count")
|
|
s.mustCreateApiKey(user.ID, "sk-gc-1", "K1", &group.ID)
|
|
|
|
count, err := s.repo.CountByGroupID(s.ctx, group.ID)
|
|
s.Require().NoError(err, "CountByGroupID")
|
|
s.Require().Equal(int64(1), count)
|
|
}
|
|
|
|
// --- ExistsByKey ---
|
|
|
|
func (s *ApiKeyRepoSuite) TestExistsByKey() {
|
|
user := s.mustCreateUser("exists@test.com")
|
|
s.mustCreateApiKey(user.ID, "sk-exists", "K", nil)
|
|
|
|
exists, err := s.repo.ExistsByKey(s.ctx, "sk-exists")
|
|
s.Require().NoError(err, "ExistsByKey")
|
|
s.Require().True(exists)
|
|
|
|
notExists, err := s.repo.ExistsByKey(s.ctx, "sk-not-exists")
|
|
s.Require().NoError(err)
|
|
s.Require().False(notExists)
|
|
}
|
|
|
|
// --- SearchApiKeys ---
|
|
|
|
func (s *ApiKeyRepoSuite) TestSearchApiKeys() {
|
|
user := s.mustCreateUser("search@test.com")
|
|
s.mustCreateApiKey(user.ID, "sk-search-1", "Production Key", nil)
|
|
s.mustCreateApiKey(user.ID, "sk-search-2", "Development Key", nil)
|
|
|
|
found, err := s.repo.SearchApiKeys(s.ctx, user.ID, "prod", 10)
|
|
s.Require().NoError(err, "SearchApiKeys")
|
|
s.Require().Len(found, 1)
|
|
s.Require().Contains(found[0].Name, "Production")
|
|
}
|
|
|
|
func (s *ApiKeyRepoSuite) TestSearchApiKeys_NoKeyword() {
|
|
user := s.mustCreateUser("searchnokw@test.com")
|
|
s.mustCreateApiKey(user.ID, "sk-nk-1", "K1", nil)
|
|
s.mustCreateApiKey(user.ID, "sk-nk-2", "K2", nil)
|
|
|
|
found, err := s.repo.SearchApiKeys(s.ctx, user.ID, "", 10)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(found, 2)
|
|
}
|
|
|
|
func (s *ApiKeyRepoSuite) TestSearchApiKeys_NoUserID() {
|
|
user := s.mustCreateUser("searchnouid@test.com")
|
|
s.mustCreateApiKey(user.ID, "sk-nu-1", "TestKey", nil)
|
|
|
|
found, err := s.repo.SearchApiKeys(s.ctx, 0, "testkey", 10)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(found, 1)
|
|
}
|
|
|
|
// --- ClearGroupIDByGroupID ---
|
|
|
|
func (s *ApiKeyRepoSuite) TestClearGroupIDByGroupID() {
|
|
user := s.mustCreateUser("cleargrp@test.com")
|
|
group := s.mustCreateGroup("g-clear-bulk")
|
|
|
|
k1 := s.mustCreateApiKey(user.ID, "sk-clr-1", "K1", &group.ID)
|
|
k2 := s.mustCreateApiKey(user.ID, "sk-clr-2", "K2", &group.ID)
|
|
s.mustCreateApiKey(user.ID, "sk-clr-3", "K3", nil) // no group
|
|
|
|
affected, err := s.repo.ClearGroupIDByGroupID(s.ctx, group.ID)
|
|
s.Require().NoError(err, "ClearGroupIDByGroupID")
|
|
s.Require().Equal(int64(2), affected)
|
|
|
|
got1, _ := s.repo.GetByID(s.ctx, k1.ID)
|
|
got2, _ := s.repo.GetByID(s.ctx, k2.ID)
|
|
s.Require().Nil(got1.GroupID)
|
|
s.Require().Nil(got2.GroupID)
|
|
|
|
count, _ := s.repo.CountByGroupID(s.ctx, group.ID)
|
|
s.Require().Zero(count)
|
|
}
|
|
|
|
// --- Combined CRUD/Search/ClearGroupID (original test preserved as integration) ---
|
|
|
|
func (s *ApiKeyRepoSuite) TestCRUD_Search_ClearGroupID() {
|
|
user := s.mustCreateUser("k@example.com")
|
|
group := s.mustCreateGroup("g-k")
|
|
key := s.mustCreateApiKey(user.ID, "sk-test-1", "My Key", &group.ID)
|
|
key.GroupID = &group.ID
|
|
|
|
got, err := s.repo.GetByKey(s.ctx, key.Key)
|
|
s.Require().NoError(err, "GetByKey")
|
|
s.Require().Equal(key.ID, got.ID)
|
|
s.Require().NotNil(got.User)
|
|
s.Require().Equal(user.ID, got.User.ID)
|
|
s.Require().NotNil(got.Group)
|
|
s.Require().Equal(group.ID, got.Group.ID)
|
|
|
|
key.Name = "Renamed"
|
|
key.Status = service.StatusDisabled
|
|
key.GroupID = nil
|
|
s.Require().NoError(s.repo.Update(s.ctx, key), "Update")
|
|
|
|
got2, err := s.repo.GetByID(s.ctx, key.ID)
|
|
s.Require().NoError(err, "GetByID")
|
|
s.Require().Equal("sk-test-1", got2.Key, "Update should not change key")
|
|
s.Require().Equal(user.ID, got2.UserID, "Update should not change user_id")
|
|
s.Require().Equal("Renamed", got2.Name)
|
|
s.Require().Equal(service.StatusDisabled, got2.Status)
|
|
s.Require().Nil(got2.GroupID)
|
|
|
|
keys, page, err := s.repo.ListByUserID(s.ctx, user.ID, pagination.PaginationParams{Page: 1, PageSize: 10})
|
|
s.Require().NoError(err, "ListByUserID")
|
|
s.Require().Equal(int64(1), page.Total)
|
|
s.Require().Len(keys, 1)
|
|
|
|
exists, err := s.repo.ExistsByKey(s.ctx, "sk-test-1")
|
|
s.Require().NoError(err, "ExistsByKey")
|
|
s.Require().True(exists, "expected key to exist")
|
|
|
|
found, err := s.repo.SearchApiKeys(s.ctx, user.ID, "renam", 10)
|
|
s.Require().NoError(err, "SearchApiKeys")
|
|
s.Require().Len(found, 1)
|
|
s.Require().Equal(key.ID, found[0].ID)
|
|
|
|
// ClearGroupIDByGroupID
|
|
k2 := s.mustCreateApiKey(user.ID, "sk-test-2", "Group Key", &group.ID)
|
|
k2.GroupID = &group.ID
|
|
|
|
countBefore, err := s.repo.CountByGroupID(s.ctx, group.ID)
|
|
s.Require().NoError(err, "CountByGroupID")
|
|
s.Require().Equal(int64(1), countBefore, "expected 1 key in group before clear")
|
|
|
|
affected, err := s.repo.ClearGroupIDByGroupID(s.ctx, group.ID)
|
|
s.Require().NoError(err, "ClearGroupIDByGroupID")
|
|
s.Require().Equal(int64(1), affected, "expected 1 affected row")
|
|
|
|
got3, err := s.repo.GetByID(s.ctx, k2.ID)
|
|
s.Require().NoError(err, "GetByID")
|
|
s.Require().Nil(got3.GroupID, "expected GroupID cleared")
|
|
|
|
countAfter, err := s.repo.CountByGroupID(s.ctx, group.ID)
|
|
s.Require().NoError(err, "CountByGroupID after clear")
|
|
s.Require().Equal(int64(0), countAfter, "expected 0 keys in group after clear")
|
|
}
|
|
|
|
func (s *ApiKeyRepoSuite) mustCreateUser(email string) *service.User {
|
|
s.T().Helper()
|
|
|
|
u, err := s.client.User.Create().
|
|
SetEmail(email).
|
|
SetPasswordHash("test-password-hash").
|
|
SetStatus(service.StatusActive).
|
|
SetRole(service.RoleUser).
|
|
Save(s.ctx)
|
|
s.Require().NoError(err, "create user")
|
|
return userEntityToService(u)
|
|
}
|
|
|
|
func (s *ApiKeyRepoSuite) mustCreateGroup(name string) *service.Group {
|
|
s.T().Helper()
|
|
|
|
g, err := s.client.Group.Create().
|
|
SetName(name).
|
|
SetStatus(service.StatusActive).
|
|
Save(s.ctx)
|
|
s.Require().NoError(err, "create group")
|
|
return groupEntityToService(g)
|
|
}
|
|
|
|
func (s *ApiKeyRepoSuite) mustCreateApiKey(userID int64, key, name string, groupID *int64) *service.ApiKey {
|
|
s.T().Helper()
|
|
|
|
k := &service.ApiKey{
|
|
UserID: userID,
|
|
Key: key,
|
|
Name: name,
|
|
GroupID: groupID,
|
|
Status: service.StatusActive,
|
|
}
|
|
s.Require().NoError(s.repo.Create(s.ctx, k), "create api key")
|
|
return k
|
|
}
|