test(live): stop the live suites inheriting the production database
Gitea Actions Runner Test / test-job (push) Successful in 1s
CI / check (push) Successful in 31s
CI / tests-unit (push) Successful in 1m57s
CI / tests-integration (push) Successful in 2m1s
CI / tests-ui (push) Successful in 2m51s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 1m42s
Gitea Actions Runner Test / test-job (push) Successful in 1s
CI / check (push) Successful in 31s
CI / tests-unit (push) Successful in 1m57s
CI / tests-integration (push) Successful in 2m1s
CI / tests-ui (push) Successful in 2m51s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 1m42s
Seven suites read .env with a bare `process.env[key] = value`, which overwrites whatever the shell already set. That made the DATABASE_URL from the production .env authoritative, so a single environment variable was enough to aim them at the live hotel database: RUN_CATALOG_AUDIT_LIVE=1 pnpm vitest run src/lib/services/catalog-audit-repair-live.test.ts Three of those suites then repair the catalog in place: catalog-audit-repair-live and catalog-repair-direct-live rewrite catalog_items and delete duplicate classnames, and clone-bulk-import-live bulk-imports every cloneable item. None of that is undoable, and nothing in their output said the target was production rather than a sandbox. Added src/test/live-env.ts with one shared loader, and pointed all seven suites at it: - Values already in the real environment win, so an explicit DATABASE_URL on the command line is always respected. - DATABASE_URL defaults to the sandbox on port 3307 rather than inheriting the production one from .env. - Anything that is not loopback is treated as production and redirected. - Reaching production requires ALLOW_PRODUCTION_LIVE_DB=1 and logs a warning saying the suite repairs the catalog. Tests in src/test/live-env.test.ts run the loader against a temporary .env so the real project file is never read, and cover the redirect, the shell override, non-loopback detection, the opt-in and quote stripping. A second block asserts each of the seven suites no longer contains an inline `process.env[...] =` assignment. Verified four of them fail against the old loader. This does not enable the suites; they stay gated behind their RUN_* flags. It only removes the possibility of them silently hitting production. Unit suite: 3330 passed, 12 skipped. Typecheck and lint clean.
This commit is contained in:
1 parent
f705c67fc7
commit
8218039c64
9 files changed
+221
-119
No files matched your search
@@ -1,9 +1,9 @@
|
||||
// @ts-nocheck
|
||||
// Runs repairMissingIcons + repairMissingNitros directly (no source comparison
|
||||
// phase, which is the slow part of runCatalogAudit). Logs progress to a file.
|
||||
import { appendFileSync, existsSync, readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import { appendFileSync } from "node:fs";
|
||||
import { beforeAll, describe, expect, it } from "vitest";
|
||||
import { loadEnvForLiveTests } from "@/test/live-env";
|
||||
|
||||
const runLive = process.env.RUN_CATALOG_AUDIT_LIVE === "1";
|
||||
const LOG = "/tmp/catalog-asset-repair.log";
|
||||
@@ -16,21 +16,7 @@ describe.skipIf(!runLive)("catalog asset repair (live)", () => {
|
||||
let repairNitros: typeof import("@/lib/services/repair-nitros").repairMissingNitros;
|
||||
|
||||
beforeAll(async () => {
|
||||
const envFile = resolve(process.cwd(), ".env");
|
||||
if (existsSync(envFile)) {
|
||||
for (const line of readFileSync(envFile, "utf8").split(/\r?\n/)) {
|
||||
const m = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*?)\s*$/);
|
||||
if (!m) continue;
|
||||
let value = m[2].trim();
|
||||
if (
|
||||
(value.startsWith('"') && value.endsWith('"')) ||
|
||||
(value.startsWith("'") && value.endsWith("'"))
|
||||
) {
|
||||
value = value.slice(1, -1);
|
||||
}
|
||||
process.env[m[1]] = value;
|
||||
}
|
||||
}
|
||||
loadEnvForLiveTests();
|
||||
process.env.SKIP_ENV_VALIDATION = "1";
|
||||
|
||||
[repairIcons, repairNitros] = await Promise.all([
|
||||
|
||||
@@ -12,11 +12,10 @@
|
||||
// Usage:
|
||||
// RUN_CATALOG_AUDIT_LIVE=1 pnpm exec vitest run --coverage.enabled=false \
|
||||
// src/lib/services/catalog-audit-live.test.ts
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { readdir } from "node:fs/promises";
|
||||
import { resolve } from "node:path";
|
||||
import { sql } from "drizzle-orm";
|
||||
import { beforeAll, describe, expect, it } from "vitest";
|
||||
import { loadEnvForLiveTests } from "@/test/live-env";
|
||||
|
||||
const runLive = process.env.RUN_CATALOG_AUDIT_LIVE === "1";
|
||||
const KNOWN_EVENT_TYPES = new Set([
|
||||
@@ -41,21 +40,7 @@ describe.skipIf(!runLive)("catalog audit live (read-only)", () => {
|
||||
beforeAll(async () => {
|
||||
// vitest's test.env injects a throwaway DATABASE_URL (root:root@:3306).
|
||||
// Replace it with the real .env value so the audit targets the hotel DB.
|
||||
const envFile = resolve(process.cwd(), ".env");
|
||||
if (existsSync(envFile)) {
|
||||
for (const line of readFileSync(envFile, "utf8").split(/\r?\n/)) {
|
||||
const m = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*?)\s*$/);
|
||||
if (!m) continue;
|
||||
let value = m[2].trim();
|
||||
if (
|
||||
(value.startsWith('"') && value.endsWith('"')) ||
|
||||
(value.startsWith("'") && value.endsWith("'"))
|
||||
) {
|
||||
value = value.slice(1, -1);
|
||||
}
|
||||
process.env[m[1]] = value;
|
||||
}
|
||||
}
|
||||
loadEnvForLiveTests();
|
||||
|
||||
const [dbMod, auditMod, typesMod] = await Promise.all([
|
||||
import("@/lib/db"),
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
// Run with the REAL DATABASE_URL loaded from .env:
|
||||
// RUN_CATALOG_AUDIT_LIVE=1 pnpm exec vitest run --coverage.enabled=false \
|
||||
// src/lib/services/catalog-audit-repair-live.test.ts
|
||||
import { appendFileSync, existsSync, readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import { appendFileSync } from "node:fs";
|
||||
import { beforeAll, describe, expect, it } from "vitest";
|
||||
import { loadEnvForLiveTests } from "@/test/live-env";
|
||||
|
||||
const runLive = process.env.RUN_CATALOG_AUDIT_LIVE === "1";
|
||||
const LOG = "/tmp/catalog-audit-repair.log";
|
||||
@@ -27,21 +27,7 @@ describe.skipIf(!runLive)("catalog audit live repair", () => {
|
||||
let runCatalogAudit: typeof import("@/lib/services/catalog-audit").runCatalogAudit;
|
||||
|
||||
beforeAll(async () => {
|
||||
const envFile = resolve(process.cwd(), ".env");
|
||||
if (existsSync(envFile)) {
|
||||
for (const line of readFileSync(envFile, "utf8").split(/\r?\n/)) {
|
||||
const m = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*?)\s*$/);
|
||||
if (!m) continue;
|
||||
let value = m[2].trim();
|
||||
if (
|
||||
(value.startsWith('"') && value.endsWith('"')) ||
|
||||
(value.startsWith("'") && value.endsWith("'"))
|
||||
) {
|
||||
value = value.slice(1, -1);
|
||||
}
|
||||
process.env[m[1]] = value;
|
||||
}
|
||||
}
|
||||
loadEnvForLiveTests();
|
||||
|
||||
const auditMod = await import("@/lib/services/catalog-audit");
|
||||
runCatalogAudit = auditMod.runCatalogAudit;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// @ts-nocheck
|
||||
// Read-only audit run that writes the full summary to a log file.
|
||||
import { appendFileSync, existsSync, readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import { appendFileSync } from "node:fs";
|
||||
import { beforeAll, describe, expect, it } from "vitest";
|
||||
import { loadEnvForLiveTests } from "@/test/live-env";
|
||||
|
||||
const runLive = process.env.RUN_CATALOG_AUDIT_LIVE === "1";
|
||||
const LOG = "/tmp/catalog-audit-summary.log";
|
||||
@@ -14,21 +14,7 @@ describe.skipIf(!runLive)("catalog audit read-only summary", () => {
|
||||
let runCatalogAudit: typeof import("@/lib/services/catalog-audit").runCatalogAudit;
|
||||
|
||||
beforeAll(async () => {
|
||||
const envFile = resolve(process.cwd(), ".env");
|
||||
if (existsSync(envFile)) {
|
||||
for (const line of readFileSync(envFile, "utf8").split(/\r?\n/)) {
|
||||
const m = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*?)\s*$/);
|
||||
if (!m) continue;
|
||||
let value = m[2].trim();
|
||||
if (
|
||||
(value.startsWith('"') && value.endsWith('"')) ||
|
||||
(value.startsWith("'") && value.endsWith("'"))
|
||||
) {
|
||||
value = value.slice(1, -1);
|
||||
}
|
||||
process.env[m[1]] = value;
|
||||
}
|
||||
}
|
||||
loadEnvForLiveTests();
|
||||
|
||||
const auditMod = await import("@/lib/services/catalog-audit");
|
||||
runCatalogAudit = auditMod.runCatalogAudit;
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
// @ts-nocheck
|
||||
// Direct repair execution against the live DB. Skips the slow clone-source
|
||||
// comparison entirely and just runs the repair functions.
|
||||
import { appendFileSync, existsSync, readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import { appendFileSync } from "node:fs";
|
||||
|
||||
const LOG = "/tmp/catalog-repair.log";
|
||||
const log = (msg: string) => {
|
||||
@@ -12,6 +11,7 @@ const log = (msg: string) => {
|
||||
|
||||
import { sql } from "drizzle-orm";
|
||||
import { beforeAll, describe, expect, it } from "vitest";
|
||||
import { loadEnvForLiveTests } from "@/test/live-env";
|
||||
|
||||
const runLive = process.env.RUN_CATALOG_AUDIT_LIVE === "1";
|
||||
|
||||
@@ -20,21 +20,7 @@ describe.skipIf(!runLive)("catalog repair direct (live)", () => {
|
||||
let repair: typeof import("@/lib/services/catalog-repair");
|
||||
|
||||
beforeAll(async () => {
|
||||
const envFile = resolve(process.cwd(), ".env");
|
||||
if (existsSync(envFile)) {
|
||||
for (const line of readFileSync(envFile, "utf8").split(/\r?\n/)) {
|
||||
const m = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*?)\s*$/);
|
||||
if (!m) continue;
|
||||
let value = m[2].trim();
|
||||
if (
|
||||
(value.startsWith('"') && value.endsWith('"')) ||
|
||||
(value.startsWith("'") && value.endsWith("'"))
|
||||
) {
|
||||
value = value.slice(1, -1);
|
||||
}
|
||||
process.env[m[1]] = value;
|
||||
}
|
||||
}
|
||||
loadEnvForLiveTests();
|
||||
|
||||
const [dbMod, repairMod] = await Promise.all([
|
||||
import("@/lib/db"),
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
// Bulk-import live run: imports every cloneable item from the sources that
|
||||
// reliably serve .nitro assets (SodaStudios, Hubbly). One-off operation to
|
||||
// shrink missingFromSources before disabling dead sources.
|
||||
import { appendFileSync, existsSync, readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import { appendFileSync } from "node:fs";
|
||||
import { beforeAll, describe, expect, it } from "vitest";
|
||||
import { loadEnvForLiveTests } from "@/test/live-env";
|
||||
|
||||
const runLive = process.env.RUN_CLONE_BULK_LIVE === "1";
|
||||
const LOG = "/tmp/clone-bulk.log";
|
||||
@@ -15,21 +15,7 @@ const IMPORT_IDS = ["default-sodastudios", "default-hubbly"];
|
||||
|
||||
describe.skipIf(!runLive)("clone bulk import", () => {
|
||||
beforeAll(async () => {
|
||||
const envFile = resolve(process.cwd(), ".env");
|
||||
if (existsSync(envFile)) {
|
||||
for (const line of readFileSync(envFile, "utf8").split(/\r?\n/)) {
|
||||
const m = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*?)\s*$/);
|
||||
if (!m) continue;
|
||||
let value = m[2].trim();
|
||||
if (
|
||||
(value.startsWith('"') && value.endsWith('"')) ||
|
||||
(value.startsWith("'") && value.endsWith("'"))
|
||||
) {
|
||||
value = value.slice(1, -1);
|
||||
}
|
||||
process.env[m[1]] = value;
|
||||
}
|
||||
}
|
||||
loadEnvForLiveTests();
|
||||
});
|
||||
|
||||
it("imports clonable items from delivering sources", async () => {
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
// Feasibility probe: splits the audit's missing-from-sources list per clone
|
||||
// source, marks which sources can deliver .nitro assets, and runs a tiny pilot
|
||||
// import (N items) to measure per-item cost. Writes a report to /tmp.
|
||||
import { appendFileSync, existsSync, readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import { appendFileSync } from "node:fs";
|
||||
import { beforeAll, describe, expect, it } from "vitest";
|
||||
import { loadEnvForLiveTests } from "@/test/live-env";
|
||||
|
||||
const runLive = process.env.RUN_CLONE_FEASIBILITY_LIVE === "1";
|
||||
const LOG = "/tmp/clone-feasibility.log";
|
||||
@@ -12,21 +12,7 @@ const log = (msg: string) => appendFileSync(LOG, `${msg}\n`);
|
||||
|
||||
describe.skipIf(!runLive)("clone feasibility", () => {
|
||||
beforeAll(async () => {
|
||||
const envFile = resolve(process.cwd(), ".env");
|
||||
if (existsSync(envFile)) {
|
||||
for (const line of readFileSync(envFile, "utf8").split(/\r?\n/)) {
|
||||
const m = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*?)\s*$/);
|
||||
if (!m) continue;
|
||||
let value = m[2].trim();
|
||||
if (
|
||||
(value.startsWith('"') && value.endsWith('"')) ||
|
||||
(value.startsWith("'") && value.endsWith("'"))
|
||||
) {
|
||||
value = value.slice(1, -1);
|
||||
}
|
||||
process.env[m[1]] = value;
|
||||
}
|
||||
}
|
||||
loadEnvForLiveTests();
|
||||
});
|
||||
|
||||
it("reports per-source clonable counts + pilot", async () => {
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join, resolve } from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
|
||||
import { loadEnvForLiveTests } from "./live-env";
|
||||
|
||||
const ORIGINAL_ENV = { ...process.env };
|
||||
const SANDBOX = "mysql://[email protected]:3307/habbo_sandbox?charset=utf8mb4";
|
||||
|
||||
/**
|
||||
* Run the helper against a temporary .env so the real project file is never
|
||||
* read and the ambient environment cannot leak in.
|
||||
*/
|
||||
function runWithDotEnv(dotEnv: string | null, shell: Record<string, string>) {
|
||||
const dir = mkdtempSync(join(tmpdir(), "live-env-test-"));
|
||||
const previousCwd = process.cwd();
|
||||
try {
|
||||
if (dotEnv !== null) writeFileSync(join(dir, ".env"), dotEnv);
|
||||
// process.chdir keeps process.cwd() inside the helper pointing at dir.
|
||||
process.chdir(dir);
|
||||
for (const key of Object.keys(process.env)) delete process.env[key];
|
||||
Object.assign(process.env, shell);
|
||||
loadEnvForLiveTests();
|
||||
return { ...process.env };
|
||||
} finally {
|
||||
process.chdir(previousCwd);
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
for (const key of Object.keys(process.env)) delete process.env[key];
|
||||
Object.assign(process.env, ORIGINAL_ENV);
|
||||
});
|
||||
|
||||
describe("loadEnvForLiveTests", () => {
|
||||
// The suites these protect rewrite the catalog, delete duplicate
|
||||
// classnames and bulk-import thousands of rows. A single env var used to be
|
||||
// enough to aim them at the live hotel database.
|
||||
it("never inherits a production database from .env", () => {
|
||||
const env = runWithDotEnv(
|
||||
[
|
||||
"DATABASE_URL='mysql://cms:[email protected]:3306/habbo'",
|
||||
"HOTEL_NAME='Test Hotel'",
|
||||
].join("\n"),
|
||||
{},
|
||||
);
|
||||
expect(env.DATABASE_URL).toBe(SANDBOX);
|
||||
// Non-database settings still load, so the suites stay usable.
|
||||
expect(env.HOTEL_NAME).toBe("Test Hotel");
|
||||
});
|
||||
|
||||
it("keeps an explicit shell override", () => {
|
||||
const env = runWithDotEnv(
|
||||
"DATABASE_URL='mysql://cms:[email protected]:3306/habbo'",
|
||||
{
|
||||
DATABASE_URL: "mysql://[email protected]:3399/other?charset=utf8mb4",
|
||||
},
|
||||
);
|
||||
expect(env.DATABASE_URL).toBe(
|
||||
"mysql://[email protected]:3399/other?charset=utf8mb4",
|
||||
);
|
||||
});
|
||||
|
||||
it("treats any non-loopback database as production", () => {
|
||||
const env = runWithDotEnv(null, {
|
||||
DATABASE_URL: "mysql://user:[email protected]:3306/habbo",
|
||||
});
|
||||
expect(env.DATABASE_URL).toBe(SANDBOX);
|
||||
});
|
||||
|
||||
it("allows production only behind an explicit opt-in", () => {
|
||||
const production = "mysql://cms:[email protected]:3306/habbo";
|
||||
const env = runWithDotEnv(null, {
|
||||
DATABASE_URL: production,
|
||||
ALLOW_PRODUCTION_LIVE_DB: "1",
|
||||
});
|
||||
expect(env.DATABASE_URL).toBe(production);
|
||||
});
|
||||
|
||||
it("stays on the sandbox when the opt-in is set but the URL is safe", () => {
|
||||
const env = runWithDotEnv(null, {
|
||||
DATABASE_URL: "mysql://[email protected]:3307/habbo_sandbox?charset=utf8mb4",
|
||||
ALLOW_PRODUCTION_LIVE_DB: "1",
|
||||
});
|
||||
expect(env.DATABASE_URL).toBe(SANDBOX);
|
||||
});
|
||||
|
||||
it("supplies a sandbox when .env has no database at all", () => {
|
||||
const env = runWithDotEnv("HOTEL_NAME='Test Hotel'", {});
|
||||
expect(env.DATABASE_URL).toBe(SANDBOX);
|
||||
});
|
||||
|
||||
it("strips quotes the way the previous inline loader did", () => {
|
||||
const env = runWithDotEnv(
|
||||
['AUTH_SECRET="quoted-secret"', "OTHER='single'"].join("\n"),
|
||||
{},
|
||||
);
|
||||
expect(env.AUTH_SECRET).toBe("quoted-secret");
|
||||
expect(env.OTHER).toBe("single");
|
||||
});
|
||||
});
|
||||
|
||||
describe("live suites no longer inline the .env loader", () => {
|
||||
const suites = [
|
||||
"catalog-audit-repair-live",
|
||||
"catalog-audit-live",
|
||||
"clone-bulk-import-live",
|
||||
"clone-feasibility-live",
|
||||
"catalog-repair-direct-live",
|
||||
"catalog-asset-repair-live",
|
||||
"catalog-audit-summary-live",
|
||||
];
|
||||
|
||||
it.each(suites)("%s delegates to the shared loader", (name) => {
|
||||
const source = readFileSync(
|
||||
resolve(process.cwd(), "src/lib/services", `${name}.test.ts`),
|
||||
"utf8",
|
||||
);
|
||||
// A bare assignment would overwrite an operator's explicit DATABASE_URL,
|
||||
// which is how production used to win.
|
||||
expect(source).not.toMatch(/process\.env\[m\[1\]\]\s*=/);
|
||||
expect(source).toContain("loadEnvForLiveTests()");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,75 @@
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
|
||||
/**
|
||||
* Sandbox database for the live suites. Production runs on port 3306; the
|
||||
* throwaway MariaDB used by the rehearsal suites listens on 3307.
|
||||
*/
|
||||
export const SANDBOX_DATABASE_URL =
|
||||
"mysql://[email protected]:3307/habbo_sandbox?charset=utf8mb4";
|
||||
|
||||
/**
|
||||
* Load .env for the live suites without ever pointing them at production.
|
||||
*
|
||||
* Every live suite used to read .env with a bare
|
||||
* `process.env[key] = value`, which overwrites whatever the shell already set.
|
||||
* That made the DATABASE_URL from the production .env authoritative: setting
|
||||
* RUN_CATALOG_AUDIT_LIVE=1 pointed three suites — catalog-audit-repair-live,
|
||||
* catalog-repair-direct-live and clone-bulk-import-live — at the live hotel
|
||||
* database, where they rewrite the catalog, delete duplicate classnames and
|
||||
* bulk-import thousands of rows. The failure mode was silent: the gate is a
|
||||
* single environment variable, and nothing in the suite said the target was
|
||||
* production.
|
||||
*
|
||||
* Rules now:
|
||||
* 1. Values already present in the real environment win, so an explicit
|
||||
* DATABASE_URL on the command line is always respected.
|
||||
* 2. DATABASE_URL defaults to the sandbox, never to production.
|
||||
* 3. Targeting production requires ALLOW_PRODUCTION_LIVE_DB=1 and says so
|
||||
* loudly, because it is destructive and not undoable.
|
||||
*/
|
||||
export function loadEnvForLiveTests(): void {
|
||||
const allowProduction = process.env.ALLOW_PRODUCTION_LIVE_DB === "1";
|
||||
const envFile = resolve(process.cwd(), ".env");
|
||||
|
||||
if (existsSync(envFile)) {
|
||||
for (const line of readFileSync(envFile, "utf8").split(/\r?\n/)) {
|
||||
const match = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*?)\s*$/);
|
||||
if (!match) continue;
|
||||
let value = match[2].trim();
|
||||
if (
|
||||
(value.startsWith('"') && value.endsWith('"')) ||
|
||||
(value.startsWith("'") && value.endsWith("'"))
|
||||
) {
|
||||
value = value.slice(1, -1);
|
||||
}
|
||||
// Already-set values win: the shell is a deliberate override.
|
||||
if (process.env[match[1]] === undefined) {
|
||||
process.env[match[1]] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const databaseUrl = process.env.DATABASE_URL ?? "";
|
||||
const targetsProduction =
|
||||
databaseUrl.includes(":3306") ||
|
||||
(databaseUrl.includes("@") && !databaseUrl.includes("127.0.0.1"));
|
||||
|
||||
if (allowProduction) {
|
||||
if (targetsProduction) {
|
||||
console.warn(
|
||||
"[live-test] ALLOW_PRODUCTION_LIVE_DB=1 and DATABASE_URL points at " +
|
||||
"a remote database. This suite repairs the catalog in place.",
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (targetsProduction || !databaseUrl) {
|
||||
process.env.DATABASE_URL = SANDBOX_DATABASE_URL;
|
||||
console.warn(
|
||||
`[live-test] redirected DATABASE_URL to the sandbox at ${SANDBOX_DATABASE_URL}. ` +
|
||||
"Set ALLOW_PRODUCTION_LIVE_DB=1 to override.",
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user