Files

773 lines
30 KiB
TypeScript
Raw Permalink Normal View History

2026-08-27 21:09:14 +00:00
import { SQL } from "bun";
import { afterAll, beforeEach, describe, expect, test } from "bun:test";
import { isWindows, tempDir } from "harness";
import { unlinkSync } from "js/node/fs/export-star-from";
declare module "bun" {
namespace SQL {
export interface PostgresOrMySQLOptions {
sslMode?: number;
}
}
}
describe("SQL adapter environment variable precedence", () => {
const originalEnv = { ...process.env };
// prettier-ignore
const SQL_ENV_VARS = [
'DATABASE_URL', 'DATABASEURL',
'TLS_DATABASE_URL',
'POSTGRES_URL', 'PGURL', 'PG_URL',
'TLS_POSTGRES_DATABASE_URL',
'MYSQL_URL', 'MYSQLURL',
'TLS_MYSQL_DATABASE_URL',
'MARIADB_URL', 'MARIADBURL',
'TLS_MARIADB_DATABASE_URL',
'SQLITE_URL', 'SQLITEURL',
'PGHOST', 'PGUSER', 'PGPASSWORD', 'PGDATABASE', 'PGPORT',
'PGSSLMODE', 'PG_SSLMODE',
'MYSQL_HOST', 'MYSQL_USER', 'MYSQL_PASSWORD', 'MYSQL_DATABASE', 'MYSQL_PORT'
];
beforeEach(() => {
for (const key of Object.keys(process.env).concat(...Object.keys(Bun.env), ...Object.keys(import.meta.env))) {
delete process.env[key];
delete Bun.env[key];
delete import.meta.env[key];
}
for (const key in originalEnv) {
process.env[key] = originalEnv[key];
Bun.env[key] = originalEnv[key];
import.meta.env[key] = originalEnv[key];
}
for (const key of SQL_ENV_VARS) {
delete process.env[key];
delete Bun.env[key];
delete import.meta.env[key];
}
});
afterAll(() => {
for (const key of Object.keys(process.env).concat(...Object.keys(Bun.env), ...Object.keys(import.meta.env))) {
delete process.env[key];
delete Bun.env[key];
delete import.meta.env[key];
}
for (const key in originalEnv) {
process.env[key] = originalEnv[key];
Bun.env[key] = originalEnv[key];
import.meta.env[key] = originalEnv[key];
}
for (const key of SQL_ENV_VARS) {
delete process.env[key];
delete Bun.env[key];
delete import.meta.env[key];
}
});
test("should not prioritize DATABASE_URL over explicit options (issue #22147)", () => {
process.env.DATABASE_URL = "foo_url";
const options = new SQL({
hostname: "bar_url",
username: "postgres",
password: "postgres",
port: 5432,
});
expect(options.options.adapter).toBe("postgres");
expect(options.options.hostname).toBe("bar_url");
expect(options.options.port).toBe(5432);
expect(options.options.username).toBe("postgres");
});
test("should only read PostgreSQL env vars when adapter is postgres", () => {
process.env.PGHOST = "pg-host";
process.env.PGUSER = "pg-user";
process.env.PGPASSWORD = "pg-pass";
process.env.MYSQL_URL = "mysql://mysql-host/db";
const options = new SQL({
adapter: "postgres",
});
expect(options.options.hostname).toBe("pg-host");
expect(options.options.username).toBe("pg-user");
expect(options.options.password).toBe("pg-pass");
// Should not use MYSQL_URL
expect(options.options.hostname).not.toBe("mysql-host");
});
test("should only read MySQL env vars when adapter is mysql", () => {
process.env.PGHOST = "pg-host";
process.env.PGUSER = "pg-user";
process.env.MYSQL_URL = "mysql://mysql-host/db";
const options = new SQL({
adapter: "mysql",
});
// Should use MYSQL_URL and not read PostgreSQL env vars
expect(options.options.hostname).toBe("mysql-host");
expect(options.options.username).not.toBe("pg-user");
});
test("should infer postgres adapter from postgres:// protocol", () => {
const options = new SQL("postgres://user:pass@host:5432/db");
expect(options.options.adapter).toBe("postgres");
});
test("should infer mysql adapter from mysql:// protocol", () => {
const options = new SQL("mysql://user:pass@host:3306/db");
expect(options.options.adapter).toBe("mysql");
});
test("should default to postgres when no protocol specified", () => {
const options = new SQL("user:pass@host/db");
expect(options.options.adapter).toBe("postgres");
});
test("adapter-specific env vars should take precedence over generic ones", () => {
process.env.USER = "generic-user";
process.env.PGUSER = "postgres-user";
const options = new SQL({
adapter: "postgres",
});
expect(options.options.username).toBe("postgres-user");
});
test("should infer mysql adapter from MYSQL_URL env var", () => {
process.env.MYSQL_URL = "mysql://user:pass@host:3306/db";
const options = new SQL();
expect(options.options.adapter).toBe("mysql");
expect(options.options.hostname).toBe("host");
expect(options.options.port).toBe(3306);
});
test("should default to port 3306 for MySQL when no port specified", () => {
process.env.MYSQL_URL = "mysql://user:pass@host/db";
const options = new SQL();
expect(options.options.adapter).toBe("mysql");
expect(options.options.hostname).toBe("host");
expect(options.options.port).toBe(3306); // Should default to MySQL port
});
test("should default to port 3306 for explicit MySQL adapter", () => {
const options = new SQL({
adapter: "mysql",
hostname: "localhost",
});
expect(options.options.adapter).toBe("mysql");
expect(options.options.port).toBe(3306); // Should default to MySQL port
});
test("should infer postgres adapter from POSTGRES_URL env var", () => {
process.env.POSTGRES_URL = "postgres://user:pass@host:5432/db";
const options = new SQL();
expect(options.options.adapter).toBe("postgres");
expect(options.options.hostname).toBe("host");
expect(options.options.port).toBe(5432);
});
test("POSTGRES_URL should take precedence over MYSQL_URL", () => {
process.env.POSTGRES_URL = "postgres://pg-host:5432/pgdb";
process.env.MYSQL_URL = "mysql://mysql-host:3306/mysqldb";
const options = new SQL();
expect(options.options.adapter).toBe("postgres");
expect(options.options.hostname).toBe("pg-host");
expect(options.options.port).toBe(5432);
});
test("should infer mysql from MYSQL_URL even without protocol", () => {
process.env.MYSQL_URL = "root@localhost:3306/test";
const options = new SQL();
expect(options.options.adapter).toBe("mysql");
expect(options.options.hostname).toBe("localhost");
expect(options.options.port).toBe(3306);
expect(options.options.username).toBe("root");
});
test("should infer postgres from POSTGRES_URL even without protocol", () => {
process.env.POSTGRES_URL = "user@localhost:5432/test";
const options = new SQL();
expect(options.options.adapter).toBe("postgres");
expect(options.options.hostname).toBe("localhost");
expect(options.options.port).toBe(5432);
expect(options.options.username).toBe("user");
});
test("environment variable name should override protocol (PGURL with mysql protocol should be postgres)", () => {
process.env.PGURL = "mysql://host:3306/db";
const options = new SQL();
expect(options.options.adapter).toBe("postgres");
expect(options.options.hostname).toBe("host");
expect(options.options.port).toBe(3306);
});
test("environment variable name should override protocol (MYSQL_URL with postgres protocol should be mysql)", () => {
process.env.MYSQL_URL = "postgres://host:5432/db";
const options = new SQL();
expect(options.options.adapter).toBe("mysql");
expect(options.options.hostname).toBe("host");
expect(options.options.port).toBe(5432);
});
test("should use MySQL-specific environment variables", () => {
process.env.MYSQL_HOST = "mysql-server";
process.env.MYSQL_PORT = "3307";
process.env.MYSQL_USER = "admin";
process.env.MYSQL_PASSWORD = "secret";
process.env.MYSQL_DATABASE = "production";
const options = new SQL({ adapter: "mysql" });
expect(options.options.adapter).toBe("mysql");
expect(options.options.hostname).toBe("mysql-server");
expect(options.options.port).toBe(3307);
expect(options.options.username).toBe("admin");
expect(options.options.password).toBe("secret");
expect(options.options.database).toBe("production");
});
test("MySQL-specific env vars should take precedence over generic ones", () => {
process.env.USER = "generic-user";
process.env.MYSQL_USER = "mysql-user";
const options = new SQL({ adapter: "mysql" });
expect(options.options.username).toBe("mysql-user");
});
test("should default to database name 'mysql' for MySQL adapter", () => {
const options = new SQL({ adapter: "mysql", hostname: "localhost" });
expect(options.options.adapter).toBe("mysql");
expect(options.options.database).toBe("mysql");
});
test("should default to username as database name for PostgreSQL adapter", () => {
const options = new SQL({ adapter: "postgres", hostname: "localhost", username: "testuser" });
expect(options.options.adapter).toBe("postgres");
expect(options.options.database).toBe("testuser");
});
test("should infer mysql adapter from TLS_MYSQL_DATABASE_URL", () => {
process.env.TLS_MYSQL_DATABASE_URL = "mysql://user:pass@host:3306/db";
const options = new SQL();
expect(options.options.adapter).toBe("mysql");
expect(options.options.hostname).toBe("host");
expect(options.options.port).toBe(3306);
expect(options.options.sslMode).toBe(2); // SSLMode.require
});
test("should infer postgres adapter from TLS_POSTGRES_DATABASE_URL", () => {
process.env.TLS_POSTGRES_DATABASE_URL = "postgres://user:pass@host:5432/db";
const options = new SQL();
expect(options.options.adapter).toBe("postgres");
expect(options.options.hostname).toBe("host");
expect(options.options.port).toBe(5432);
expect(options.options.sslMode).toBe(2); // SSLMode.require
});
test("should infer adapter from TLS_DATABASE_URL using protocol", () => {
process.env.TLS_DATABASE_URL = "mysql://user:pass@host:3306/db";
const options = new SQL();
expect(options.options.adapter).toBe("mysql");
expect(options.options.hostname).toBe("host");
expect(options.options.port).toBe(3306);
expect(options.options.sslMode).toBe(2); // SSLMode.require
});
describe("PGSSLMODE", () => {
test.each([
["disable", 0],
["allow", 1],
["prefer", 1],
["require", 2],
["verify-ca", 3],
["verify-full", 4],
])("PGSSLMODE=%s is honoured alongside PGHOST/PGPORT/...", (mode, expected) => {
process.env.PGHOST = "pg-host";
process.env.PGPORT = "5432";
process.env.PGUSER = "pg-user";
process.env.PGPASSWORD = "pg-pass";
process.env.PGDATABASE = "pg-db";
process.env.PGSSLMODE = mode;
const options = new SQL({ adapter: "postgres" });
expect(options.options).toMatchObject({
adapter: "postgres",
hostname: "pg-host",
port: 5432,
username: "pg-user",
database: "pg-db",
sslMode: expected,
});
});
test("PGSSLMODE applies when the adapter is defaulted (no explicit adapter, no URL)", () => {
process.env.PGHOST = "pg-host";
process.env.PGSSLMODE = "require";
const options = new SQL({ max: 1 });
expect(options.options.adapter).toBe("postgres");
expect(options.options.sslMode).toBe(2); // SSLMode.require
});
test("PGSSLMODE applies alongside DATABASE_URL (postgres URL without ?sslmode=)", () => {
process.env.DATABASE_URL = "postgres://user@host:5432/db";
process.env.PGSSLMODE = "require";
const options = new SQL();
expect(options.options.adapter).toBe("postgres");
expect(options.options.hostname).toBe("host");
expect(options.options.sslMode).toBe(2); // SSLMode.require
});
test("PGSSLMODE applies to an explicit URL string without ?sslmode=", () => {
process.env.PGSSLMODE = "verify-full";
const options = new SQL("postgres://user@host:5432/db");
expect(options.options.sslMode).toBe(4); // SSLMode.verify_full
});
test("PG_SSLMODE spelling is accepted like PG_HOST et al.", () => {
process.env.PG_SSLMODE = "verify-full";
const options = new SQL({ adapter: "postgres" });
expect(options.options.sslMode).toBe(4); // SSLMode.verify_full
});
test("URL ?sslmode= overrides PGSSLMODE", () => {
process.env.PGSSLMODE = "require";
process.env.POSTGRES_URL = "postgres://user@host:5432/db?sslmode=disable";
const options = new SQL();
expect(options.options.sslMode).toBe(0); // SSLMode.disable (URL wins)
});
test("PGSSLMODE does not leak into the MySQL adapter", () => {
process.env.PGSSLMODE = "require";
const options = new SQL({ adapter: "mysql", hostname: "localhost" });
expect(options.options.adapter).toBe("mysql");
expect(options.options.sslMode).toBe(0); // SSLMode.disable
});
test("invalid PGSSLMODE value throws", () => {
process.env.PGSSLMODE = "bogus";
expect(() => new SQL({ adapter: "postgres" })).toThrow("sslmode");
});
test("PGSSLMODE=prefer does not downgrade an explicit tls: true below require", () => {
process.env.PGSSLMODE = "prefer";
const options = new SQL({ adapter: "postgres", hostname: "h", tls: true });
expect(options.options.sslMode).toBe(2); // SSLMode.require
});
test("PGSSLMODE=allow does not downgrade an explicit ssl: {} below require", () => {
process.env.PGSSLMODE = "allow";
const options = new SQL({ adapter: "postgres", hostname: "h", ssl: {} });
expect(options.options.sslMode).toBe(2); // SSLMode.require
});
test("PGSSLMODE=verify-full still upgrades past an explicit tls: true", () => {
process.env.PGSSLMODE = "verify-full";
const options = new SQL({ adapter: "postgres", hostname: "h", tls: true });
expect(options.options.sslMode).toBe(4); // SSLMode.verify_full
});
test.each([{ tls: false }, { ssl: false }, { tls: false, ssl: false }])(
"an explicit %p disables a mode selected by PGSSLMODE or the URL",
tlsOptions => {
process.env.PGSSLMODE = "require";
const fromEnv = new SQL({ adapter: "postgres", hostname: "h", ...tlsOptions });
expect(fromEnv.options.sslMode).toBe(0);
expect(fromEnv.options.tls).toBeUndefined();
const fromUrl = new SQL("postgres://u@h:5432/db?sslmode=verify-full&ssl=true", tlsOptions);
expect(fromUrl.options.query).toBe("");
expect(fromUrl.options.sslMode).toBe(0);
expect(fromUrl.options.tls).toBeUndefined();
},
);
test("an unset tls option keeps the mode selected by PGSSLMODE", () => {
process.env.PGSSLMODE = "require";
const options = new SQL({ adapter: "postgres", hostname: "h", tls: undefined });
expect(options.options.sslMode).toBe(2);
expect(options.options.tls).toEqual({ serverName: "h" });
});
test("a tls object alongside ssl: false still requests an encrypted connection", () => {
const options = new SQL({ adapter: "postgres", hostname: "h", tls: {}, ssl: false });
expect(options.options.sslMode).toBe(2);
expect(options.options.tls).toBeTypeOf("object");
});
});
describe("TLS settings from the connection URL query string", () => {
test.each([
["mysql://u:p@h/db?ssl-mode=DISABLED", 0, undefined],
["mysql://u:p@h/db?ssl_mode=preferred", 1, { serverName: "h" }],
["mysql://u:p@h/db?ssl-mode=REQUIRED", 2, { serverName: "h" }],
["mysql://u:p@h/db?ssl-mode=VERIFY_CA", 3, { serverName: "h" }],
["mysql://u:p@h/db?ssl-mode=VERIFY_IDENTITY", 4, { serverName: "h" }],
["postgres://u@h:5432/db?ssl=prefer", 1, { serverName: "h" }],
["postgres://u@h:5432/db?ssl=require", 2, { serverName: "h" }],
["postgres://u@h:5432/db?tls=verify-ca", 3, { serverName: "h" }],
["postgres://u@h:5432/db?ssl=verify-full", 4, { serverName: "h" }],
] as const)("%s selects sslMode %d", (url, expectedMode, expectedTls) => {
const options = new SQL(url);
expect(options.options.hostname).toBe("h");
expect(options.options.query).toBe("");
expect(options.options.sslMode).toBe(expectedMode);
expect(options.options.tls).toEqual(expectedTls);
});
test.each(["mysql://u:p@h/db?ssl=true", "mysql://u:p@h/db?ssl=1", "postgres://u@h:5432/db?tls=TRUE"])(
"%s requires an encrypted connection",
url => {
const options = new SQL(url);
expect(options.options.query).toBe("");
expect(options.options.sslMode).toBe(2);
expect(options.options.tls).toEqual({ serverName: "h" });
const withExplicitTls = new SQL(url, { tls: { ca: "x" } });
expect(withExplicitTls.options.sslMode).toBe(4);
expect(withExplicitTls.options.tls).toEqual({ ca: "x", serverName: "h" });
},
);
test.each(["mysql://u:p@h/db?ssl=false", "mysql://u:p@h/db?ssl=0", "postgres://u@h:5432/db?tls="])(
"%s leaves TLS disabled and is not forwarded as a startup parameter",
url => {
const options = new SQL(url);
expect(options.options.query).toBe("");
expect(options.options.sslMode).toBe(0);
expect(options.options.tls).toBeUndefined();
},
);
test.each(["postgres://u@h:5432/db?ssl=false", "postgres://u@h:5432/db?tls=0"])(
"%s disables a mode selected by PGSSLMODE",
url => {
process.env.PGSSLMODE = "verify-full";
const options = new SQL(url);
expect(options.options.query).toBe("");
expect(options.options.sslMode).toBe(0);
expect(options.options.tls).toBeUndefined();
},
);
test("an empty ?tls= is treated as unset and keeps the mode selected by PGSSLMODE", () => {
process.env.PGSSLMODE = "require";
const options = new SQL("postgres://u@h:5432/db?tls=");
expect(options.options.query).toBe("");
expect(options.options.sslMode).toBe(2);
expect(options.options.tls).toEqual({ serverName: "h" });
});
test("an explicit tls option takes priority over ?ssl=false", () => {
const options = new SQL("postgres://u@h:5432/db?ssl=false", { tls: true });
expect(options.options.sslMode).toBe(2);
expect(options.options.tls).toBe(true);
});
test.each(["mysql://u:p@h/db?ssl=bogus", 'mysql://u:p@h/db?ssl={"rejectUnauthorized":true}'])(
"%s throws for an unrecognised ssl value",
url => {
expect(() => new SQL(url)).toThrow("sslmode");
},
);
});
describe("tls/ssl option given as an sslmode string", () => {
test.each([
["disable", 0, undefined],
["allow", 1, { serverName: "h" }],
["prefer", 1, { serverName: "h" }],
["require", 2, { serverName: "h" }],
["verify-ca", 3, { serverName: "h" }],
["verify-full", 4, { serverName: "h" }],
] as const)("ssl: %p selects sslMode %d", (mode, expectedMode, expectedTls) => {
const options = new SQL({ adapter: "postgres", hostname: "h", ssl: mode as any });
expect(options.options.sslMode).toBe(expectedMode);
expect(options.options.tls).toEqual(expectedTls);
});
test("tls: 'verify-full' is accepted for the mysql adapter", () => {
const options = new SQL({ adapter: "mysql", hostname: "h", tls: "verify-full" as any });
expect(options.options.adapter).toBe("mysql");
expect(options.options.sslMode).toBe(4);
expect(options.options.tls).toEqual({ serverName: "h" });
});
test("ssl: 'verify-full' takes priority over URL ?sslmode=require", () => {
const options = new SQL("postgres://u@h:5432/db?sslmode=require", { ssl: "verify-full" as any });
expect(options.options.sslMode).toBe(4);
expect(options.options.tls).toEqual({ serverName: "h" });
});
test("ssl: 'verify-ca' takes priority over PGSSLMODE=require", () => {
process.env.PGSSLMODE = "require";
const options = new SQL({ adapter: "postgres", hostname: "h", ssl: "verify-ca" as any });
expect(options.options.sslMode).toBe(3);
expect(options.options.tls).toEqual({ serverName: "h" });
});
test("an unrecognised ssl string throws", () => {
expect(() => new SQL({ adapter: "postgres", hostname: "h", ssl: "bogus" as any })).toThrow("sslmode");
expect(() => new SQL("postgres://u@h:5432/db?sslmode=require", { tls: "bogus" as any })).toThrow("sslmode");
});
});
describe("tls.caFile", () => {
test("tls: { caFile } enables certificate verification like tls: { ca }", () => {
using dir = tempDir("sql-tls-cafile", { "ca.pem": "" });
const caFile = `${dir}/ca.pem`;
const options = new SQL({ adapter: "postgres", hostname: "h", tls: { caFile } });
expect(options.options.sslMode).toBe(4);
expect(options.options.tls).toEqual({ caFile, serverName: "h" });
const mysqlOptions = new SQL("mysql://u:p@h/db", { tls: { caFile } });
expect(mysqlOptions.options.adapter).toBe("mysql");
expect(mysqlOptions.options.sslMode).toBe(4);
expect(mysqlOptions.options.tls).toEqual({ caFile, serverName: "h" });
const optedOut = new SQL({ adapter: "postgres", hostname: "h", tls: { caFile, rejectUnauthorized: false } });
expect(optedOut.options.sslMode).toBe(2);
expect(optedOut.options.tls).toMatchObject({ caFile, rejectUnauthorized: false });
const fromUrl = new SQL("postgres://u@h:5432/db?sslmode=verify-ca", { tls: { caFile } });
expect(fromUrl.options.sslMode).toBe(3);
expect(fromUrl.options.tls).toEqual({ caFile, serverName: "h" });
});
});
describe("Adapter-Protocol Validation", () => {
test("should work with explicit adapter and URL without protocol", () => {
const options = new SQL("user:pass@host:3306/db", { adapter: "mysql" });
expect(options.options.adapter).toBe("mysql");
expect(options.options.hostname).toBe("host");
expect(options.options.port).toBe(3306);
});
test("should work with explicit adapter and matching protocol", () => {
const options = new SQL("mysql://user:pass@host:3306/db", { adapter: "mysql" });
expect(options.options.adapter).toBe("mysql");
expect(options.options.hostname).toBe("host");
expect(options.options.port).toBe(3306);
});
test.skipIf(isWindows)("should work with unix:// protocol and explicit adapter", () => {
using sock = Bun.listen({
unix: "/tmp/thisisacoolmysql.sock",
socket: {
data: console.log,
},
});
const options = new SQL(`unix://${sock.unix}`, { adapter: "mysql" });
expect(options.options.adapter).toBe("mysql");
expect(options.options.path).toBe("/tmp/thisisacoolmysql.sock");
unlinkSync(sock.unix);
});
test.skipIf(isWindows)("postgres URL with a host uses the pathname as the database name, not a socket path", () => {
using dir = tempDir("sql-url-pathname-pg", { placeholder: "" });
const options = new SQL(`postgres://user:pass@dbhost:5432${dir}`);
expect(options.options.adapter).toBe("postgres");
expect(options.options.hostname).toBe("dbhost");
expect(options.options.port).toBe(5432);
expect(options.options.database).toBe(String(dir).slice(1));
expect(options.options.path).toBeUndefined();
});
test.skipIf(isWindows)("mysql URL with a host uses the pathname as the database name, not a socket path", () => {
using dir = tempDir("sql-url-pathname-mysql", { placeholder: "" });
const options = new SQL(`mysql://user:pass@dbhost:3306${dir}`);
expect(options.options.adapter).toBe("mysql");
expect(options.options.hostname).toBe("dbhost");
expect(options.options.port).toBe(3306);
expect(options.options.database).toBe(String(dir).slice(1));
expect(options.options.path).toBeUndefined();
});
test("should work with sqlite:// protocol and sqlite adapter", () => {
const options = new SQL("sqlite:///tmp/test.db", { adapter: "sqlite" });
expect(options.options.adapter).toBe("sqlite");
expect(options.options.filename).toBe("/tmp/test.db");
});
test("should work with sqlite:// protocol without adapter", () => {
const options = new SQL("sqlite:///tmp/test.db");
expect(options.options.adapter).toBe("sqlite");
expect(options.options.filename).toBe("/tmp/test.db");
});
describe("Explicit options override URL parameters", () => {
test("explicit hostname should override URL hostname", () => {
const options = new SQL("postgres://urluser:urlpass@urlhost:1234/urldb", {
hostname: "explicithost",
});
expect(options.options.hostname).toBe("explicithost");
expect(options.options.port).toBe(1234); // URL port should remain
expect(options.options.username).toBe("urluser"); // URL username should remain
expect(options.options.database).toBe("urldb"); // URL database should remain
});
test("explicit port should override URL port", () => {
const options = new SQL("postgres://urluser:urlpass@urlhost:1234/urldb", {
port: 5432,
});
expect(options.options.hostname).toBe("urlhost"); // URL hostname should remain
expect(options.options.port).toBe(5432);
expect(options.options.username).toBe("urluser"); // URL username should remain
expect(options.options.database).toBe("urldb"); // URL database should remain
});
test("explicit username should override URL username", () => {
const options = new SQL("postgres://urluser:urlpass@urlhost:1234/urldb", {
username: "explicituser",
});
expect(options.options.hostname).toBe("urlhost"); // URL hostname should remain
expect(options.options.port).toBe(1234); // URL port should remain
expect(options.options.username).toBe("explicituser");
expect(options.options.database).toBe("urldb"); // URL database should remain
});
test("explicit password should override URL password", () => {
const options = new SQL("postgres://urluser:urlpass@urlhost:1234/urldb", {
password: "explicitpass",
});
expect(options.options.hostname).toBe("urlhost"); // URL hostname should remain
expect(options.options.port).toBe(1234); // URL port should remain
expect(options.options.username).toBe("urluser"); // URL username should remain
expect(options.options.password).toBe("explicitpass");
expect(options.options.database).toBe("urldb"); // URL database should remain
});
test("explicit database should override URL database", () => {
const options = new SQL("postgres://urluser:urlpass@urlhost:1234/urldb", {
database: "explicitdb",
});
expect(options.options.hostname).toBe("urlhost"); // URL hostname should remain
expect(options.options.port).toBe(1234); // URL port should remain
expect(options.options.username).toBe("urluser"); // URL username should remain
expect(options.options.database).toBe("explicitdb");
});
test("multiple explicit options should override corresponding URL parameters", () => {
const options = new SQL("postgres://urluser:urlpass@urlhost:1234/urldb", {
hostname: "explicithost",
port: 5432,
username: "explicituser",
password: "explicitpass",
database: "explicitdb",
});
expect(options.options.hostname).toBe("explicithost");
expect(options.options.port).toBe(5432);
expect(options.options.username).toBe("explicituser");
expect(options.options.password).toBe("explicitpass");
expect(options.options.database).toBe("explicitdb");
});
test("should work with MySQL URLs and explicit options", () => {
const options = new SQL("mysql://urluser:urlpass@urlhost:3306/urldb", {
hostname: "explicithost",
port: 3307,
username: "explicituser",
});
expect(options.options.adapter).toBe("mysql");
expect(options.options.hostname).toBe("explicithost");
expect(options.options.port).toBe(3307);
expect(options.options.username).toBe("explicituser");
expect(options.options.password).toBe("urlpass"); // URL password should remain
expect(options.options.database).toBe("urldb"); // URL database should remain
});
test("should work with alternative option names (user, pass, db, host)", () => {
const options = new SQL("postgres://urluser:urlpass@urlhost:1234/urldb", {
host: "explicithost",
user: "explicituser",
pass: "explicitpass",
db: "explicitdb",
});
expect(options.options.hostname).toBe("explicithost");
expect(options.options.username).toBe("explicituser");
expect(options.options.password).toBe("explicitpass");
expect(options.options.database).toBe("explicitdb");
});
test("explicit options should override URL even when environment variables are present", () => {
process.env.PGHOST = "envhost";
process.env.PGPORT = "9999";
process.env.PGUSER = "envuser";
const options = new SQL("postgres://urluser:urlpass@urlhost:1234/urldb", {
hostname: "explicithost",
port: 5432,
username: "explicituser",
});
expect(options.options.hostname).toBe("explicithost");
expect(options.options.port).toBe(5432);
expect(options.options.username).toBe("explicituser");
expect(options.options.password).toBe("urlpass"); // URL password should remain since no explicit password
expect(options.options.database).toBe("urldb"); // URL database should remain
});
test("explicit options should have higher precedence than environment-specific variables", () => {
process.env.MYSQL_HOST = "mysqlhost";
process.env.MYSQL_USER = "mysqluser";
process.env.MYSQL_PASSWORD = "mysqlpass";
const options = new SQL("mysql://urluser:urlpass@urlhost:3306/urldb", {
hostname: "explicithost",
username: "explicituser",
});
expect(options.options.adapter).toBe("mysql");
expect(options.options.hostname).toBe("explicithost");
expect(options.options.username).toBe("explicituser");
expect(options.options.password).toBe("urlpass"); // URL password (not env)
expect(options.options.database).toBe("urldb"); // URL database should remain
});
});
});
});