Verify legacy migration completion
This commit is contained in:
@@ -43,6 +43,11 @@ Run in this order for local database workflows:
|
||||
5. Migrate legacy consumers:
|
||||
- `npm run db:migrate:legacy-consumers`
|
||||
|
||||
The migration command finishes with a cutover verification across the complete
|
||||
database. It exits with an error while any legacy consumer lacks a migration
|
||||
mapping, including consumers that cannot be migrated because they have no circuit
|
||||
list assignment. Do not remove or disable the legacy path until this check passes.
|
||||
|
||||
## Validation Checks
|
||||
|
||||
After migration, verify:
|
||||
@@ -72,6 +77,6 @@ Actions:
|
||||
|
||||
Do not delete legacy consumers yet.
|
||||
|
||||
- legacy endpoints/views may still depend on them
|
||||
- legacy endpoints/views remain enabled only until the cutover verification passes
|
||||
- migration trace tables reference old/new mapping
|
||||
- keeping legacy rows allows comparison and rollback validation during transition
|
||||
|
||||
@@ -6,8 +6,9 @@ import { LegacyConsumerMigrationService } from "../src/domain/services/legacy-co
|
||||
|
||||
const projectRepository = new ProjectRepository();
|
||||
const circuitListRepository = new CircuitListRepository();
|
||||
const migrationRepository = new LegacyConsumerMigrationRepository(db);
|
||||
const migrationService = new LegacyConsumerMigrationService(
|
||||
new LegacyConsumerMigrationRepository(db)
|
||||
migrationRepository
|
||||
);
|
||||
|
||||
async function run() {
|
||||
@@ -30,8 +31,21 @@ async function run() {
|
||||
}
|
||||
}
|
||||
|
||||
const unmigratedConsumers =
|
||||
await migrationRepository.listUnmigratedConsumers();
|
||||
if (unmigratedConsumers.length > 0) {
|
||||
const withoutCircuitList = unmigratedConsumers.filter(
|
||||
(consumer) => !consumer.circuitListId
|
||||
).length;
|
||||
throw new Error(
|
||||
`Cutover verification failed: ${unmigratedConsumers.length} legacy consumer(s) remain unmigrated` +
|
||||
` (${withoutCircuitList} without a circuit list).`
|
||||
);
|
||||
}
|
||||
|
||||
console.log("Legacy consumer migration summary:");
|
||||
console.table(reports);
|
||||
console.log("Cutover verification passed: all legacy consumers are mapped.");
|
||||
}
|
||||
|
||||
run().catch((error) => {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import crypto from "node:crypto";
|
||||
import { eq, inArray } from "drizzle-orm";
|
||||
import { eq, inArray, isNull } from "drizzle-orm";
|
||||
import type { AppDatabase } from "../database-context.js";
|
||||
import { circuitDeviceRows } from "../schema/circuit-device-rows.js";
|
||||
import { circuits } from "../schema/circuits.js";
|
||||
import { consumers } from "../schema/consumers.js";
|
||||
import { legacyConsumerCircuitMigrations } from "../schema/legacy-consumer-circuit-migrations.js";
|
||||
import { legacyConsumerMigrationReports } from "../schema/legacy-consumer-migration-report.js";
|
||||
import {
|
||||
@@ -36,6 +37,21 @@ export interface LegacyMigrationReportPersistenceInput {
|
||||
export class LegacyConsumerMigrationRepository {
|
||||
constructor(private readonly database: AppDatabase) {}
|
||||
|
||||
async listUnmigratedConsumers() {
|
||||
return this.database
|
||||
.select({
|
||||
id: consumers.id,
|
||||
projectId: consumers.projectId,
|
||||
circuitListId: consumers.circuitListId,
|
||||
})
|
||||
.from(consumers)
|
||||
.leftJoin(
|
||||
legacyConsumerCircuitMigrations,
|
||||
eq(legacyConsumerCircuitMigrations.consumerId, consumers.id)
|
||||
)
|
||||
.where(isNull(legacyConsumerCircuitMigrations.consumerId));
|
||||
}
|
||||
|
||||
async listMigratedConsumerIds(circuitListId: string) {
|
||||
const rows = await this.database
|
||||
.select({ consumerId: legacyConsumerCircuitMigrations.consumerId })
|
||||
|
||||
@@ -12,6 +12,7 @@ import { LegacyConsumerMigrationRepository } from "../src/db/repositories/legacy
|
||||
import { circuitDeviceRows } from "../src/db/schema/circuit-device-rows.js";
|
||||
import { circuitSections } from "../src/db/schema/circuit-sections.js";
|
||||
import { circuits } from "../src/db/schema/circuits.js";
|
||||
import { consumers } from "../src/db/schema/consumers.js";
|
||||
import { legacyConsumerCircuitMigrations } from "../src/db/schema/legacy-consumer-circuit-migrations.js";
|
||||
import { legacyConsumerMigrationReports } from "../src/db/schema/legacy-consumer-migration-report.js";
|
||||
import { projects } from "../src/db/schema/projects.js";
|
||||
@@ -97,6 +98,61 @@ function assertNoMigrationWrites(context: DatabaseContext) {
|
||||
}
|
||||
|
||||
describe("legacy consumer migration repository", () => {
|
||||
it("reports every consumer without a completed migration mapping", async () => {
|
||||
const { context, circuitListId, sectionId } = createTestDatabase();
|
||||
try {
|
||||
context.db
|
||||
.insert(consumers)
|
||||
.values([
|
||||
{
|
||||
id: "consumer-1",
|
||||
projectId: "project-1",
|
||||
circuitListId,
|
||||
name: "Light 1",
|
||||
quantity: 1,
|
||||
installedPowerPerUnitKw: 0.1,
|
||||
demandFactor: 1,
|
||||
},
|
||||
{
|
||||
id: "consumer-2",
|
||||
projectId: "project-1",
|
||||
circuitListId: null,
|
||||
name: "Light 2",
|
||||
quantity: 1,
|
||||
installedPowerPerUnitKw: 0.2,
|
||||
demandFactor: 1,
|
||||
},
|
||||
])
|
||||
.run();
|
||||
const repository = new LegacyConsumerMigrationRepository(context.db);
|
||||
|
||||
assert.deepEqual(
|
||||
(await repository.listUnmigratedConsumers()).sort((left, right) =>
|
||||
left.id.localeCompare(right.id)
|
||||
),
|
||||
[
|
||||
{
|
||||
id: "consumer-1",
|
||||
projectId: "project-1",
|
||||
circuitListId,
|
||||
},
|
||||
{
|
||||
id: "consumer-2",
|
||||
projectId: "project-1",
|
||||
circuitListId: null,
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
repository.persistCircuitListMigration(
|
||||
migrationInput(circuitListId, sectionId)
|
||||
);
|
||||
assert.deepEqual(await repository.listUnmigratedConsumers(), []);
|
||||
} finally {
|
||||
context.close();
|
||||
}
|
||||
});
|
||||
|
||||
it("commits circuits, rows, mappings and report together", () => {
|
||||
const { context, circuitListId, sectionId } = createTestDatabase();
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user