kaneo (minimalist kanban) fork to experiment adding a tangled integration
github.com/usekaneo/kaneo
1import { dirname, resolve } from "node:path";
2import { fileURLToPath } from "node:url";
3import { sql } from "drizzle-orm";
4import { migrate } from "drizzle-orm/node-postgres/migrator";
5import { Client } from "pg";
6import db from "../../../apps/api/src/database";
7
8const currentDir = dirname(fileURLToPath(import.meta.url));
9const migrationsFolder = resolve(currentDir, "../../../apps/api/drizzle");
10
11let migrationPromise: Promise<void> | null = null;
12
13function getDatabaseName(connectionString: string) {
14 return new URL(connectionString).pathname.replace(/^\//, "");
15}
16
17function getAdminDatabaseUrl(connectionString: string) {
18 const url = new URL(connectionString);
19 url.pathname = "/postgres";
20 return url.toString();
21}
22
23function quoteIdentifier(identifier: string) {
24 return `"${identifier.replaceAll('"', '""')}"`;
25}
26
27async function ensureTestDatabaseExists() {
28 const connectionString = process.env.DATABASE_URL;
29
30 if (!connectionString) {
31 throw new Error("DATABASE_URL must be defined for integration tests");
32 }
33
34 const databaseName = getDatabaseName(connectionString);
35
36 if (!databaseName.endsWith("_test")) {
37 throw new Error(
38 `Refusing to manage non-test database "${databaseName}". DATABASE_URL must point to a test database.`,
39 );
40 }
41
42 const adminClient = new Client({
43 connectionString: getAdminDatabaseUrl(connectionString),
44 });
45
46 await adminClient.connect();
47
48 try {
49 const result = await adminClient.query(
50 "SELECT 1 FROM pg_database WHERE datname = $1",
51 [databaseName],
52 );
53
54 if (result.rowCount === 0) {
55 await adminClient.query(
56 `CREATE DATABASE ${quoteIdentifier(databaseName)}`,
57 );
58 }
59 } finally {
60 await adminClient.end();
61 }
62}
63
64export async function ensureTestDatabaseMigrated() {
65 if (!migrationPromise) {
66 migrationPromise = (async () => {
67 await ensureTestDatabaseExists();
68 await migrate(db, {
69 migrationsFolder,
70 });
71 })();
72 }
73
74 try {
75 await migrationPromise;
76 } catch (error) {
77 migrationPromise = null;
78 throw error;
79 }
80}
81
82export async function resetTestDatabase() {
83 await ensureTestDatabaseMigrated();
84
85 await db.execute(
86 sql.raw(`
87 TRUNCATE TABLE
88 "activity",
89 "account",
90 "apikey",
91 "asset",
92 "column",
93 "comment",
94 "external_link",
95 "github_integration",
96 "integration",
97 "invitation",
98 "label",
99 "notification",
100 "project",
101 "session",
102 "task",
103 "task_relation",
104 "team",
105 "team_member",
106 "time_entry",
107 "verification",
108 "workflow_rule",
109 "workspace",
110 "workspace_member",
111 "user"
112 RESTART IDENTITY CASCADE
113 `),
114 );
115}