material_manage_system/internal/repository/warehouse_test.go
文睿 张 5d508cd2bb fix: 给不存在的仓库建库位时误报创建成功
CreateLocation 的 INSERT...SELECT 匹配不到仓库时影响 0 行,MySQL 不报错:
err 为 nil、LastInsertId() 返回 0。此前只看 err,接口回 201
"Location created successfully" 且 data.id 为 0,而库里一行都没写进去——
刷新后库位消失,重试还是"成功"。

改为以 RowsAffected 为准(INSERT 的 RowsAffected 就是实际插入行数,不受
clientFoundRows 影响),影响 0 行返回 ErrWarehouseNotFound,接口回 400
"仓库不存在,可能已被删除或不属于当前组织"。与既有的 execExpectingRow、
role_permissions 插入保持同一套路。

实测(docker,重建后端镜像后):
  不存在的仓库 999  ->  HTTP 400,warehouse_locations 1 行 -> 1 行
  真实仓库 1        ->  HTTP 201 id=3,1 行 -> 2 行,库中确有此行

新增 warehouse_test.go 覆盖两个分支,变异验证:去掉 affected==0 判断后
"影响 0 行"用例失败(返回 <nil>,正是线上症状)。
2026-09-22 18:21:31 +08:00

69 lines
2.4 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package repository
import (
"errors"
"regexp"
"testing"
"github.com/DATA-DOG/go-sqlmock"
"github.com/wmms/wmms/internal/model"
"github.com/wmms/wmms/pkg/database"
)
// 与 warehouse.go 里的语句逐字一致:换行和缩进都参与匹配。
const createLocationSQL = `INSERT INTO warehouse_locations (warehouse_id, code, name, status)
SELECT id, ?, ?, ? FROM warehouses WHERE id = ? AND organization_id = ?`
func newWarehouseMock(t *testing.T) sqlmock.Sqlmock {
t.Helper()
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("open sql mock: %v", err)
}
t.Cleanup(func() { database.DB = nil; db.Close() })
database.DB = db
return mock
}
// 仓库不存在时(id 打错、仓库已被删、或这条仓库属于别的组织)这条 INSERT...SELECT
// 影响 0 行,而 MySQL 既不报错、LastInsertId() 也只回 0。此前只看 err,接口会回
// 201「Location created successfully」,库里却没有新行,刷新后库位消失、重试还是
// "成功"(实测请求前后 warehouse_locations 行数都是 1)。
func TestCreateLocationReportsMissingWarehouse(t *testing.T) {
mock := newWarehouseMock(t)
mock.ExpectExec(regexp.QuoteMeta(createLocationSQL)).
WithArgs("L1", "库位一", "active", int64(999), int64(1)).
WillReturnResult(sqlmock.NewResult(0, 0))
item := &model.WarehouseLocation{WarehouseID: 999, Code: "L1", Name: "库位一", Status: "active"}
err := NewWarehouseRepository().CreateLocation(item, 1)
if !errors.Is(err, ErrWarehouseNotFound) {
t.Fatalf("影响 0 行应当返回 ErrWarehouseNotFound,实际 %v", err)
}
if item.ID != 0 {
t.Fatalf("没插进去就不该有 id,实际 %d", item.ID)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Fatal(err)
}
}
// 反面:真的插进去了就必须回填 id,别把成功路径也一起判成失败。
func TestCreateLocationReturnsInsertedID(t *testing.T) {
mock := newWarehouseMock(t)
mock.ExpectExec(regexp.QuoteMeta(createLocationSQL)).
WithArgs("L1", "库位一", "active", int64(3), int64(1)).
WillReturnResult(sqlmock.NewResult(7, 1))
item := &model.WarehouseLocation{WarehouseID: 3, Code: "L1", Name: "库位一", Status: "active"}
if err := NewWarehouseRepository().CreateLocation(item, 1); err != nil {
t.Fatalf("插入成功不应报错,实际 %v", err)
}
if item.ID != 7 {
t.Fatalf("应当回填自增 id 7,实际 %d", item.ID)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Fatal(err)
}
}