package imeiqueue import ( "context" "encoding/json" "errors" "reflect" "testing" "time" "git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts" "git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model" ) func stubImeiTrigger(t *testing.T) { t.Helper() var ( now = imeiNow runID = imeiRunID enabled = imeiRabbitEnabled publish = imeiPublish ) t.Cleanup(func() { imeiNow, imeiRunID, imeiRabbitEnabled, imeiPublish = now, runID, enabled, publish }) imeiNow = func() time.Time { return time.Date(2026, 10, 13, 4, 0, 0, 0, time.FixedZone("UTC+8", 8*60*60)) } imeiRunID = func() string { return "test-run" } imeiRabbitEnabled = func(context.Context) bool { return true } } func TestImeiTriggerPublishesOneRabbitMQStartWithScope(t *testing.T) { stubImeiTrigger(t) if consts.QueueImeiQueueStart != "iotlcm.imei.queue.start" { t.Fatal("IMEI start queue does not match Worker") } calls := 0 var messages []model.ImeiQueueStartMessage imeiPublish = func(ctx context.Context, queue string, data any) error { calls++ if ctx == nil || queue != consts.QueueImeiQueueStart { t.Errorf("queue=%q, context=%v", queue, ctx) } messages = append(messages, data.(model.ImeiQueueStartMessage)) return nil } for _, scope := range []*model.SyncScope{nil, {Channels: []string{}}, {Channels: []string{"b", "a", "b"}, TriggerSource: model.TriggerSourceManual}} { runID, err := New().StartImeiRunScoped(nil, scope) if err != nil || runID != "imei-20261013040000-test-run" { t.Fatalf("run=%q, error=%v", runID, err) } } if calls != 3 || messages[0].Scope != nil || messages[1].Scope != nil { t.Fatalf("calls=%d, messages=%+v", calls, messages) } message := messages[2] if message.Operation != "imei_full_sync" || message.TriggeredAt != "2026-10-13T04:00:00+08:00" || !reflect.DeepEqual(message.Scope.Channels, []string{"a", "b"}) || message.Scope.TriggerSource != model.TriggerSourceManual { t.Fatalf("message=%+v", message) } for i, message := range messages { data, err := json.Marshal(message) if err != nil { t.Fatal(err) } var body map[string]any if err := json.Unmarshal(data, &body); err != nil { t.Fatal(err) } if body["runId"] != message.RunID || body["operation"] != "imei_full_sync" || body["triggeredAt"] != message.TriggeredAt { t.Fatalf("Worker JSON contract: %s", data) } if _, present := body["scope"]; present != (i == 2) { t.Fatalf("full/scoped message compatibility: %s", data) } } } func TestImeiTriggerRejectsInvalidScopeDisabledMQAndPublishFailure(t *testing.T) { stubImeiTrigger(t) calls := 0 publishErr := errors.New("broker unavailable") imeiPublish = func(context.Context, string, any) error { calls++; return publishErr } if _, err := New().StartImeiRunScoped(context.Background(), &model.SyncScope{Channels: []string{"bad/channel"}}); err == nil { t.Fatal("invalid scope accepted") } imeiRabbitEnabled = func(context.Context) bool { return false } if _, err := New().StartImeiRun(context.Background()); err == nil { t.Fatal("disabled RabbitMQ accepted") } if calls != 0 { t.Fatal("invalid scope or disabled RabbitMQ invoked publisher") } imeiRabbitEnabled = func(context.Context) bool { return true } if runID, err := New().StartImeiRun(context.Background()); runID != "" || !errors.Is(err, publishErr) { t.Fatalf("run=%q, error=%v", runID, err) } if calls != 1 { t.Fatalf("failed publish retried unexpectedly: %d", calls) } }