material_manage_system/pkg/database/database_test.go
文睿 张 696e90893b fix: 主数据编辑的两类静默失败
UPDATE 漏列:
- code_rules 的 SET 补上 category_id。此前改分类提示"保存成功"但不落库,
  是唯一用户可达的活缺陷(同页的分类下拉没有 disabled,且规则只能改不能删,
  没有"删了重建"这条退路)
- code_rules 有 uk_code_rules_org_category,补列后改到已占用的分类会撞唯一键,
  新建与更新统一翻成中文的 ErrDuplicateCodeRule
- 分类/单位/仓库/库位/伙伴的编码定为不可变:分类编码是编码规则前缀的来源
  (init.sql 的种子数据直接取 material_categories.code),伙伴编码是对外业务
  编号,改掉会让历史数据脱节且查不回来。service 的 Update* 不再要求编码非空
  ——原先是"必须传、传了却被丢弃";新建侧的必填校验保留
- 顺带补齐两处同款漏网:organization.go 的 code 与 partner.go 的 partner_code
  (前者 service 本就不校验编码,自洽,无需改)

记录不存在时的静默成功:
- DSN 开启 clientFoundRows,让 RowsAffected 返回"匹配"行数而非"实际改变"行数。
  否则"编辑时一个字段都没改"和"记录不存在"都返回 0,无法区分,直接加检查会把
  正常保存误判成记录不存在(真 MySQL 8.0 实测)
- 新增 repository.execExpectingRow,7 个 Update* 影响 0 行时返回 ErrRecordNotFound;
  伙伴走事务内的 SELECT ... FOR UPDATE,只需翻译 sql.ErrNoRows
- 既有 4 处 RowsAffected 用法逐处核对过,均不受该 flag 影响

测试:补 6 个 Update* 的存在性检查、编码必填约束的两端、唯一键翻译、DSN 拼接;
全部做过变异验证(抽掉实现后对应测试确实变红)。
2026-09-21 17:26:53 +08:00

65 lines
2.1 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 database
import "testing"
// Init 靠 appendDSNParam 把 parseTime 和 clientFoundRows 补进 DSN。这里钉住拼接
// 规则:没有 ? 要用 ? 起头,已有 ? 要用 & 续接,已经写过的参数不再重复追加。
func TestAppendDSNParam(t *testing.T) {
cases := []struct {
name string
dsn string
key string
value string
want string
}{
{
name: "没有查询串时用问号起头",
dsn: "root:pw@tcp(db:3306)/wmms",
key: "parseTime",
value: "true",
want: "root:pw@tcp(db:3306)/wmms?parseTime=true",
},
{
name: "已有查询串时用与号续接",
dsn: "root:pw@tcp(db:3306)/wmms?charset=utf8mb4",
key: "parseTime",
value: "true",
want: "root:pw@tcp(db:3306)/wmms?charset=utf8mb4&parseTime=true",
},
{
name: "已经配过就不重复追加",
dsn: "root:pw@tcp(db:3306)/wmms?parseTime=true",
key: "parseTime",
value: "true",
want: "root:pw@tcp(db:3306)/wmms?parseTime=true",
},
{
// 按参数名判断,不按完整参数值:调用方显式关了 clientFoundRows,
// 不能被当成"没配过"再追加一个 true 进去。
name: "显式配置的反值优先",
dsn: "root:pw@tcp(db:3306)/wmms?clientFoundRows=false",
key: "clientFoundRows",
value: "true",
want: "root:pw@tcp(db:3306)/wmms?clientFoundRows=false",
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
if got := appendDSNParam(c.dsn, c.key, c.value); got != c.want {
t.Errorf("appendDSNParam(%q, %q, %q)\n got = %q\nwant = %q", c.dsn, c.key, c.value, got, c.want)
}
})
}
}
// Init 实际走的就是这条路径。两个参数缺一不可:漏了 clientFoundRows,Update* 的
// 存在性检查会把「编辑时一个字段都没改」误判成「记录不存在」,成了比原缺陷更糟的
// 回归——正常的保存会被拒绝。
func TestNormalizeDSNAddsBothParams(t *testing.T) {
got := normalizeDSN("root:pw@tcp(db:3306)/wmms?charset=utf8mb4")
want := "root:pw@tcp(db:3306)/wmms?charset=utf8mb4&parseTime=true&clientFoundRows=true"
if got != want {
t.Fatalf("normalizeDSN()\n got = %q\nwant = %q", got, want)
}
}