@cedarjs/testing 7.0.0-canary.3181 → 7.0.0-canary.3188
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -20,4 +20,31 @@ export declare function redactDatabaseUrl(url: string): string;
|
|
|
20
20
|
* yet), this is a no-op — Prisma itself will surface the problem.
|
|
21
21
|
*/
|
|
22
22
|
export declare function checkTestDatabaseUrlMatchesProvider(databaseUrl: string, usedFallback: boolean): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Guards `cedar test api`'s destructive database reset by *identity*, not
|
|
25
|
+
* just provider: refuses to run when the resolved test database is the same
|
|
26
|
+
* database as `DATABASE_URL` — same host, port, and database name, or the
|
|
27
|
+
* same sqlite file.
|
|
28
|
+
*
|
|
29
|
+
* This is deliberately the only check, rather than also requiring the
|
|
30
|
+
* database's *name* to look test-like (e.g. containing "test" or "e2e"). A
|
|
31
|
+
* naming convention doesn't hold for managed Postgres providers with a
|
|
32
|
+
* fixed default database name shared by every project — every Supabase
|
|
33
|
+
* project's database is named `postgres`, for instance — so it can't tell a
|
|
34
|
+
* real project's database apart from a dedicated test one by name alone.
|
|
35
|
+
* Whether the two connection strings resolve to the same database is the
|
|
36
|
+
* one fact that holds across every provider.
|
|
37
|
+
*
|
|
38
|
+
* When there's no `DATABASE_URL` at all to compare against, this fails
|
|
39
|
+
* closed rather than skipping the check: an explicit `TEST_DATABASE_URL`
|
|
40
|
+
* with nothing to confirm it's not the app's real database is refused,
|
|
41
|
+
* since there'd be no way to tell it apart from one that is. Cedar's own
|
|
42
|
+
* generated sqlite fallback (`usedFallback`) is exempt from that fail-closed
|
|
43
|
+
* refusal — it's a path only Cedar controls, so there's no need to force a
|
|
44
|
+
* `DATABASE_URL` to exist just to permit it — but it still runs through the
|
|
45
|
+
* same-database comparison below whenever a `DATABASE_URL` is present, so a
|
|
46
|
+
* project whose real database happens to live at that same generated path
|
|
47
|
+
* is still caught.
|
|
48
|
+
*/
|
|
49
|
+
export declare function checkTestDatabaseIdentity(testDatabaseUrl: string, mainDatabaseUrl: string | undefined, usedFallback: boolean): void;
|
|
23
50
|
//# sourceMappingURL=checkTestDatabase.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checkTestDatabase.d.ts","sourceRoot":"","sources":["../../src/api/checkTestDatabase.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"checkTestDatabase.d.ts","sourceRoot":"","sources":["../../src/api/checkTestDatabase.ts"],"names":[],"mappings":"AAoBA;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAIrD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,mCAAmC,CACvD,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,OAAO,iBAmCtB;AAuHD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,yBAAyB,CACvC,eAAe,EAAE,MAAM,EACvB,eAAe,EAAE,MAAM,GAAG,SAAS,EACnC,YAAY,EAAE,OAAO,QAkCtB"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import path from "node:path";
|
|
1
2
|
import { getPrismaDatasourceProvider } from "@cedarjs/project-config";
|
|
2
3
|
const PROVIDER_URL_SCHEMES = {
|
|
3
4
|
sqlite: ["file:"],
|
|
@@ -38,7 +39,108 @@ async function checkTestDatabaseUrlMatchesProvider(databaseUrl, usedFallback) {
|
|
|
38
39
|
${reason}`
|
|
39
40
|
);
|
|
40
41
|
}
|
|
42
|
+
const DEFAULT_PORTS = {
|
|
43
|
+
"postgresql:": "5432",
|
|
44
|
+
"mysql:": "3306",
|
|
45
|
+
"sqlserver:": "1433",
|
|
46
|
+
"mongodb:": "27017"
|
|
47
|
+
};
|
|
48
|
+
function normalizeScheme(scheme) {
|
|
49
|
+
return scheme === "postgres:" ? "postgresql:" : scheme;
|
|
50
|
+
}
|
|
51
|
+
function normalizeHost(host) {
|
|
52
|
+
return host.toLowerCase().replace(/\.$/, "");
|
|
53
|
+
}
|
|
54
|
+
function parseSqlServerIdentity(url) {
|
|
55
|
+
const [authority, ...params] = url.split(";");
|
|
56
|
+
const match = authority.match(
|
|
57
|
+
/^([a-z][a-z0-9+.-]*:)\/\/([^:/?#]+)(?::(\d+))?/i
|
|
58
|
+
);
|
|
59
|
+
if (!match) {
|
|
60
|
+
return void 0;
|
|
61
|
+
}
|
|
62
|
+
const [, scheme, host, port] = match;
|
|
63
|
+
const databaseParam = params.find(
|
|
64
|
+
(param) => /^\s*(?:database|initial\s+catalog)\s*=/i.test(param)
|
|
65
|
+
);
|
|
66
|
+
const database = databaseParam?.slice(databaseParam.indexOf("=") + 1).trim();
|
|
67
|
+
return {
|
|
68
|
+
scheme: scheme.toLowerCase(),
|
|
69
|
+
host: normalizeHost(host),
|
|
70
|
+
port,
|
|
71
|
+
database
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function parseUriIdentity(url) {
|
|
75
|
+
try {
|
|
76
|
+
const parsed = new URL(url);
|
|
77
|
+
const database = decodeURIComponent(parsed.pathname.replace(/^\//, "")).split("/")[0] || void 0;
|
|
78
|
+
return {
|
|
79
|
+
scheme: parsed.protocol.toLowerCase(),
|
|
80
|
+
host: parsed.hostname ? normalizeHost(parsed.hostname) : void 0,
|
|
81
|
+
port: parsed.port || void 0,
|
|
82
|
+
database
|
|
83
|
+
};
|
|
84
|
+
} catch {
|
|
85
|
+
return void 0;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function parseDatabaseIdentity(url) {
|
|
89
|
+
const scheme = getUrlScheme(url);
|
|
90
|
+
if (!scheme) {
|
|
91
|
+
return void 0;
|
|
92
|
+
}
|
|
93
|
+
if (scheme === "file:") {
|
|
94
|
+
return { scheme, database: path.resolve(url.slice("file:".length)) };
|
|
95
|
+
}
|
|
96
|
+
if (scheme === "sqlserver:") {
|
|
97
|
+
return parseSqlServerIdentity(url);
|
|
98
|
+
}
|
|
99
|
+
return parseUriIdentity(url);
|
|
100
|
+
}
|
|
101
|
+
function identitiesMatch(a, b) {
|
|
102
|
+
const schemeA = normalizeScheme(a.scheme);
|
|
103
|
+
const schemeB = normalizeScheme(b.scheme);
|
|
104
|
+
if (schemeA !== schemeB) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
if (schemeA === "file:") {
|
|
108
|
+
return !!a.database && a.database === b.database;
|
|
109
|
+
}
|
|
110
|
+
if (!a.database || !b.database || a.database !== b.database) {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
const portA = a.port || DEFAULT_PORTS[schemeA] || "";
|
|
114
|
+
const portB = b.port || DEFAULT_PORTS[schemeA] || "";
|
|
115
|
+
return (a.host ?? "") === (b.host ?? "") && portA === portB;
|
|
116
|
+
}
|
|
117
|
+
function checkTestDatabaseIdentity(testDatabaseUrl, mainDatabaseUrl, usedFallback) {
|
|
118
|
+
if (!mainDatabaseUrl) {
|
|
119
|
+
if (usedFallback) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const redactedTestUrl = redactDatabaseUrl(testDatabaseUrl);
|
|
123
|
+
throw new Error(
|
|
124
|
+
`TEST_DATABASE_URL (${redactedTestUrl}) is set, but there's no DATABASE_URL to confirm it isn't the same database your app actually uses. Refusing to run a destructive reset without that confirmation.
|
|
125
|
+
|
|
126
|
+
Set DATABASE_URL (even to a placeholder) so Cedar can verify TEST_DATABASE_URL points somewhere different.`
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
const sameRawUrl = mainDatabaseUrl === testDatabaseUrl;
|
|
130
|
+
const testIdentity = parseDatabaseIdentity(testDatabaseUrl);
|
|
131
|
+
const mainIdentity = parseDatabaseIdentity(mainDatabaseUrl);
|
|
132
|
+
const sameParsedIdentity = !!testIdentity && !!mainIdentity && identitiesMatch(testIdentity, mainIdentity);
|
|
133
|
+
if (sameRawUrl || sameParsedIdentity) {
|
|
134
|
+
const redactedTestUrl = redactDatabaseUrl(testDatabaseUrl);
|
|
135
|
+
throw new Error(
|
|
136
|
+
`TEST_DATABASE_URL (${redactedTestUrl}) points at the same database as DATABASE_URL. Refusing to run a destructive reset against your app's main database.
|
|
137
|
+
|
|
138
|
+
Set TEST_DATABASE_URL to a dedicated test database.`
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
41
142
|
export {
|
|
143
|
+
checkTestDatabaseIdentity,
|
|
42
144
|
checkTestDatabaseUrlMatchesProvider,
|
|
43
145
|
redactDatabaseUrl
|
|
44
146
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CedarApiVitestEnv.d.ts","sourceRoot":"","sources":["../../../src/api/vitest/CedarApiVitestEnv.ts"],"names":[],"mappings":"AAEA,OAAO,2BAA2B,CAAA;AAElC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"CedarApiVitestEnv.d.ts","sourceRoot":"","sources":["../../../src/api/vitest/CedarApiVitestEnv.ts"],"names":[],"mappings":"AAEA,OAAO,2BAA2B,CAAA;AAElC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAWtD,QAAA,MAAM,yBAAyB,EAAE,WAwFhC,CAAA;AAED,eAAe,yBAAyB,CAAA"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import "dotenv-defaults/config.js";
|
|
3
3
|
import execa from "execa";
|
|
4
|
-
import { getPaths } from "@cedarjs/project-config";
|
|
4
|
+
import { getConfig, getPaths } from "@cedarjs/project-config";
|
|
5
5
|
import { getPackageManager } from "@cedarjs/project-config/packageManager";
|
|
6
6
|
import {
|
|
7
|
+
checkTestDatabaseIdentity,
|
|
7
8
|
checkTestDatabaseUrlMatchesProvider,
|
|
8
9
|
redactDatabaseUrl
|
|
9
10
|
} from "../checkTestDatabase.js";
|
|
@@ -18,6 +19,7 @@ const CedarApiVitestEnvironment = {
|
|
|
18
19
|
};
|
|
19
20
|
}
|
|
20
21
|
const cedarPaths = getPaths();
|
|
22
|
+
const mainDatabaseUrl = "CEDAR_APP_DATABASE_URL" in process.env ? process.env.CEDAR_APP_DATABASE_URL : process.env.DATABASE_URL;
|
|
21
23
|
const defaultDb = `file:${path.join(cedarPaths.generated.base, "test.db")}`;
|
|
22
24
|
const usedFallback = !process.env.TEST_DATABASE_URL;
|
|
23
25
|
process.env.DATABASE_URL = process.env.TEST_DATABASE_URL || defaultDb;
|
|
@@ -25,16 +27,26 @@ const CedarApiVitestEnvironment = {
|
|
|
25
27
|
process.env.DATABASE_URL,
|
|
26
28
|
usedFallback
|
|
27
29
|
);
|
|
30
|
+
const testConfig = getConfig().test;
|
|
31
|
+
checkTestDatabaseIdentity(
|
|
32
|
+
process.env.DATABASE_URL,
|
|
33
|
+
mainDatabaseUrl,
|
|
34
|
+
usedFallback
|
|
35
|
+
);
|
|
28
36
|
console.log(
|
|
29
37
|
`Setting up test database: ${redactDatabaseUrl(process.env.DATABASE_URL)}`
|
|
30
38
|
);
|
|
31
39
|
const command = process.env.TEST_DATABASE_STRATEGY === "reset" ? ["prisma", "migrate", "reset", "--force"] : ["prisma", "db", "push", "--force-reset", "--accept-data-loss"];
|
|
32
40
|
const pm = getPackageManager();
|
|
33
41
|
const pmExec = pm === "pnpm" ? pm : "npx";
|
|
42
|
+
const env = { ...process.env };
|
|
43
|
+
if (testConfig.autoConsentToDbReset && !("PRISMA_USER_CONSENT_FOR_DANGEROUS_AI_ACTION" in env)) {
|
|
44
|
+
env.PRISMA_USER_CONSENT_FOR_DANGEROUS_AI_ACTION = "Cedar auto-consent: cedar.toml's [test] autoConsentToDbReset is true, and the reset target passed Cedar's test-database identity guard.";
|
|
45
|
+
}
|
|
34
46
|
execa.sync(pmExec, ["cedar", ...command], {
|
|
35
47
|
cwd: cedarPaths.api.base,
|
|
36
48
|
stdio: "inherit",
|
|
37
|
-
env
|
|
49
|
+
env
|
|
38
50
|
});
|
|
39
51
|
return {
|
|
40
52
|
teardown() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cedarjs/testing",
|
|
3
|
-
"version": "7.0.0-canary.
|
|
3
|
+
"version": "7.0.0-canary.3188",
|
|
4
4
|
"description": "Tools, wrappers and configuration for testing a Cedar project.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -54,9 +54,9 @@
|
|
|
54
54
|
"test:watch": "vitest watch"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@cedarjs/context": "7.0.0-canary.
|
|
58
|
-
"@cedarjs/graphql-server": "7.0.0-canary.
|
|
59
|
-
"@cedarjs/project-config": "7.0.0-canary.
|
|
57
|
+
"@cedarjs/context": "7.0.0-canary.3188",
|
|
58
|
+
"@cedarjs/graphql-server": "7.0.0-canary.3188",
|
|
59
|
+
"@cedarjs/project-config": "7.0.0-canary.3188",
|
|
60
60
|
"@testing-library/dom": "10.4.1",
|
|
61
61
|
"@testing-library/jest-dom": "6.9.1",
|
|
62
62
|
"@testing-library/react": "16.3.3",
|
|
@@ -73,19 +73,19 @@
|
|
|
73
73
|
},
|
|
74
74
|
"devDependencies": {
|
|
75
75
|
"@arethetypeswrong/cli": "0.18.5",
|
|
76
|
-
"@cedarjs/auth": "7.0.0-canary.
|
|
77
|
-
"@cedarjs/framework-tools": "7.0.0-canary.
|
|
78
|
-
"@cedarjs/router": "7.0.0-canary.
|
|
79
|
-
"@cedarjs/web": "7.0.0-canary.
|
|
76
|
+
"@cedarjs/auth": "7.0.0-canary.3188",
|
|
77
|
+
"@cedarjs/framework-tools": "7.0.0-canary.3188",
|
|
78
|
+
"@cedarjs/router": "7.0.0-canary.3188",
|
|
79
|
+
"@cedarjs/web": "7.0.0-canary.3188",
|
|
80
80
|
"concurrently": "9.2.4",
|
|
81
81
|
"publint": "0.3.24",
|
|
82
82
|
"typescript": "5.9.3",
|
|
83
83
|
"vitest": "4.1.11"
|
|
84
84
|
},
|
|
85
85
|
"peerDependencies": {
|
|
86
|
-
"@cedarjs/auth": "7.0.0-canary.
|
|
87
|
-
"@cedarjs/router": "7.0.0-canary.
|
|
88
|
-
"@cedarjs/web": "7.0.0-canary.
|
|
86
|
+
"@cedarjs/auth": "7.0.0-canary.3188",
|
|
87
|
+
"@cedarjs/router": "7.0.0-canary.3188",
|
|
88
|
+
"@cedarjs/web": "7.0.0-canary.3188",
|
|
89
89
|
"vitest": "4.1.11"
|
|
90
90
|
},
|
|
91
91
|
"peerDependenciesMeta": {
|