1- import { beforeEach , describe , expect , it , test } from 'vitest' ;
2- import { WasmResolver } from './WasmResolver' ;
1+ import { beforeEach , describe , expect , it , vi } from 'vitest' ;
2+ import { UnsafeWasmResolver , WasmResolver } from './WasmResolver' ;
33import { readFileSync } from 'node:fs' ;
4- import { ResolveReason } from './proto/api' ;
5- import { spawnSync } from 'node:child_process' ;
6- import { error } from 'node:console' ;
7- import { stderr } from 'node:process' ;
4+ import { ResolveWithStickyRequest , ResolveReason } from './proto/api' ;
5+ import { WriteFlagLogsRequest } from './proto/test-only' ;
86
97const moduleBytes = readFileSync ( __dirname + '/../../../wasm/confidence_resolver.wasm' ) ;
108const stateBytes = readFileSync ( __dirname + '/../../../wasm/resolver_state.pb' ) ;
119
10+ const module = new WebAssembly . Module ( moduleBytes ) ;
1211const CLIENT_SECRET = 'mkjJruAATQWjeY7foFIWfVAcBWnci2YF' ;
1312
13+ const RESOLVE_REQUEST :ResolveWithStickyRequest = {
14+ resolveRequest : {
15+ flags : [ 'flags/tutorial-feature' ] ,
16+ clientSecret : CLIENT_SECRET ,
17+ apply : true ,
18+ evaluationContext : {
19+ targeting_key : 'tutorial_visitor' ,
20+ visitor_id : 'tutorial_visitor' ,
21+ } ,
22+ } ,
23+ materializationsPerUnit : { } ,
24+ failFastOnSticky : false
25+ } ;
26+
27+ const SET_STATE_REQUEST = { state : stateBytes , accountId : 'confidence-test' } ;
28+
29+
1430let wasmResolver : WasmResolver ;
15- beforeEach ( async ( ) => {
16- wasmResolver = await WasmResolver . load ( new WebAssembly . Module ( moduleBytes ) ) ;
17- } ) ;
18-
19- it ( 'should fail to resolve without state' , ( ) => {
20- expect ( ( ) => {
21- wasmResolver . resolveWithSticky ( {
22- resolveRequest : { flags : [ ] , clientSecret : 'xyz' , apply : false } ,
23- materializationsPerUnit : { } ,
24- failFastOnSticky : false
25- } ) ;
26- } ) . toThrowError ( 'Resolver state not set' ) ;
27- } ) ;
2831
29- describe ( 'with state' , ( ) => {
32+ describe ( 'basic operation' , ( ) => {
33+
3034 beforeEach ( ( ) => {
31- wasmResolver . setResolverState ( { state : stateBytes , accountId : 'confidence-test' } ) ;
35+ wasmResolver = new WasmResolver ( module ) ;
36+ } ) ;
37+
38+ it ( 'should fail to resolve without state' , ( ) => {
39+ expect ( ( ) => {
40+ wasmResolver . resolveWithSticky ( RESOLVE_REQUEST ) ;
41+ } ) . toThrowError ( 'Resolver state not set' ) ;
3242 } ) ;
43+
44+ describe ( 'with state' , ( ) => {
45+ beforeEach ( ( ) => {
46+ wasmResolver . setResolverState ( SET_STATE_REQUEST ) ;
47+ } ) ;
48+
49+ it ( 'should resolve flags' , ( ) => {
50+ const resp = wasmResolver . resolveWithSticky ( RESOLVE_REQUEST ) ;
3351
34- it ( 'should resolve flags' , ( ) => {
35- try {
36- const resp = wasmResolver . resolveWithSticky ( {
37- resolveRequest : {
38- flags : [ 'flags/tutorial-feature' ] ,
39- clientSecret : CLIENT_SECRET ,
40- apply : true ,
41- evaluationContext : {
42- targeting_key : 'tutorial_visitor' ,
43- visitor_id : 'tutorial_visitor' ,
44- } ,
45- } ,
46- materializationsPerUnit : { } ,
47- failFastOnSticky : false
52+ expect ( resp ) . toMatchObject ( {
53+ success : {
54+ response : {
55+ resolvedFlags : [
56+ {
57+ reason : ResolveReason . RESOLVE_REASON_MATCH ,
58+ } ,
59+ ] ,
60+ }
61+ }
4862 } ) ;
63+ } ) ;
64+
65+ describe ( 'flushLogs' , ( ) => {
66+
67+ it ( 'should be empty before any resolve' , ( ) => {
68+ const logs = wasmResolver . flushLogs ( ) ;
69+ expect ( logs . length ) . toBe ( 0 ) ;
70+ } )
71+
72+ it ( 'should contain logs after a resolve' , ( ) => {
73+ wasmResolver . resolveWithSticky ( RESOLVE_REQUEST ) ;
74+
75+ const decoded = WriteFlagLogsRequest . decode ( wasmResolver . flushLogs ( ) ) ;
76+
77+ expect ( decoded . flagAssigned . length ) . toBe ( 1 )
78+ expect ( decoded . clientResolveInfo . length ) . toBe ( 1 ) ;
79+ expect ( decoded . flagResolveInfo . length ) . toBe ( 1 ) ;
80+ } )
81+ } )
82+ } ) ;
83+ } )
4984
50- expect ( resp . success ) . toBeDefined ( ) ;
51- expect ( resp . success ?. response ) . toMatchObject ( {
52- resolvedFlags : [
53- {
54- reason : ResolveReason . RESOLVE_REASON_MATCH ,
55- } ,
56- ] ,
57- } ) ;
58- } catch ( e ) {
59- console . log ( 'yo' , e ) ;
60- }
85+ describe ( 'panic handling' , ( ) => {
86+
87+ const resolveWithStickySpy = vi . spyOn ( UnsafeWasmResolver . prototype , 'resolveWithSticky' ) ;
88+ const setResolverStateSpy = vi . spyOn ( UnsafeWasmResolver . prototype , 'setResolverState' ) ;
89+
90+ const throwUnreachable = ( ) => {
91+ throw new WebAssembly . RuntimeError ( 'unreachable' ) ;
92+ }
93+
94+ beforeEach ( ( ) => {
95+ vi . resetAllMocks ( ) ;
96+ wasmResolver = new WasmResolver ( module ) ;
6197 } ) ;
6298
63- describe ( 'flushLogs' , ( ) => {
6499
65- it ( 'should be empty before any resolve' , ( ) => {
66- const logs = wasmResolver . flushLogs ( ) ;
67- expect ( logs . length ) . toBe ( 0 ) ;
68- } )
100+ it ( 'throws and reloads the instance on panic' , ( ) => {
101+ wasmResolver . setResolverState ( SET_STATE_REQUEST )
102+ resolveWithStickySpy . mockImplementationOnce ( throwUnreachable ) ;
69103
70- it ( 'should contain logs after a resolve' , ( ) => {
71- wasmResolver . resolveWithSticky ( {
72- resolveRequest : {
73- flags : [ 'flags/tutorial-feature' ] ,
74- clientSecret : CLIENT_SECRET ,
75- apply : true ,
76- evaluationContext : {
77- targeting_key : 'tutorial_visitor' ,
78- visitor_id : 'tutorial_visitor' ,
79- } ,
80- } ,
81- materializationsPerUnit : { } ,
82- failFastOnSticky : false
83- } ) ;
104+
105+ expect ( ( ) => {
106+ wasmResolver . resolveWithSticky ( RESOLVE_REQUEST )
107+ } ) . to . throw ( 'unreachable' ) ;
84108
85- const decoded = decodeBuffer ( wasmResolver . flushLogs ( ) ) ;
109+ // now it should succeed since the instance is reloaded
110+ expect ( ( ) => {
111+ wasmResolver . resolveWithSticky ( RESOLVE_REQUEST )
112+ } ) . to . not . throw ( ) ;
86113
87- expect ( decoded ) . contains ( 'flag_assigned' ) ;
88- expect ( decoded ) . contains ( 'client_resolve_info' ) ;
89- expect ( decoded ) . contains ( 'flag_resolve_info' ) ;
90- } )
91114 } )
92- } ) ;
93115
116+ it ( 'can handle panic in setResolverState' , ( ) => {
117+ setResolverStateSpy . mockImplementation ( throwUnreachable ) ;
118+
119+ expect ( ( ) => {
120+ wasmResolver . setResolverState ( SET_STATE_REQUEST )
121+ } ) . to . throw ( 'unreachable' ) ;
122+
123+ expect ( ( ) => {
124+ wasmResolver . resolveWithSticky ( RESOLVE_REQUEST )
125+ } ) . to . throw ( 'state not set' ) ;
126+
127+ } )
128+
129+ it ( 'tries to extracts logs from panicked instance' , ( ) => {
130+ wasmResolver . setResolverState ( SET_STATE_REQUEST )
131+
132+ // create some logs
133+ wasmResolver . resolveWithSticky ( RESOLVE_REQUEST ) ;
134+
135+ resolveWithStickySpy . mockImplementationOnce ( throwUnreachable ) ;
136+
137+ expect ( ( ) => {
138+ wasmResolver . resolveWithSticky ( RESOLVE_REQUEST )
139+ } ) . to . throw ( 'unreachable' ) ;
140+
141+ const logs = wasmResolver . flushLogs ( ) ;
142+
143+ expect ( logs . length ) . toBeGreaterThan ( 0 ) ;
144+
145+ } ) ;
146+
147+
148+ } )
94149
95- function decodeBuffer ( input :Uint8Array ) :string {
96- const res = spawnSync ( 'protoc' , [
97- `-I${ __dirname } /../../../confidence-resolver/protos` ,
98- `--decode=confidence.flags.resolver.v1.WriteFlagLogsRequest` ,
99- `confidence/flags/resolver/v1/internal_api.proto`
100- ] , { input, encoding : 'utf8' } ) ;
101- if ( res . error ) {
102- throw res . error ;
103- }
104- if ( res . status !== 0 ) {
105- throw new Error ( res . stderr )
106- }
107- return res . stdout ;
108- }
0 commit comments