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>,正是线上症状)。
69 lines
2.4 KiB
Go
69 lines
2.4 KiB
Go
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)
|
||
}
|
||
}
|