package logic import ( "testing" "tool-api/internal/consts" ) // TestClassifyImportRows 覆盖判据:同号同名→update、同名异号→conflict、缺号/格式错→error、新号→new。 func TestClassifyImportRows(t *testing.T) { existing := []existingEmployee{ {Id: 1, Name: "李四", Phone: "13900000002"}, {Id: 2, Name: "王五", Phone: "13700000003"}, } rows := []importRow{ {Name: "张三", Phone: "13800000001"}, // new {Name: "李四", Phone: "13900000002"}, // update(同手机号) {Name: "王五", Phone: "18800000009"}, // conflict(同名不同号) {Name: "赵六", Phone: ""}, // error(缺号) {Name: "钱七", Phone: "abc123"}, // error(格式非法) } got := classifyImportRows(rows, existing) want := []string{ consts.ImportTypeNew, consts.ImportTypeUpdate, consts.ImportTypeConflict, consts.ImportTypeError, consts.ImportTypeError, } if len(got) != len(want) { t.Fatalf("len=%d want %d", len(got), len(want)) } for i := range want { if got[i].Type != want[i] { t.Fatalf("row %d type=%s want %s", i, got[i].Type, want[i]) } } if got[1].MatchedEmployeeId != 1 { t.Fatalf("update 应命中现有员工 id=1,got %d", got[1].MatchedEmployeeId) } if got[2].MatchedEmployeeId != 2 { t.Fatalf("conflict 应命中同名员工 id=2,got %d", got[2].MatchedEmployeeId) } } // TestClassifyImportRowsPhoneNormalized 手机号去空格后再判重。 func TestClassifyImportRowsPhoneNormalized(t *testing.T) { existing := []existingEmployee{{Id: 7, Name: "李四", Phone: "13900000002"}} got := classifyImportRows([]importRow{{Name: "李四", Phone: " 139 0000 0002 "}}, existing) if got[0].Type != consts.ImportTypeUpdate || got[0].MatchedEmployeeId != 7 { t.Fatalf("去空格后应命中同一人,got type=%s id=%d", got[0].Type, got[0].MatchedEmployeeId) } } // TestPlanImportOpsUnresolvedConflict 未裁决冲突 → unresolved>0(确认接口据此返回 4008)。 func TestPlanImportOpsUnresolvedConflict(t *testing.T) { rows := []classifiedRow{ {Idx: 0, Type: consts.ImportTypeConflict, Name: "王五", Phone: "18800000009", MatchedEmployeeId: 2}, {Idx: 1, Type: consts.ImportTypeNew, Name: "张三", Phone: "13800000001"}, {Idx: 2, Type: consts.ImportTypeError, Name: "赵六", Phone: ""}, {Idx: 3, Type: consts.ImportTypeUpdate, Name: "李四", Phone: "13900000002", MatchedEmployeeId: 1}, } ops, unresolved := planImportOps(rows, nil) if unresolved != 1 { t.Fatalf("未裁决冲突计数=%d want 1", unresolved) } for _, op := range ops { if op.RowIdx == 0 { t.Fatalf("未裁决的冲突行不应产生操作,got %v", op) } } } // TestPlanImportOpsMergeCreateIgnore 覆盖三种裁决各自的结果。 func TestPlanImportOpsMergeCreateIgnore(t *testing.T) { base := []classifiedRow{ {Idx: 0, Type: consts.ImportTypeConflict, Name: "王五", Phone: "18800000009", MatchedEmployeeId: 2}, } // merge → update 现有员工(含更新手机号) ops, unresolved := planImportOps(base, map[int]string{0: consts.ImportActionMerge}) if unresolved != 0 || len(ops) != 1 { t.Fatalf("merge: unresolved=%d ops=%d", unresolved, len(ops)) } if ops[0].Kind != "update" || ops[0].UpdateId != 2 || ops[0].Phone != "18800000009" { t.Fatalf("merge 应为 update 更新 id=2 且手机号为新号,got %+v", ops[0]) } // create → insert(手机号不同,不违反 uk_ent_phone) ops, unresolved = planImportOps(base, map[int]string{0: consts.ImportActionCreate}) if unresolved != 0 || len(ops) != 1 || ops[0].Kind != "insert" { t.Fatalf("create 应为 insert,got unresolved=%d ops=%+v", unresolved, ops) } // ignore → skip ops, unresolved = planImportOps(base, map[int]string{0: consts.ImportActionIgnore}) if unresolved != 0 || len(ops) != 1 || ops[0].Kind != "skip" { t.Fatalf("ignore 应为 skip,got unresolved=%d ops=%+v", unresolved, ops) } // 非法动作 → 视为未裁决 _, unresolved = planImportOps(base, map[int]string{0: "whatever"}) if unresolved != 1 { t.Fatalf("非法裁决动作应按未裁决处理,got unresolved=%d", unresolved) } } // TestReimportSameFileAllUpdate 覆盖判据④:重复导入同一文件 → 全部为 update(不重复新增)。 func TestReimportSameFileAllUpdate(t *testing.T) { rows := []importRow{ {Name: "张三", Phone: "13800000001"}, {Name: "李四", Phone: "13900000002"}, } // 首次导入:库中无人 → 全部 new first := classifyImportRows(rows, nil) for _, c := range first { if c.Type != consts.ImportTypeNew { t.Fatalf("首次导入应为 new,got %s", c.Type) } } // 再次导入同一文件:库中已有这两人 → 全部 update existing := []existingEmployee{ {Id: 1, Name: "张三", Phone: "13800000001"}, {Id: 2, Name: "李四", Phone: "13900000002"}, } second := classifyImportRows(rows, existing) for _, c := range second { if c.Type != consts.ImportTypeUpdate { t.Fatalf("重复导入应全部为 update,got %s(row %d)", c.Type, c.Idx) } } } // TestCountClassified 预览计数口径。 func TestCountClassified(t *testing.T) { rows := []classifiedRow{ {Type: consts.ImportTypeNew}, {Type: consts.ImportTypeNew}, {Type: consts.ImportTypeUpdate}, {Type: consts.ImportTypeError}, {Type: consts.ImportTypeConflict}, } c := countClassified(rows) if c.inserted != 2 || c.updated != 1 || c.skipped != 1 || c.conflict != 1 { t.Fatalf("counts=%+v want inserted2/updated1/skipped1/conflict1", c) } }