111 lines
4.4 KiB
Go
111 lines
4.4 KiB
Go
package repository
|
||
|
||
import (
|
||
"errors"
|
||
"regexp"
|
||
"strings"
|
||
"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)
|
||
}
|
||
}
|
||
|
||
// warehouses(organization_id) 的唯一约束是「每公司一个仓库」的兜底。撞约束属于
|
||
// 正常业务冲突,要翻成可展示的中文错误(接口据此回 409),不能把原始英文
|
||
// Duplicate entry 冒到前端。
|
||
func TestCreateWarehouseReportsDuplicatePerCompany(t *testing.T) {
|
||
mock := newWarehouseMock(t)
|
||
mock.ExpectExec("INSERT INTO warehouses").
|
||
WithArgs(int64(1), "WH2", "二号仓", "active").
|
||
WillReturnError(errors.New("Error 1062: Duplicate entry '1' for key 'uk_warehouses_organization'"))
|
||
|
||
err := NewWarehouseRepository().Create(&model.Warehouse{OrganizationID: 1, Code: "WH2", Name: "二号仓", Status: "active"})
|
||
if !errors.Is(err, ErrDuplicateWarehouse) {
|
||
t.Fatalf("撞唯一键应当翻成 ErrDuplicateWarehouse,实际 %v", err)
|
||
}
|
||
if !strings.Contains(err.Error(), "已配置仓库") {
|
||
t.Fatalf("错误消息应当提示已配置仓库,实际 %q", err.Error())
|
||
}
|
||
if err := mock.ExpectationsWereMet(); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
}
|
||
|
||
// warehouse_locations(warehouse_id) 的唯一约束是「每仓库一个库位」的兜底,冲突同样
|
||
// 要翻成中文并回 409;停用的库位也占名额,因此冲突与库位状态无关。
|
||
func TestCreateWarehouseLocationReportsDuplicatePerWarehouse(t *testing.T) {
|
||
mock := newWarehouseMock(t)
|
||
mock.ExpectExec(regexp.QuoteMeta(createLocationSQL)).
|
||
WithArgs("L2", "库位二", "active", int64(3), int64(1)).
|
||
WillReturnError(errors.New("Error 1062: Duplicate entry '3' for key 'uk_warehouse_locations_warehouse'"))
|
||
|
||
err := NewWarehouseRepository().CreateLocation(&model.WarehouseLocation{WarehouseID: 3, Code: "L2", Name: "库位二", Status: "active"}, 1)
|
||
if !errors.Is(err, ErrDuplicateLocation) {
|
||
t.Fatalf("撞唯一键应当翻成 ErrDuplicateLocation,实际 %v", err)
|
||
}
|
||
if !strings.Contains(err.Error(), "已配置库位") {
|
||
t.Fatalf("错误消息应当提示已配置库位,实际 %q", err.Error())
|
||
}
|
||
if err := mock.ExpectationsWereMet(); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
}
|