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 拼接; 全部做过变异验证(抽掉实现后对应测试确实变红)。
65 lines
2.1 KiB
Go
65 lines
2.1 KiB
Go
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)
|
||
}
|
||
}
|