createProductLogic_test.go 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. package product
  2. import (
  3. "errors"
  4. "sync"
  5. "testing"
  6. productModel "perms-system-server/internal/model/product"
  7. "perms-system-server/internal/response"
  8. "perms-system-server/internal/svc"
  9. "perms-system-server/internal/testutil"
  10. "perms-system-server/internal/testutil/ctxhelper"
  11. "perms-system-server/internal/types"
  12. "github.com/stretchr/testify/assert"
  13. "github.com/stretchr/testify/require"
  14. "golang.org/x/crypto/bcrypt"
  15. )
  16. // TC-0064: 正常创建
  17. func TestCreateProduct_Success(t *testing.T) {
  18. ctx := ctxhelper.SuperAdminCtx()
  19. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  20. conn := testutil.GetTestSqlConn()
  21. code := testutil.UniqueId()
  22. logic := NewCreateProductLogic(ctx, svcCtx)
  23. resp, err := logic.CreateProduct(&types.CreateProductReq{
  24. Code: code,
  25. Name: "测试产品",
  26. Remark: "集成测试",
  27. })
  28. require.NoError(t, err)
  29. require.NotNil(t, resp)
  30. t.Cleanup(func() {
  31. testutil.CleanTableByField(ctx, conn, "`sys_product_member`", "productCode", code)
  32. testutil.CleanTableByField(ctx, conn, "`sys_user`", "username", "admin_"+code)
  33. testutil.CleanTable(ctx, conn, "`sys_product`", resp.Id)
  34. })
  35. assert.True(t, resp.Id > 0)
  36. assert.Equal(t, code, resp.Code)
  37. assert.NotEmpty(t, resp.AppKey)
  38. assert.NotEmpty(t, resp.AppSecret)
  39. assert.Equal(t, "admin_"+code, resp.AdminUser)
  40. assert.NotEmpty(t, resp.AdminPassword)
  41. }
  42. // TC-0064: 正常创建
  43. func TestCreateProduct_VerifyDB(t *testing.T) {
  44. ctx := ctxhelper.SuperAdminCtx()
  45. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  46. conn := testutil.GetTestSqlConn()
  47. code := testutil.UniqueId()
  48. logic := NewCreateProductLogic(ctx, svcCtx)
  49. resp, err := logic.CreateProduct(&types.CreateProductReq{
  50. Code: code,
  51. Name: "DB验证产品",
  52. Remark: "验证数据库记录",
  53. })
  54. require.NoError(t, err)
  55. t.Cleanup(func() {
  56. testutil.CleanTableByField(ctx, conn, "`sys_product_member`", "productCode", code)
  57. testutil.CleanTableByField(ctx, conn, "`sys_user`", "username", "admin_"+code)
  58. testutil.CleanTable(ctx, conn, "`sys_product`", resp.Id)
  59. })
  60. product, err := svcCtx.SysProductModel.FindOne(ctx, resp.Id)
  61. require.NoError(t, err)
  62. assert.Equal(t, code, product.Code)
  63. assert.Equal(t, "DB验证产品", product.Name)
  64. assert.Equal(t, resp.AppKey, product.AppKey)
  65. require.NoError(t, bcrypt.CompareHashAndPassword([]byte(product.AppSecret), []byte(resp.AppSecret)),
  66. "DB should store bcrypt hash of appSecret, verifiable with plaintext from response")
  67. assert.Equal(t, int64(1), product.Status)
  68. var userCount int64
  69. err = conn.QueryRowCtx(ctx, &userCount,
  70. "SELECT COUNT(*) FROM `sys_user` WHERE `username` = ?", "admin_"+code)
  71. require.NoError(t, err)
  72. assert.Equal(t, int64(1), userCount)
  73. var memberCount int64
  74. err = conn.QueryRowCtx(ctx, &memberCount,
  75. "SELECT COUNT(*) FROM `sys_product_member` WHERE `productCode` = ? AND `memberType` = 'ADMIN'", code)
  76. require.NoError(t, err)
  77. assert.Equal(t, int64(1), memberCount)
  78. }
  79. // TC-0067: 编码已存在
  80. func TestCreateProduct_DuplicateCode(t *testing.T) {
  81. ctx := ctxhelper.SuperAdminCtx()
  82. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  83. conn := testutil.GetTestSqlConn()
  84. code := testutil.UniqueId()
  85. logic := NewCreateProductLogic(ctx, svcCtx)
  86. resp, err := logic.CreateProduct(&types.CreateProductReq{
  87. Code: code,
  88. Name: "第一个产品",
  89. })
  90. require.NoError(t, err)
  91. t.Cleanup(func() {
  92. testutil.CleanTableByField(ctx, conn, "`sys_product_member`", "productCode", code)
  93. testutil.CleanTableByField(ctx, conn, "`sys_user`", "username", "admin_"+code)
  94. testutil.CleanTable(ctx, conn, "`sys_product`", resp.Id)
  95. })
  96. logic2 := NewCreateProductLogic(ctx, svcCtx)
  97. _, err = logic2.CreateProduct(&types.CreateProductReq{
  98. Code: code,
  99. Name: "重复产品",
  100. })
  101. require.Error(t, err)
  102. var codeErr *response.CodeError
  103. require.True(t, errors.As(err, &codeErr))
  104. assert.Equal(t, 409, codeErr.Code())
  105. assert.Equal(t, "产品编码已存在", codeErr.Error())
  106. }
  107. // TC-0068: 并发创建同编码
  108. func TestCreateProduct_ConcurrentSameCode(t *testing.T) {
  109. ctx := ctxhelper.SuperAdminCtx()
  110. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  111. conn := testutil.GetTestSqlConn()
  112. code := testutil.UniqueId()
  113. t.Cleanup(func() {
  114. testutil.CleanTableByField(ctx, conn, "`sys_product_member`", "productCode", code)
  115. testutil.CleanTableByField(ctx, conn, "`sys_user`", "username", "admin_"+code)
  116. testutil.CleanTableByField(ctx, conn, "`sys_product`", "code", code)
  117. })
  118. var wg sync.WaitGroup
  119. results := make(chan error, 2)
  120. for i := 0; i < 2; i++ {
  121. wg.Add(1)
  122. go func() {
  123. defer wg.Done()
  124. logic := NewCreateProductLogic(ctx, svcCtx)
  125. _, err := logic.CreateProduct(&types.CreateProductReq{
  126. Code: code,
  127. Name: "并发测试产品",
  128. })
  129. results <- err
  130. }()
  131. }
  132. wg.Wait()
  133. close(results)
  134. var errs []error
  135. for err := range results {
  136. errs = append(errs, err)
  137. }
  138. require.Len(t, errs, 2)
  139. successCount := 0
  140. failCount := 0
  141. for _, err := range errs {
  142. if err == nil {
  143. successCount++
  144. } else {
  145. failCount++
  146. }
  147. }
  148. assert.Equal(t, 1, successCount, "exactly one goroutine should succeed")
  149. assert.Equal(t, 1, failCount, "exactly one goroutine should fail (409 or DB duplicate)")
  150. }
  151. // TC-0535: createProduct非超管拒绝
  152. func TestCreateProduct_NonSuperAdminRejected(t *testing.T) {
  153. ctx := ctxhelper.AdminCtx("test_product")
  154. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  155. logic := NewCreateProductLogic(ctx, svcCtx)
  156. _, err := logic.CreateProduct(&types.CreateProductReq{Code: "test", Name: "test"})
  157. require.Error(t, err)
  158. var ce *response.CodeError
  159. require.True(t, errors.As(err, &ce))
  160. assert.Equal(t, 403, ce.Code())
  161. }
  162. // TC-0069~0593: createProduct 编码格式校验(M-8 修复验证)
  163. func TestCreateProduct_InvalidCodeFormat(t *testing.T) {
  164. ctx := ctxhelper.SuperAdminCtx()
  165. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  166. logic := NewCreateProductLogic(ctx, svcCtx)
  167. cases := []struct {
  168. name string
  169. code string
  170. }{
  171. {"空", ""},
  172. {"数字开头", "1abc"},
  173. {"下划线开头", "_abc"},
  174. {"中划线开头", "-abc"},
  175. {"包含中文", "产品A"},
  176. {"单字母(过短)", "a"},
  177. {"包含空格", "ab c"},
  178. {"包含特殊字符!", "ab!c"},
  179. {"包含斜杠", "ab/c"},
  180. }
  181. for _, c := range cases {
  182. t.Run(c.name, func(t *testing.T) {
  183. _, err := logic.CreateProduct(&types.CreateProductReq{Code: c.code, Name: "x"})
  184. require.Error(t, err, "code=%q 应被拒绝", c.code)
  185. var ce *response.CodeError
  186. require.True(t, errors.As(err, &ce))
  187. assert.Equal(t, 400, ce.Code())
  188. })
  189. }
  190. }
  191. // TC-0074: createProduct 编码长度>64 被拒绝
  192. func TestCreateProduct_CodeTooLong(t *testing.T) {
  193. ctx := ctxhelper.SuperAdminCtx()
  194. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  195. logic := NewCreateProductLogic(ctx, svcCtx)
  196. long := "a"
  197. for i := 0; i < 64; i++ {
  198. long += "b"
  199. }
  200. _, err := logic.CreateProduct(&types.CreateProductReq{Code: long, Name: "x"})
  201. require.Error(t, err)
  202. var ce *response.CodeError
  203. require.True(t, errors.As(err, &ce))
  204. assert.Equal(t, 400, ce.Code())
  205. }
  206. // TC-0075: createProduct 合法编码(包含下划线、中划线、数字)
  207. func TestCreateProduct_ValidCodeWithSymbols(t *testing.T) {
  208. ctx := ctxhelper.SuperAdminCtx()
  209. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  210. conn := testutil.GetTestSqlConn()
  211. code := "a_1-" + testutil.UniqueId()
  212. logic := NewCreateProductLogic(ctx, svcCtx)
  213. resp, err := logic.CreateProduct(&types.CreateProductReq{Code: code, Name: "x"})
  214. require.NoError(t, err)
  215. t.Cleanup(func() {
  216. testutil.CleanTableByField(ctx, conn, "`sys_product_member`", "productCode", code)
  217. testutil.CleanTableByField(ctx, conn, "`sys_user`", "username", "admin_"+code)
  218. testutil.CleanTable(ctx, conn, "`sys_product`", resp.Id)
  219. })
  220. assert.Equal(t, code, resp.Code)
  221. }
  222. // suppress unused import
  223. var _ = (*productModel.SysProduct)(nil)