@@ -2,12 +2,16 @@ import type { Result } from "neverthrow";
22import { describe , expect , it } from "bun:test" ;
33
44import { createTransport } from "./client.js" ;
5- import { indexedTaggedUnion , Result as ScaleResult , str , _void } from "./scale.js" ;
5+ import { CallError , indexedTaggedUnion , Result as ScaleResult , str , _void } from "./scale.js" ;
6+ import type { Codec } from "./scale.js" ;
67import { createClient , SubscriptionError } from "./generated/client.js" ;
78import * as T from "./generated/types.js" ;
89import * as W from "./generated/wire-table.js" ;
910import { encodeWireMessage } from "./transport.js" ;
1011
12+ /** Wrap a codec in the `{ V1: [0, codec] }` indexed-tagged-union envelope. */
13+ const versionedV1 = < T > ( codec : Codec < T > ) => indexedTaggedUnion ( { V1 : [ 0 , codec ] } ) ;
14+
1115function toHex ( u : Uint8Array ) : string {
1216 return Array . from ( u )
1317 . map ( ( b ) => b . toString ( 16 ) . padStart ( 2 , "0" ) )
@@ -56,9 +60,34 @@ function providerFixture() {
5660
5761/** Encode a V1 host-handshake response result payload. */
5862function handshakeResponsePayload ( value : { success : true ; value : undefined } ) : Uint8Array {
59- return indexedTaggedUnion ( {
60- V1 : [ 0 , ScaleResult ( _void , T . HostHandshakeError ) ] ,
61- } ) . enc ( { tag : "V1" , value } ) ;
63+ return versionedV1 ( ScaleResult ( _void , CallError ( T . VersionedHostHandshakeError ) ) ) . enc ( {
64+ tag : "V1" ,
65+ value,
66+ } ) ;
67+ }
68+
69+ function accountGetResponsePayload (
70+ value :
71+ | {
72+ success : true ;
73+ value : T . HostAccountGetResponse ;
74+ }
75+ | {
76+ success : false ;
77+ value : { tag : "Domain" ; value : T . VersionedHostAccountGetError } ;
78+ } ,
79+ ) : Uint8Array {
80+ return versionedV1 (
81+ ScaleResult ( T . HostAccountGetResponse , CallError ( T . VersionedHostAccountGetError ) ) ,
82+ ) . enc ( { tag : "V1" , value } ) ;
83+ }
84+
85+ /** Encode a raw testing echo error response payload. */
86+ function testingEchoErrorPayload ( reason : string ) : Uint8Array {
87+ return ScaleResult ( _void , CallError ( T . V01TestingVersionProbeError ) ) . enc ( {
88+ success : false ,
89+ value : { tag : "HostFailure" , value : { reason } } ,
90+ } ) ;
6291}
6392
6493describe ( "generated client transport" , ( ) => {
@@ -88,6 +117,29 @@ describe("generated client transport", () => {
88117 expect ( toHex ( fixture . sent [ 0 ] ) ) . toBe ( toHex ( expectedFrame ) ) ;
89118 } ) ;
90119
120+ it ( "uses the latest generated request version for testing probes" , ( ) => {
121+ const fixture = providerFixture ( ) ;
122+ const transport = createTransport ( fixture . provider ) ;
123+ const client = createClient ( transport ) ;
124+
125+ const request = {
126+ message : "hello from test" ,
127+ marker : 42 ,
128+ } ;
129+ void client . testing . versionProbe ( request ) ;
130+
131+ const expectedPayload = T . VersionedTestingVersionProbeRequest . enc ( {
132+ tag : "V2" ,
133+ value : request ,
134+ } ) ;
135+ const expectedFrame = new Uint8Array ( str . enc ( "p:1" ) . length + 1 + expectedPayload . length ) ;
136+ expectedFrame . set ( str . enc ( "p:1" ) , 0 ) ;
137+ expectedFrame [ str . enc ( "p:1" ) . length ] = W . TESTING_VERSION_PROBE . request ;
138+ expectedFrame . set ( expectedPayload , str . enc ( "p:1" ) . length + 1 ) ;
139+
140+ expect ( toHex ( fixture . sent [ 0 ] ) ) . toBe ( toHex ( expectedFrame ) ) ;
141+ } ) ;
142+
91143 it ( "uses the transport codec version for generated handshake calls" , ( ) => {
92144 const fixture = providerFixture ( ) ;
93145 const transport = createTransport ( fixture . provider ) ;
@@ -129,6 +181,63 @@ describe("generated client transport", () => {
129181 expect ( result . isOk ( ) ) . toBe ( true ) ;
130182 } ) ;
131183
184+ it ( "decodes request domain errors from the versioned response envelope" , async ( ) => {
185+ const fixture = providerFixture ( ) ;
186+ const transport = createTransport ( fixture . provider ) ;
187+ const client = createClient ( transport ) ;
188+
189+ const response = client . account . getAccount ( {
190+ productAccountId : { dotNsIdentifier : "foo" , derivationIndex : 0 } ,
191+ } ) ;
192+ const reason = { tag : "V1" , value : { tag : "NotConnected" , value : undefined } } as const ;
193+ const frame = unwrap (
194+ encodeWireMessage ( {
195+ requestId : "p:1" ,
196+ payload : {
197+ id : W . ACCOUNT_GET_ACCOUNT . response ,
198+ value : accountGetResponsePayload ( {
199+ success : false ,
200+ value : { tag : "Domain" , value : reason } ,
201+ } ) ,
202+ } ,
203+ } ) ,
204+ "encode account_get error response" ,
205+ ) ;
206+ fixture . receive ( frame ) ;
207+
208+ const result = await response ;
209+ expect ( result . isErr ( ) ) . toBe ( true ) ;
210+ expect ( result . _unsafeUnwrapErr ( ) ) . toEqual ( { tag : "Domain" , value : reason } ) ;
211+ } ) ;
212+
213+ it ( "returns framework call errors as typed Err values" , async ( ) => {
214+ const fixture = providerFixture ( ) ;
215+ const transport = createTransport ( fixture . provider ) ;
216+ const client = createClient ( transport ) ;
217+
218+ const response = client . testing . echoError ( {
219+ error : { tag : "HostFailure" , value : { reason : "forced by test" } } ,
220+ } ) ;
221+ const frame = unwrap (
222+ encodeWireMessage ( {
223+ requestId : "p:1" ,
224+ payload : {
225+ id : W . TESTING_ECHO_ERROR . response ,
226+ value : testingEchoErrorPayload ( "forced by test" ) ,
227+ } ,
228+ } ) ,
229+ "encode testing framework error response" ,
230+ ) ;
231+ fixture . receive ( frame ) ;
232+
233+ const result = await response ;
234+ expect ( result . isErr ( ) ) . toBe ( true ) ;
235+ expect ( result . _unsafeUnwrapErr ( ) ) . toEqual ( {
236+ tag : "HostFailure" ,
237+ value : { reason : "forced by test" } ,
238+ } ) ;
239+ } ) ;
240+
132241 it ( "auto-responds to an inbound handshake with the versioned-result shape" , ( ) => {
133242 const fixture = providerFixture ( ) ;
134243 createTransport ( fixture . provider ) ;
@@ -225,14 +334,18 @@ describe("generated client transport", () => {
225334 } ) ;
226335
227336 const reason = { tag : "PermissionDenied" , value : undefined } as const ;
337+ const callError = {
338+ tag : "Domain" ,
339+ value : { tag : "V1" , value : reason } ,
340+ } as const ;
228341 const frame = unwrap (
229342 encodeWireMessage ( {
230343 requestId : sub . subscriptionId ,
231344 payload : {
232345 id : W . PAYMENT_BALANCE_SUBSCRIBE . interrupt ,
233- value : T . VersionedHostPaymentBalanceSubscribeError . enc ( {
346+ value : versionedV1 ( CallError ( T . VersionedHostPaymentBalanceSubscribeError ) ) . enc ( {
234347 tag : "V1" ,
235- value : reason ,
348+ value : callError ,
236349 } ) ,
237350 } ,
238351 } ) ,
@@ -243,7 +356,7 @@ describe("generated client transport", () => {
243356 expect ( completions ) . toEqual ( [ ] ) ;
244357 expect ( errors ) . toHaveLength ( 1 ) ;
245358 expect ( errors [ 0 ] ) . toBeInstanceOf ( SubscriptionError ) ;
246- expect ( ( errors [ 0 ] as SubscriptionError ) . reason ) . toEqual ( reason ) ;
359+ expect ( ( errors [ 0 ] as SubscriptionError ) . reason ) . toEqual ( callError ) ;
247360 expect ( fixture . sent ) . toHaveLength ( 1 ) ;
248361 } ) ;
249362
@@ -258,15 +371,18 @@ describe("generated client transport", () => {
258371 . subscribe ( { error : ( error ) => errors . push ( error ) } ) ;
259372
260373 const reason = "Denied" ;
374+ const callError = {
375+ tag : "Domain" ,
376+ value : { tag : "V1" , value : reason } ,
377+ } as const ;
261378 const frame = unwrap (
262379 encodeWireMessage ( {
263380 requestId : sub . subscriptionId ,
264381 payload : {
265382 id : W . COIN_PAYMENT_REBALANCE_PURSE . interrupt ,
266- value : T . VersionedHostCoinPaymentRebalancePurseError . enc ( {
267- tag : "V1" ,
268- value : reason ,
269- } ) ,
383+ value : versionedV1 (
384+ CallError ( T . VersionedHostCoinPaymentRebalancePurseError ) ,
385+ ) . enc ( { tag : "V1" , value : callError } ) ,
270386 } ,
271387 } ) ,
272388 "encode typed coin payment interrupt" ,
@@ -275,7 +391,7 @@ describe("generated client transport", () => {
275391
276392 expect ( errors ) . toHaveLength ( 1 ) ;
277393 expect ( errors [ 0 ] ) . toBeInstanceOf ( SubscriptionError ) ;
278- expect ( ( errors [ 0 ] as SubscriptionError ) . reason ) . toEqual ( reason ) ;
394+ expect ( ( errors [ 0 ] as SubscriptionError ) . reason ) . toEqual ( callError ) ;
279395 } ) ;
280396
281397 it ( "treats a malformed receive payload as terminal and sends _stop" , ( ) => {
0 commit comments