package allocatedispatch import ( "context" "errors" _ "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" "testing" "time" "git.5iot.com/iotlcm-public/iotlcm-app/internal/model/entity" ) func TestDue(t *testing.T) { now := time.Now() for _, test := range []struct { status int age time.Duration due bool }{{1, 59 * time.Second, false}, {1, 2 * time.Minute, true}, {2, 29 * time.Minute, false}, {2, 31 * time.Minute, true}, {3, time.Hour, false}, {4, time.Hour, false}, {5, time.Hour, false}} { batch := &entity.BatchMain{BatchType: 3, SourceChannel: "mini_program", BatchAsyncStatus: test.status, UpdatedAt: gtime.New(now.Add(-test.age))} if due(batch, now, 30*time.Minute) != test.due { t.Fatal(test) } } if due(&entity.BatchMain{BatchType: 3, SourceChannel: "console", BatchAsyncStatus: 1, UpdatedAt: gtime.New(now.Add(-time.Hour))}, now, 30*time.Minute) { t.Fatal("foreign channel replayed") } if due(&entity.BatchMain{BatchType: 3, SourceChannel: "mini_program", BatchAsyncStatus: 1}, now, 30*time.Minute) { t.Fatal("missing time replayed") } } func TestRecoveryPersistsFailureAndIsolatesParents(t *testing.T) { ctx := context.Background() if err := gdb.SetConfig(gdb.Config{"default": gdb.ConfigGroup{{Type: "sqlite", Link: "sqlite::@file(:memory:)", MaxOpenConnCount: 1}}}); err != nil { t.Fatal(err) } t.Cleanup(func() { _ = g.DB().Close(ctx) }) for _, sql := range []string{ `CREATE TABLE batch_main (id INTEGER PRIMARY KEY,batch_no TEXT,batch_type INTEGER,source_batch_no TEXT,source_channel TEXT,batch_async_status INTEGER,tenant_id TEXT,created_by INTEGER,batch_info TEXT,created_at TEXT,updated_at TEXT,start_time TEXT)`, `INSERT INTO batch_main VALUES (1,'PARENT',3,'','mini_program',1,'A',7,'{"jobRole":"allocate_parent"}','2020-01-01','2020-01-01',NULL), (2,'CHILD',3,'PARENT','mini_program',1,'A',7,'{"jobRole":"allocate_chunk"}','2020-01-01','2020-01-01',NULL), (3,'FOREIGN',3,'PARENT','mini_program',1,'B',7,'{"jobRole":"allocate_chunk"}','2020-01-01','2020-01-01',NULL), (4,'DONE',3,'','mini_program',3,'A',7,'{"jobRole":"allocate_parent"}','2020-01-01','2020-01-01',NULL), (5,'OTHER',101,'','mini_program',1,'A',7,'{}','2020-01-01','2020-01-01',NULL), (6,'CONSOLE',3,'','console',1,'A',7,'{"jobRole":"allocate_parent"}','2020-01-01','2020-01-01',NULL)`, } { if _, err := g.DB().Exec(ctx, sql); err != nil { t.Fatal(err) } } for _, fail := range []bool{true, false} { var calls []string cursor, err := recoverPage(ctx, 0, func(ctx context.Context, queue, batchNo string) error { calls = append(calls, batchNo) if queue != "iotlcm.batch.execute" { t.Fatal(queue) } if fail { return errors.New("queue offline") } return nil }) if err != nil || cursor != 0 || len(calls) != 2 || calls[0] != "CHILD" || calls[1] != "PARENT" { t.Fatal(calls, cursor, err) } } if _, err := g.DB().Exec(ctx, `UPDATE batch_main SET batch_async_status=3 WHERE batch_no='PARENT'`); err != nil { t.Fatal(err) } _, err := recoverPage(ctx, 0, func(context.Context, string, string) error { t.Fatal("terminal parent replayed"); return nil }) if err != nil { t.Fatal(err) } cancelled, cancel := context.WithCancel(ctx) cancel() if _, err := recoverPage(cancelled, 0, func(context.Context, string, string) error { t.Fatal("cancelled replay"); return nil }); err == nil { t.Fatal("cancel ignored") } }