// Runnable self-check for the device-reset feature and the Postgres migration set. // // set POSTGRES_CONNECTION=... (optional; defaults to the local dev database) // dotnet run --project Test/GreenHome.SelfCheck // // It (1) applies the Postgres migrations to the target database — proving the new // Data/Migrations/Postgres set is complete — and (2) asserts that a queued reset is // consumed exactly once, which is the whole guarantee behind "RST goes out on the // next request only, never again". using AutoMapper; using GreenHome.Application; using GreenHome.Domain; using GreenHome.Infrastructure.Data; using GreenHome.Infrastructure.Services.Telemetry; using Microsoft.EntityFrameworkCore; var connectionString = Environment.GetEnvironmentVariable("POSTGRES_CONNECTION") ?? "Host=localhost;Port=5432;Database=greenhome;Username=greenhome;Password=greenhome"; Console.WriteLine($"Target: {connectionString}"); var options = new DbContextOptionsBuilder() .UseNpgsql(connectionString) .Options; await using var db = new PostgresGreenHomeDbContext(options); // --reset-db wipes the target schema and lets Migrate() rebuild it from Data/Migrations/Postgres // — the one-time step needed because EnsureCreated() left tables behind with no migration history. if (args.Contains("--reset-db")) { Console.WriteLine("--reset-db -> dropping schema public"); await db.Database.ExecuteSqlRawAsync("DROP SCHEMA public CASCADE; CREATE SCHEMA public;"); } await db.Database.MigrateAsync(); var applied = await db.Database.GetAppliedMigrationsAsync(); Console.WriteLine($"Migrations applied ({applied.Count()}): {string.Join(", ", applied.Select(m => m.Split('_').Last()))}"); var mapper = new MapperConfiguration(cfg => cfg.AddProfile()).CreateMapper(); var telemetry = new TelemetryService(db, mapper); // throwaway owner + device so the check is self-contained. // CreatedAt is deliberately left at its Domain default (DateTime.UtcNow): writing a Kind=Utc value // must work now that the columns are timestamptz. That line is the regression test for the // "Cannot write DateTime with Kind=UTC to PostgreSQL type 'timestamp without time zone'" bug. var user = new User { Mobile = "0" + DateTime.UtcNow.Ticks.ToString()[^10..], Name = "Self", Family = "Check" }; db.Users.Add(user); await db.SaveChangesAsync(); var device = new Device { DeviceName = "SELFCHK", UserId = user.Id, Location = string.Empty, NeshanLocation = string.Empty, ResetRequested = true }; db.Devices.Add(device); await db.SaveChangesAsync(); try { var storedUser = await db.Users.AsNoTracking().FirstAsync(u => u.Id == user.Id); Assert(storedUser.CreatedAt.Kind == DateTimeKind.Utc, "a timestamptz column must read back as Kind=Utc"); var first = await telemetry.ConsumeResetRequestAsync(device.Id, default); var second = await telemetry.ConsumeResetRequestAsync(device.Id, default); Assert(first, "first ConsumeResetRequestAsync must return true"); Assert(!second, "second ConsumeResetRequestAsync must return false (one-shot)"); Assert(!(await db.Devices.AsNoTracking().FirstAsync(d => d.Id == device.Id)).ResetRequested, "ResetRequested must be cleared in the database"); Console.WriteLine("PASS: a queued reset is delivered exactly once."); } finally { db.Devices.Remove(device); db.Users.Remove(user); await db.SaveChangesAsync(); } static void Assert(bool condition, string message) { if (!condition) throw new InvalidOperationException($"FAIL: {message}"); }