File
Blob: src/workerd/api/tests/rpc-error-test.js
| 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 |
| 4 | import { notStrictEqual, ok, strictEqual, throws } from 'assert'; |
| 5 | |
| 6 | export const test = { |
| 7 | async test(_, env) { |
| 8 | try { |
| 9 | await env.RPC.foo(null); |
| 10 | throw new Error('Expected error not thrown'); |
| 11 | } catch (e) { |
| 12 | // The error returned by RPC will have a stack2 property that contains |
| 13 | // the original stack from the worker where the error was thrown. |
| 14 | notStrictEqual(e.stack2, undefined); |
| 15 | // Because we are not setting the trusted option when deserializing the |
| 16 | // error, the stack of the error reported here should be different from |
| 17 | // the original stack. |
| 18 | notStrictEqual(e.stack2, e.stack); |
| 19 | } |
| 20 | |
| 21 | try { |
| 22 | const bar = await env.RPC.bar(null); |
| 23 | const baz = await bar.baz(null); |
| 24 | await baz.qux(null); |
| 25 | throw new Error('Expected error not thrown'); |
| 26 | } catch (e) { |
| 27 | // The error returned by RPC will have a stack2 property that contains |
| 28 | // the original stack from the worker where the error was thrown. |
| 29 | notStrictEqual(e.stack2, undefined); |
| 30 | // Because we are not setting the trusted option when deserializing the |
| 31 | // error, the stack of the error reported here should be different from |
| 32 | // the original stack. |
| 33 | notStrictEqual(e.stack2, e.stack); |
| 34 | } |
| 35 | |
| 36 | try { |
| 37 | await env.RPC.xyz(null); |
| 38 | throw new Error('Expected error not thrown'); |
| 39 | } catch (e) { |
| 40 | // The error returned by RPC will have a stack2 property that contains |
| 41 | // the original stack from the worker where the error was thrown. |
| 42 | notStrictEqual(e.stack2, undefined); |
| 43 | // Because we are not setting the trusted option when deserializing the |
| 44 | // error, the stack of the error reported here should be different from |
| 45 | // the original stack. |
| 46 | notStrictEqual(e.stack2, e.stack); |
| 47 | } |
| 48 | |
| 49 | { |
| 50 | const err = await env.RPC.abc(null).a; |
| 51 | // The error returned by RPC will have a stack2 property that contains |
| 52 | // the original stack from the worker where the error was thrown. |
| 53 | notStrictEqual(err.stack2, undefined); |
| 54 | // Because we are not setting the trusted option when deserializing the |
| 55 | // error, the stack of the error reported here should be different from |
| 56 | // the original stack. |
| 57 | notStrictEqual(err.stack2, err.stack); |
| 58 | } |
| 59 | }, |
| 60 | }; |
| 61 | |
| 62 | export let structuredCloneNotBroken = { |
| 63 | test(controller, env, ctx) { |
| 64 | // At one point, enhanced_error_serialization inadvertently broke structuredClone()'s support |
| 65 | // for host objects. |
| 66 | let orig = new Headers({ foo: '123', bar: 'abc' }); |
| 67 | let cloned = structuredClone(orig); |
| 68 | ok(cloned instanceof Headers); |
| 69 | strictEqual(cloned.get('foo'), '123'); |
| 70 | strictEqual(cloned.get('bar'), 'abc'); |
| 71 | |
| 72 | throws(() => structuredClone(new TextEncoder()), { |
| 73 | name: 'DataCloneError', |
| 74 | code: DOMException.DATA_CLONE_ERR, |
| 75 | message: |
| 76 | 'Could not serialize object of type "TextEncoder". This type does not support ' + |
| 77 | 'serialization.', |
| 78 | }); |
| 79 | }, |
| 80 | }; |
| 81 | |
| 82 | // DOMException is structured cloneable |
| 83 | export let domExceptionClone = { |
| 84 | test() { |
| 85 | const de1 = new DOMException('hello', 'NotAllowedError'); |
| 86 | |
| 87 | de1.foo = 'abc'; |
| 88 | |
| 89 | const de2 = structuredClone(de1); |
| 90 | ok(de2 instanceof DOMException); |
| 91 | strictEqual(de1.name, de2.name); |
| 92 | strictEqual(de1.message, de2.message); |
| 93 | strictEqual(de1.stack, de2.stack); |
| 94 | strictEqual(de1.code, de2.code); |
| 95 | notStrictEqual(de1, de2); |
| 96 | |
| 97 | // TODO(bug): This is supposed to work, but currently doesn't because the "native error" |
| 98 | // handling in jsg::Serializer does not kick in for DOMException. Instead, DOMException's |
| 99 | // own serializer (in dom-exception.c++) is used, and it hasn't been updated to handle |
| 100 | // serializing application properties. |
| 101 | // strictEqual(de1.foo, de2.foo); |
| 102 | // strictEqual(de2.foo, 'abc'); |
| 103 | }, |
| 104 | }; |