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 }