more tests
continuous-integration/drone/push Build is passing

This commit is contained in:
2026-04-20 18:38:12 +10:00
parent 27cab61e89
commit 916b0b5054
6 changed files with 933 additions and 7 deletions
@@ -0,0 +1,181 @@
package handler
import (
"context"
"encoding/json"
"fmt"
"io"
"log/slog"
"net/http"
"net/http/httptest"
"strconv"
"testing"
"time"
"vctp/db"
"vctp/db/queries"
"vctp/server/models"
"github.com/jmoiron/sqlx"
_ "modernc.org/sqlite"
)
type snapshotRepairTestDatabase struct {
dbConn *sqlx.DB
logger *slog.Logger
}
func (d *snapshotRepairTestDatabase) DB() *sqlx.DB { return d.dbConn }
func (d *snapshotRepairTestDatabase) Queries() db.Querier { return queries.New(d.dbConn.DB) }
func (d *snapshotRepairTestDatabase) Logger() *slog.Logger {
if d.logger != nil {
return d.logger
}
return slog.New(slog.NewTextHandler(io.Discard, nil))
}
func (d *snapshotRepairTestDatabase) Close() error { return d.dbConn.Close() }
func newSnapshotRepairTestDB(t *testing.T) *sqlx.DB {
t.Helper()
dbConn, err := sqlx.Open("sqlite", ":memory:")
if err != nil {
t.Fatalf("failed to open sqlite test db: %v", err)
}
t.Cleanup(func() {
_ = dbConn.Close()
})
return dbConn
}
func TestSnapshotRepairSuite_RebuildsRegistryTotalsAndLifecycle(t *testing.T) {
ctx := context.Background()
dbConn := newSnapshotRepairTestDB(t)
logger := newTestLogger()
h := &Handler{
Logger: logger,
Database: &snapshotRepairTestDatabase{dbConn: dbConn, logger: logger},
}
dayStart := time.Date(2026, time.March, 16, 0, 0, 0, 0, time.UTC)
hourlyTs := dayStart.Add(2 * time.Hour).Unix()
hourlyTable := fmt.Sprintf("inventory_hourly_%d", hourlyTs)
dailyTable := fmt.Sprintf("inventory_daily_summary_%s", dayStart.Format("20060102"))
monthlyTable := fmt.Sprintf("inventory_monthly_summary_%s", dayStart.Format("200601"))
if err := db.EnsureSnapshotTable(ctx, dbConn, hourlyTable); err != nil {
t.Fatalf("failed to ensure hourly table: %v", err)
}
if err := db.EnsureSummaryTable(ctx, dbConn, dailyTable); err != nil {
t.Fatalf("failed to ensure daily summary table: %v", err)
}
if err := db.EnsureSummaryTable(ctx, dbConn, monthlyTable); err != nil {
t.Fatalf("failed to ensure monthly summary table: %v", err)
}
if _, err := dbConn.ExecContext(ctx, fmt.Sprintf(`
INSERT INTO %s (
"Name","Vcenter","VmId","VmUuid","CreationTime","DeletionTime","ResourcePool","Datacenter","Cluster","Folder",
"ProvisionedDisk","VcpuCount","RamGB","IsTemplate","PoweredOn","SrmPlaceholder","SnapshotTime"
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
`, hourlyTable),
"vm-a", "vc-a", "vm-a", "uuid-a", dayStart.Add(-24*time.Hour).Unix(), int64(0), "Tin", "dc-a", "cluster-a", "/prod",
100.0, int64(2), int64(8), "FALSE", "TRUE", "FALSE", hourlyTs,
); err != nil {
t.Fatalf("failed to seed hourly table: %v", err)
}
if _, err := dbConn.ExecContext(ctx, fmt.Sprintf(`
INSERT INTO %s (
"Name","Vcenter","VmId","VmUuid","CreationTime","DeletionTime","ResourcePool","Datacenter","Cluster","Folder",
"ProvisionedDisk","VcpuCount","RamGB","IsTemplate","PoweredOn","SrmPlaceholder","SnapshotTime","SamplesPresent",
"AvgVcpuCount","AvgRamGB","AvgProvisionedDisk","AvgIsPresent","PoolTinPct","PoolBronzePct","PoolSilverPct","PoolGoldPct","Tin","Bronze","Silver","Gold"
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
`, dailyTable),
"vm-a", "vc-a", "vm-a", "uuid-a", int64(0), int64(0), "Tin", "dc-a", "cluster-a", "/prod",
100.0, int64(2), int64(8), "FALSE", "TRUE", "FALSE", int64(0), int64(1),
2.0, 8.0, 100.0, 1.0, 100.0, 0.0, 0.0, 0.0, 100.0, 0.0, 0.0, 0.0,
); err != nil {
t.Fatalf("failed to seed daily summary table: %v", err)
}
if _, err := dbConn.ExecContext(ctx, fmt.Sprintf(`
INSERT INTO %s (
"Name","Vcenter","VmId","VmUuid","CreationTime","DeletionTime","ResourcePool","Datacenter","Cluster","Folder",
"ProvisionedDisk","VcpuCount","RamGB","IsTemplate","PoweredOn","SrmPlaceholder","SnapshotTime","SamplesPresent",
"AvgVcpuCount","AvgRamGB","AvgProvisionedDisk","AvgIsPresent","PoolTinPct","PoolBronzePct","PoolSilverPct","PoolGoldPct","Tin","Bronze","Silver","Gold"
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
`, monthlyTable),
"vm-a", "vc-a", "vm-a", "uuid-a", int64(0), int64(0), "Tin", "dc-a", "cluster-a", "/prod",
100.0, int64(2), int64(8), "FALSE", "TRUE", "FALSE", dayStart.Unix(), int64(1),
2.0, 8.0, 100.0, 1.0, 100.0, 0.0, 0.0, 0.0, 100.0, 0.0, 0.0, 0.0,
); err != nil {
t.Fatalf("failed to seed monthly summary table: %v", err)
}
req := httptest.NewRequest(http.MethodPost, "/api/snapshots/repair/all", nil)
rr := httptest.NewRecorder()
h.SnapshotRepairSuite(rr, req)
if rr.Code != http.StatusOK {
t.Fatalf("expected status %d, got %d body=%s", http.StatusOK, rr.Code, rr.Body.String())
}
var payload models.SnapshotRepairSuiteResponse
if err := json.Unmarshal(rr.Body.Bytes(), &payload); err != nil {
t.Fatalf("failed to decode response: %v", err)
}
if payload.Status != "OK" {
t.Fatalf("unexpected repair suite status: %q", payload.Status)
}
dailyRepaired, err := strconv.Atoi(payload.DailyRepaired)
if err != nil {
t.Fatalf("failed to parse daily_repaired: %v", err)
}
if dailyRepaired < 1 {
t.Fatalf("expected at least one daily table repaired, got %d", dailyRepaired)
}
monthlyRefined, err := strconv.Atoi(payload.MonthlyRefined)
if err != nil {
t.Fatalf("failed to parse monthly_refined: %v", err)
}
if monthlyRefined < 1 {
t.Fatalf("expected at least one monthly table refined, got %d", monthlyRefined)
}
monthlyFailed, err := strconv.Atoi(payload.MonthlyFailed)
if err != nil {
t.Fatalf("failed to parse monthly_failed: %v", err)
}
if monthlyFailed != 0 {
t.Fatalf("expected monthly_failed=0, got %d", monthlyFailed)
}
assertSnapshotRegistryTypeCount(t, ctx, dbConn, "hourly", 1)
assertSnapshotRegistryTypeCount(t, ctx, dbConn, "daily", 1)
assertSnapshotRegistryTypeCount(t, ctx, dbConn, "monthly", 1)
var totalsRows int
if err := dbConn.GetContext(ctx, &totalsRows, `SELECT COUNT(1) FROM vcenter_totals WHERE "Vcenter" = ?`, "vc-a"); err != nil {
t.Fatalf("failed to query vcenter_totals: %v", err)
}
if totalsRows < 1 {
t.Fatalf("expected vcenter_totals to be backfilled, got %d rows", totalsRows)
}
var dailySnapshotTime int64
if err := dbConn.GetContext(ctx, &dailySnapshotTime, fmt.Sprintf(`SELECT COALESCE("SnapshotTime",0) FROM %s WHERE "Vcenter" = ? AND "VmId" = ?`, dailyTable), "vc-a", "vm-a"); err != nil {
t.Fatalf("failed to query repaired daily snapshot time: %v", err)
}
if dailySnapshotTime == 0 {
t.Fatal("expected repaired daily summary SnapshotTime to be backfilled")
}
}
func assertSnapshotRegistryTypeCount(t *testing.T, ctx context.Context, dbConn *sqlx.DB, snapshotType string, want int) {
t.Helper()
var got int
if err := dbConn.GetContext(ctx, &got, `SELECT COUNT(1) FROM snapshot_registry WHERE snapshot_type = ?`, snapshotType); err != nil {
t.Fatalf("failed to query snapshot_registry for type %s: %v", snapshotType, err)
}
if got != want {
t.Fatalf("unexpected snapshot_registry count for %s: got %d want %d", snapshotType, got, want)
}
}