229 lines
7.2 KiB
JavaScript
229 lines
7.2 KiB
JavaScript
/* global FormData, Headers, console */
|
|
|
|
import assert from "node:assert/strict";
|
|
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import {
|
|
createKeyring,
|
|
createMemoryReplayStore,
|
|
encryptHttpBody,
|
|
decryptHttpBody,
|
|
createEncryptedRequest,
|
|
encryptResponse,
|
|
decryptEncryptedResponse,
|
|
} from "../packages/encryption/src/index.ts";
|
|
import {
|
|
createAccessToken,
|
|
createRefreshToken,
|
|
verifyAccessToken,
|
|
verifyRefreshToken,
|
|
hasScopes,
|
|
jwtCookie,
|
|
createTokenPair,
|
|
readJwtCookie,
|
|
} from "../packages/jwt/src/index.ts";
|
|
import { createDb, createRepository, batch, databaseHealth } from "../packages/db/src/index.ts";
|
|
import {
|
|
loadLocales,
|
|
resolveI18n,
|
|
makeT,
|
|
resolveLang,
|
|
renderI18nData,
|
|
} from "../packages/i18n/src/index.ts";
|
|
import {
|
|
createPicture,
|
|
createBlurPlaceholder,
|
|
imageCacheKey,
|
|
} from "../packages/image/src/index.ts";
|
|
import {
|
|
createRealtimeMessage,
|
|
parseRealtimeMessage,
|
|
roomMemberSummary,
|
|
} from "../packages/realtime/src/index.ts";
|
|
import { captchaFields, captchaHeaders, captchaTokenFrom } from "../packages/captcha/src/index.ts";
|
|
import { uploaderAttributes, formatFileSize } from "../packages/uploader/src/index.ts";
|
|
import { v, parseOrThrow, validationSummary } from "../packages/validation/src/index.ts";
|
|
import { authRoute, authFailure } from "../packages/auth/src/index.ts";
|
|
|
|
const keyring = createKeyring([
|
|
{ id: "active", secret: "MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA=", active: true },
|
|
]);
|
|
const replayStore = createMemoryReplayStore(() => 1_000);
|
|
const envelope = await encryptHttpBody(
|
|
{ value: 42 },
|
|
{
|
|
keyring,
|
|
method: "POST",
|
|
url: "https://example.test/api/items",
|
|
requestId: "request-1",
|
|
timestamp: 1_000,
|
|
},
|
|
);
|
|
const decrypted = await decryptHttpBody(envelope, {
|
|
keyring,
|
|
method: "POST",
|
|
url: "https://example.test/api/items",
|
|
replayStore,
|
|
now: () => 1_000,
|
|
});
|
|
assert.deepEqual(decrypted.body, { value: 42 });
|
|
await assert.rejects(
|
|
() =>
|
|
decryptHttpBody(envelope, {
|
|
keyring,
|
|
method: "POST",
|
|
url: "https://example.test/api/items",
|
|
replayStore,
|
|
now: () => 1_000,
|
|
}),
|
|
/REPLAY/,
|
|
);
|
|
await assert.rejects(
|
|
() =>
|
|
decryptHttpBody(envelope, {
|
|
keyring,
|
|
method: "GET",
|
|
url: "https://example.test/api/items",
|
|
now: () => 1_000,
|
|
}),
|
|
/CONTEXT/,
|
|
);
|
|
const encryptedRequest = await createEncryptedRequest(
|
|
"https://example.test/api/items",
|
|
{ value: 42 },
|
|
{ keyring, method: "POST", requestId: "request-2" },
|
|
);
|
|
const encryptedResponse = await encryptResponse({ ok: true }, encryptedRequest, { keyring });
|
|
assert.equal(
|
|
(await decryptEncryptedResponse(encryptedResponse, encryptedRequest, { keyring })).body.ok,
|
|
true,
|
|
);
|
|
|
|
const secret = "a-secure-jwt-secret-for-package-kit-tests";
|
|
const access = await createAccessToken("user-1", secret, {
|
|
scopes: ["read", "write"],
|
|
now: 100,
|
|
expiresIn: 60,
|
|
});
|
|
const refresh = await createRefreshToken("user-1", secret, {
|
|
family: "family-1",
|
|
now: 100,
|
|
expiresIn: 600,
|
|
});
|
|
const accessClaims = await verifyAccessToken(access, secret, { now: 120 });
|
|
const refreshClaims = await verifyRefreshToken(refresh, secret, { now: 120 });
|
|
assert.equal(accessClaims.sub, "user-1");
|
|
assert.equal(refreshClaims.type, "refresh");
|
|
assert.equal(hasScopes(accessClaims, ["read", "write"]), true);
|
|
assert.match(jwtCookie(access), /^__Host-wrn_token=/);
|
|
assert.equal(readJwtCookie(`__Host-wrn_token=${access}`), access);
|
|
const tokenPair = await createTokenPair("user-1", {
|
|
accessSecret: secret,
|
|
accessOptions: { now: 100 },
|
|
refreshOptions: { now: 100 },
|
|
});
|
|
assert.equal((await verifyAccessToken(tokenPair.accessToken, secret, { now: 120 })).sub, "user-1");
|
|
|
|
const queries = [];
|
|
const driver = {
|
|
dialect: "sqlite",
|
|
async query(sql, params = []) {
|
|
queries.push({ sql, params });
|
|
if (/COUNT/.test(sql)) return [{ count: 2 }];
|
|
if (/SELECT 1 AS healthy/.test(sql)) return [{ healthy: 1 }];
|
|
return [{ id: 1, name: "One" }];
|
|
},
|
|
async exec(sql, params = []) {
|
|
queries.push({ sql, params });
|
|
return { changes: 1, lastInsertId: 1 };
|
|
},
|
|
async transaction(fn) {
|
|
return fn(this);
|
|
},
|
|
close() {},
|
|
};
|
|
const db = createDb(driver);
|
|
const repository = createRepository(db, { table: "items", allowedColumns: ["name"] });
|
|
assert.equal((await repository.find(1)).name, "One");
|
|
await repository.create({ name: "Two" });
|
|
await repository.update(1, { name: "Changed" });
|
|
assert.equal(await repository.count(), 2);
|
|
assert.deepEqual(batch([1, 2, 3, 4, 5], 2), [[1, 2], [3, 4], [5]]);
|
|
assert.equal((await databaseHealth(db)).ok, true);
|
|
assert.ok(queries.some((entry) => /INSERT INTO items/.test(entry.sql)));
|
|
assert.throws(() => batch([1, 2], Number.NaN), /finite/);
|
|
|
|
const localeDir = mkdtempSync(join(tmpdir(), "wrn-i18n-"));
|
|
try {
|
|
writeFileSync(join(localeDir, "en.json"), JSON.stringify({ common: { hello: "Hello {name}" } }));
|
|
mkdirSync(join(localeDir, "fr"));
|
|
writeFileSync(join(localeDir, "fr", "common.json"), JSON.stringify({ hello: "Bonjour {name}" }));
|
|
const i18n = resolveI18n(loadLocales(localeDir, { strict: true }), {
|
|
default: "en",
|
|
locales: ["en", "fr"],
|
|
});
|
|
assert.equal(makeT(i18n, "fr")("common.hello", { name: "Ajay" }), "Bonjour Ajay");
|
|
assert.equal(resolveLang(i18n, undefined, "fr;q=0.9,en;q=0.8"), "fr");
|
|
assert.equal(resolveLang(i18n, undefined, "*"), "en");
|
|
assert.match(renderI18nData(i18n, "fr"), /Bonjour/);
|
|
} finally {
|
|
rmSync(localeDir, { recursive: true, force: true });
|
|
}
|
|
|
|
const picture = createPicture({
|
|
src: "/hero.jpg",
|
|
alt: "Hero",
|
|
width: 1200,
|
|
height: 630,
|
|
widths: [480, 768, 1200],
|
|
formats: ["avif", "webp"],
|
|
});
|
|
assert.equal(picture.sources.length, 2);
|
|
assert.match(createBlurPlaceholder(), /^data:image\/svg\+xml/);
|
|
assert.equal(
|
|
imageCacheKey({ src: "/hero.jpg", width: 480 }),
|
|
imageCacheKey({ src: "/hero.jpg", width: 480 }),
|
|
);
|
|
|
|
const message = createRealtimeMessage({
|
|
type: "message",
|
|
room: "support",
|
|
data: { text: "Hello" },
|
|
});
|
|
assert.equal(
|
|
parseRealtimeMessage(JSON.stringify(message), { room: "support", allowedTypes: ["message"] })
|
|
.room,
|
|
"support",
|
|
);
|
|
assert.throws(
|
|
() => parseRealtimeMessage(JSON.stringify(message), { room: "other" }),
|
|
/does not match/,
|
|
);
|
|
assert.deepEqual(
|
|
roomMemberSummary([
|
|
{ userId: "1", status: "online" },
|
|
{ userId: "2", status: "away" },
|
|
]),
|
|
{ total: 2, online: 1, away: 1, busy: 0 },
|
|
);
|
|
|
|
const form = new FormData();
|
|
form.set("wrn-captcha-response", "token");
|
|
assert.equal(captchaTokenFrom(form), "token");
|
|
assert.deepEqual(captchaFields("token"), { "wrn-captcha-response": "token" });
|
|
assert.equal(new Headers(captchaHeaders("token")).get("x-wrn-captcha-token"), "token");
|
|
|
|
assert.equal(uploaderAttributes({ store: "public", multiple: true })["data-uploader"], "public");
|
|
assert.match(formatFileSize(1024), /KB/);
|
|
|
|
const schema = v.object({ email: v.string().email() });
|
|
assert.deepEqual(parseOrThrow(schema, { email: "user@example.com" }), {
|
|
email: "user@example.com",
|
|
});
|
|
assert.deepEqual(validationSummary({ email: "Invalid" }), [{ field: "email", message: "Invalid" }]);
|
|
assert.equal(authRoute("signIn", { basePath: "/auth" }), "/auth/sign-in");
|
|
assert.equal((await authFailure("invalid", "Invalid").json()).ok, false);
|
|
|
|
console.log("Package kit runtime probes passed.");
|