package tasks import ( "context" "os" "path/filepath" "testing" "time" "vctp/db" "vctp/internal/settings" ) func TestSnapshotTableCompatModeSettingControlsTaskBehaviorFlag(t *testing.T) { task := &CronTask{} if !task.snapshotTableCompatModeEnabled() { t.Fatal("expected default snapshot_table_compat_mode=true when settings are absent") } task.Settings = &settings.Settings{Values: &settings.SettingsYML{}} if !task.snapshotTableCompatModeEnabled() { t.Fatal("expected default snapshot_table_compat_mode=true when value is unset") } disabled := false task.Settings.Values.Settings.SnapshotTableCompatMode = &disabled if task.snapshotTableCompatModeEnabled() { t.Fatal("expected snapshot_table_compat_mode=false to disable legacy snapshot-table writes") } enabled := true task.Settings.Values.Settings.SnapshotTableCompatMode = &enabled if !task.snapshotTableCompatModeEnabled() { t.Fatal("expected snapshot_table_compat_mode=true to enable legacy snapshot-table writes") } } func TestManualDailyAggregate_SQLFallback_LegacyTablesAndReport(t *testing.T) { ctx := context.Background() dbConn := newTasksTestDB(t) task := newTasksTestCronTaskForAggregateFlow(t, dbConn) t.Setenv("DAILY_AGG_SQL", "1") t.Setenv("DAILY_AGG_GO", "") dayStart := time.Date(2026, time.March, 15, 0, 0, 0, 0, time.UTC) t1 := dayStart.Add(1 * time.Hour).Unix() t2 := dayStart.Add(2 * time.Hour).Unix() table1, err := hourlyInventoryTableName(time.Unix(t1, 0).UTC()) if err != nil { t.Fatalf("failed to build first hourly table name: %v", err) } table2, err := hourlyInventoryTableName(time.Unix(t2, 0).UTC()) if err != nil { t.Fatalf("failed to build second hourly table name: %v", err) } for _, table := range []string{table1, table2} { if err := db.EnsureSnapshotTable(ctx, dbConn, table); err != nil { t.Fatalf("failed to ensure hourly snapshot table %s: %v", table, err) } } seeds := []hourlySeedRow{ {SnapshotTime: t1, Name: "vm-a", Vcenter: "vc-a", VmID: "vm-a", VmUUID: "uuid-a", ResourcePool: "Tin", Datacenter: "dc-a", Cluster: "cluster-a", Folder: "/prod", ProvisionedDisk: 100, VcpuCount: 2, RamGB: 8, CreationTime: dayStart.Add(-24 * time.Hour).Unix(), IsTemplate: "FALSE", PoweredOn: "TRUE", SrmPlaceholder: "FALSE"}, {SnapshotTime: t2, Name: "vm-a", Vcenter: "vc-a", VmID: "vm-a", VmUUID: "uuid-a", ResourcePool: "Gold", Datacenter: "dc-a", Cluster: "cluster-a", Folder: "/prod", ProvisionedDisk: 120, VcpuCount: 4, RamGB: 8, CreationTime: dayStart.Add(-24 * time.Hour).Unix(), IsTemplate: "FALSE", PoweredOn: "TRUE", SrmPlaceholder: "FALSE"}, {SnapshotTime: t2, Name: "vm-b", Vcenter: "vc-a", VmID: "vm-b", VmUUID: "uuid-b", ResourcePool: "Bronze", Datacenter: "dc-a", Cluster: "cluster-a", Folder: "/prod", ProvisionedDisk: 40, VcpuCount: 1, RamGB: 4, CreationTime: dayStart.Add(-48 * time.Hour).Unix(), IsTemplate: "FALSE", PoweredOn: "TRUE", SrmPlaceholder: "FALSE"}, } for _, row := range seeds { table, tableErr := hourlyInventoryTableName(time.Unix(row.SnapshotTime, 0).UTC()) if tableErr != nil { t.Fatalf("failed to build hourly table for seed row: %v", tableErr) } if err := insertHourlySnapshotSeedRow(ctx, dbConn, table, row); err != nil { t.Fatalf("failed to insert hourly snapshot seed row: %v", err) } } if err := task.aggregateDailySummaryWithMode(ctx, dayStart, true, false); err != nil { t.Fatalf("aggregateDailySummaryWithMode (legacy SQL fallback) failed: %v", err) } summaryTable, err := dailySummaryTableName(dayStart) if err != nil { t.Fatalf("failed to build daily summary table name: %v", err) } rows, err := loadDailySummaryRows(ctx, dbConn, summaryTable) if err != nil { t.Fatalf("failed to load daily summary rows: %v", err) } if len(rows) != 2 { t.Fatalf("unexpected daily summary row count: got %d want %d", len(rows), 2) } assertSnapshotRegistryRow(t, ctx, dbConn, "daily", summaryTable, dayStart.Unix(), int64(len(rows))) assertSummaryCacheMatchesByVcenter(t, ctx, dbConn, summaryTable, "daily", dayStart.Unix()) reportPath := filepath.Join(task.Settings.Values.Settings.ReportsDir, summaryTable+".xlsx") if _, err := os.Stat(reportPath); err != nil { t.Fatalf("expected daily report file at %s: %v", reportPath, err) } } func TestManualMonthlyAggregate_SQLFallback_LegacyTablesAndReport(t *testing.T) { ctx := context.Background() dbConn := newTasksTestDB(t) task := newTasksTestCronTaskForAggregateFlow(t, dbConn) t.Setenv("MONTHLY_AGG_SQL", "1") t.Setenv("MONTHLY_AGG_GO", "") monthStart := time.Date(2026, time.April, 1, 0, 0, 0, 0, time.UTC) day1 := monthStart.AddDate(0, 0, 2) day2 := monthStart.AddDate(0, 0, 3) day1Table, err := dailySummaryTableName(day1) if err != nil { t.Fatalf("failed to build day1 summary table name: %v", err) } day2Table, err := dailySummaryTableName(day2) if err != nil { t.Fatalf("failed to build day2 summary table name: %v", err) } for _, table := range []string{day1Table, day2Table} { if err := db.EnsureSummaryTable(ctx, dbConn, table); err != nil { t.Fatalf("failed to ensure daily summary table %s: %v", table, err) } } seeds := []dailySeedRow{ { SnapshotTime: day1.Unix(), Name: "vm-a", Vcenter: "vc-a", VmID: "vm-a", VmUUID: "uuid-a", ResourcePool: "Bronze", Datacenter: "dc-a", Cluster: "cluster-a", Folder: "/prod", ProvisionedDisk: 100, VcpuCount: 2, RamGB: 8, CreationTime: monthStart.Add(-72 * time.Hour).Unix(), IsTemplate: "FALSE", PoweredOn: "TRUE", SrmPlaceholder: "FALSE", SamplesPresent: 2, AvgVcpuCount: 2, AvgRamGB: 8, AvgProvisionedDisk: 100, AvgIsPresent: 1.0, PoolBronzePct: 100, Bronze: 100, }, { SnapshotTime: day2.Unix(), Name: "vm-a", Vcenter: "vc-a", VmID: "vm-a", VmUUID: "uuid-a", ResourcePool: "Tin", Datacenter: "dc-a", Cluster: "cluster-a", Folder: "/prod", ProvisionedDisk: 120, VcpuCount: 4, RamGB: 12, CreationTime: monthStart.Add(-72 * time.Hour).Unix(), IsTemplate: "FALSE", PoweredOn: "TRUE", SrmPlaceholder: "FALSE", SamplesPresent: 2, AvgVcpuCount: 4, AvgRamGB: 12, AvgProvisionedDisk: 120, AvgIsPresent: 1.0, PoolTinPct: 100, Tin: 100, }, } for _, seed := range seeds { targetTable := day1Table if seed.SnapshotTime == day2.Unix() { targetTable = day2Table } if err := insertDailySummarySeedRow(ctx, dbConn, targetTable, seed); err != nil { t.Fatalf("failed to insert daily summary seed row: %v", err) } } if err := task.aggregateMonthlySummaryWithMode(ctx, monthStart, true, false); err != nil { t.Fatalf("aggregateMonthlySummaryWithMode (legacy SQL fallback) failed: %v", err) } summaryTable, err := monthlySummaryTableName(monthStart) if err != nil { t.Fatalf("failed to build monthly summary table name: %v", err) } rows, err := loadMonthlySummaryRows(ctx, dbConn, summaryTable) if err != nil { t.Fatalf("failed to load monthly summary rows: %v", err) } if len(rows) != 1 { t.Fatalf("unexpected monthly summary row count: got %d want %d", len(rows), 1) } assertSnapshotRegistryRow(t, ctx, dbConn, "monthly", summaryTable, monthStart.Unix(), int64(len(rows))) assertSummaryCacheMatchesByVcenter(t, ctx, dbConn, summaryTable, "monthly", monthStart.Unix()) reportPath := filepath.Join(task.Settings.Values.Settings.ReportsDir, summaryTable+".xlsx") if _, err := os.Stat(reportPath); err != nil { t.Fatalf("expected monthly report file at %s: %v", reportPath, err) } }