feat: 档口入库码支持软删除与重导恢复 (#238)
This commit is contained in:
@@ -3,10 +3,13 @@ package repository
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/go-sql-driver/mysql"
|
||||
_ "modernc.org/sqlite"
|
||||
|
||||
"cmautobuy/admin/model"
|
||||
)
|
||||
|
||||
func TestInterruptApplyingInnerCodes_只收敛回写中记录(t *testing.T) {
|
||||
@@ -50,3 +53,116 @@ func TestInnerCodeImportWriteError_唯一冲突不泄漏索引细节(t *testing.
|
||||
t.Fatalf("不应回显索引细节: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSoftDeleteInnerCodeRecords_所有状态只写删除审计(t *testing.T) {
|
||||
db, err := sql.Open("sqlite", "file:inner_code_delete?mode=memory&cache=shared")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
if _, err := db.Exec(`CREATE TABLE syb_inner_code_records (
|
||||
id INTEGER PRIMARY KEY,status TEXT NOT NULL,deleted_at TEXT,deleted_by_user_id TEXT,updated_at TEXT NOT NULL
|
||||
)`); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
statuses := []string{"pending", "ready", "applying", "updated", "already_filled", "skipped", "failed", "needs_check"}
|
||||
for index, status := range statuses {
|
||||
if _, err := db.Exec(`INSERT INTO syb_inner_code_records(id,status,updated_at) VALUES(?,?,?)`, index+1, status, "old"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
ids := make([]int64, len(statuses))
|
||||
for index := range ids {
|
||||
ids[index] = int64(index + 1)
|
||||
}
|
||||
count, err := SoftDeleteInnerCodeRecords(db, ids, "user-1", "2026-08-15T02:00:00Z")
|
||||
if err != nil || count != len(statuses) {
|
||||
t.Fatalf("count=%d err=%v", count, err)
|
||||
}
|
||||
rows, err := db.Query(`SELECT status,deleted_at,deleted_by_user_id,updated_at FROM syb_inner_code_records ORDER BY id`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
index := 0
|
||||
for rows.Next() {
|
||||
var status, deletedAt, actor, updatedAt string
|
||||
if err := rows.Scan(&status, &deletedAt, &actor, &updatedAt); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if status != statuses[index] || deletedAt != "2026-08-15T02:00:00Z" || actor != "user-1" || updatedAt != deletedAt {
|
||||
t.Fatalf("第 %d 行状态或审计变化错误: %s %s %s %s", index+1, status, deletedAt, actor, updatedAt)
|
||||
}
|
||||
index++
|
||||
}
|
||||
}
|
||||
|
||||
func TestSoftDeleteInnerCodeRecords_有失效ID时整批回滚(t *testing.T) {
|
||||
db, err := sql.Open("sqlite", "file:inner_code_delete_conflict?mode=memory&cache=shared")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
if _, err := db.Exec(`CREATE TABLE syb_inner_code_records (
|
||||
id INTEGER PRIMARY KEY,status TEXT NOT NULL,deleted_at TEXT,deleted_by_user_id TEXT,updated_at TEXT NOT NULL
|
||||
); INSERT INTO syb_inner_code_records(id,status,updated_at) VALUES(1,'pending','old')`); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := SoftDeleteInnerCodeRecords(db, []int64{1, 99}, "user-1", "2026-08-15T02:00:00Z"); !errors.Is(err, ErrInnerCodeDeleteConflict) {
|
||||
t.Fatalf("期望整批冲突,实际 %v", err)
|
||||
}
|
||||
var deletedAt sql.NullString
|
||||
if err := db.QueryRow(`SELECT deleted_at FROM syb_inner_code_records WHERE id=1`).Scan(&deletedAt); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if deletedAt.Valid {
|
||||
t.Fatalf("整批回滚后不应保留删除标记: %+v", deletedAt)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFinishInnerCodeApply_软删除后仍保存后台结果(t *testing.T) {
|
||||
db, err := sql.Open("sqlite", "file:inner_code_finish_deleted?mode=memory&cache=shared")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
if _, err := db.Exec(`CREATE TABLE syb_inner_code_records (
|
||||
id INTEGER PRIMARY KEY,status TEXT NOT NULL,result_message TEXT,remote_inner_code TEXT,
|
||||
applied_at TEXT,updated_at TEXT NOT NULL,deleted_at TEXT
|
||||
); INSERT INTO syb_inner_code_records(id,status,updated_at,deleted_at)
|
||||
VALUES(1,'applying','old','2026-08-15T02:00:00Z')`); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := FinishInnerCodeApply(db, 1, model.InnerCodeUpdated, "远端核验成功", "DK-1", "2026-08-15T02:01:00Z"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var status, message, remoteCode, appliedAt, deletedAt string
|
||||
if err := db.QueryRow(`SELECT status,result_message,remote_inner_code,applied_at,deleted_at
|
||||
FROM syb_inner_code_records WHERE id=1`).Scan(&status, &message, &remoteCode, &appliedAt, &deletedAt); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if status != "updated" || message != "远端核验成功" || remoteCode != "DK-1" ||
|
||||
appliedAt != "2026-08-15T02:01:00Z" || deletedAt != "2026-08-15T02:00:00Z" {
|
||||
t.Fatalf("隐藏记录后台收尾错误: %s %s %s %s %s", status, message, remoteCode, appliedAt, deletedAt)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInnerCodeStatusPreservesImportResult(t *testing.T) {
|
||||
for _, status := range []string{"pending", "ready", "skipped", "failed"} {
|
||||
if innerCodeStatusPreservesImportResult(model.InnerCodeStatus(status)) {
|
||||
t.Errorf("%s 不应保留旧规划", status)
|
||||
}
|
||||
}
|
||||
for _, status := range []string{"applying", "updated", "already_filled", "needs_check"} {
|
||||
if !innerCodeStatusPreservesImportResult(model.InnerCodeStatus(status)) {
|
||||
t.Errorf("%s 应保留远端结果和审计", status)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInnerCodeFilterClause_正常列表固定隐藏软删除记录(t *testing.T) {
|
||||
where, args := innerCodeFilterClause(InnerCodeListFilter{BusinessDate: "2026-08-15", Status: "ready", Keyword: "ORDER"})
|
||||
if !strings.Contains(where, "deleted_at IS NULL") || len(args) != 6 {
|
||||
t.Fatalf("列表软删除过滤不完整: where=%q args=%v", where, args)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user