Host rehearsal no longer needs Node on the production machine, and a failed cleanup now stops with the rehearsal database name. Docs-only staging heads no longer block rollback. Tests no longer hardcode Windows paths, and a trailing slash on the production site still refuses the test code. pytest 12 passed; tsc exit 0; lint 0 errors; identity tests 9 passed, 0 cancelled. test:db was not run. Co-Authored-By: Grok (agent loop)
112 lines
5.0 KiB
TypeScript
112 lines
5.0 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
|
|
import { isSelfHostedIdentityEnabled, readIdentityConfig, readSelfHostedIdentityConfig } from "../src/modules/identity/config.ts";
|
|
|
|
const selfHostedEnvironment = {
|
|
AUTH_PROVIDER: "self-hosted",
|
|
SELF_HOSTED_IDENTITY_ENABLED: "true",
|
|
IDENTITY_DATABASE_URL: "postgresql://identity_runtime:test-password@postgres:5432/jyotisha",
|
|
AUTH_USER_ORIGIN: "https://staging.jyotisha.chat",
|
|
ADMIN_USER_ORIGIN: "https://admin.staging.jyotisha.chat",
|
|
BETTER_AUTH_USER_SECRET: "user-secret-that-is-at-least-32-bytes-long",
|
|
RESEND_API_KEY: "re_test_key_that_must_not_be_printed",
|
|
RESEND_FROM_EMAIL: "Jyotisha Staging <login@staging.jyotisha.chat>",
|
|
};
|
|
|
|
test("identity provider defaults to supabase", () => {
|
|
assert.deepEqual(readIdentityConfig({}), { provider: "supabase" });
|
|
assert.equal(isSelfHostedIdentityEnabled({}), false);
|
|
});
|
|
|
|
test("identity config accepts the two exact self-hosted origins and one secret", () => {
|
|
const config = readIdentityConfig(selfHostedEnvironment);
|
|
assert.equal(config.provider, "self-hosted");
|
|
if (config.provider !== "self-hosted") assert.fail();
|
|
assert.equal(config.userOrigin, "https://staging.jyotisha.chat");
|
|
assert.equal(config.adminOrigin, "https://admin.staging.jyotisha.chat");
|
|
assert.equal(config.userSecret, selfHostedEnvironment.BETTER_AUTH_USER_SECRET);
|
|
assert.equal("adminSecret" in config, false);
|
|
});
|
|
|
|
test("self-hosted identity ignores retired admin-surface variables", () => {
|
|
const config = readSelfHostedIdentityConfig({
|
|
...selfHostedEnvironment,
|
|
AUTH_ADMIN_ORIGIN: "invalid-retired-value",
|
|
BETTER_AUTH_ADMIN_SECRET: "short",
|
|
});
|
|
assert.equal(config.userOrigin, selfHostedEnvironment.AUTH_USER_ORIGIN);
|
|
assert.equal(config.adminOrigin, selfHostedEnvironment.ADMIN_USER_ORIGIN);
|
|
});
|
|
|
|
test("self-hosted provider requires its enable flag", () => {
|
|
assert.throws(() => readIdentityConfig({ ...selfHostedEnvironment, SELF_HOSTED_IDENTITY_ENABLED: "false" }), /must be true/);
|
|
});
|
|
|
|
test("self-hosted identity validates active database, origin, secret, and sender", () => {
|
|
assert.throws(() => readIdentityConfig({ ...selfHostedEnvironment, IDENTITY_DATABASE_URL: "https://invalid" }), /PostgreSQL URL/);
|
|
assert.throws(() => readIdentityConfig({ ...selfHostedEnvironment, AUTH_USER_ORIGIN: "http://staging.jyotisha.chat" }), /must use HTTPS/);
|
|
assert.throws(() => readIdentityConfig({ ...selfHostedEnvironment, ADMIN_USER_ORIGIN: "https://admin.staging.jyotisha.chat/path" }), /without a path/);
|
|
assert.throws(() => readIdentityConfig({ ...selfHostedEnvironment, ADMIN_USER_ORIGIN: selfHostedEnvironment.AUTH_USER_ORIGIN }), /must differ/);
|
|
assert.throws(() => readIdentityConfig({ ...selfHostedEnvironment, BETTER_AUTH_USER_SECRET: "short" }), /at least 32/);
|
|
assert.throws(() => readIdentityConfig({ ...selfHostedEnvironment, RESEND_FROM_EMAIL: "invalid" }), /valid email/);
|
|
});
|
|
|
|
test("localhost may use two distinct HTTP origins", () => {
|
|
const config = readIdentityConfig({
|
|
...selfHostedEnvironment,
|
|
AUTH_USER_ORIGIN: "http://localhost:3000",
|
|
ADMIN_USER_ORIGIN: "http://admin.localhost:3000",
|
|
});
|
|
assert.equal(config.provider, "self-hosted");
|
|
});
|
|
|
|
test("identity test OTP channel is opt-in and strictly six digits", () => {
|
|
const without = readIdentityConfig(selfHostedEnvironment);
|
|
assert.equal(without.provider === "self-hosted" ? without.testOtp : "unexpected", null);
|
|
|
|
const withOtp = readIdentityConfig({ ...selfHostedEnvironment, IDENTITY_TEST_OTP: "123456" });
|
|
assert.equal(withOtp.provider === "self-hosted" ? withOtp.testOtp : null, "123456");
|
|
|
|
assert.throws(
|
|
() => readIdentityConfig({ ...selfHostedEnvironment, IDENTITY_TEST_OTP: "12345" }),
|
|
/must be a 6-digit/,
|
|
);
|
|
assert.throws(
|
|
() => readIdentityConfig({ ...selfHostedEnvironment, IDENTITY_TEST_OTP: "abcdef" }),
|
|
/must be a 6-digit/,
|
|
);
|
|
});
|
|
|
|
test("production origin refuses a configured test OTP and staging still accepts one", () => {
|
|
assert.throws(
|
|
() => readIdentityConfig({
|
|
...selfHostedEnvironment,
|
|
AUTH_USER_ORIGIN: "https://jyotisha.chat",
|
|
ADMIN_USER_ORIGIN: "https://admin.jyotisha.chat",
|
|
IDENTITY_TEST_OTP: "123456",
|
|
}),
|
|
/forbidden when AUTH_USER_ORIGIN is the production site/,
|
|
);
|
|
const unset = readIdentityConfig({
|
|
...selfHostedEnvironment,
|
|
AUTH_USER_ORIGIN: "https://jyotisha.chat",
|
|
ADMIN_USER_ORIGIN: "https://admin.jyotisha.chat",
|
|
});
|
|
assert.equal(unset.provider === "self-hosted" ? unset.testOtp : "unexpected", null);
|
|
const staging = readIdentityConfig({ ...selfHostedEnvironment, IDENTITY_TEST_OTP: "123456" });
|
|
assert.equal(staging.provider === "self-hosted" ? staging.testOtp : null, "123456");
|
|
});
|
|
|
|
test("production origin with a trailing slash still refuses a test OTP", () => {
|
|
assert.throws(
|
|
() => readIdentityConfig({
|
|
...selfHostedEnvironment,
|
|
AUTH_USER_ORIGIN: "https://jyotisha.chat/",
|
|
ADMIN_USER_ORIGIN: "https://admin.jyotisha.chat",
|
|
IDENTITY_TEST_OTP: "123456",
|
|
}),
|
|
/forbidden when AUTH_USER_ORIGIN is the production site/,
|
|
);
|
|
});
|