Skip to content

Commit a651fdd

Browse files
authored
Add proving machine tests (#749)
* Add actor mock helper and tests * format tests * fix tests * wip fix tests * address cr feedback
1 parent 37863c8 commit a651fdd

File tree

5 files changed

+665
-0
lines changed

5 files changed

+665
-0
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11
2+
3+
import { jest } from '@jest/globals';
4+
5+
// Minimal actor stub used to observe send calls and emit state transitions
6+
7+
export const actorMock = {
8+
start: jest.fn(),
9+
stop: jest.fn(),
10+
send: jest.fn(),
11+
subscribe: jest.fn((cb: (state: any) => void) => {
12+
(actorMock as any)._callback = cb;
13+
return { unsubscribe: jest.fn() };
14+
}),
15+
};
16+
17+
export function emitState(stateValue: string) {
18+
const cb = (actorMock as any)._callback;
19+
if (cb) {
20+
cb({ value: stateValue, matches: (v: string) => v === stateValue });
21+
}
22+
}
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
// SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11
2+
3+
import { jest } from '@jest/globals';
4+
5+
import { useProtocolStore } from '../../../src/stores/protocolStore';
6+
import { useSelfAppStore } from '../../../src/stores/selfAppStore';
7+
import { useProvingStore } from '../../../src/utils/proving/provingMachine';
8+
9+
jest.mock('xstate', () => {
10+
const actual = jest.requireActual('xstate') as any;
11+
const { actorMock } = require('./actorMock');
12+
return { ...actual, createActor: jest.fn(() => actorMock) };
13+
});
14+
15+
jest.mock('../../../src/utils/analytics', () => () => ({
16+
trackEvent: jest.fn(),
17+
}));
18+
19+
jest.mock('@selfxyz/common', () => {
20+
const actual = jest.requireActual('@selfxyz/common') as any;
21+
return {
22+
...actual,
23+
getSolidityPackedUserContextData: jest.fn(() => '0x1234'),
24+
};
25+
});
26+
27+
jest.mock('../../../src/utils/proving/provingInputs', () => ({
28+
generateTEEInputsRegister: jest.fn(() => ({
29+
inputs: { r: 1 },
30+
circuitName: 'reg',
31+
endpointType: 'celo',
32+
endpoint: 'https://reg',
33+
})),
34+
generateTEEInputsDSC: jest.fn(() => ({
35+
inputs: { d: 1 },
36+
circuitName: 'dsc',
37+
endpointType: 'celo',
38+
endpoint: 'https://dsc',
39+
})),
40+
generateTEEInputsDisclose: jest.fn(() => ({
41+
inputs: { s: 1 },
42+
circuitName: 'vc_and_disclose',
43+
endpointType: 'https',
44+
endpoint: 'https://dis',
45+
})),
46+
}));
47+
48+
jest.mock('../../../src/utils/proving/provingUtils', () => {
49+
const actual = jest.requireActual(
50+
'../../../src/utils/proving/provingUtils',
51+
) as any;
52+
return {
53+
...actual,
54+
getPayload: jest.fn(() => ({ mocked: true })),
55+
encryptAES256GCM: jest.fn(() => ({
56+
nonce: [0],
57+
cipher_text: [1],
58+
auth_tag: [2],
59+
})),
60+
};
61+
});
62+
63+
const {
64+
getPayload,
65+
encryptAES256GCM,
66+
} = require('../../../src/utils/proving/provingUtils');
67+
const {
68+
generateTEEInputsRegister,
69+
generateTEEInputsDSC,
70+
generateTEEInputsDisclose,
71+
} = require('../../../src/utils/proving/provingInputs');
72+
73+
function setupDefaultStores() {
74+
useProvingStore.setState({
75+
circuitType: 'register',
76+
passportData: { documentCategory: 'passport', mock: false },
77+
secret: 'sec',
78+
uuid: '123',
79+
sharedKey: Buffer.alloc(32, 1),
80+
env: 'prod',
81+
});
82+
83+
useSelfAppStore.setState({
84+
selfApp: createMockSelfApp(),
85+
});
86+
87+
useProtocolStore.setState({
88+
passport: createMockProtocolState(),
89+
id_card: createMockProtocolState(),
90+
});
91+
}
92+
93+
function createMockSelfApp() {
94+
return {
95+
chainID: 42220 as const,
96+
userId: 'u',
97+
userDefinedData: '0x0',
98+
endpointType: 'https' as const,
99+
endpoint: 'https://e',
100+
scope: 's',
101+
sessionId: '',
102+
appName: '',
103+
logoBase64: '',
104+
header: '',
105+
userIdType: 'uuid' as const,
106+
devMode: false,
107+
disclosures: {},
108+
version: 1,
109+
};
110+
}
111+
112+
function createMockProtocolState() {
113+
return {
114+
dsc_tree: 'tree',
115+
csca_tree: [['a']],
116+
commitment_tree: null,
117+
deployed_circuits: null,
118+
circuits_dns_mapping: null,
119+
alternative_csca: {},
120+
fetch_deployed_circuits: jest.fn(() => Promise.resolve()),
121+
fetch_circuits_dns_mapping: jest.fn(() => Promise.resolve()),
122+
fetch_csca_tree: jest.fn(() => Promise.resolve()),
123+
fetch_dsc_tree: jest.fn(() => Promise.resolve()),
124+
fetch_identity_tree: jest.fn(() => Promise.resolve()),
125+
fetch_alternative_csca: jest.fn(() => Promise.resolve()),
126+
fetch_all: jest.fn(() => Promise.resolve()),
127+
};
128+
}
129+
130+
describe('_generatePayload', () => {
131+
beforeEach(() => {
132+
jest.clearAllMocks();
133+
setupDefaultStores();
134+
});
135+
136+
it('register circuit', async () => {
137+
useProvingStore.setState({ circuitType: 'register' });
138+
const payload = await useProvingStore.getState()._generatePayload();
139+
expect(generateTEEInputsRegister).toHaveBeenCalled();
140+
expect(getPayload).toHaveBeenCalled();
141+
expect(encryptAES256GCM).toHaveBeenCalled();
142+
expect(useProvingStore.getState().endpointType).toBe('celo');
143+
expect(payload.params).toEqual({
144+
uuid: '123',
145+
nonce: [0],
146+
cipher_text: [1],
147+
auth_tag: [2],
148+
});
149+
});
150+
151+
it('dsc circuit', async () => {
152+
useProvingStore.setState({ circuitType: 'dsc' });
153+
const payload = await useProvingStore.getState()._generatePayload();
154+
expect(generateTEEInputsDSC).toHaveBeenCalled();
155+
expect(generateTEEInputsDSC).toHaveBeenCalledWith(
156+
expect.objectContaining({
157+
documentCategory: 'passport',
158+
mock: false,
159+
}),
160+
[['a']], // csca_tree
161+
'prod', // env
162+
);
163+
expect(useProvingStore.getState().endpointType).toBe('celo');
164+
expect(payload.params.uuid).toBe('123');
165+
expect(payload.params).toEqual({
166+
uuid: '123',
167+
nonce: [0],
168+
cipher_text: [1],
169+
auth_tag: [2],
170+
});
171+
});
172+
173+
it('disclose circuit', async () => {
174+
useProvingStore.setState({ circuitType: 'disclose' });
175+
const payload = await useProvingStore.getState()._generatePayload();
176+
expect(generateTEEInputsDisclose).toHaveBeenCalled();
177+
expect(useProvingStore.getState().endpointType).toBe('https');
178+
expect(payload.params.uuid).toBe('123');
179+
});
180+
181+
it('handles missing passport data', async () => {
182+
useProvingStore.setState({ passportData: null });
183+
await expect(
184+
useProvingStore.getState()._generatePayload(),
185+
).rejects.toThrow();
186+
});
187+
188+
it('handles input generation failure', async () => {
189+
// Reset all mocks first
190+
jest.clearAllMocks();
191+
setupDefaultStores();
192+
193+
generateTEEInputsRegister.mockImplementation(() => {
194+
throw new Error('Input generation failed');
195+
});
196+
await expect(useProvingStore.getState()._generatePayload()).rejects.toThrow(
197+
'Input generation failed',
198+
);
199+
200+
// Restore the mock to its original implementation
201+
generateTEEInputsRegister.mockRestore();
202+
});
203+
204+
it('handles encryption failure', async () => {
205+
// Reset all mocks first
206+
jest.clearAllMocks();
207+
setupDefaultStores();
208+
209+
// Restore all original implementations first
210+
generateTEEInputsRegister.mockImplementation(() => ({
211+
inputs: { r: 1 },
212+
circuitName: 'reg',
213+
endpointType: 'celo',
214+
endpoint: 'https://reg',
215+
}));
216+
217+
encryptAES256GCM.mockImplementation(() => {
218+
throw new Error('Encryption failed');
219+
});
220+
await expect(useProvingStore.getState()._generatePayload()).rejects.toThrow(
221+
'Encryption failed',
222+
);
223+
});
224+
});

0 commit comments

Comments
 (0)