173 lines
5.5 KiB
TypeScript
173 lines
5.5 KiB
TypeScript
import { randomUUID } from "node:crypto";
|
|
import { readFile } from "node:fs/promises";
|
|
import { fileURLToPath } from "node:url";
|
|
import { afterEach, describe, expect, it } from "vitest";
|
|
import postgres from "postgres";
|
|
import {
|
|
getEmbeddedPostgresTestSupport,
|
|
startEmbeddedPostgresTestDatabase,
|
|
} from "./test-embedded-postgres.js";
|
|
|
|
const cleanups: Array<() => Promise<void>> = [];
|
|
const support = await getEmbeddedPostgresTestSupport();
|
|
const d = support.supported ? describe : describe.skip;
|
|
|
|
afterEach(async () => {
|
|
while (cleanups.length > 0) await cleanups.pop()?.();
|
|
});
|
|
|
|
async function resolverPolicyMigrationStatements(): Promise<string[]> {
|
|
const migrationSql = await readFile(
|
|
fileURLToPath(new URL("./migrations/0218_mushy_jack_murdock.sql", import.meta.url)),
|
|
"utf8",
|
|
);
|
|
return migrationSql
|
|
.split("--> statement-breakpoint")
|
|
.map((statement) => statement.trim())
|
|
.filter((statement) => statement.length > 0);
|
|
}
|
|
|
|
d("issue interaction resolver policy migration", () => {
|
|
it("replays safely and completes a partial prior run", async () => {
|
|
const dbh = await startEmbeddedPostgresTestDatabase("resolver-policy-migration-");
|
|
cleanups.push(() => dbh.cleanup());
|
|
const sql = postgres(dbh.connectionString, { max: 1, onnotice: () => {} });
|
|
cleanups.push(async () => {
|
|
await sql.end();
|
|
});
|
|
|
|
const companyId = randomUUID();
|
|
const issueId = randomUUID();
|
|
const legacyInteractionId = randomUUID();
|
|
const partialInteractionId = randomUUID();
|
|
|
|
await sql`
|
|
INSERT INTO "companies" ("id", "name", "issue_prefix")
|
|
VALUES (${companyId}, 'Resolver migration test', 'RMT')
|
|
`;
|
|
await sql`
|
|
INSERT INTO "issues" ("id", "company_id", "title", "identifier")
|
|
VALUES (${issueId}, ${companyId}, 'Test migration replay', 'RMT-1')
|
|
`;
|
|
await sql.unsafe(
|
|
'ALTER TABLE "issue_thread_interactions" ALTER COLUMN "resolver_policy_provenance" DROP NOT NULL',
|
|
);
|
|
await sql.unsafe(
|
|
'ALTER TABLE "issue_thread_interactions" ALTER COLUMN "effective_resolver_policy_source" DROP NOT NULL',
|
|
);
|
|
await sql`
|
|
INSERT INTO "issue_thread_interactions" (
|
|
"id",
|
|
"company_id",
|
|
"issue_id",
|
|
"kind",
|
|
"requested_resolver_policy",
|
|
"effective_resolver_policy",
|
|
"resolver_policy_provenance",
|
|
"effective_resolver_policy_source",
|
|
"payload"
|
|
)
|
|
VALUES
|
|
(
|
|
${legacyInteractionId},
|
|
${companyId},
|
|
${issueId},
|
|
'request_confirmation',
|
|
'board_or_agents',
|
|
'board_only',
|
|
NULL,
|
|
NULL,
|
|
'{}'::jsonb
|
|
),
|
|
(
|
|
${partialInteractionId},
|
|
${companyId},
|
|
${issueId},
|
|
'request_confirmation',
|
|
'human_only',
|
|
'human_only',
|
|
'explicit',
|
|
NULL,
|
|
'{}'::jsonb
|
|
)
|
|
`;
|
|
|
|
const statements = await resolverPolicyMigrationStatements();
|
|
expect(statements.length).toBeGreaterThan(0);
|
|
for (const statement of statements) await sql.unsafe(statement);
|
|
|
|
const rowsAfterFirstRun = await sql<{
|
|
id: string;
|
|
requested_resolver_policy: string;
|
|
effective_resolver_policy: string;
|
|
resolver_policy_provenance: string;
|
|
effective_resolver_policy_source: string;
|
|
}[]>`
|
|
SELECT
|
|
"id",
|
|
"requested_resolver_policy",
|
|
"effective_resolver_policy",
|
|
"resolver_policy_provenance",
|
|
"effective_resolver_policy_source"
|
|
FROM "issue_thread_interactions"
|
|
WHERE "id" IN (${legacyInteractionId}, ${partialInteractionId})
|
|
ORDER BY "id"
|
|
`;
|
|
expect(rowsAfterFirstRun).toEqual(
|
|
[
|
|
{
|
|
id: legacyInteractionId,
|
|
requested_resolver_policy: "not_creator",
|
|
effective_resolver_policy: "human_only",
|
|
resolver_policy_provenance: "legacy_inherited_restriction",
|
|
effective_resolver_policy_source: "company_cap",
|
|
},
|
|
{
|
|
id: partialInteractionId,
|
|
requested_resolver_policy: "human_only",
|
|
effective_resolver_policy: "human_only",
|
|
resolver_policy_provenance: "explicit",
|
|
effective_resolver_policy_source: "requested",
|
|
},
|
|
].sort((left, right) => left.id.localeCompare(right.id)),
|
|
);
|
|
|
|
for (const statement of statements) await sql.unsafe(statement);
|
|
const rowsAfterReplay = await sql`
|
|
SELECT
|
|
"id",
|
|
"requested_resolver_policy",
|
|
"effective_resolver_policy",
|
|
"resolver_policy_provenance",
|
|
"effective_resolver_policy_source"
|
|
FROM "issue_thread_interactions"
|
|
WHERE "id" IN (${legacyInteractionId}, ${partialInteractionId})
|
|
ORDER BY "id"
|
|
`;
|
|
expect(rowsAfterReplay).toEqual(rowsAfterFirstRun);
|
|
|
|
const columns = await sql<
|
|
{ column_name: string; is_nullable: string; column_default: string }[]
|
|
>`
|
|
SELECT "column_name", "is_nullable", "column_default"
|
|
FROM "information_schema"."columns"
|
|
WHERE "table_schema" = 'public'
|
|
AND "table_name" = 'issue_thread_interactions'
|
|
AND "column_name" IN ('resolver_policy_provenance', 'effective_resolver_policy_source')
|
|
ORDER BY "column_name"
|
|
`;
|
|
expect(columns).toEqual([
|
|
{
|
|
column_name: "effective_resolver_policy_source",
|
|
is_nullable: "NO",
|
|
column_default: "'requested'::text",
|
|
},
|
|
{
|
|
column_name: "resolver_policy_provenance",
|
|
is_nullable: "NO",
|
|
column_default: "'inherited'::text",
|
|
},
|
|
]);
|
|
}, 240_000);
|
|
});
|