test: 补齐 Update 的 happy-path 用例,锁定「编辑走 UPDATE」而非 INSERT
六个 Update* 此前只有 UpdateRequiresID 覆盖到拒绝分支,方法体里的 s.repo.Update*(item) 从未被执行。这 12 个方法互为复制粘贴的模板, 尾行一旦误写成 Create* 全部测试仍然绿灯,生产行为会退化成「编辑即新增」。 新增两条用例:PartnerService.Update(事务:SELECT ... FOR UPDATE + 两条 UPDATE)与 MasterDataService.UpdateCategory(单条 UPDATE),均登记 UPDATE 期望并以 ExpectationsWereMet 兜底。把尾行改成 Create* 实测两条 用例都会失败并指名收到了 INSERT。 同时 gofmt internal/service/admin_master.go:新增中文注释紧贴前一个函数的 },补上缺失的空行,纯空白改动。
This commit is contained in:
parent
f3124e96cc
commit
72bc6c9af2
|
|
@ -68,6 +68,7 @@ func NewMasterDataService() *MasterDataService {
|
|||
func (s *MasterDataService) ListCategories(orgID int64) ([]model.MaterialCategory, error) {
|
||||
return s.repo.ListCategories(orgID)
|
||||
}
|
||||
|
||||
// CreateCategory 新建分类。请求体可能带着前端表单残留的 id,这里不读取 item.ID,
|
||||
// repository 的 INSERT 也不写 id 列,所以脏 id 不会把新建变成覆盖。
|
||||
func (s *MasterDataService) CreateCategory(item *model.MaterialCategory) error {
|
||||
|
|
@ -104,6 +105,7 @@ func prepareCategory(item *model.MaterialCategory) error {
|
|||
func (s *MasterDataService) ListUnits(orgID int64) ([]model.Unit, error) {
|
||||
return s.repo.ListUnits(orgID)
|
||||
}
|
||||
|
||||
// CreateUnit 新建单位。同样不读取 item.ID,避免表单残留的脏 id 把新建变成覆盖。
|
||||
func (s *MasterDataService) CreateUnit(item *model.Unit) error {
|
||||
if err := prepareUnit(item); err != nil {
|
||||
|
|
@ -154,6 +156,7 @@ func (s *MasterDataService) NextMaterialCode(orgID, categoryID int64) (*model.Ne
|
|||
}
|
||||
return &model.NextMaterialCode{Available: true, Code: rule.FormatCode(rule.NextSequence)}, nil
|
||||
}
|
||||
|
||||
// CreateCodeRule 新建编码规则。同样不读取 item.ID,避免表单残留的脏 id 把新建变成覆盖。
|
||||
func (s *MasterDataService) CreateCodeRule(item *model.CodeRule) error {
|
||||
if err := prepareCodeRule(item); err != nil {
|
||||
|
|
@ -202,6 +205,7 @@ func (s *PartnerService) List(orgID int64, role string) ([]model.BusinessPartner
|
|||
}
|
||||
return s.repo.List(orgID, role)
|
||||
}
|
||||
|
||||
// Create 新建伙伴。请求体可能带着前端表单残留的 id,这里不读取 item.ID,
|
||||
// repository 的 INSERT 也不写 id 列,所以脏 id 不会把新建变成覆盖。
|
||||
func (s *PartnerService) Create(item *model.BusinessPartner) error {
|
||||
|
|
@ -251,6 +255,7 @@ func NewWarehouseService() *WarehouseService {
|
|||
func (s *WarehouseService) List(orgID int64) ([]model.Warehouse, error) {
|
||||
return s.repo.List(orgID)
|
||||
}
|
||||
|
||||
// Create 新建仓库。不读取 item.ID,脏 id 不会把新建变成覆盖。
|
||||
func (s *WarehouseService) Create(item *model.Warehouse) error {
|
||||
if err := prepareWarehouse(item); err != nil {
|
||||
|
|
|
|||
|
|
@ -131,3 +131,69 @@ func TestLocationCreateRequiresWarehouseID(t *testing.T) {
|
|||
t.Fatal("缺少 warehouse_id 的库位创建应当被拒绝")
|
||||
}
|
||||
}
|
||||
|
||||
// 上面的 UpdateRequiresID 只覆盖了拒绝分支,方法体里真正的 s.repo.Update 从未被执行。
|
||||
// 六个 Update* 互为复制粘贴的模板,若某一处尾行被误写成 s.repo.Create,
|
||||
// 生产行为会退化成「编辑即新增」——与本次修复的缺陷同级。这里补上走通的路径。
|
||||
func TestPartnerUpdateUpdatesInsteadOfInserting(t *testing.T) {
|
||||
db, mock, err := sqlmock.New()
|
||||
if err != nil {
|
||||
t.Fatalf("sqlmock: %v", err)
|
||||
}
|
||||
database.DB = db
|
||||
t.Cleanup(func() { database.DB = nil; db.Close() })
|
||||
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectQuery("SELECT business_partner_id FROM organization_partners").
|
||||
WithArgs(int64(12), int64(1)).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"business_partner_id"}).AddRow(int64(7)))
|
||||
mock.ExpectExec("UPDATE business_partners").WillReturnResult(sqlmock.NewResult(0, 1))
|
||||
mock.ExpectExec("UPDATE organization_partners").WillReturnResult(sqlmock.NewResult(0, 1))
|
||||
mock.ExpectCommit()
|
||||
|
||||
item := &model.BusinessPartner{
|
||||
ID: 12,
|
||||
OrganizationID: 1,
|
||||
PartnerCode: "SUP-007",
|
||||
LegalName: "伙伴改名",
|
||||
IsSupplier: true,
|
||||
}
|
||||
if err := NewPartnerService().Update(item); err != nil {
|
||||
t.Fatalf("Update: %v", err)
|
||||
}
|
||||
// 只登记了 SELECT ... FOR UPDATE 与两条 UPDATE。若尾行被误写成 Create,
|
||||
// 事务里发出的 INSERT 与这里的期望不匹配:要么 Create 直接报错、要么
|
||||
// 这两条 UPDATE 永不被消费,ExpectationsWereMet 必然失败。
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Fatalf("应当执行 UPDATE,实际 SQL 不是更新: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCategoryUpdateUpdatesInsteadOfInserting(t *testing.T) {
|
||||
db, mock, err := sqlmock.New()
|
||||
if err != nil {
|
||||
t.Fatalf("sqlmock: %v", err)
|
||||
}
|
||||
database.DB = db
|
||||
t.Cleanup(func() { database.DB = nil; db.Close() })
|
||||
|
||||
mock.ExpectExec("UPDATE material_categories").
|
||||
WithArgs("改名后的分类", "说明", nil, "active", int64(9), int64(1)).
|
||||
WillReturnResult(sqlmock.NewResult(0, 1))
|
||||
|
||||
item := &model.MaterialCategory{
|
||||
ID: 9,
|
||||
OrganizationID: 1,
|
||||
Code: "A01",
|
||||
Name: "改名后的分类",
|
||||
Description: "说明",
|
||||
}
|
||||
if err := NewMasterDataService().UpdateCategory(item); err != nil {
|
||||
t.Fatalf("UpdateCategory: %v", err)
|
||||
}
|
||||
// 这里只登记了 UPDATE;误写成 Create 时发出的 INSERT 不会被消费,
|
||||
// Expectation 落空,ExpectationsWereMet 报错。
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Fatalf("应当执行 UPDATE,实际 SQL 不是插入: %v", err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user