material_manage_system/internal/repository/material_import_test.go
2026-10-10 15:20:18 +08:00

202 lines
7.7 KiB
Go
Raw 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 (
"fmt"
"regexp"
"strings"
"testing"
"github.com/DATA-DOG/go-sqlmock"
"github.com/wmms/wmms/internal/model"
"github.com/wmms/wmms/pkg/database"
)
const importLockRuleQuery = `SELECT id, organization_id, category_id, prefix, sequence_length, next_sequence FROM code_rules WHERE organization_id = ? AND category_id = ? AND status = 'active' FOR UPDATE`
const insertMaterialBatchStmt = `INSERT INTO materials (organization_id, material_code, name, material_image, spec, manufacturer_model, category_id, base_unit_id, unit, status, version) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
func newImportMock(t *testing.T) (sqlmock.Sqlmock, func()) {
t.Helper()
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("open sql mock: %v", err)
}
previous := database.DB
database.DB = db
return mock, func() {
database.DB = previous
db.Close()
}
}
func materialCategoryPtr(id int64) *int64 { return &id }
func TestMaterialRepoImportTxAllocatesCodesAndInsertsOneByOne(t *testing.T) {
mock, cleanup := newImportMock(t)
defer cleanup()
mock.ExpectBegin()
mock.ExpectQuery(regexp.QuoteMeta(importLockRuleQuery)).WithArgs(int64(1), int64(12)).
WillReturnRows(sqlmock.NewRows([]string{"id", "organization_id", "category_id", "prefix", "sequence_length", "next_sequence"}).
AddRow(5, 1, 12, "A01", 4, 201))
// 已占用编码探测:A010201 已被手工录入占用,应跳到 A010202
mock.ExpectQuery(regexp.QuoteMeta(`SELECT material_code FROM materials WHERE organization_id = ? AND material_code LIKE ?`)).
WithArgs(int64(1), "A01%").
WillReturnRows(sqlmock.NewRows([]string{"material_code"}).AddRow("A010201"))
mock.ExpectExec(regexp.QuoteMeta(`UPDATE code_rules SET next_sequence = ? WHERE id = ?`)).
WithArgs(int64(204), int64(5)).WillReturnResult(sqlmock.NewResult(0, 1))
// 两行折叠成一条多值 INSERT
mock.ExpectExec(regexp.QuoteMeta(insertMaterialBatchStmt)).
WithArgs(
int64(1), "A010202", "螺栓", nil, "", nil, int64(12), nil, "个", "active", 1,
int64(1), "A010203", "螺母", nil, "", nil, int64(12), nil, "个", "active", 1,
).
WillReturnResult(sqlmock.NewResult(31, 2))
mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, material_code FROM materials WHERE organization_id = ? AND material_code IN (?,?)`)).
WithArgs(int64(1), "A010202", "A010203").
WillReturnRows(sqlmock.NewRows([]string{"id", "material_code"}).
AddRow(31, "A010202").AddRow(32, "A010203"))
tx, err := database.DB.Begin()
if err != nil {
t.Fatalf("begin: %v", err)
}
categoryID := int64(12)
materials := []*model.Material{
{OrganizationID: 1, Name: "螺栓", CategoryID: &categoryID, Unit: "个", Status: "active", Version: 1},
{OrganizationID: 1, Name: "螺母", CategoryID: &categoryID, Unit: "个", Status: "active", Version: 1},
}
if err := NewMaterialRepo().ImportTx(tx, 1, materials); err != nil {
t.Fatalf("import: %v", err)
}
if materials[0].MaterialCode != "A010202" || materials[0].ID != 31 {
t.Fatalf("first = %+v", materials[0])
}
if materials[1].MaterialCode != "A010203" || materials[1].ID != 32 {
t.Fatalf("second = %+v", materials[1])
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Fatal(err)
}
}
func TestMaterialRepoImportTxReportsMissingCodeRule(t *testing.T) {
mock, cleanup := newImportMock(t)
defer cleanup()
mock.ExpectBegin()
mock.ExpectQuery(regexp.QuoteMeta(importLockRuleQuery)).WithArgs(int64(1), int64(12)).
WillReturnRows(sqlmock.NewRows([]string{"id", "organization_id", "category_id", "prefix", "sequence_length", "next_sequence"}))
tx, err := database.DB.Begin()
if err != nil {
t.Fatalf("begin: %v", err)
}
materials := []*model.Material{
{OrganizationID: 1, Name: "螺栓", CategoryID: materialCategoryPtr(12), Unit: "个", Status: "active", Version: 1},
}
err = NewMaterialRepo().ImportTx(tx, 1, materials)
if err == nil || !strings.Contains(err.Error(), "编码规则") {
t.Fatalf("err = %v, want 编码规则错误", err)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Fatal(err)
}
}
func TestMaterialRepoImportTxBatchesInsertsOverFiveHundredRows(t *testing.T) {
// 501 行 → 两批插入(500 + 1)。插入语句与回查参数很长,这里用子串松匹配,
// 只断言"分了两批"以及编码序号按批连续。
mock, cleanup := newImportMock(t)
defer cleanup()
mock.ExpectBegin()
mock.ExpectQuery(regexp.QuoteMeta(importLockRuleQuery)).WithArgs(int64(1), int64(12)).
WillReturnRows(sqlmock.NewRows([]string{"id", "organization_id", "category_id", "prefix", "sequence_length", "next_sequence"}).
AddRow(5, 1, 12, "A01", 4, 1))
mock.ExpectQuery(`SELECT material_code FROM materials`).
WillReturnRows(sqlmock.NewRows([]string{"material_code"}))
mock.ExpectExec(`UPDATE code_rules SET next_sequence`).WillReturnResult(sqlmock.NewResult(0, 1))
mock.ExpectExec(`INSERT INTO materials`).WillReturnResult(sqlmock.NewResult(1000, 500))
mock.ExpectQuery(`SELECT id, material_code FROM materials`).WillReturnRows(idRowsFor(1, 500))
mock.ExpectExec(`INSERT INTO materials`).WillReturnResult(sqlmock.NewResult(1500, 1))
mock.ExpectQuery(`SELECT id, material_code FROM materials`).WillReturnRows(idRowsFor(501, 501))
tx, err := database.DB.Begin()
if err != nil {
t.Fatalf("begin: %v", err)
}
categoryID := int64(12)
materials := make([]*model.Material, 0, 501)
for i := 0; i < 501; i++ {
materials = append(materials, &model.Material{
OrganizationID: 1, Name: "物料", CategoryID: &categoryID, Unit: "个", Status: "active", Version: 1,
})
}
if err := NewMaterialRepo().ImportTx(tx, 1, materials); err != nil {
t.Fatalf("import: %v", err)
}
if materials[0].MaterialCode != "A010001" || materials[500].MaterialCode != "A010501" {
t.Fatalf("codes = %s ... %s", materials[0].MaterialCode, materials[500].MaterialCode)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Fatal(err)
}
}
// idRowsFor 造出回查语句的返回行:序号 [from, to] 对应的物料 ID 与编码。
func idRowsFor(from, to int) *sqlmock.Rows {
rows := sqlmock.NewRows([]string{"id", "material_code"})
for i := from; i <= to; i++ {
rows.AddRow(int64(i), fmt.Sprintf("A01%04d", i))
}
return rows
}
func TestMaterialLogRepoCreateBatchTx(t *testing.T) {
mock, cleanup := newImportMock(t)
defer cleanup()
mock.ExpectBegin()
mock.ExpectExec(`INSERT INTO material_logs`).WillReturnResult(sqlmock.NewResult(1, 2))
tx, err := database.DB.Begin()
if err != nil {
t.Fatalf("begin: %v", err)
}
logs := []*model.MaterialLog{
{OperationType: "CREATE", MaterialID: 31, Operator: "tester", Description: "批量导入"},
{OperationType: "CREATE", MaterialID: 32, Operator: "tester", Description: "批量导入"},
}
if err := NewMaterialLogRepo().CreateBatchTx(tx, logs); err != nil {
t.Fatalf("create batch: %v", err)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Fatal(err)
}
}
func TestMaterialImportLogRepoCreate(t *testing.T) {
mock, cleanup := newImportMock(t)
defer cleanup()
mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO material_import_logs (organization_id, operator, file_name, total_rows, success_rows, failed_rows, status, failure_reason) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)).
WithArgs(int64(1), "tester", "物料.xlsx", 200, 0, 3, "failed", "共 3 行校验未通过").
WillReturnResult(sqlmock.NewResult(9, 1))
log := &model.MaterialImportLog{
OrganizationID: 1, Operator: "tester", FileName: "物料.xlsx",
TotalRows: 200, SuccessRows: 0, FailedRows: 3,
Status: "failed", FailureReason: "共 3 行校验未通过",
}
if err := NewMaterialImportLogRepo().Create(log); err != nil {
t.Fatalf("create: %v", err)
}
if log.ID != 9 {
t.Fatalf("id = %d, want 9", log.ID)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Fatal(err)
}
}