Skip to content

Commit a7ec742

Browse files
authored
ENG-681 Automate database testing (#957)
1 parent 7d2ecec commit a7ec742

7 files changed

Lines changed: 169 additions & 22 deletions

File tree

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: Database tests
2+
on:
3+
pull_request:
4+
paths:
5+
- "packages/database/**"
6+
env:
7+
SUPABASE_USE_DB: local
8+
SUPABASE_PROJECT_ID: test
9+
GITHUB_TEST: test
10+
11+
jobs:
12+
build:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@v6
16+
- name: Install pnpm
17+
uses: pnpm/action-setup@v4
18+
with:
19+
version: 10.15.1
20+
run_install: false
21+
- name: Setup node
22+
uses: actions/setup-node@v4
23+
with:
24+
node-version: "20"
25+
cache: "pnpm"
26+
- name: Install Dependencies
27+
run: pnpm install --frozen-lockfile
28+
# - name: Get supabase version
29+
# working-directory: ./packages/database
30+
# run: echo "SUPABASE_VERSION=$(./node_modules/.bin/supabase --version)" >> $GITHUB_ENV
31+
# - name: Cache Docker images.
32+
# uses: AndreKurait/docker-cache@0.6.0
33+
# with:
34+
# key: docker-${{ runner.os }}-${{ env.SUPABASE_VERSION }}
35+
- name: Setup database
36+
working-directory: ./packages/database
37+
run: pnpm run setup
38+
- name: Serve and run tests
39+
working-directory: ./packages/database
40+
run: pnpm run test:withserve

packages/database/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"lint:fix": "eslint --fix . && tsx scripts/lintSchemas.ts -f && tsx scripts/lintFunctions.ts",
3434
"migrate": "tsx scripts/migrate.ts",
3535
"test": "pnpm run build && cucumber-js",
36+
"test:withserve": "pnpm run build && tsx scripts/serveAndTest.ts",
3637
"genenv": "tsx scripts/createEnv.mts",
3738
"gentypes": "tsx scripts/genTypes.ts",
3839
"dbdiff": "supabase stop && supabase db diff --use-pg-schema",

packages/database/scripts/createEnv.mts

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,17 @@ const getVercelToken = () => {
2929
};
3030

3131
const makeFnEnv = (envTxt: string): string => {
32-
return envTxt.split('\n').filter(l=>l.match(/^SUPABASE_\w+_KEY/)).map((l)=> l.replace('SUPABASE_', 'SB_')).join('\n');
33-
}
32+
return envTxt
33+
.split("\n")
34+
.filter((l) => l.match(/^SUPABASE_\w+_KEY/))
35+
.map((l) => l.replace("SUPABASE_", "SB_"))
36+
.join("\n");
37+
};
3438

3539
const makeLocalEnv = () => {
3640
execSync("supabase start", {
37-
cwd: projectRoot, stdio: "inherit"
41+
cwd: projectRoot,
42+
stdio: "inherit",
3843
});
3944
const stdout = execSync("supabase status -o env", {
4045
encoding: "utf8",
@@ -54,8 +59,8 @@ const makeLocalEnv = () => {
5459
);
5560
writeFileSync(
5661
join(projectRoot, "supabase/functions/.env"),
57-
makeFnEnv(prefixed)
58-
)
62+
makeFnEnv(prefixed),
63+
);
5964
};
6065

6166
const makeBranchEnv = async (vercel: Vercel, vercelToken: string) => {
@@ -94,11 +99,11 @@ const makeBranchEnv = async (vercel: Vercel, vercelToken: string) => {
9499
throw err;
95100
}
96101
appendFileSync(".env.branch", `NEXT_API_ROOT="https://${url}/api"\n`);
97-
const fromVercel = readFileSync('.env.branch').toString();
102+
const fromVercel = readFileSync(".env.branch").toString();
98103
writeFileSync(
99104
join(projectRoot, "supabase/functions/.env"),
100-
makeFnEnv(fromVercel)
101-
)
105+
makeFnEnv(fromVercel),
106+
);
102107
};
103108

