310 lines
10 KiB
Go
310 lines
10 KiB
Go
package trafficusage
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
|
|
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
|
)
|
|
|
|
func stubDailyTrigger(t *testing.T) {
|
|
t.Helper()
|
|
originalNow := dailyUsageNow
|
|
originalRunID := dailyUsageRunID
|
|
originalEnabled := dailyUsageRabbitEnabled
|
|
originalPublish := dailyUsagePublish
|
|
t.Cleanup(func() {
|
|
dailyUsageNow = originalNow
|
|
dailyUsageRunID = originalRunID
|
|
dailyUsageRabbitEnabled = originalEnabled
|
|
dailyUsagePublish = originalPublish
|
|
})
|
|
}
|
|
|
|
// TestStartDailyPublishesPreviousShanghaiDay pins the period: the run must cover
|
|
// the previous calendar day in Asia/Shanghai, matching what the legacy admin
|
|
// scan wrote, so the migration cannot shift the day by one.
|
|
func TestStartDailyPublishesPreviousShanghaiDay(t *testing.T) {
|
|
stubDailyTrigger(t)
|
|
|
|
dailyUsageNow = func() time.Time {
|
|
// 2026-10-10 06:00 +08:00 -> the previous day is 20261009.
|
|
return time.Date(2026, time.October, 10, 6, 0, 0, 0, time.FixedZone("UTC+8", 8*60*60))
|
|
}
|
|
dailyUsageRunID = func() string { return "test-run" }
|
|
dailyUsageRabbitEnabled = func(context.Context) bool { return true }
|
|
|
|
var publishedQueue string
|
|
var published model.TrafficUsageDailyStartMessage
|
|
dailyUsagePublish = func(_ context.Context, queue string, data any) error {
|
|
publishedQueue = queue
|
|
published = data.(model.TrafficUsageDailyStartMessage)
|
|
return nil
|
|
}
|
|
|
|
if err := NewTrafficUsageDailySync().StartDaily(context.Background()); err != nil {
|
|
t.Fatalf("StartDaily() error = %v", err)
|
|
}
|
|
if publishedQueue != consts.QueueTrafficUsageDailyStart {
|
|
t.Fatalf("queue = %q, want %q", publishedQueue, consts.QueueTrafficUsageDailyStart)
|
|
}
|
|
if published.QueryDate != "20261009" {
|
|
t.Fatalf("queryDate = %q, want 20261009", published.QueryDate)
|
|
}
|
|
if published.RunID != "daily-usage-20261009-test-run" {
|
|
t.Fatalf("runId = %q", published.RunID)
|
|
}
|
|
if published.Operation != model.OperationPreviousDayUsageFullSync {
|
|
t.Fatalf("operation = %q", published.Operation)
|
|
}
|
|
}
|
|
|
|
// TestStartDailyUsesShanghaiDayAcrossUTCMidnight guards the timezone bug that
|
|
// the legacy code carried: a UTC instant just after midnight is still the
|
|
// previous day in Shanghai, so the period must not be computed in UTC.
|
|
func TestStartDailyUsesShanghaiDayAcrossUTCMidnight(t *testing.T) {
|
|
stubDailyTrigger(t)
|
|
|
|
// 2026-10-09 17:30 UTC == 2026-10-10 01:30 +08:00 -> previous day 20261009.
|
|
dailyUsageNow = func() time.Time {
|
|
return time.Date(2026, time.October, 9, 17, 30, 0, 0, time.UTC)
|
|
}
|
|
dailyUsageRunID = func() string { return "tz-run" }
|
|
dailyUsageRabbitEnabled = func(context.Context) bool { return true }
|
|
|
|
var published model.TrafficUsageDailyStartMessage
|
|
dailyUsagePublish = func(_ context.Context, _ string, data any) error {
|
|
published = data.(model.TrafficUsageDailyStartMessage)
|
|
return nil
|
|
}
|
|
|
|
if err := NewTrafficUsageDailySync().StartDaily(context.Background()); err != nil {
|
|
t.Fatalf("StartDaily() error = %v", err)
|
|
}
|
|
if published.QueryDate != "20261009" {
|
|
t.Fatalf("queryDate = %q, want 20261009 (Shanghai, not UTC)", published.QueryDate)
|
|
}
|
|
if published.TriggeredAt != "2026-10-10T01:30:00+08:00" {
|
|
t.Fatalf("triggeredAt = %q, want the Shanghai instant", published.TriggeredAt)
|
|
}
|
|
}
|
|
|
|
func TestStartDailyRunScopedForwardsScope(t *testing.T) {
|
|
stubDailyTrigger(t)
|
|
|
|
dailyUsageNow = func() time.Time {
|
|
return time.Date(2026, time.October, 10, 6, 0, 0, 0, time.FixedZone("UTC+8", 8*60*60))
|
|
}
|
|
dailyUsageRunID = func() string { return "scoped-run" }
|
|
dailyUsageRabbitEnabled = func(context.Context) bool { return true }
|
|
|
|
var published model.TrafficUsageDailyStartMessage
|
|
dailyUsagePublish = func(_ context.Context, _ string, data any) error {
|
|
published = data.(model.TrafficUsageDailyStartMessage)
|
|
return nil
|
|
}
|
|
|
|
runID, err := NewTrafficUsageDailySync().StartDailyRunScoped(context.Background(), &model.SyncScope{Channels: []string{"217"}})
|
|
if err != nil {
|
|
t.Fatalf("StartDailyRunScoped() error = %v", err)
|
|
}
|
|
if runID != "daily-usage-20261009-scoped-run" {
|
|
t.Fatalf("runId = %q", runID)
|
|
}
|
|
if published.Scope == nil || published.Scope.ChannelCount() != 1 || !published.Scope.Allows("217") {
|
|
t.Fatalf("scope = %+v, want channel 217 only", published.Scope)
|
|
}
|
|
}
|
|
|
|
func TestStartDailyRejectsInvalidScope(t *testing.T) {
|
|
stubDailyTrigger(t)
|
|
|
|
dailyUsageRabbitEnabled = func(context.Context) bool { return true }
|
|
called := false
|
|
dailyUsagePublish = func(context.Context, string, any) error {
|
|
called = true
|
|
return nil
|
|
}
|
|
|
|
if _, err := NewTrafficUsageDailySync().StartDailyRunScoped(context.Background(), &model.SyncScope{Channels: []string{"invalid/chan"}}); err == nil {
|
|
t.Fatal("expected an invalid chanKey to be rejected")
|
|
}
|
|
if called {
|
|
t.Fatal("publisher called for a rejected scope")
|
|
}
|
|
}
|
|
|
|
func TestStartDailyReturnsPublishError(t *testing.T) {
|
|
stubDailyTrigger(t)
|
|
|
|
dailyUsageNow = func() time.Time { return time.Date(2026, time.October, 10, 6, 0, 0, 0, time.UTC) }
|
|
dailyUsageRunID = func() string { return "err-run" }
|
|
dailyUsageRabbitEnabled = func(context.Context) bool { return true }
|
|
dailyUsagePublish = func(context.Context, string, any) error {
|
|
return errors.New("broker unavailable")
|
|
}
|
|
|
|
if err := NewTrafficUsageDailySync().StartDaily(context.Background()); err == nil {
|
|
t.Fatal("StartDaily() error = nil, want publish error")
|
|
}
|
|
}
|
|
|
|
func TestStartDailySkipsWhenRabbitMQDisabled(t *testing.T) {
|
|
stubDailyTrigger(t)
|
|
|
|
dailyUsageRabbitEnabled = func(context.Context) bool { return false }
|
|
called := false
|
|
dailyUsagePublish = func(context.Context, string, any) error {
|
|
called = true
|
|
return nil
|
|
}
|
|
|
|
if err := NewTrafficUsageDailySync().StartDaily(context.Background()); err == nil {
|
|
t.Fatal("StartDaily() error = nil, want disabled error")
|
|
}
|
|
if called {
|
|
t.Fatal("publisher called while RabbitMQ is disabled")
|
|
}
|
|
}
|
|
|
|
// dailyBackfillClock pins "now" for the period-aware daily tests: 2026-10-20
|
|
// 06:00 +08:00, so the default day is 20261019 and nothing later is valid.
|
|
func dailyBackfillClock(t *testing.T) {
|
|
t.Helper()
|
|
stubDailyTrigger(t)
|
|
dailyUsageNow = func() time.Time {
|
|
return time.Date(2026, time.October, 20, 6, 0, 0, 0, time.FixedZone("UTC+8", 8*60*60))
|
|
}
|
|
dailyUsageRunID = func() string { return "backfill-run" }
|
|
dailyUsageRabbitEnabled = func(context.Context) bool { return true }
|
|
}
|
|
|
|
func captureDailyStart(t *testing.T) *model.TrafficUsageDailyStartMessage {
|
|
t.Helper()
|
|
published := &model.TrafficUsageDailyStartMessage{}
|
|
dailyUsagePublish = func(_ context.Context, _ string, data any) error {
|
|
*published = data.(model.TrafficUsageDailyStartMessage)
|
|
return nil
|
|
}
|
|
return published
|
|
}
|
|
|
|
func TestStartDailyRunScopedAtUsesGivenDate(t *testing.T) {
|
|
dailyBackfillClock(t)
|
|
published := captureDailyStart(t)
|
|
|
|
runID, err := NewTrafficUsageDailySync().StartDailyRunScopedAt(context.Background(), nil, "20261014")
|
|
if err != nil {
|
|
t.Fatalf("StartDailyRunScopedAt() error = %v", err)
|
|
}
|
|
if published.QueryDate != "20261014" {
|
|
t.Fatalf("queryDate = %q, want 20261014", published.QueryDate)
|
|
}
|
|
if runID != "daily-usage-20261014-backfill-run" {
|
|
t.Fatalf("runId = %q", runID)
|
|
}
|
|
if published.Operation != model.OperationPreviousDayUsageFullSync {
|
|
t.Fatalf("operation = %q", published.Operation)
|
|
}
|
|
}
|
|
|
|
func TestStartDailyRunScopedAtRejectsFutureDate(t *testing.T) {
|
|
dailyBackfillClock(t)
|
|
called := false
|
|
dailyUsagePublish = func(context.Context, string, any) error {
|
|
called = true
|
|
return nil
|
|
}
|
|
|
|
for _, queryDate := range []string{"20261020", "20261021", "20270101"} {
|
|
if _, err := NewTrafficUsageDailySync().StartDailyRunScopedAt(context.Background(), nil, queryDate); err == nil {
|
|
t.Fatalf("queryDate %q accepted, want a future-date error", queryDate)
|
|
}
|
|
}
|
|
if called {
|
|
t.Fatal("publisher called for a rejected date")
|
|
}
|
|
}
|
|
|
|
func TestStartDailyRunScopedAtRejectsMalformedDate(t *testing.T) {
|
|
dailyBackfillClock(t)
|
|
called := false
|
|
dailyUsagePublish = func(context.Context, string, any) error {
|
|
called = true
|
|
return nil
|
|
}
|
|
|
|
for _, queryDate := range []string{"2026-10-14", "abc", "2026101", "202610140", "20261314"} {
|
|
if _, err := NewTrafficUsageDailySync().StartDailyRunScopedAt(context.Background(), nil, queryDate); err == nil {
|
|
t.Fatalf("queryDate %q accepted, want a format error", queryDate)
|
|
}
|
|
}
|
|
if called {
|
|
t.Fatal("publisher called for a malformed date")
|
|
}
|
|
}
|
|
|
|
func TestStartDailyRunScopedAtRejectsDateBeforeLowerBound(t *testing.T) {
|
|
dailyBackfillClock(t)
|
|
called := false
|
|
dailyUsagePublish = func(context.Context, string, any) error {
|
|
called = true
|
|
return nil
|
|
}
|
|
|
|
for _, queryDate := range []string{"20191231", "19990101"} {
|
|
if _, err := NewTrafficUsageDailySync().StartDailyRunScopedAt(context.Background(), nil, queryDate); err == nil {
|
|
t.Fatalf("queryDate %q accepted, want a lower-bound error", queryDate)
|
|
}
|
|
}
|
|
if called {
|
|
t.Fatal("publisher called for a date below the lower bound")
|
|
}
|
|
}
|
|
|
|
func TestStartDailyRunScopedAtEmptyFallsBackToYesterday(t *testing.T) {
|
|
dailyBackfillClock(t)
|
|
published := captureDailyStart(t)
|
|
|
|
runID, err := NewTrafficUsageDailySync().StartDailyRunScopedAt(context.Background(), nil, "")
|
|
if err != nil {
|
|
t.Fatalf("StartDailyRunScopedAt() error = %v", err)
|
|
}
|
|
if published.QueryDate != "20261019" {
|
|
t.Fatalf("queryDate = %q, want 20261019 (yesterday)", published.QueryDate)
|
|
}
|
|
if runID != "daily-usage-20261019-backfill-run" {
|
|
t.Fatalf("runId = %q", runID)
|
|
}
|
|
}
|
|
|
|
// TestStartDailyRunScopedAtEmptyMatchesLegacyScoped keeps the backward
|
|
// compatibility promise concrete: the old entry point and an empty period must
|
|
// publish byte-identical messages.
|
|
func TestStartDailyRunScopedAtEmptyMatchesLegacyScoped(t *testing.T) {
|
|
dailyBackfillClock(t)
|
|
var messages []model.TrafficUsageDailyStartMessage
|
|
dailyUsagePublish = func(_ context.Context, _ string, data any) error {
|
|
messages = append(messages, data.(model.TrafficUsageDailyStartMessage))
|
|
return nil
|
|
}
|
|
|
|
legacyID, err := NewTrafficUsageDailySync().StartDailyRunScoped(context.Background(), nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
atID, err := NewTrafficUsageDailySync().StartDailyRunScopedAt(context.Background(), nil, "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(messages) != 2 {
|
|
t.Fatalf("published %d messages, want 2", len(messages))
|
|
}
|
|
if legacyID != atID || messages[0] != messages[1] {
|
|
t.Fatalf("legacy = %+v/%q, at = %+v/%q", messages[0], legacyID, messages[1], atID)
|
|
}
|
|
}
|