Skip to content
File

Blob: src/workerd/api/tests/importable-env-test.js

javascript126 lines
1// Copyright (c) 2025 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4import {
5 strictEqual,
6 deepStrictEqual,
7 notStrictEqual,
8 ok,
9 rejects,
10} from 'node:assert';
11import { env, withEnv } from 'cloudflare:workers';
12 
13import { AsyncLocalStorage } from 'node:async_hooks';
14 
15// The env is populated at the top level scope.
16strictEqual(env.FOO, 'BAR');
17 
18// Cache exists and is accessible.
19ok(env.CACHE);
20// But fails when used because we're not in an io-context
21await rejects(
22 env.CACHE.read('hello', async () => {}),
23 {
24 message: /Disallowed operation called within global scope./,
25 }
26);
27 
28export const importableEnv = {
29 async test(_, argEnv) {
30 // Accessing the cache initially at the global scope didn't break anything
31 const cached = await argEnv.CACHE.read('hello', async () => {
32 return {
33 value: 123,
34 expiration: Date.now() + 10000,
35 };
36 });
37 strictEqual(cached, 123);
38 
39 // They aren't the same objects...
40 notStrictEqual(env, argEnv);
41 // But have all the same stuff...
42 deepStrictEqual(env, argEnv);
43 
44 deepStrictEqual(Object.keys(env).sort(), ['CACHE', 'FOO', 'RPC']);
45 
46 // It is populated inside a request
47 strictEqual(env.FOO, 'BAR');
48 
49 // And following async operations.
50 await scheduler.wait(10);
51 strictEqual(env.FOO, 'BAR');
52 
53 // Mutations to the env carry through as expected...
54 env.BAR = 123;
55 const { env: otherEnv } = await import('child');
56 strictEqual(otherEnv.FOO, 'BAR');
57 strictEqual(otherEnv.BAR, 123);
58 deepStrictEqual(argEnv, otherEnv);
59 
60 // Using withEnv to replace the env...
61 const { env: otherEnv2 } = await withEnv({ BAZ: 1 }, async () => {
62 await scheduler.wait(0);
63 return import('child2');
64 });
65 strictEqual(otherEnv2.FOO, undefined);
66 strictEqual(otherEnv2.BAZ, 1);
67 
68 // Original env is unmodified
69 strictEqual(env.BAZ, undefined);
70 
71 // Verify that JSRPC calls appropriately see the environment.
72 const remote = await argEnv.RPC.rpcTarget(undefined);
73 await Promise.all([remote.test(), remote.test2()]);
74 },
75 
76 get fetch() {
77 // It's a weird edge case, yes, but let's make sure that the env is
78 // available in getters when extracting the default handlers.
79 strictEqual(env.FOO, 'BAR');
80 return async () => {};
81 },
82 
83 // Verifies that the environment is available and correctly propagated
84 // in custom events like jsrpc.
85 rpcTarget() {
86 strictEqual(env.FOO, 'BAR');
87 return withEnv({ FOO: 'BAZ' }, () => {
88 // Arguably, returned RPC targets should probably automatically capture
89 // the current async context and propagate that on subsequent calls,
90 // but they currently do not... therefore we have to manually capture
91 // the async context and be sure to enter it ourselves below.
92 const runInAsyncContext = AsyncLocalStorage.snapshot();
93 return {
94 test() {
95 // This one runs with the modified env
96 runInAsyncContext(() => strictEqual(env.FOO, 'BAZ'));
97 },
98 test2() {
99 // This one runs with the original env
100 strictEqual(env.FOO, 'BAR');
101 },
102 };
103 });
104 },
105};
106 
107export const nullableEnv = {
108 async test(_, argEnv) {
109 strictEqual(env.FOO, 'BAR');
110 
111 await withEnv(null, async () => {
112 strictEqual(env.FOO, undefined);
113 strictEqual(env.CACHE, undefined);
114 strictEqual(typeof env, 'object');
115 });
116 
117 await withEnv(undefined, async () => {
118 strictEqual(env.FOO, undefined);
119 strictEqual(env.CACHE, undefined);
120 strictEqual(typeof env, 'object');
121 });
122 
123 strictEqual(env.FOO, 'BAR');
124 },
125};