2bec257289
iotlcm-worker-svr:异步执行中枢(RabbitMQ 消费者 + 全量同步编排)。 - Go module:iotlcm-worker-svr → git.5iot.com/iotlcm-public/iotlcm-worker-svr(214 文件) - 凭据与内部标识脱敏:MySQL/Redis 凭据、私网与生产 IP、内网域名、 库名 xiujie_admin → iotlcm、哨兵名占位 - 159 处真实手机号 → 合法 11 位编造号(保留号段与后 4 位,维持互异, 不破坏「校验 MSISDN 格式」等测试断言) - 移除内部资产:CI 触发器、Jenkins 触发测试文档、预演门禁与镜像 digest 记录 - Dockerfile 改用公共基础镜像(golang:1.25-alpine → alpine:3.23), 移除写死的 git 凭据 - README 重写(架构/全量同步模型/配置/已知失败),补 Apache-2.0 LICENSE,LF 行尾 + .gitattributes 已知:3 个测试包在源仓同一提交上同样失败(client/upstream、 service/allocate、service/inbound),属既有缺陷,本仓未引入亦未修复。
245 lines
8.6 KiB
Go
245 lines
8.6 KiB
Go
package workorder
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
_ "github.com/gogf/gf/contrib/drivers/sqlite/v2"
|
|
"github.com/gogf/gf/v2/database/gdb"
|
|
"github.com/gogf/gf/v2/frame/g"
|
|
"github.com/gogf/gf/v2/os/gtime"
|
|
|
|
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/model"
|
|
)
|
|
|
|
func TestMain(m *testing.M) {
|
|
g.Log().SetWriter(io.Discard)
|
|
code := m.Run()
|
|
_ = os.RemoveAll("resource")
|
|
os.Exit(code)
|
|
}
|
|
|
|
func TestAutoCloseResolved(t *testing.T) {
|
|
ctx := context.Background()
|
|
if err := gdb.SetConfig(gdb.Config{
|
|
"default": gdb.ConfigGroup{
|
|
{
|
|
Type: "sqlite",
|
|
Name: filepath.Join(t.TempDir(), "workorder.db"),
|
|
},
|
|
},
|
|
}); err != nil {
|
|
t.Fatalf("configure test database: %v", err)
|
|
}
|
|
defer g.DB().Close(ctx)
|
|
|
|
createWorkOrderTables(t, ctx)
|
|
service := New()
|
|
|
|
t.Run("resolved order is closed once", func(t *testing.T) {
|
|
insertWorkOrder(t, ctx, "WO-RESOLVED", statusResolved)
|
|
message := model.WorkOrderAutoCloseMessage{WoNo: "WO-RESOLVED"}
|
|
if err := service.AutoCloseResolved(ctx, message); err != nil {
|
|
t.Fatalf("AutoCloseResolved returned error: %v", err)
|
|
}
|
|
if status := workOrderStatus(t, ctx, message.WoNo); status != statusClosed {
|
|
t.Fatalf("expected closed status, got %d", status)
|
|
}
|
|
if count := workOrderRecordCount(t, ctx, message.WoNo); count != 1 {
|
|
t.Fatalf("expected one close record, got %d", count)
|
|
}
|
|
|
|
if err := service.AutoCloseResolved(ctx, message); err != nil {
|
|
t.Fatalf("duplicate AutoCloseResolved returned error: %v", err)
|
|
}
|
|
if count := workOrderRecordCount(t, ctx, message.WoNo); count != 1 {
|
|
t.Fatalf("duplicate message added records, got %d", count)
|
|
}
|
|
})
|
|
|
|
t.Run("non-resolved order is ignored", func(t *testing.T) {
|
|
insertWorkOrder(t, ctx, "WO-PROCESSING", 2)
|
|
if err := service.AutoCloseResolved(ctx, model.WorkOrderAutoCloseMessage{WoNo: "WO-PROCESSING"}); err != nil {
|
|
t.Fatalf("AutoCloseResolved returned error: %v", err)
|
|
}
|
|
if status := workOrderStatus(t, ctx, "WO-PROCESSING"); status != 2 {
|
|
t.Fatalf("non-resolved order changed status to %d", status)
|
|
}
|
|
if count := workOrderRecordCount(t, ctx, "WO-PROCESSING"); count != 0 {
|
|
t.Fatalf("non-resolved order added %d records", count)
|
|
}
|
|
})
|
|
|
|
t.Run("stale resolve message is ignored", func(t *testing.T) {
|
|
const woNo = "WO-STALE-MESSAGE"
|
|
insertWorkOrder(t, ctx, woNo, statusResolved)
|
|
resolveTime := gtime.Now().Add(-73 * time.Hour)
|
|
if _, err := g.DB().Model("t_wo_main").Ctx(ctx).Where("wo_no", woNo).Data(g.Map{"resolve_time": resolveTime}).Update(); err != nil {
|
|
t.Fatalf("set resolve time: %v", err)
|
|
}
|
|
if err := service.AutoCloseResolved(ctx, model.WorkOrderAutoCloseMessage{WoNo: woNo, ResolveTimeTs: resolveTime.Timestamp() - 1}); err != nil {
|
|
t.Fatalf("AutoCloseResolved returned error: %v", err)
|
|
}
|
|
if status := workOrderStatus(t, ctx, woNo); status != statusResolved {
|
|
t.Fatalf("stale message changed status to %d", status)
|
|
}
|
|
if count := workOrderRecordCount(t, ctx, woNo); count != 0 {
|
|
t.Fatalf("stale message added %d records", count)
|
|
}
|
|
})
|
|
|
|
t.Run("record failure rolls back status update", func(t *testing.T) {
|
|
for _, statement := range []string{
|
|
`CREATE TABLE t_message_log (message_log_id INTEGER PRIMARY KEY AUTOINCREMENT, message_type INTEGER, send_user_type INTEGER, user_ids TEXT, message_title TEXT, message_content TEXT, message_link TEXT, send_time DATETIME, is_recall INTEGER, tenant_id TEXT, created_dept INTEGER, created_by INTEGER, created_at DATETIME, updated_at DATETIME)`,
|
|
`CREATE TABLE t_user_message (message_id INTEGER PRIMARY KEY AUTOINCREMENT, message_log_id INTEGER, user_id INTEGER, is_read INTEGER, tenant_id TEXT, created_dept INTEGER, created_by INTEGER, created_at DATETIME, updated_at DATETIME)`,
|
|
} {
|
|
if _, err := g.DB().Exec(ctx, statement); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
insertWorkOrder(t, ctx, "WO-NOTICE", statusResolved)
|
|
if _, err := g.DB().Exec(ctx, `UPDATE t_wo_main SET creator_user_id = 88, creator_dept_id = 30, creator_type = 1 WHERE wo_no = ?`, "WO-NOTICE"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for attempt := 0; attempt < 2; attempt++ {
|
|
if err := service.AutoCloseResolved(ctx, model.WorkOrderAutoCloseMessage{WoNo: "WO-NOTICE"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
count, err := g.DB().Model("t_user_message").Ctx(ctx).Where("user_id", 88).Where("tenant_id", "tenant").Count()
|
|
if err != nil || count != 1 {
|
|
t.Fatalf("expected one tenant-scoped notice, count=%d err=%v", count, err)
|
|
}
|
|
insertWorkOrder(t, ctx, "WO-NOTICE-ROLLBACK", statusResolved)
|
|
if _, err := g.DB().Exec(ctx, `UPDATE t_wo_main SET creator_user_id = 88 WHERE wo_no = ?`, "WO-NOTICE-ROLLBACK"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := g.DB().Exec(ctx, `DROP TABLE t_user_message`); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := service.AutoCloseResolved(ctx, model.WorkOrderAutoCloseMessage{WoNo: "WO-NOTICE-ROLLBACK"}); err == nil {
|
|
t.Fatal("expected notice failure")
|
|
}
|
|
if status := workOrderStatus(t, ctx, "WO-NOTICE-ROLLBACK"); status != statusResolved {
|
|
t.Fatalf("notice failure did not roll back: %d", status)
|
|
}
|
|
if _, err := g.DB().Exec(ctx, `UPDATE t_wo_main SET creator_user_id = 0 WHERE wo_no = ?`, "WO-NOTICE-ROLLBACK"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, name := range []string{"WO-NO-TIME", "WO-EARLY"} {
|
|
insertWorkOrder(t, ctx, name, statusResolved)
|
|
var resolveTime *gtime.Time
|
|
if name == "WO-EARLY" {
|
|
resolveTime = gtime.Now()
|
|
}
|
|
if _, err := g.DB().Exec(ctx, `UPDATE t_wo_main SET resolve_time = ? WHERE wo_no = ?`, resolveTime, name); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := service.AutoCloseResolved(ctx, model.WorkOrderAutoCloseMessage{WoNo: name}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if status := workOrderStatus(t, ctx, name); status != statusResolved {
|
|
t.Fatalf("premature close: %s status=%d", name, status)
|
|
}
|
|
}
|
|
insertWorkOrder(t, ctx, "WO-SCAN", statusResolved)
|
|
if err := service.CloseOverdue(ctx); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if status := workOrderStatus(t, ctx, "WO-SCAN"); status != statusClosed {
|
|
t.Fatalf("overdue scan did not close order: %d", status)
|
|
}
|
|
insertWorkOrder(t, ctx, "WO-ROLLBACK", statusResolved)
|
|
if _, err := g.DB().Exec(ctx, `DROP TABLE t_wo_record`); err != nil {
|
|
t.Fatalf("drop record table: %v", err)
|
|
}
|
|
if err := service.AutoCloseResolved(ctx, model.WorkOrderAutoCloseMessage{WoNo: "WO-ROLLBACK"}); err == nil {
|
|
t.Fatal("expected record insert error")
|
|
}
|
|
if status := workOrderStatus(t, ctx, "WO-ROLLBACK"); status != statusResolved {
|
|
t.Fatalf("transaction did not roll back, status=%d", status)
|
|
}
|
|
})
|
|
}
|
|
|
|
func createWorkOrderTables(t *testing.T, ctx context.Context) {
|
|
t.Helper()
|
|
statements := []string{
|
|
`CREATE TABLE t_wo_main (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
wo_no TEXT NOT NULL UNIQUE,
|
|
creator_user_id INTEGER,
|
|
creator_dept_id INTEGER,
|
|
creator_type INTEGER,
|
|
current_status INTEGER NOT NULL,
|
|
resolve_time DATETIME,
|
|
handler_dept_id INTEGER,
|
|
handler_user_id INTEGER,
|
|
tenant_id TEXT,
|
|
created_dept INTEGER,
|
|
created_by INTEGER,
|
|
updated_by INTEGER,
|
|
close_time DATETIME,
|
|
created_at DATETIME,
|
|
updated_at DATETIME
|
|
)`,
|
|
`CREATE TABLE t_wo_record (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
wo_no TEXT NOT NULL,
|
|
operation_type INTEGER,
|
|
operation_dept_id INTEGER,
|
|
operation_user_id INTEGER,
|
|
before_status INTEGER,
|
|
after_status INTEGER,
|
|
tenant_id TEXT,
|
|
created_dept INTEGER,
|
|
created_by INTEGER,
|
|
created_at DATETIME,
|
|
updated_at DATETIME
|
|
)`,
|
|
}
|
|
for _, statement := range statements {
|
|
if _, err := g.DB().Exec(ctx, statement); err != nil {
|
|
t.Fatalf("create test table: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func insertWorkOrder(t *testing.T, ctx context.Context, woNo string, status int) {
|
|
t.Helper()
|
|
_, err := g.DB().Exec(ctx, `
|
|
INSERT INTO t_wo_main (
|
|
wo_no, current_status, handler_dept_id, handler_user_id,
|
|
tenant_id, created_dept, created_by, updated_by
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
`, woNo, status, 10, 20, "tenant", 30, 40, 50)
|
|
if err != nil {
|
|
t.Fatalf("insert work order: %v", err)
|
|
}
|
|
if _, err := g.DB().Exec(ctx, `UPDATE t_wo_main SET resolve_time = ? WHERE wo_no = ?`, gtime.Now().Add(-73*time.Hour), woNo); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func workOrderStatus(t *testing.T, ctx context.Context, woNo string) int {
|
|
t.Helper()
|
|
value, err := g.DB().Model("t_wo_main").Ctx(ctx).Where("wo_no", woNo).Value("current_status")
|
|
if err != nil {
|
|
t.Fatalf("query work order status: %v", err)
|
|
}
|
|
return value.Int()
|
|
}
|
|
|
|
func workOrderRecordCount(t *testing.T, ctx context.Context, woNo string) int {
|
|
t.Helper()
|
|
count, err := g.DB().Model("t_wo_record").Ctx(ctx).Where("wo_no", woNo).Count()
|
|
if err != nil {
|
|
t.Fatalf("count work order records: %v", err)
|
|
}
|
|
return count
|
|
}
|