Skip to content
File

Blob: tests/worker/utils.test.ts

typescript112 lines
1import { env } from "cloudflare:workers";
2import { describe, expect, it } from "vitest";
3 
4import { buildGitCheckoutAuth, redactSecrets } from "@/worker/sandbox/git";
5import { decryptSecret, encryptSecret, validateAppEncryptionConfig } from "@/worker/security/secrets";
6import { decodeBase64, decodeBase64Url, encodeBase64, encodeBase64Url } from "@/worker/services/crypto";
7 
8import { registerWorkerRuntimeHooks } from "../helpers/worker-hooks";
9 
10describe("worker utilities", () => {
11 registerWorkerRuntimeHooks();
12 const textDecoder = new TextDecoder();
13 const textEncoder = new TextEncoder();
14 
15 const buildEncryptionEnv = (
16 overrides: Partial<Pick<Env, "APP_ENCRYPTION_KEY_CURRENT_VERSION" | "APP_ENCRYPTION_KEYS_JSON">>,
17 ) =>
18 ({
19 APP_ENCRYPTION_KEY_CURRENT_VERSION:
20 overrides.APP_ENCRYPTION_KEY_CURRENT_VERSION ?? env.APP_ENCRYPTION_KEY_CURRENT_VERSION,
21 APP_ENCRYPTION_KEYS_JSON: overrides.APP_ENCRYPTION_KEYS_JSON ?? env.APP_ENCRYPTION_KEYS_JSON,
22 }) as unknown as Env;
23 
24 it("round-trips encrypted secrets with the test app key", async () => {
25 const encrypted = await encryptSecret(env, "super-secret-token");
26 const decrypted = await decryptSecret(env, encrypted);
27 
28 expect(decrypted).toBe("super-secret-token");
29 });
30 
31 it("accepts a valid app encryption configuration", async () => {
32 await expect(validateAppEncryptionConfig(env)).resolves.toBeUndefined();
33 });
34 
35 it("rejects a missing current key version", async () => {
36 const invalidEnv = buildEncryptionEnv({
37 APP_ENCRYPTION_KEYS_JSON: JSON.stringify({
38 2: Buffer.alloc(32, 3).toString("base64"),
39 }),
40 });
41 
42 await expect(validateAppEncryptionConfig(invalidEnv)).rejects.toMatchObject({
43 code: "encryption_not_configured",
44 });
45 });
46 
47 it("rejects invalid encryption key JSON", async () => {
48 const invalidEnv = buildEncryptionEnv({
49 APP_ENCRYPTION_KEYS_JSON: "{not-json",
50 });
51 
52 await expect(validateAppEncryptionConfig(invalidEnv)).rejects.toMatchObject({
53 code: "encryption_not_configured",
54 });
55 });
56 
57 it("rejects invalid encryption key material", async () => {
58 const invalidEnv = buildEncryptionEnv({
59 APP_ENCRYPTION_KEYS_JSON: JSON.stringify({
60 1: "not-base64",
61 }),
62 });
63 
64 await expect(validateAppEncryptionConfig(invalidEnv)).rejects.toMatchObject({
65 code: "encryption_not_configured",
66 });
67 });
68 
69 it("rejects encryption keys with the wrong length", async () => {
70 const invalidEnv = buildEncryptionEnv({
71 APP_ENCRYPTION_KEYS_JSON: JSON.stringify({
72 1: Buffer.alloc(16, 9).toString("base64"),
73 }),
74 });
75 
76 await expect(validateAppEncryptionConfig(invalidEnv)).rejects.toMatchObject({
77 code: "encryption_not_configured",
78 });
79 });
80 
81 it("round-trips standard and URL-safe base64 helpers", () => {
82 const bytes = new Uint8Array([0, 1, 2, 251, 252, 253, 254, 255]);
83 expect(Array.from(decodeBase64(encodeBase64(bytes)))).toEqual(Array.from(bytes));
84 expect(Array.from(decodeBase64Url(encodeBase64Url(bytes)))).toEqual(Array.from(bytes));
85 expect(Array.from(decodeBase64Url(encodeBase64Url(bytes).replace(/=+$/u, "")))).toEqual(Array.from(bytes));
86 
87 const jsonBytes = textEncoder.encode(JSON.stringify({ message: "hello tessera" }));
88 expect(textDecoder.decode(decodeBase64Url(encodeBase64Url(jsonBytes)))).toBe('{"message":"hello tessera"}');
89 });
90 
91 it("builds provider-specific git auth headers and redacts secrets", () => {
92 const github = buildGitCheckoutAuth("https://github.com/example/repo", "ghp_secret");
93 const gitlab = buildGitCheckoutAuth("https://gitlab.com/example/repo", "glpat-secret");
94 const custom = buildGitCheckoutAuth("https://codeberg.org/example/repo", "builder:token-123");
95 
96 expect(github.hasAuthHeader).toBe(true);
97 expect(github.sessionEnv.ANVIL_GIT_AUTH_HEADER).toContain("Basic");
98 expect(github.redactionSecrets).toContain("ghp_secret");
99 
100 expect(gitlab.hasAuthHeader).toBe(true);
101 expect(gitlab.redactionSecrets).toContain("glpat-secret");
102 
103 expect(custom.hasAuthHeader).toBe(true);
104 expect(custom.redactionSecrets).toContain("builder:token-123");
105 expect(custom.redactionSecrets).toContain("token-123");
106 
107 expect(redactSecrets("token-123 and ghp_secret", [...custom.redactionSecrets, ...github.redactionSecrets])).toBe(
108 "[REDACTED] and [REDACTED]",
109 );
110 });
111});