package dept import ( "context" "errors" "testing" deptModel "perms-system-server/internal/model/dept" "perms-system-server/internal/testutil/ctxhelper" "perms-system-server/internal/testutil/mocks" "perms-system-server/internal/types" "github.com/stretchr/testify/assert" "github.com/zeromicro/go-zero/core/stores/sqlx" "go.uber.org/mock/gomock" ) type fakeResult struct{ id int64 } func (r fakeResult) LastInsertId() (int64, error) { return r.id, nil } func (r fakeResult) RowsAffected() (int64, error) { return 1, nil } // TC-0097: 事务回滚-Insert失败 func TestCreateDept_Mock_InsertWithTxFail(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() dbErr := errors.New("db error") mockDept := mocks.NewMockSysDeptModel(ctrl) mockDept.EXPECT().TransactCtx(gomock.Any(), gomock.Any()). DoAndReturn(func(ctx context.Context, fn func(context.Context, sqlx.Session) error) error { return fn(ctx, nil) }) mockDept.EXPECT().InsertWithTx(gomock.Any(), nil, gomock.Any()). Return(nil, dbErr) svcCtx := mocks.NewMockServiceContext(mocks.MockModels{ Dept: mockDept, }) logic := NewCreateDeptLogic(ctxhelper.SuperAdminCtx(), svcCtx) resp, err := logic.CreateDept(&types.CreateDeptReq{ ParentId: 0, Name: "TestDept", }) assert.Error(t, err) assert.ErrorIs(t, err, dbErr) assert.Nil(t, resp) } // TC-0094: 不传DeptType默认NORMAL func TestCreateDept_Mock_DefaultDeptType(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() mockDept := mocks.NewMockSysDeptModel(ctrl) mockDept.EXPECT().TransactCtx(gomock.Any(), gomock.Any()). DoAndReturn(func(ctx context.Context, fn func(context.Context, sqlx.Session) error) error { return fn(ctx, nil) }) mockDept.EXPECT().InsertWithTx(gomock.Any(), nil, gomock.Any()). DoAndReturn(func(ctx context.Context, s sqlx.Session, data *deptModel.SysDept) (interface{ LastInsertId() (int64, error); RowsAffected() (int64, error) }, error) { assert.Equal(t, "NORMAL", data.DeptType) return fakeResult{id: 99}, nil }) // L-R17-5:Insert 后 CreateDept 以单条 `UPDATE sys_dept SET path=?,updateTime=?` 回写 path, // 相对老路径 `FindOneWithTx → UpdateWithTx` 砍掉一次 SELECT、缩短 X 锁;mock 契约必须同步。 mockDept.EXPECT().UpdatePathWithTx(gomock.Any(), nil, int64(99), "/99/", gomock.Any()).Return(nil) svcCtx := mocks.NewMockServiceContext(mocks.MockModels{Dept: mockDept}) logic := NewCreateDeptLogic(ctxhelper.SuperAdminCtx(), svcCtx) resp, err := logic.CreateDept(&types.CreateDeptReq{ ParentId: 0, Name: "TestDept", }) assert.NoError(t, err) assert.NotNil(t, resp) } // TC-0095: 传DeptType=DEV func TestCreateDept_Mock_DevDeptType(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() mockDept := mocks.NewMockSysDeptModel(ctrl) mockDept.EXPECT().TransactCtx(gomock.Any(), gomock.Any()). DoAndReturn(func(ctx context.Context, fn func(context.Context, sqlx.Session) error) error { return fn(ctx, nil) }) mockDept.EXPECT().InsertWithTx(gomock.Any(), nil, gomock.Any()). DoAndReturn(func(ctx context.Context, s sqlx.Session, data *deptModel.SysDept) (interface{ LastInsertId() (int64, error); RowsAffected() (int64, error) }, error) { assert.Equal(t, "DEV", data.DeptType) return fakeResult{id: 100}, nil }) mockDept.EXPECT().UpdatePathWithTx(gomock.Any(), nil, int64(100), "/100/", gomock.Any()).Return(nil) svcCtx := mocks.NewMockServiceContext(mocks.MockModels{Dept: mockDept}) logic := NewCreateDeptLogic(ctxhelper.SuperAdminCtx(), svcCtx) resp, err := logic.CreateDept(&types.CreateDeptReq{ ParentId: 0, Name: "TestDevDept", DeptType: "DEV", }) assert.NoError(t, err) assert.NotNil(t, resp) } // TC-0098: 事务回滚—path 回写失败,整个 tx 必须回滚并把 dbErr 原样冒泡出去。 func TestCreateDept_Mock_UpdateWithTxFail(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() dbErr := errors.New("db error") mockDept := mocks.NewMockSysDeptModel(ctrl) mockDept.EXPECT().TransactCtx(gomock.Any(), gomock.Any()). DoAndReturn(func(ctx context.Context, fn func(context.Context, sqlx.Session) error) error { return fn(ctx, nil) }) mockDept.EXPECT().InsertWithTx(gomock.Any(), nil, gomock.Any()). Return(fakeResult{id: 99}, nil) // L-R17-5:path 回写压缩成 UpdatePathWithTx 单 UPDATE;mock 在此注入失败以验证 tx 回滚。 mockDept.EXPECT().UpdatePathWithTx(gomock.Any(), nil, int64(99), "/99/", gomock.Any()). Return(dbErr) svcCtx := mocks.NewMockServiceContext(mocks.MockModels{ Dept: mockDept, }) logic := NewCreateDeptLogic(ctxhelper.SuperAdminCtx(), svcCtx) resp, err := logic.CreateDept(&types.CreateDeptReq{ ParentId: 0, Name: "TestDept", }) assert.Error(t, err) assert.ErrorIs(t, err, dbErr) assert.Nil(t, resp) }