th3w1zard1's picture
Deploy GPTR trask-http from community-bots local CI
3a25f97 verified
import test from "node:test";
import assert from "node:assert/strict";
import { trimTrailingSlashes, normalizeOrigin, isLocalhostUrl, isWebSocketUpgradeHeader } from "./index.js";
import { extractBearerToken, requireBearerToken } from "./bearer-tokens.js";
import { isCardGameType, normalizeCardGameType } from "./game-mode.js";
import { normalizeAuthHandlerError } from "./auth.js";
import { buildLocalWebOrigins } from "./cors.js";
// ---------------------------------------------------------------------------
// trimTrailingSlashes
// ---------------------------------------------------------------------------
test("trimTrailingSlashes removes one trailing slash", () => {
assert.equal(trimTrailingSlashes("https://example.com/"), "https://example.com");
});
test("trimTrailingSlashes removes multiple trailing slashes", () => {
assert.equal(trimTrailingSlashes("https://example.com///"), "https://example.com");
});
test("trimTrailingSlashes is idempotent when no trailing slash", () => {
assert.equal(trimTrailingSlashes("https://example.com"), "https://example.com");
});
test("trimTrailingSlashes handles empty string", () => {
assert.equal(trimTrailingSlashes(""), "");
});
// ---------------------------------------------------------------------------
// normalizeOrigin
// ---------------------------------------------------------------------------
test("normalizeOrigin extracts origin from a full URL", () => {
assert.equal(normalizeOrigin("https://example.com/path?q=1"), "https://example.com");
});
test("normalizeOrigin trims whitespace", () => {
assert.equal(normalizeOrigin(" https://example.com "), "https://example.com");
});
test("normalizeOrigin returns undefined for undefined input", () => {
assert.equal(normalizeOrigin(undefined), undefined);
});
test("normalizeOrigin returns undefined for empty string", () => {
assert.equal(normalizeOrigin(""), undefined);
});
test("normalizeOrigin falls back to trimmed string for invalid URL", () => {
const result = normalizeOrigin("localhost:3000/");
assert.ok(result);
assert.ok(!result.endsWith("/"));
});
// ---------------------------------------------------------------------------
// isLocalhostUrl
// ---------------------------------------------------------------------------
test("isLocalhostUrl returns true for localhost URLs", () => {
assert.equal(isLocalhostUrl("http://localhost:3000"), true);
assert.equal(isLocalhostUrl("http://localhost"), true);
});
test("isLocalhostUrl returns true for 127.0.0.1", () => {
assert.equal(isLocalhostUrl("http://127.0.0.1:4001"), true);
});
test("isLocalhostUrl returns false for public URLs", () => {
assert.equal(isLocalhostUrl("https://example.com"), false);
assert.equal(isLocalhostUrl("https://api.openkotor.com"), false);
});
// ---------------------------------------------------------------------------
// isWebSocketUpgradeHeader
// ---------------------------------------------------------------------------
test("isWebSocketUpgradeHeader returns true for 'websocket' (case-insensitive)", () => {
assert.equal(isWebSocketUpgradeHeader("websocket"), true);
assert.equal(isWebSocketUpgradeHeader("WebSocket"), true);
assert.equal(isWebSocketUpgradeHeader("WEBSOCKET"), true);
});
test("isWebSocketUpgradeHeader returns false for null/undefined", () => {
assert.equal(isWebSocketUpgradeHeader(null), false);
assert.equal(isWebSocketUpgradeHeader(undefined), false);
});
test("isWebSocketUpgradeHeader returns false for other values", () => {
assert.equal(isWebSocketUpgradeHeader("http/1.1"), false);
assert.equal(isWebSocketUpgradeHeader(""), false);
});
// ---------------------------------------------------------------------------
// extractBearerToken / requireBearerToken
// ---------------------------------------------------------------------------
test("extractBearerToken extracts the token from a valid header", () => {
assert.equal(extractBearerToken("Bearer mytoken123"), "mytoken123");
});
test("extractBearerToken is case-insensitive on 'Bearer'", () => {
assert.equal(extractBearerToken("bearer mytoken"), "mytoken");
assert.equal(extractBearerToken("BEARER abc"), "abc");
});
test("extractBearerToken returns null for null/undefined", () => {
assert.equal(extractBearerToken(null), null);
assert.equal(extractBearerToken(undefined), null);
});
test("extractBearerToken returns null for empty string", () => {
assert.equal(extractBearerToken(""), null);
});
test("extractBearerToken returns null for header without Bearer prefix", () => {
assert.equal(extractBearerToken("Basic dXNlcjpwYXNz"), null);
assert.equal(extractBearerToken("justtoken"), null);
});
test("requireBearerToken returns the token when present", () => {
assert.equal(requireBearerToken("Bearer valid-token"), "valid-token");
});
test("requireBearerToken throws 401 when header is missing", () => {
assert.throws(
() => requireBearerToken(undefined),
(err: { status?: number }) => {
assert.equal(err.status, 401);
return true;
},
);
});
test("requireBearerToken throws 401 for malformed header", () => {
assert.throws(
() => requireBearerToken("Basic dXNlcjpwYXNz"),
(err: { status?: number }) => {
assert.equal(err.status, 401);
return true;
},
);
});
// ---------------------------------------------------------------------------
// isCardGameType / normalizeCardGameType
// ---------------------------------------------------------------------------
test("isCardGameType returns true for known game types", () => {
assert.equal(isCardGameType("pazaak"), true);
assert.equal(isCardGameType("blackjack"), true);
assert.equal(isCardGameType("poker"), true);
});
test("isCardGameType returns false for unknown values", () => {
assert.equal(isCardGameType("chess"), false);
assert.equal(isCardGameType(""), false);
assert.equal(isCardGameType(null), false);
assert.equal(isCardGameType(42), false);
});
test("normalizeCardGameType returns the value when it is a valid game type", () => {
assert.equal(normalizeCardGameType("pazaak"), "pazaak");
assert.equal(normalizeCardGameType("blackjack"), "blackjack");
});
test("normalizeCardGameType returns the fallback for unknown values", () => {
assert.equal(normalizeCardGameType("chess"), "blackjack");
assert.equal(normalizeCardGameType(null), "blackjack");
assert.equal(normalizeCardGameType(undefined), "blackjack");
});
test("normalizeCardGameType respects a custom fallback", () => {
assert.equal(normalizeCardGameType("invalid", "pazaak"), "pazaak");
});
// ---------------------------------------------------------------------------
// normalizeAuthHandlerError
// ---------------------------------------------------------------------------
test("normalizeAuthHandlerError extracts status from Error with status property", () => {
const err = Object.assign(new Error("Unauthorized"), { status: 401 });
const result = normalizeAuthHandlerError(err);
assert.equal(result.status, 401);
assert.equal(result.message, "Unauthorized");
});
test("normalizeAuthHandlerError defaults status to 500 when not present", () => {
const result = normalizeAuthHandlerError(new Error("Unknown failure"));
assert.equal(result.status, 500);
assert.equal(result.message, "Unknown failure");
});
test("normalizeAuthHandlerError handles plain string", () => {
const result = normalizeAuthHandlerError("Something bad");
assert.equal(result.status, 500);
assert.equal(result.message, "Something bad");
});
test("normalizeAuthHandlerError handles an object with status but no message", () => {
const result = normalizeAuthHandlerError({ status: 403 });
assert.equal(result.status, 403);
assert.ok(typeof result.message === "string");
});
// ---------------------------------------------------------------------------
// buildLocalWebOrigins
// ---------------------------------------------------------------------------
test("buildLocalWebOrigins generates http://localhost and 127.0.0.1 pairs for each port", () => {
const origins = buildLocalWebOrigins([3000]);
assert.ok(origins.includes("http://localhost:3000"));
assert.ok(origins.includes("http://127.0.0.1:3000"));
assert.equal(origins.length, 2);
});
test("buildLocalWebOrigins handles multiple ports", () => {
const origins = buildLocalWebOrigins([3000, 5173]);
assert.equal(origins.length, 4);
});
test("buildLocalWebOrigins uses DEFAULT_LOCAL_WEB_PORTS when called with no args", () => {
const origins = buildLocalWebOrigins();
assert.ok(origins.length >= 6); // at least 3 default ports × 2
});
// ---------------------------------------------------------------------------
// createObjectEnvLookup
// ---------------------------------------------------------------------------
import { createObjectEnvLookup, resolveSocialAuthProviderConfig, listSocialAuthProviders, buildSocialAuthAuthorizeUrl } from "./oauth.js";
test("createObjectEnvLookup returns a string value from the source object", () => {
const lookup = createObjectEnvLookup({ MY_KEY: "my-value" });
assert.equal(lookup("MY_KEY"), "my-value");
});
test("createObjectEnvLookup returns undefined for missing keys", () => {
const lookup = createObjectEnvLookup({});
assert.equal(lookup("MISSING"), undefined);
});
test("createObjectEnvLookup returns undefined for non-string values", () => {
const lookup = createObjectEnvLookup({ NUM: 42, BOOL: true });
assert.equal(lookup("NUM"), undefined);
assert.equal(lookup("BOOL"), undefined);
});
// ---------------------------------------------------------------------------
// resolveSocialAuthProviderConfig
// ---------------------------------------------------------------------------
test("resolveSocialAuthProviderConfig marks enabled=true when clientId and clientSecret are present", () => {
const lookup = createObjectEnvLookup({
DISCORD_CLIENT_ID: "my-id",
DISCORD_CLIENT_SECRET: "my-secret",
DISCORD_CALLBACK_URL: "https://example.com/cb",
});
const config = resolveSocialAuthProviderConfig("discord", lookup);
assert.equal(config.enabled, true);
assert.equal(config.clientId, "my-id");
assert.equal(config.clientSecret, "my-secret");
});
test("resolveSocialAuthProviderConfig marks enabled=false when clientSecret is missing", () => {
const lookup = createObjectEnvLookup({ DISCORD_CLIENT_ID: "my-id" });
const config = resolveSocialAuthProviderConfig("discord", lookup);
assert.equal(config.enabled, false);
});
test("resolveSocialAuthProviderConfig resolves fallback env keys", () => {
const lookup = createObjectEnvLookup({ ALT_DISCORD_ID: "alt-id", ALT_DISCORD_SECRET: "alt-secret" });
const config = resolveSocialAuthProviderConfig("discord", lookup, {
fallbackEnvKeys: {
discord: { clientId: "ALT_DISCORD_ID", clientSecret: "ALT_DISCORD_SECRET" },
},
});
assert.equal(config.clientId, "alt-id");
assert.equal(config.enabled, true);
});
// ---------------------------------------------------------------------------
// listSocialAuthProviders
// ---------------------------------------------------------------------------
test("listSocialAuthProviders returns an entry for each of the three providers", () => {
const lookup = createObjectEnvLookup({});
const providers = listSocialAuthProviders(lookup);
const ids = providers.map((p) => p.provider);
assert.ok(ids.includes("google"));
assert.ok(ids.includes("discord"));
assert.ok(ids.includes("github"));
assert.equal(ids.length, 3);
});
test("listSocialAuthProviders marks providers as disabled when env is empty", () => {
const lookup = createObjectEnvLookup({});
const providers = listSocialAuthProviders(lookup);
assert.ok(providers.every((p) => !p.enabled));
});
// ---------------------------------------------------------------------------
// buildSocialAuthAuthorizeUrl
// ---------------------------------------------------------------------------
const baseInput = {
clientId: "client-123",
redirectUri: "https://example.com/callback",
state: "csrf-state",
};
test("buildSocialAuthAuthorizeUrl uses startUrl template when provided", () => {
const url = buildSocialAuthAuthorizeUrl("discord", {
...baseInput,
startUrl: "https://auth.example.com/start?state={state}&cb={callback}&id={clientId}",
});
assert.ok(url.includes("csrf-state"), "state should be substituted");
assert.ok(url.includes("client-123"), "clientId should be substituted");
assert.ok(url.includes("callback"), "redirectUri should be substituted");
});
test("buildSocialAuthAuthorizeUrl builds a valid Google URL", () => {
const url = buildSocialAuthAuthorizeUrl("google", baseInput);
assert.ok(url.startsWith("https://accounts.google.com"));
const parsed = new URL(url);
assert.equal(parsed.searchParams.get("client_id"), "client-123");
assert.equal(parsed.searchParams.get("response_type"), "code");
assert.equal(parsed.searchParams.get("state"), "csrf-state");
});
test("buildSocialAuthAuthorizeUrl builds a valid Discord URL", () => {
const url = buildSocialAuthAuthorizeUrl("discord", baseInput);
assert.ok(url.includes("discord.com"));
const parsed = new URL(url);
assert.equal(parsed.searchParams.get("client_id"), "client-123");
assert.ok(parsed.searchParams.get("scope")?.includes("identify"));
});
test("buildSocialAuthAuthorizeUrl builds a valid GitHub URL", () => {
const url = buildSocialAuthAuthorizeUrl("github", baseInput);
assert.ok(url.startsWith("https://github.com/login/oauth/authorize"));
const parsed = new URL(url);
assert.equal(parsed.searchParams.get("client_id"), "client-123");
assert.ok(parsed.searchParams.get("scope")?.includes("read:user"));
});
test("buildSocialAuthAuthorizeUrl respects custom discordApiBase", () => {
const url = buildSocialAuthAuthorizeUrl("discord", baseInput, {
discordApiBase: "https://discord.example.com/api/v10",
});
assert.ok(url.startsWith("https://discord.example.com"));
});
// ---------------------------------------------------------------------------
// parseConfiguredBases and buildApiUrl (browser.ts)
// ---------------------------------------------------------------------------
import { parseConfiguredBases, buildApiUrl, resolveBrowserApiBases } from "./browser.js";
test("parseConfiguredBases splits a comma-separated string", () => {
const result = parseConfiguredBases("https://a.com,https://b.com,https://c.com");
assert.deepEqual(result, ["https://a.com", "https://b.com", "https://c.com"]);
});
test("parseConfiguredBases trims whitespace around each entry", () => {
const result = parseConfiguredBases(" https://a.com , https://b.com ");
assert.deepEqual(result, ["https://a.com", "https://b.com"]);
});
test("parseConfiguredBases filters empty entries", () => {
const result = parseConfiguredBases("https://a.com,,https://b.com");
assert.deepEqual(result, ["https://a.com", "https://b.com"]);
});
test("parseConfiguredBases returns empty array for null/undefined", () => {
assert.deepEqual(parseConfiguredBases(null), []);
assert.deepEqual(parseConfiguredBases(undefined), []);
});
test("parseConfiguredBases returns empty array for empty string", () => {
assert.deepEqual(parseConfiguredBases(""), []);
});
test("parseConfiguredBases handles a single entry without comma", () => {
assert.deepEqual(parseConfiguredBases("https://example.com"), ["https://example.com"]);
});
test("buildApiUrl joins base and path correctly", () => {
assert.equal(buildApiUrl("/api/query", "https://api.example.com"), "https://api.example.com/api/query");
});
test("buildApiUrl adds leading slash to path if missing", () => {
assert.equal(buildApiUrl("api/query", "https://api.example.com"), "https://api.example.com/api/query");
});
test("buildApiUrl strips trailing slashes from base", () => {
assert.equal(buildApiUrl("/api/query", "https://api.example.com///"), "https://api.example.com/api/query");
});
test("buildApiUrl returns path as-is when base is empty string", () => {
assert.equal(buildApiUrl("/api/query", ""), "/api/query");
});
test("resolveBrowserApiBases returns configuredBases when provided", () => {
const result = resolveBrowserApiBases({ configuredBases: ["https://my-api.com"] });
assert.deepEqual(result, ["https://my-api.com"]);
});
test("resolveBrowserApiBases returns [''] when no location and no configuredBases", () => {
const result = resolveBrowserApiBases({ location: undefined, configuredBases: undefined });
assert.deepEqual(result, [""]);
});
test("resolveBrowserApiBases injects local API port for localhost", () => {
const result = resolveBrowserApiBases({
localApiPort: 4001,
location: { protocol: "http:", hostname: "localhost", port: "5173" },
});
assert.ok(result.some((b) => b.includes("4001")));
});
// ---------------------------------------------------------------------------
// buildBrowserCorsAllowedOrigins and resolveCorsHeaders (cors.ts)
// ---------------------------------------------------------------------------
import { buildBrowserCorsAllowedOrigins, resolveCorsHeaders } from "./cors.js";
test("buildBrowserCorsAllowedOrigins includes discordsays.com when discordAppId is set", () => {
const origins = buildBrowserCorsAllowedOrigins({ discordAppId: "123456789" });
assert.ok(origins.some((o) => o.includes("123456789") && o.includes("discordsays.com")));
});
test("buildBrowserCorsAllowedOrigins omits discordsays.com when discordAppId is absent", () => {
const origins = buildBrowserCorsAllowedOrigins({});
assert.ok(!origins.some((o) => o.includes("discordsays.com")));
});
test("buildBrowserCorsAllowedOrigins includes publicWebOrigin when provided", () => {
const origins = buildBrowserCorsAllowedOrigins({ publicWebOrigin: "https://openkotor.github.io" });
assert.ok(origins.some((o) => o === "https://openkotor.github.io"));
});
test("buildBrowserCorsAllowedOrigins includes default local ports", () => {
const origins = buildBrowserCorsAllowedOrigins({});
assert.ok(origins.some((o) => o.includes("localhost:3000")));
assert.ok(origins.some((o) => o.includes("localhost:5173")));
});
test("resolveCorsHeaders sets Allow-Origin for a matching origin", () => {
const result = resolveCorsHeaders(
{ method: "GET", origin: "https://example.com" },
["https://example.com"],
);
assert.equal(result.headers["Access-Control-Allow-Origin"], "https://example.com");
});
test("resolveCorsHeaders does NOT set Allow-Origin for an unknown origin", () => {
const result = resolveCorsHeaders(
{ method: "GET", origin: "https://evil.com" },
["https://allowed.com"],
);
assert.equal(result.headers["Access-Control-Allow-Origin"], undefined);
});
test("resolveCorsHeaders marks preflight correctly for OPTIONS request", () => {
const result = resolveCorsHeaders({ method: "OPTIONS", origin: "https://example.com" }, []);
assert.equal(result.isPreflight, true);
});
test("resolveCorsHeaders marks non-OPTIONS as not preflight", () => {
const result = resolveCorsHeaders({ method: "POST", origin: "https://example.com" }, []);
assert.equal(result.isPreflight, false);
});
test("resolveCorsHeaders includes Vary: Origin header", () => {
const result = resolveCorsHeaders({ method: "GET" }, []);
assert.equal(result.headers["Vary"], "Origin");
});