refactor(数据库): 迁移持久层到 Ent 并清理 GORM
将仓储层/基础设施改为 Ent + 原生 SQL 执行路径,并移除 AutoMigrate 与 GORM 依赖。 重构内容包括: - 仓储层改用 Ent/SQL(含 usage_log/account 等复杂查询),统一错误映射 - 基础设施与 setup 初始化切换为 Ent + SQL migrations - 集成测试与 fixtures 迁移到 Ent 事务模型 - 清理遗留 GORM 模型/依赖,补充迁移与文档说明 - 增加根目录 Makefile 便于前后端编译 测试: - go test -tags unit ./... - go test -tags integration ./...
This commit is contained in:
@@ -7,29 +7,47 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
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"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type RedeemCodeRepoSuite struct {
|
||||
suite.Suite
|
||||
ctx context.Context
|
||||
db *gorm.DB
|
||||
repo *redeemCodeRepository
|
||||
ctx context.Context
|
||||
client *dbent.Client
|
||||
repo *redeemCodeRepository
|
||||
}
|
||||
|
||||
func (s *RedeemCodeRepoSuite) SetupTest() {
|
||||
s.ctx = context.Background()
|
||||
s.db = testTx(s.T())
|
||||
s.repo = NewRedeemCodeRepository(s.db).(*redeemCodeRepository)
|
||||
entClient, _ := testEntSQLTx(s.T())
|
||||
s.client = entClient
|
||||
s.repo = NewRedeemCodeRepository(entClient).(*redeemCodeRepository)
|
||||
}
|
||||
|
||||
func TestRedeemCodeRepoSuite(t *testing.T) {
|
||||
suite.Run(t, new(RedeemCodeRepoSuite))
|
||||
}
|
||||
|
||||
func (s *RedeemCodeRepoSuite) createUser(email string) *dbent.User {
|
||||
u, err := s.client.User.Create().
|
||||
SetEmail(email).
|
||||
SetPasswordHash("test-password-hash").
|
||||
Save(s.ctx)
|
||||
s.Require().NoError(err, "create user")
|
||||
return u
|
||||
}
|
||||
|
||||
func (s *RedeemCodeRepoSuite) createGroup(name string) *dbent.Group {
|
||||
g, err := s.client.Group.Create().
|
||||
SetName(name).
|
||||
Save(s.ctx)
|
||||
s.Require().NoError(err, "create group")
|
||||
return g
|
||||
}
|
||||
|
||||
// --- Create / CreateBatch / GetByID / GetByCode ---
|
||||
|
||||
func (s *RedeemCodeRepoSuite) TestCreate() {
|
||||
@@ -70,10 +88,19 @@ func (s *RedeemCodeRepoSuite) TestCreateBatch() {
|
||||
func (s *RedeemCodeRepoSuite) TestGetByID_NotFound() {
|
||||
_, err := s.repo.GetByID(s.ctx, 999999)
|
||||
s.Require().Error(err, "expected error for non-existent ID")
|
||||
s.Require().ErrorIs(err, service.ErrRedeemCodeNotFound)
|
||||
}
|
||||
|
||||
func (s *RedeemCodeRepoSuite) TestGetByCode() {
|
||||
mustCreateRedeemCode(s.T(), s.db, &redeemCodeModel{Code: "GET-BY-CODE", Type: service.RedeemTypeBalance})
|
||||
_, err := s.client.RedeemCode.Create().
|
||||
SetCode("GET-BY-CODE").
|
||||
SetType(service.RedeemTypeBalance).
|
||||
SetStatus(service.StatusUnused).
|
||||
SetValue(0).
|
||||
SetNotes("").
|
||||
SetValidityDays(30).
|
||||
Save(s.ctx)
|
||||
s.Require().NoError(err, "seed redeem code")
|
||||
|
||||
got, err := s.repo.GetByCode(s.ctx, "GET-BY-CODE")
|
||||
s.Require().NoError(err, "GetByCode")
|
||||
@@ -83,25 +110,35 @@ func (s *RedeemCodeRepoSuite) TestGetByCode() {
|
||||
func (s *RedeemCodeRepoSuite) TestGetByCode_NotFound() {
|
||||
_, err := s.repo.GetByCode(s.ctx, "NON-EXISTENT")
|
||||
s.Require().Error(err, "expected error for non-existent code")
|
||||
s.Require().ErrorIs(err, service.ErrRedeemCodeNotFound)
|
||||
}
|
||||
|
||||
// --- Delete ---
|
||||
|
||||
func (s *RedeemCodeRepoSuite) TestDelete() {
|
||||
code := mustCreateRedeemCode(s.T(), s.db, &redeemCodeModel{Code: "TO-DELETE", Type: service.RedeemTypeBalance})
|
||||
created, err := s.client.RedeemCode.Create().
|
||||
SetCode("TO-DELETE").
|
||||
SetType(service.RedeemTypeBalance).
|
||||
SetStatus(service.StatusUnused).
|
||||
SetValue(0).
|
||||
SetNotes("").
|
||||
SetValidityDays(30).
|
||||
Save(s.ctx)
|
||||
s.Require().NoError(err)
|
||||
|
||||
err := s.repo.Delete(s.ctx, code.ID)
|
||||
err = s.repo.Delete(s.ctx, created.ID)
|
||||
s.Require().NoError(err, "Delete")
|
||||
|
||||
_, err = s.repo.GetByID(s.ctx, code.ID)
|
||||
_, err = s.repo.GetByID(s.ctx, created.ID)
|
||||
s.Require().Error(err, "expected error after delete")
|
||||
s.Require().ErrorIs(err, service.ErrRedeemCodeNotFound)
|
||||
}
|
||||
|
||||
// --- List / ListWithFilters ---
|
||||
|
||||
func (s *RedeemCodeRepoSuite) TestList() {
|
||||
mustCreateRedeemCode(s.T(), s.db, &redeemCodeModel{Code: "LIST-1", Type: service.RedeemTypeBalance})
|
||||
mustCreateRedeemCode(s.T(), s.db, &redeemCodeModel{Code: "LIST-2", Type: service.RedeemTypeBalance})
|
||||
s.Require().NoError(s.repo.Create(s.ctx, &service.RedeemCode{Code: "LIST-1", Type: service.RedeemTypeBalance, Value: 0, Status: service.StatusUnused}))
|
||||
s.Require().NoError(s.repo.Create(s.ctx, &service.RedeemCode{Code: "LIST-2", Type: service.RedeemTypeBalance, Value: 0, Status: service.StatusUnused}))
|
||||
|
||||
codes, page, err := s.repo.List(s.ctx, pagination.PaginationParams{Page: 1, PageSize: 10})
|
||||
s.Require().NoError(err, "List")
|
||||
@@ -110,8 +147,8 @@ func (s *RedeemCodeRepoSuite) TestList() {
|
||||
}
|
||||
|
||||
func (s *RedeemCodeRepoSuite) TestListWithFilters_Type() {
|
||||
mustCreateRedeemCode(s.T(), s.db, &redeemCodeModel{Code: "TYPE-BAL", Type: service.RedeemTypeBalance})
|
||||
mustCreateRedeemCode(s.T(), s.db, &redeemCodeModel{Code: "TYPE-SUB", Type: service.RedeemTypeSubscription})
|
||||
s.Require().NoError(s.repo.Create(s.ctx, &service.RedeemCode{Code: "TYPE-BAL", Type: service.RedeemTypeBalance, Value: 0, Status: service.StatusUnused}))
|
||||
s.Require().NoError(s.repo.Create(s.ctx, &service.RedeemCode{Code: "TYPE-SUB", Type: service.RedeemTypeSubscription, Value: 0, Status: service.StatusUnused}))
|
||||
|
||||
codes, _, err := s.repo.ListWithFilters(s.ctx, pagination.PaginationParams{Page: 1, PageSize: 10}, service.RedeemTypeSubscription, "", "")
|
||||
s.Require().NoError(err)
|
||||
@@ -120,8 +157,8 @@ func (s *RedeemCodeRepoSuite) TestListWithFilters_Type() {
|
||||
}
|
||||
|
||||
func (s *RedeemCodeRepoSuite) TestListWithFilters_Status() {
|
||||
mustCreateRedeemCode(s.T(), s.db, &redeemCodeModel{Code: "STAT-UNUSED", Type: service.RedeemTypeBalance, Status: service.StatusUnused})
|
||||
mustCreateRedeemCode(s.T(), s.db, &redeemCodeModel{Code: "STAT-USED", Type: service.RedeemTypeBalance, Status: service.StatusUsed})
|
||||
s.Require().NoError(s.repo.Create(s.ctx, &service.RedeemCode{Code: "STAT-UNUSED", Type: service.RedeemTypeBalance, Value: 0, Status: service.StatusUnused}))
|
||||
s.Require().NoError(s.repo.Create(s.ctx, &service.RedeemCode{Code: "STAT-USED", Type: service.RedeemTypeBalance, Value: 0, Status: service.StatusUsed}))
|
||||
|
||||
codes, _, err := s.repo.ListWithFilters(s.ctx, pagination.PaginationParams{Page: 1, PageSize: 10}, "", service.StatusUsed, "")
|
||||
s.Require().NoError(err)
|
||||
@@ -130,8 +167,8 @@ func (s *RedeemCodeRepoSuite) TestListWithFilters_Status() {
|
||||
}
|
||||
|
||||
func (s *RedeemCodeRepoSuite) TestListWithFilters_Search() {
|
||||
mustCreateRedeemCode(s.T(), s.db, &redeemCodeModel{Code: "ALPHA-CODE", Type: service.RedeemTypeBalance})
|
||||
mustCreateRedeemCode(s.T(), s.db, &redeemCodeModel{Code: "BETA-CODE", Type: service.RedeemTypeBalance})
|
||||
s.Require().NoError(s.repo.Create(s.ctx, &service.RedeemCode{Code: "ALPHA-CODE", Type: service.RedeemTypeBalance, Value: 0, Status: service.StatusUnused}))
|
||||
s.Require().NoError(s.repo.Create(s.ctx, &service.RedeemCode{Code: "BETA-CODE", Type: service.RedeemTypeBalance, Value: 0, Status: service.StatusUnused}))
|
||||
|
||||
codes, _, err := s.repo.ListWithFilters(s.ctx, pagination.PaginationParams{Page: 1, PageSize: 10}, "", "", "alpha")
|
||||
s.Require().NoError(err)
|
||||
@@ -140,12 +177,17 @@ func (s *RedeemCodeRepoSuite) TestListWithFilters_Search() {
|
||||
}
|
||||
|
||||
func (s *RedeemCodeRepoSuite) TestListWithFilters_GroupPreload() {
|
||||
group := mustCreateGroup(s.T(), s.db, &groupModel{Name: "g-preload"})
|
||||
mustCreateRedeemCode(s.T(), s.db, &redeemCodeModel{
|
||||
Code: "WITH-GROUP",
|
||||
Type: service.RedeemTypeSubscription,
|
||||
GroupID: &group.ID,
|
||||
})
|
||||
group := s.createGroup(uniqueTestValue(s.T(), "g-preload"))
|
||||
_, err := s.client.RedeemCode.Create().
|
||||
SetCode("WITH-GROUP").
|
||||
SetType(service.RedeemTypeSubscription).
|
||||
SetStatus(service.StatusUnused).
|
||||
SetValue(0).
|
||||
SetNotes("").
|
||||
SetValidityDays(30).
|
||||
SetGroupID(group.ID).
|
||||
Save(s.ctx)
|
||||
s.Require().NoError(err)
|
||||
|
||||
codes, _, err := s.repo.ListWithFilters(s.ctx, pagination.PaginationParams{Page: 1, PageSize: 10}, "", "", "")
|
||||
s.Require().NoError(err)
|
||||
@@ -157,7 +199,13 @@ func (s *RedeemCodeRepoSuite) TestListWithFilters_GroupPreload() {
|
||||
// --- Update ---
|
||||
|
||||
func (s *RedeemCodeRepoSuite) TestUpdate() {
|
||||
code := redeemCodeModelToService(mustCreateRedeemCode(s.T(), s.db, &redeemCodeModel{Code: "UPDATE-ME", Type: service.RedeemTypeBalance, Value: 10}))
|
||||
code := &service.RedeemCode{
|
||||
Code: "UPDATE-ME",
|
||||
Type: service.RedeemTypeBalance,
|
||||
Value: 10,
|
||||
Status: service.StatusUnused,
|
||||
}
|
||||
s.Require().NoError(s.repo.Create(s.ctx, code))
|
||||
|
||||
code.Value = 50
|
||||
err := s.repo.Update(s.ctx, code)
|
||||
@@ -171,8 +219,9 @@ func (s *RedeemCodeRepoSuite) TestUpdate() {
|
||||
// --- Use ---
|
||||
|
||||
func (s *RedeemCodeRepoSuite) TestUse() {
|
||||
user := mustCreateUser(s.T(), s.db, &userModel{Email: "use@test.com"})
|
||||
code := mustCreateRedeemCode(s.T(), s.db, &redeemCodeModel{Code: "USE-ME", Type: service.RedeemTypeBalance, Status: service.StatusUnused})
|
||||
user := s.createUser(uniqueTestValue(s.T(), "use") + "@example.com")
|
||||
code := &service.RedeemCode{Code: "USE-ME", Type: service.RedeemTypeBalance, Value: 0, Status: service.StatusUnused}
|
||||
s.Require().NoError(s.repo.Create(s.ctx, code))
|
||||
|
||||
err := s.repo.Use(s.ctx, code.ID, user.ID)
|
||||
s.Require().NoError(err, "Use")
|
||||
@@ -186,8 +235,9 @@ func (s *RedeemCodeRepoSuite) TestUse() {
|
||||
}
|
||||
|
||||
func (s *RedeemCodeRepoSuite) TestUse_Idempotency() {
|
||||
user := mustCreateUser(s.T(), s.db, &userModel{Email: "idem@test.com"})
|
||||
code := mustCreateRedeemCode(s.T(), s.db, &redeemCodeModel{Code: "IDEM-CODE", Type: service.RedeemTypeBalance, Status: service.StatusUnused})
|
||||
user := s.createUser(uniqueTestValue(s.T(), "idem") + "@example.com")
|
||||
code := &service.RedeemCode{Code: "IDEM-CODE", Type: service.RedeemTypeBalance, Value: 0, Status: service.StatusUnused}
|
||||
s.Require().NoError(s.repo.Create(s.ctx, code))
|
||||
|
||||
err := s.repo.Use(s.ctx, code.ID, user.ID)
|
||||
s.Require().NoError(err, "Use first time")
|
||||
@@ -199,8 +249,9 @@ func (s *RedeemCodeRepoSuite) TestUse_Idempotency() {
|
||||
}
|
||||
|
||||
func (s *RedeemCodeRepoSuite) TestUse_AlreadyUsed() {
|
||||
user := mustCreateUser(s.T(), s.db, &userModel{Email: "already@test.com"})
|
||||
code := mustCreateRedeemCode(s.T(), s.db, &redeemCodeModel{Code: "ALREADY-USED", Type: service.RedeemTypeBalance, Status: service.StatusUsed})
|
||||
user := s.createUser(uniqueTestValue(s.T(), "already") + "@example.com")
|
||||
code := &service.RedeemCode{Code: "ALREADY-USED", Type: service.RedeemTypeBalance, Value: 0, Status: service.StatusUsed}
|
||||
s.Require().NoError(s.repo.Create(s.ctx, code))
|
||||
|
||||
err := s.repo.Use(s.ctx, code.ID, user.ID)
|
||||
s.Require().Error(err, "expected error for already used code")
|
||||
@@ -210,25 +261,34 @@ func (s *RedeemCodeRepoSuite) TestUse_AlreadyUsed() {
|
||||
// --- ListByUser ---
|
||||
|
||||
func (s *RedeemCodeRepoSuite) TestListByUser() {
|
||||
user := mustCreateUser(s.T(), s.db, &userModel{Email: "listby@test.com"})
|
||||
user := s.createUser(uniqueTestValue(s.T(), "listby") + "@example.com")
|
||||
base := time.Date(2025, 1, 1, 12, 0, 0, 0, time.UTC)
|
||||
|
||||
// Create codes with explicit used_at for ordering
|
||||
c1 := mustCreateRedeemCode(s.T(), s.db, &redeemCodeModel{
|
||||
Code: "USER-1",
|
||||
Type: service.RedeemTypeBalance,
|
||||
Status: service.StatusUsed,
|
||||
UsedBy: &user.ID,
|
||||
})
|
||||
s.db.Model(c1).Update("used_at", base)
|
||||
usedAt1 := base
|
||||
_, err := s.client.RedeemCode.Create().
|
||||
SetCode("USER-1").
|
||||
SetType(service.RedeemTypeBalance).
|
||||
SetStatus(service.StatusUsed).
|
||||
SetValue(0).
|
||||
SetNotes("").
|
||||
SetValidityDays(30).
|
||||
SetUsedBy(user.ID).
|
||||
SetUsedAt(usedAt1).
|
||||
Save(s.ctx)
|
||||
s.Require().NoError(err)
|
||||
|
||||
c2 := mustCreateRedeemCode(s.T(), s.db, &redeemCodeModel{
|
||||
Code: "USER-2",
|
||||
Type: service.RedeemTypeBalance,
|
||||
Status: service.StatusUsed,
|
||||
UsedBy: &user.ID,
|
||||
})
|
||||
s.db.Model(c2).Update("used_at", base.Add(1*time.Hour))
|
||||
usedAt2 := base.Add(1 * time.Hour)
|
||||
_, err = s.client.RedeemCode.Create().
|
||||
SetCode("USER-2").
|
||||
SetType(service.RedeemTypeBalance).
|
||||
SetStatus(service.StatusUsed).
|
||||
SetValue(0).
|
||||
SetNotes("").
|
||||
SetValidityDays(30).
|
||||
SetUsedBy(user.ID).
|
||||
SetUsedAt(usedAt2).
|
||||
Save(s.ctx)
|
||||
s.Require().NoError(err)
|
||||
|
||||
codes, err := s.repo.ListByUser(s.ctx, user.ID, 10)
|
||||
s.Require().NoError(err, "ListByUser")
|
||||
@@ -239,17 +299,21 @@ func (s *RedeemCodeRepoSuite) TestListByUser() {
|
||||
}
|
||||
|
||||
func (s *RedeemCodeRepoSuite) TestListByUser_WithGroupPreload() {
|
||||
user := mustCreateUser(s.T(), s.db, &userModel{Email: "grp@test.com"})
|
||||
group := mustCreateGroup(s.T(), s.db, &groupModel{Name: "g-listby"})
|
||||
user := s.createUser(uniqueTestValue(s.T(), "grp") + "@example.com")
|
||||
group := s.createGroup(uniqueTestValue(s.T(), "g-listby"))
|
||||
|
||||
c := mustCreateRedeemCode(s.T(), s.db, &redeemCodeModel{
|
||||
Code: "WITH-GRP",
|
||||
Type: service.RedeemTypeSubscription,
|
||||
Status: service.StatusUsed,
|
||||
UsedBy: &user.ID,
|
||||
GroupID: &group.ID,
|
||||
})
|
||||
s.db.Model(c).Update("used_at", time.Now())
|
||||
_, err := s.client.RedeemCode.Create().
|
||||
SetCode("WITH-GRP").
|
||||
SetType(service.RedeemTypeSubscription).
|
||||
SetStatus(service.StatusUsed).
|
||||
SetValue(0).
|
||||
SetNotes("").
|
||||
SetValidityDays(30).
|
||||
SetUsedBy(user.ID).
|
||||
SetUsedAt(time.Now()).
|
||||
SetGroupID(group.ID).
|
||||
Save(s.ctx)
|
||||
s.Require().NoError(err)
|
||||
|
||||
codes, err := s.repo.ListByUser(s.ctx, user.ID, 10)
|
||||
s.Require().NoError(err)
|
||||
@@ -259,14 +323,18 @@ func (s *RedeemCodeRepoSuite) TestListByUser_WithGroupPreload() {
|
||||
}
|
||||
|
||||
func (s *RedeemCodeRepoSuite) TestListByUser_DefaultLimit() {
|
||||
user := mustCreateUser(s.T(), s.db, &userModel{Email: "deflimit@test.com"})
|
||||
c := mustCreateRedeemCode(s.T(), s.db, &redeemCodeModel{
|
||||
Code: "DEF-LIM",
|
||||
Type: service.RedeemTypeBalance,
|
||||
Status: service.StatusUsed,
|
||||
UsedBy: &user.ID,
|
||||
})
|
||||
s.db.Model(c).Update("used_at", time.Now())
|
||||
user := s.createUser(uniqueTestValue(s.T(), "deflimit") + "@example.com")
|
||||
_, err := s.client.RedeemCode.Create().
|
||||
SetCode("DEF-LIM").
|
||||
SetType(service.RedeemTypeBalance).
|
||||
SetStatus(service.StatusUsed).
|
||||
SetValue(0).
|
||||
SetNotes("").
|
||||
SetValidityDays(30).
|
||||
SetUsedBy(user.ID).
|
||||
SetUsedAt(time.Now()).
|
||||
Save(s.ctx)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// limit <= 0 should default to 10
|
||||
codes, err := s.repo.ListByUser(s.ctx, user.ID, 0)
|
||||
@@ -277,12 +345,13 @@ func (s *RedeemCodeRepoSuite) TestListByUser_DefaultLimit() {
|
||||
// --- Combined original test ---
|
||||
|
||||
func (s *RedeemCodeRepoSuite) TestCreateBatch_Filters_Use_Idempotency_ListByUser() {
|
||||
user := mustCreateUser(s.T(), s.db, &userModel{Email: "rc@example.com"})
|
||||
group := mustCreateGroup(s.T(), s.db, &groupModel{Name: "g-rc"})
|
||||
user := s.createUser(uniqueTestValue(s.T(), "rc") + "@example.com")
|
||||
group := s.createGroup(uniqueTestValue(s.T(), "g-rc"))
|
||||
groupID := group.ID
|
||||
|
||||
codes := []service.RedeemCode{
|
||||
{Code: "CODEA", Type: service.RedeemTypeBalance, Value: 1, Status: service.StatusUnused, CreatedAt: time.Now()},
|
||||
{Code: "CODEB", Type: service.RedeemTypeSubscription, Value: 0, Status: service.StatusUnused, GroupID: &group.ID, ValidityDays: 7, CreatedAt: time.Now()},
|
||||
{Code: "CODEA", Type: service.RedeemTypeBalance, Value: 1, Status: service.StatusUnused, Notes: ""},
|
||||
{Code: "CODEB", Type: service.RedeemTypeSubscription, Value: 0, Status: service.StatusUnused, Notes: "", GroupID: &groupID, ValidityDays: 7},
|
||||
}
|
||||
s.Require().NoError(s.repo.CreateBatch(s.ctx, codes), "CreateBatch")
|
||||
|
||||
@@ -303,10 +372,16 @@ func (s *RedeemCodeRepoSuite) TestCreateBatch_Filters_Use_Idempotency_ListByUser
|
||||
codeA, err := s.repo.GetByCode(s.ctx, "CODEA")
|
||||
s.Require().NoError(err, "GetByCode")
|
||||
|
||||
// Use fixed time instead of time.Sleep for deterministic ordering
|
||||
s.db.Model(&redeemCodeModel{}).Where("id = ?", codeB.ID).Update("used_at", time.Date(2025, 1, 1, 12, 0, 0, 0, time.UTC))
|
||||
// Use fixed time instead of time.Sleep for deterministic ordering.
|
||||
_, err = s.client.RedeemCode.UpdateOneID(codeB.ID).
|
||||
SetUsedAt(time.Date(2025, 1, 1, 12, 0, 0, 0, time.UTC)).
|
||||
Save(s.ctx)
|
||||
s.Require().NoError(err)
|
||||
s.Require().NoError(s.repo.Use(s.ctx, codeA.ID, user.ID), "Use codeA")
|
||||
s.db.Model(&redeemCodeModel{}).Where("id = ?", codeA.ID).Update("used_at", time.Date(2025, 1, 1, 13, 0, 0, 0, time.UTC))
|
||||
_, err = s.client.RedeemCode.UpdateOneID(codeA.ID).
|
||||
SetUsedAt(time.Date(2025, 1, 1, 13, 0, 0, 0, time.UTC)).
|
||||
Save(s.ctx)
|
||||
s.Require().NoError(err)
|
||||
|
||||
used, err := s.repo.ListByUser(s.ctx, user.ID, 10)
|
||||
s.Require().NoError(err, "ListByUser")
|
||||
|
||||
Reference in New Issue
Block a user