1// @effect-diagnostics-next-line nodeBuiltinImport:off -- Effect's Crypto has no generateKeyPairSync.
2import * as NodeCrypto from "node:crypto";
3
4import { describe, expect, it } from "@effect/vitest";
5import * as Effect from "effect/Effect";
6
7import { RelayJwtError, signRelayJwt, verifyRelayJwt } from "./relayJwt.ts";
8
9describe("relayJwt", () => {
10 it.effect("preserves signing context and the JOSE cause", () =>
11 Effect.gen(function* () {
12 const error = yield* signRelayJwt({
13 privateKey: "not-a-private-key",
14 typ: "test-sign+jwt",
15 payload: { sub: "subject" },
16 }).pipe(Effect.flip);
17
18 expect(error.operation).toBe("sign");
19 expect(error.typ).toBe("test-sign+jwt");
20 expect(error.cause).toBeInstanceOf(Error);
21 expect(error.message).toBe('Failed to sign relay JWT of type "test-sign+jwt".');
22 }),
23 );
24
25 it.effect("preserves verification request context and the JOSE cause", () =>
26 Effect.gen(function* () {
27 const keyPair = NodeCrypto.generateKeyPairSync("ed25519", {
28 publicKeyEncoding: { format: "pem", type: "spki" },
29 privateKeyEncoding: { format: "pem", type: "pkcs8" },
30 });
31 const error = yield* verifyRelayJwt({
32 publicKey: keyPair.publicKey,
33 token: "not-a-jwt",
34 typ: "test-verify+jwt",
35 issuer: "https://issuer.example.test",
36 audience: "test-audience",
37 nowEpochSeconds: 100,
38 }).pipe(Effect.flip);
39
40 expect(error.operation).toBe("verify");
41 expect(error.typ).toBe("test-verify+jwt");
42 expect(error.issuer).toBe("https://issuer.example.test");
43 expect(error.audience).toBe("test-audience");
44 expect(error.cause).toBeInstanceOf(Error);
45 expect(error.message).toBe('Failed to verify relay JWT of type "test-verify+jwt".');
46 }),
47 );
48
49 it("extracts stable diagnostic codes without copying cause text into the error message", () => {
50 const error = new RelayJwtError({
51 operation: "verify",
52 typ: "test+jwt",
53 cause: { code: "ERR_JWT_EXPIRED", message: "sensitive library detail" },
54 });
55
56 expect(RelayJwtError.diagnosticCode(error)).toBe("ERR_JWT_EXPIRED");
57 expect(error.message).not.toContain("sensitive library detail");
58 });
59});