Skip to content

Commit 814b07d

Browse files
committed
fix: @W-22105418 [264][LWS] near-membrane testing
1 parent e259627 commit 814b07d

14 files changed

Lines changed: 1140 additions & 26 deletions

File tree

Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
// @ts-nocheck
2+
import {
3+
createBlueConnector,
4+
createMembraneMarshall,
5+
createRedConnector,
6+
VirtualEnvironment,
7+
} from '../../dist/index.mjs.js';
8+
9+
function createEnv(options = {}) {
10+
return new VirtualEnvironment({
11+
blueConnector: createBlueConnector(globalThis),
12+
redConnector: createRedConnector(globalThis.eval),
13+
...options,
14+
});
15+
}
16+
17+
describe('createMembraneMarshall', () => {
18+
it('returns a function (Connector)', () => {
19+
const connector = createMembraneMarshall(globalThis);
20+
expect(typeof connector).toBe('function');
21+
});
22+
it('returns a Connector that returns a HooksCallback', () => {
23+
const connector = createMembraneMarshall(globalThis);
24+
let hooksCalled = false;
25+
const result = connector('test', (...hooks) => {
26+
hooksCalled = true;
27+
expect(hooks.length).toBe(37);
28+
});
29+
expect(hooksCalled).toBe(true);
30+
expect(typeof result).toBe('function');
31+
});
32+
it('works without a globalObject argument', () => {
33+
expect(() => createMembraneMarshall()).not.toThrow();
34+
});
35+
});
36+
37+
describe('Membrane proxy traps', () => {
38+
afterEach(() => {
39+
jest.resetAllMocks();
40+
});
41+
42+
describe('get trap', () => {
43+
it('accesses properties on cross-realm objects', () => {
44+
const env = createEnv();
45+
globalThis.memTestGetObj = { x: 42, y: 'hello' };
46+
env.link('globalThis');
47+
expect(env.evaluate('memTestGetObj.x')).toBe(42);
48+
expect(env.evaluate('memTestGetObj.y')).toBe('hello');
49+
delete globalThis.memTestGetObj;
50+
});
51+
it('accesses nested properties', () => {
52+
const env = createEnv();
53+
globalThis.memTestNested = { a: { b: { c: 99 } } };
54+
env.link('globalThis');
55+
expect(env.evaluate('memTestNested.a.b.c')).toBe(99);
56+
delete globalThis.memTestNested;
57+
});
58+
});
59+
60+
describe('set trap', () => {
61+
it('sets properties on cross-realm objects', () => {
62+
const env = createEnv({
63+
liveTargetCallback() {
64+
return true;
65+
},
66+
});
67+
globalThis.memTestSetObj = { x: 1 };
68+
env.link('globalThis');
69+
env.evaluate('memTestSetObj.x = 100');
70+
expect(globalThis.memTestSetObj.x).toBe(100);
71+
delete globalThis.memTestSetObj;
72+
});
73+
});
74+
75+
describe('has trap', () => {
76+
it('detects property existence with in operator', () => {
77+
const env = createEnv();
78+
globalThis.memTestHasObj = { present: true };
79+
env.link('globalThis');
80+
expect(env.evaluate('"present" in memTestHasObj')).toBe(true);
81+
expect(env.evaluate('"absent" in memTestHasObj')).toBe(false);
82+
delete globalThis.memTestHasObj;
83+
});
84+
});
85+
86+
describe('deleteProperty trap', () => {
87+
it('deletes properties on cross-realm objects', () => {
88+
const env = createEnv({
89+
liveTargetCallback() {
90+
return true;
91+
},
92+
});
93+
globalThis.memTestDelObj = { toDelete: 1, toKeep: 2 };
94+
env.link('globalThis');
95+
env.evaluate('delete memTestDelObj.toDelete');
96+
expect(globalThis.memTestDelObj.toDelete).toBeUndefined();
97+
expect(globalThis.memTestDelObj.toKeep).toBe(2);
98+
delete globalThis.memTestDelObj;
99+
});
100+
});
101+
102+
describe('ownKeys trap', () => {
103+
it('returns own keys of cross-realm objects', () => {
104+
const env = createEnv();
105+
globalThis.memTestOwnKeysObj = { a: 1, b: 2, c: 3 };
106+
env.link('globalThis');
107+
const keys = env.evaluate('Object.keys(memTestOwnKeysObj)');
108+
expect(keys).toEqual(['a', 'b', 'c']);
109+
delete globalThis.memTestOwnKeysObj;
110+
});
111+
});
112+
113+
describe('getOwnPropertyDescriptor trap', () => {
114+
it('returns descriptors for cross-realm objects', () => {
115+
const env = createEnv();
116+
globalThis.memTestGopdObj = {};
117+
Object.defineProperty(globalThis.memTestGopdObj, 'x', {
118+
value: 42,
119+
writable: false,
120+
enumerable: true,
121+
configurable: true,
122+
});
123+
env.link('globalThis');
124+
const desc = env.evaluate('Object.getOwnPropertyDescriptor(memTestGopdObj, "x")');
125+
expect(desc.value).toBe(42);
126+
expect(desc.writable).toBe(false);
127+
expect(desc.enumerable).toBe(true);
128+
expect(desc.configurable).toBe(true);
129+
delete globalThis.memTestGopdObj;
130+
});
131+
});
132+
133+
describe('defineProperty trap', () => {
134+
it('defines properties on cross-realm objects', () => {
135+
const env = createEnv({
136+
liveTargetCallback() {
137+
return true;
138+
},
139+
});
140+
globalThis.memTestDefObj = {};
141+
env.link('globalThis');
142+
env.evaluate(
143+
'Object.defineProperty(memTestDefObj, "defined", { value: 123, configurable: true })'
144+
);
145+
expect(globalThis.memTestDefObj.defined).toBe(123);
146+
delete globalThis.memTestDefObj;
147+
});
148+
});
149+
150+
describe('getPrototypeOf trap', () => {
151+
it('returns prototype of cross-realm objects', () => {
152+
const env = createEnv();
153+
globalThis.memTestProtoObj = {};
154+
env.link('globalThis');
155+
expect(
156+
env.evaluate('Object.getPrototypeOf(memTestProtoObj) === Object.prototype')
157+
).toBe(true);
158+
delete globalThis.memTestProtoObj;
159+
});
160+
});
161+
162+
describe('isExtensible trap', () => {
163+
it('reports extensibility of cross-realm objects', () => {
164+
const env = createEnv();
165+
globalThis.memTestExtObj = {};
166+
env.link('globalThis');
167+
expect(env.evaluate('Object.isExtensible(memTestExtObj)')).toBe(true);
168+
delete globalThis.memTestExtObj;
169+
});
170+
});
171+
172+
describe('apply trap', () => {
173+
it('calls functions across the membrane', () => {
174+
const env = createEnv();
175+
globalThis.memTestApplyFn = (a: number, b: number) => a + b;
176+
env.link('globalThis');
177+
expect(env.evaluate('memTestApplyFn(3, 4)')).toBe(7);
178+
delete globalThis.memTestApplyFn;
179+
});
180+
it('calls functions with 0 args', () => {
181+
const env = createEnv();
182+
globalThis.memTestNoArgsFn = () => 'zero';
183+
env.link('globalThis');
184+
expect(env.evaluate('memTestNoArgsFn()')).toBe('zero');
185+
delete globalThis.memTestNoArgsFn;
186+
});
187+
it('calls functions with 1 arg', () => {
188+
const env = createEnv();
189+
globalThis.memTestOneArgFn = (a: number) => a * 2;
190+
env.link('globalThis');
191+
expect(env.evaluate('memTestOneArgFn(5)')).toBe(10);
192+
delete globalThis.memTestOneArgFn;
193+
});
194+
it('calls functions with many args', () => {
195+
const env = createEnv();
196+
globalThis.memTestManyArgsFn = (...args: number[]) => args.reduce((s, n) => s + n, 0);
197+
env.link('globalThis');
198+
expect(env.evaluate('memTestManyArgsFn(1,2,3,4,5,6)')).toBe(21);
199+
delete globalThis.memTestManyArgsFn;
200+
});
201+
});
202+
203+
describe('construct trap', () => {
204+
it('constructs instances across the membrane', () => {
205+
const env = createEnv();
206+
globalThis.memTestCtor = class {
207+
value: number;
208+
209+
constructor(v: number) {
210+
this.value = v;
211+
}
212+
};
213+
env.link('globalThis');
214+
const result = env.evaluate('new memTestCtor(42)');
215+
expect(result.value).toBe(42);
216+
delete globalThis.memTestCtor;
217+
});
218+
});
219+
220+
describe('frozen objects across the membrane', () => {
221+
it('preserves frozen state', () => {
222+
const env = createEnv();
223+
globalThis.memTestFrozenObj = Object.freeze({ a: 1, b: 2 });
224+
env.link('globalThis');
225+
expect(env.evaluate('Object.isFrozen(memTestFrozenObj)')).toBe(true);
226+
expect(env.evaluate('memTestFrozenObj.a')).toBe(1);
227+
delete globalThis.memTestFrozenObj;
228+
});
229+
});
230+
231+
describe('non-extensible objects across the membrane', () => {
232+
it('preserves non-extensible state', () => {
233+
const env = createEnv();
234+
globalThis.memTestNonExtObj = Object.preventExtensions({ a: 1 });
235+
env.link('globalThis');
236+
expect(env.evaluate('Object.isExtensible(memTestNonExtObj)')).toBe(false);
237+
delete globalThis.memTestNonExtObj;
238+
});
239+
});
240+
241+
describe('sealed objects across the membrane', () => {
242+
it('preserves sealed state', () => {
243+
const env = createEnv();
244+
globalThis.memTestSealedObj = Object.seal({ a: 1 });
245+
env.link('globalThis');
246+
expect(env.evaluate('Object.isSealed(memTestSealedObj)')).toBe(true);
247+
delete globalThis.memTestSealedObj;
248+
});
249+
});
250+
251+
describe('error propagation across traps', () => {
252+
it('propagates errors thrown in blue functions to red', () => {
253+
const env = createEnv();
254+
globalThis.memTestThrowFn = () => {
255+
throw new TypeError('blue error');
256+
};
257+
env.link('globalThis');
258+
expect(() => env.evaluate('memTestThrowFn()')).toThrow('blue error');
259+
delete globalThis.memTestThrowFn;
260+
});
261+
it('propagates errors thrown in red to blue', () => {
262+
const env = createEnv();
263+
expect(() => env.evaluate('throw new TypeError("red error")')).toThrow('red error');
264+
});
265+
});
266+
});