104109
const makeProductionEnv = async (vercel: Vercel, vercelToken: string) => {
@@ -117,33 +122,37 @@ const makeProductionEnv = async (vercel: Vercel, vercelToken: string) => {
117122
`vercel -t ${vercelToken} env pull --environment production .env.production`,
118123
);
119124
appendFileSync(".env.production", `NEXT_API_ROOT="https://${url}/api"\n`);
120-
const fromVercel = readFileSync('.env.production').toString();
125+
const fromVercel = readFileSync(".env.production").toString();
121126
writeFileSync(
122127
join(projectRoot, "supabase/functions/.env"),
123-
makeFnEnv(fromVercel)
124-
)
128+
makeFnEnv(fromVercel),
129+
);
125130
};
126131

127132
const main = async (variant: Variant) => {
128133
if (process.env.ROAM_BUILD_SCRIPT) {
129134
// special case: production build
130135
try {
131-
const response = execSync('curl https://discoursegraphs.com/api/supabase/env');
136+
const response = execSync(
137+
"curl https://discoursegraphs.com/api/supabase/env",
138+
);
132139
const asJson = JSON.parse(response.toString()) as Record<string, string>;
133140
writeFileSync(
134141
join(projectRoot, ".env"),
135-
Object.entries(asJson).map(([k,v])=>`${k}=${v}`).join('\n')
142+
Object.entries(asJson)
143+
.map(([k, v]) => `${k}=${v}`)
144+
.join("\n"),
136145
);
137146
return;
138147
} catch (e) {
139148
if (process.env.SUPABASE_URL && process.env.SUPABASE_PUBLISHABLE_KEY)
140149
return;
141150
throw new Error("Could not get environment from site");
142151
}
143-
}
144-
else if (
152+
} else if (
145153
process.env.HOME === "/vercel" ||
146-
process.env.GITHUB_ACTIONS !== undefined
154+
(process.env.GITHUB_ACTIONS === "true" &&
155+
process.env.GITHUB_TEST !== "test")
147156
)
148157
// Do not execute in deployment or github action.
149158
return;

packages/database/scripts/migrate.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ import { getVariant } from "@repo/database/dbDotEnv";
66
const __dirname = dirname(__filename);
77
const projectRoot = join(__dirname, "..");
88

9-
if (process.env.HOME === "/vercel" || process.env.GITHUB_ACTIONS === "true") {
9+
if (
10+
process.env.HOME === "/vercel" ||
11+
(process.env.GITHUB_ACTIONS === "true" && process.env.GITHUB_TEST !== "test")
12+
) {
1013
console.log("Skipping in production environment");
1114
process.exit(0);
1215
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { spawn, execSync } from "node:child_process";
2+
import { join, dirname } from "path";
3+
4+
const scriptDir = dirname(__filename);
5+
const projectRoot = join(scriptDir, "..");
6+
7+
if (
8+
process.env.GITHUB_ACTIONS === "true" &&
9+
process.env.GITHUB_TEST !== "test"
10+
) {
11+
console.error("Please set the GITHUB_TEST variable to 'test'");
12+
process.exit(2);
13+
}
14+
if (process.env.SUPABASE_PROJECT_ID !== "test") {
15+
console.error("Please set the SUPABASE_PROJECT_ID variable to 'test'");
16+
process.exit(2);
17+
}
18+
19+
const serve = spawn("supabase", ["functions", "serve"], {
20+
cwd: projectRoot,
21+
detached: true,
22+
stdio: ["ignore", "pipe", "inherit"],
23+
});
24+
25+
let resolveCallback: ((value: unknown) => void) | undefined = undefined;
26+
let rejectCallback: ((reason: unknown) => void) | undefined = undefined;
27+
let serveSuccess = false;
28+
let timeoutClear: NodeJS.Timeout | undefined = undefined;
29+
30+
const servingReady = new Promise((rsc, rjc) => {
31+
resolveCallback = rsc;
32+
rejectCallback = rjc;
33+
34+
// Add timeout
35+
timeoutClear = setTimeout(() => {
36+
rjc(new Error("Timeout waiting for functions to serve"));
37+
}, 30000); // 30 second timeout
38+
});
39+
40+
serve.stdout.on("data", (data: Buffer) => {
41+
const output = data.toString();
42+
console.log(`stdout: ${output}`);
43+
if (output.includes("Serving functions ")) {
44+
console.log("Found serving functions");
45+
serveSuccess = true;
46+
clearTimeout(timeoutClear);
47+
if (resolveCallback === undefined) throw new Error("did not get callback");
48+
resolveCallback(null);
49+
}
50+
});
51+
serve.on("close", () => {
52+
if (!serveSuccess && rejectCallback)
53+
rejectCallback(new Error("serve closed without being ready"));
54+
});
55+
serve.on("error", (err) => {
56+
if (rejectCallback) rejectCallback(err);
57+
});
58+
59+
const doTest = async () => {
60+
try {
61+
await servingReady;
62+
execSync("cucumber-js", {
63+
cwd: projectRoot,
64+
stdio: "inherit",
65+
});
66+
// will throw on failure
67+
} finally {
68+
if (serve.pid)
69+
try {
70+
process.kill(-serve.pid);
71+
} catch (e) {
72+
console.error("Could not kill the process");
73+
// maybe it just ended on its own.
74+
}
75+
}
76+
};
77+
78+
doTest()
79+
.then(() => {
80+
console.log("success");
81+
clearTimeout(timeoutClear);
82+
})
83+
.catch((err) => {
84+
console.error(err);
85+
clearTimeout(timeoutClear);
86+
process.exit(1);
87+
});

packages/database/src/dbDotEnv.mjs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { readFileSync, existsSync } from "node:fs";
22
import { join, dirname, basename } from "node:path";
3+
import process from "node:process";
4+
import console from "node:console";
35
import { fileURLToPath } from "node:url";
46
import dotenv from "dotenv";
57

@@ -14,7 +16,6 @@ const findRoot = () => {
1416
}
1517
return dir;
1618
};
17-
1819
export const getVariant = () => {
1920
const useDbArgPos = (process.argv || []).indexOf("--use-db");
2021
let variant =
@@ -23,9 +24,8 @@ export const getVariant = () => {
2324
: process.env["SUPABASE_USE_DB"];
2425
if (variant === undefined) {
2526
dotenv.config();
26-
const dbGlobalEnv = join(findRoot(),'.env');
27-
if (existsSync(dbGlobalEnv))
28-
dotenv.config({path: dbGlobalEnv});
27+
const dbGlobalEnv = join(findRoot(), ".env");
28+
if (existsSync(dbGlobalEnv)) dotenv.config({ path: dbGlobalEnv });
2929
variant = process.env["SUPABASE_USE_DB"];
3030
}
3131
const processHasVars =
@@ -39,7 +39,11 @@ export const getVariant = () => {
3939
throw new Error("Invalid variant: " + variant);
4040
}
4141

42-
if (process.env.HOME === "/vercel" || process.env.GITHUB_ACTIONS === "true") {
42+
if (
43+
process.env.HOME === "/vercel" ||
44+
(process.env.GITHUB_ACTIONS === "true" &&
45+
process.env.GITHUB_TEST !== "test")
46+
) {
4347
// deployment should have variables
4448
if (!processHasVars) {
4549
console.error("Missing SUPABASE variables in deployment");
@@ -76,9 +80,11 @@ export const envContents = () => {
7680
if (!path) {
7781
// Fallback to process.env when running in production environments
7882
const raw = {
83+
/* eslint-disable @typescript-eslint/naming-convention */
7984
SUPABASE_URL: process.env.SUPABASE_URL,
8085
SUPABASE_PUBLISHABLE_KEY: process.env.SUPABASE_PUBLISHABLE_KEY,
8186
NEXT_API_ROOT: process.env.NEXT_API_ROOT,
87+
/* eslint-enable @typescript-eslint/naming-convention */
8288
};
8389
return Object.fromEntries(Object.entries(raw).filter(([, v]) => !!v));
8490
}

turbo.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"GEMINI_API_KEY",
3030
"GH_CLIENT_SECRET_PROD",
3131
"GITHUB_ACTIONS",
32+
"GITHUB_TEST",
3233
"HOME",
3334
"OPENAI_API_KEY",
3435
"POSTGRES_PASSWORD",

0 commit comments

Comments
 (0)