packages/near-membrane-base/src/__tests__/virtual-environment.spec.ts

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,4 +272,96 @@ describe('VirtualEnvironment', () => {
272272
env.remapProto(a, b);
273273
});
274274
});
275+
276+
describe('VirtualEnvironment.prototype.link', () => {
277+
it('links a single property key', () => {
278+
const env = new VirtualEnvironment({
279+
blueConnector: createBlueConnector(globalThis),
280+
redConnector: createRedConnector(globalThis.eval),
281+
});
282+
globalThis.linkTestSingle = { value: 42 };
283+
expect(() => env.link('linkTestSingle')).not.toThrow();
284+
delete globalThis.linkTestSingle;
285+
});
286+
it('links a multi-key property path', () => {
287+
const env = new VirtualEnvironment({
288+
blueConnector: createBlueConnector(globalThis),
289+
redConnector: createRedConnector(globalThis.eval),
290+
});
291+
globalThis.linkTestChainA = { linkTestChainB: { linkTestChainC: 99 } };
292+
expect(() => env.link('linkTestChainA', 'linkTestChainB')).not.toThrow();
293+
delete globalThis.linkTestChainA;
294+
});
295+
});
296+
297+
describe('VirtualEnvironment.prototype.lazyRemapProperties', () => {
298+
it('installs lazy property descriptors for given ownKeys', () => {
299+
const env = new VirtualEnvironment({
300+
blueConnector: createBlueConnector(globalThis),
301+
redConnector: createRedConnector(globalThis.eval),
302+
});
303+
const target = { lazyPropA: 1, lazyPropB: 2 };
304+
expect(() => env.lazyRemapProperties(target, ['lazyPropA', 'lazyPropB'])).not.toThrow();
305+
});
306+
it('accepts unforgeableGlobalThisKeys parameter', () => {
307+
const env = new VirtualEnvironment({
308+
blueConnector: createBlueConnector(globalThis),
309+
redConnector: createRedConnector(globalThis.eval),
310+
});
311+
const target = { x: 1 };
312+
expect(() => env.lazyRemapProperties(target, ['x'], ['window'])).not.toThrow();
313+
});
314+
it('ignores primitive targets', () => {
315+
const env = new VirtualEnvironment({
316+
blueConnector: createBlueConnector(globalThis),
317+
redConnector: createRedConnector(globalThis.eval),
318+
});
319+
expect(() => env.lazyRemapProperties(null as any, ['a'])).not.toThrow();
320+
expect(() => env.lazyRemapProperties(42 as any, ['a'])).not.toThrow();
321+
});
322+
});
323+
324+
describe('VirtualEnvironment.prototype.trackAsFastTarget', () => {
325+
it('does not throw for object targets', () => {
326+
const env = new VirtualEnvironment({
327+
blueConnector: createBlueConnector(globalThis),
328+
redConnector: createRedConnector(globalThis.eval),
329+
});
330+
const target = { fast: true };
331+
expect(() => env.trackAsFastTarget(target)).not.toThrow();
332+
});
333+
it('does not throw for function targets', () => {
334+
const env = new VirtualEnvironment({
335+
blueConnector: createBlueConnector(globalThis),
336+
redConnector: createRedConnector(globalThis.eval),
337+
});
338+
expect(() => env.trackAsFastTarget(() => {})).not.toThrow();
339+
});
340+
it('ignores primitive targets', () => {
341+
const env = new VirtualEnvironment({
342+
blueConnector: createBlueConnector(globalThis),
343+
redConnector: createRedConnector(globalThis.eval),
344+
});
345+
expect(() => env.trackAsFastTarget(null as any)).not.toThrow();
346+
expect(() => env.trackAsFastTarget(42 as any)).not.toThrow();
347+
});
348+
});
349+
350+
describe('options.instrumentation', () => {
351+
it('accepts an instrumentation object without error', () => {
352+
const mockInstrumentation = {
353+
startActivity: () => ({ stop() {}, error() {} }),
354+
log() {},
355+
error() {},
356+
};
357+
expect(() => {
358+
const env = new VirtualEnvironment({
359+
blueConnector: createBlueConnector(globalThis),
360+
redConnector: createRedConnector(globalThis.eval),
361+
instrumentation: mockInstrumentation,
362+
});
363+
env.evaluate('1 + 1');
364+
}).not.toThrow();
365+
});
366+
});
275367
});

0 commit comments

Comments
 (0)