@robhowley/pi-openrouter 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -5
- package/extensions/openrouter/__tests__/cache.test.ts +769 -0
- package/extensions/openrouter/__tests__/client.test.ts +333 -15
- package/extensions/openrouter/__tests__/commands.test.ts +816 -0
- package/extensions/openrouter/__tests__/fixtures.ts +140 -1
- package/extensions/openrouter/__tests__/format.test.ts +19 -0
- package/extensions/openrouter/__tests__/hooks.test.ts +529 -0
- package/extensions/openrouter/__tests__/index.test.ts +112 -363
- package/extensions/openrouter/__tests__/local-usage.test.ts +777 -0
- package/extensions/openrouter/__tests__/normalizers.test.ts +288 -0
- package/extensions/openrouter/__tests__/overlay.test.ts +225 -0
- package/extensions/openrouter/__tests__/session-state.test.ts +233 -0
- package/extensions/openrouter/__tests__/session.test.ts +44 -43
- package/extensions/openrouter/__tests__/status-bar.test.ts +262 -0
- package/extensions/openrouter/account-client.ts +11 -61
- package/extensions/openrouter/cache.ts +203 -91
- package/extensions/openrouter/client.ts +49 -3
- package/extensions/openrouter/commands.ts +555 -0
- package/extensions/openrouter/format.ts +7 -4
- package/extensions/openrouter/hooks.ts +288 -0
- package/extensions/openrouter/index.ts +13 -990
- package/extensions/openrouter/local-usage.ts +158 -30
- package/extensions/openrouter/models/__tests__/cache.test.ts +63 -2
- package/extensions/openrouter/models/__tests__/mapper.test.ts +29 -0
- package/extensions/openrouter/models/__tests__/override-commands.test.ts +668 -0
- package/extensions/openrouter/models/__tests__/sync.test.ts +156 -4
- package/extensions/openrouter/models/cache.ts +27 -2
- package/extensions/openrouter/models/mapper.ts +35 -69
- package/extensions/openrouter/models/override-commands.ts +434 -0
- package/extensions/openrouter/models/skip-hints.ts +19 -0
- package/extensions/openrouter/models/sync.ts +22 -10
- package/extensions/openrouter/models/types.ts +2 -1
- package/extensions/openrouter/normalizers.ts +128 -0
- package/extensions/openrouter/overlay.ts +19 -8
- package/extensions/openrouter/session-state.ts +110 -0
- package/extensions/openrouter/session.ts +16 -0
- package/extensions/openrouter/status-bar.ts +101 -0
- package/extensions/openrouter/types.ts +28 -9
- package/package.json +1 -1
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
fetchUserModels,
|
|
4
|
+
isConfigured,
|
|
5
|
+
getApiKey,
|
|
6
|
+
getModelSyncApiKey,
|
|
7
|
+
getUsageApiKey,
|
|
8
|
+
isConfiguredForModelSync,
|
|
9
|
+
isConfiguredForUsage,
|
|
10
|
+
ApiError,
|
|
11
|
+
AuthError,
|
|
12
|
+
} from '../client.js';
|
|
3
13
|
import { restoreEnv } from './fixtures.js';
|
|
4
14
|
import type { Mock } from 'vitest';
|
|
5
15
|
|
|
@@ -8,14 +18,31 @@ vi.mock('@openrouter/sdk/sdk/sdk.js', () => ({
|
|
|
8
18
|
OpenRouter: vi.fn(),
|
|
9
19
|
}));
|
|
10
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Helper to set or clear OPENROUTER_API_KEY and OPENROUTER_MANAGEMENT_KEY.
|
|
23
|
+
* Pass undefined to delete the env var.
|
|
24
|
+
*/
|
|
25
|
+
function setKeys(apiKey?: string, mgmtKey?: string) {
|
|
26
|
+
if (apiKey === undefined) {
|
|
27
|
+
delete process.env['OPENROUTER_API_KEY'];
|
|
28
|
+
} else {
|
|
29
|
+
process.env['OPENROUTER_API_KEY'] = apiKey;
|
|
30
|
+
}
|
|
31
|
+
if (mgmtKey === undefined) {
|
|
32
|
+
delete process.env['OPENROUTER_MANAGEMENT_KEY'];
|
|
33
|
+
} else {
|
|
34
|
+
process.env['OPENROUTER_MANAGEMENT_KEY'] = mgmtKey;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
11
38
|
/**
|
|
12
39
|
* Factory function to create a minimal mock SDK client.
|
|
13
40
|
* Reduces boilerplate from ~25 lines to 1 line per test.
|
|
14
41
|
*/
|
|
15
|
-
function createMockSDKClient(overrides: { listForUser?: Mock } = {}) {
|
|
42
|
+
function createMockSDKClient(overrides: { listForUser?: Mock; getCredits?: Mock } = {}) {
|
|
16
43
|
return {
|
|
17
44
|
models: { listForUser: overrides.listForUser ?? vi.fn() },
|
|
18
|
-
credits: {},
|
|
45
|
+
credits: { getCredits: overrides.getCredits ?? vi.fn() },
|
|
19
46
|
analytics: {},
|
|
20
47
|
chat: {},
|
|
21
48
|
embeddings: {},
|
|
@@ -46,14 +73,14 @@ describe('fetchUserModels', () => {
|
|
|
46
73
|
});
|
|
47
74
|
|
|
48
75
|
it('should throw AuthError when API key not set', async () => {
|
|
49
|
-
|
|
76
|
+
setKeys(undefined, undefined);
|
|
50
77
|
|
|
51
|
-
await expect(fetchUserModels()).rejects.toThrow('
|
|
78
|
+
await expect(fetchUserModels()).rejects.toThrow('OpenRouter API key not configured');
|
|
52
79
|
await expect(fetchUserModels()).rejects.toBeInstanceOf(AuthError);
|
|
53
80
|
});
|
|
54
81
|
|
|
55
82
|
it('should fetch models with SDK', async () => {
|
|
56
|
-
|
|
83
|
+
setKeys('test-key', undefined);
|
|
57
84
|
|
|
58
85
|
const mockResponse = {
|
|
59
86
|
data: [
|
|
@@ -81,7 +108,7 @@ describe('fetchUserModels', () => {
|
|
|
81
108
|
});
|
|
82
109
|
|
|
83
110
|
it('should throw ApiError on 401 unauthorized', async () => {
|
|
84
|
-
|
|
111
|
+
setKeys('invalid-key', undefined);
|
|
85
112
|
|
|
86
113
|
const MockOpenRouter = vi.mocked((await import('@openrouter/sdk/sdk/sdk.js')).OpenRouter);
|
|
87
114
|
const mockClient = createMockSDKClient({
|
|
@@ -96,7 +123,7 @@ describe('fetchUserModels', () => {
|
|
|
96
123
|
});
|
|
97
124
|
|
|
98
125
|
it('should throw ApiError on rate limit (429)', async () => {
|
|
99
|
-
|
|
126
|
+
setKeys('test-key', undefined);
|
|
100
127
|
|
|
101
128
|
const MockOpenRouter = vi.mocked((await import('@openrouter/sdk/sdk/sdk.js')).OpenRouter);
|
|
102
129
|
const mockClient = createMockSDKClient({
|
|
@@ -111,7 +138,7 @@ describe('fetchUserModels', () => {
|
|
|
111
138
|
});
|
|
112
139
|
|
|
113
140
|
it('should throw ApiError on server error', async () => {
|
|
114
|
-
|
|
141
|
+
setKeys('test-key', undefined);
|
|
115
142
|
|
|
116
143
|
const MockOpenRouter = vi.mocked((await import('@openrouter/sdk/sdk/sdk.js')).OpenRouter);
|
|
117
144
|
const mockClient = createMockSDKClient({
|
|
@@ -132,17 +159,17 @@ describe('isConfigured', () => {
|
|
|
132
159
|
});
|
|
133
160
|
|
|
134
161
|
it('should return true when API key is set', () => {
|
|
135
|
-
|
|
162
|
+
setKeys('test-key', undefined);
|
|
136
163
|
expect(isConfigured()).toBe(true);
|
|
137
164
|
});
|
|
138
165
|
|
|
139
166
|
it('should return false when API key is not set', () => {
|
|
140
|
-
|
|
167
|
+
setKeys(undefined, undefined);
|
|
141
168
|
expect(isConfigured()).toBe(false);
|
|
142
169
|
});
|
|
143
170
|
|
|
144
171
|
it('should return false when API key is empty string', () => {
|
|
145
|
-
|
|
172
|
+
setKeys('', undefined);
|
|
146
173
|
expect(isConfigured()).toBe(false);
|
|
147
174
|
});
|
|
148
175
|
});
|
|
@@ -153,17 +180,308 @@ describe('getApiKey', () => {
|
|
|
153
180
|
});
|
|
154
181
|
|
|
155
182
|
it('should return the API key when set', () => {
|
|
156
|
-
|
|
183
|
+
setKeys('test-key', undefined);
|
|
157
184
|
expect(getApiKey()).toBe('test-key');
|
|
158
185
|
});
|
|
159
186
|
|
|
160
187
|
it('should return undefined when API key is not set', () => {
|
|
161
|
-
|
|
188
|
+
setKeys(undefined, undefined);
|
|
162
189
|
expect(getApiKey()).toBeUndefined();
|
|
163
190
|
});
|
|
164
191
|
|
|
165
192
|
it('should return empty string when API key is empty', () => {
|
|
166
|
-
|
|
193
|
+
setKeys('', undefined);
|
|
167
194
|
expect(getApiKey()).toBe('');
|
|
168
195
|
});
|
|
169
196
|
});
|
|
197
|
+
|
|
198
|
+
// Phase 1 regression tests
|
|
199
|
+
describe('getModelSyncApiKey', () => {
|
|
200
|
+
beforeEach(() => {
|
|
201
|
+
restoreEnv();
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it.each([
|
|
205
|
+
{
|
|
206
|
+
apiKey: 'api-key',
|
|
207
|
+
mgmtKey: 'mgmt-key',
|
|
208
|
+
expected: 'api-key',
|
|
209
|
+
desc: 'should return OPENROUTER_API_KEY when both keys are present',
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
apiKey: undefined,
|
|
213
|
+
mgmtKey: 'mgmt-key',
|
|
214
|
+
expected: 'mgmt-key',
|
|
215
|
+
desc: 'should return OPENROUTER_MANAGEMENT_KEY when only management key is present',
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
apiKey: undefined,
|
|
219
|
+
mgmtKey: undefined,
|
|
220
|
+
expected: undefined,
|
|
221
|
+
desc: 'should return undefined when no keys are present',
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
apiKey: '',
|
|
225
|
+
mgmtKey: 'mgmt-key',
|
|
226
|
+
expected: 'mgmt-key',
|
|
227
|
+
desc: 'should treat empty strings as absent',
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
apiKey: ' ',
|
|
231
|
+
mgmtKey: 'mgmt-key',
|
|
232
|
+
expected: 'mgmt-key',
|
|
233
|
+
desc: 'should treat whitespace-only strings as absent',
|
|
234
|
+
},
|
|
235
|
+
])('$desc', ({ apiKey, mgmtKey, expected }) => {
|
|
236
|
+
setKeys(apiKey, mgmtKey);
|
|
237
|
+
expect(getModelSyncApiKey()).toBe(expected);
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
describe('getUsageApiKey', () => {
|
|
242
|
+
beforeEach(() => {
|
|
243
|
+
restoreEnv();
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it.each([
|
|
247
|
+
{
|
|
248
|
+
apiKey: 'api-key',
|
|
249
|
+
mgmtKey: 'mgmt-key',
|
|
250
|
+
expected: 'mgmt-key',
|
|
251
|
+
desc: 'should return OPENROUTER_MANAGEMENT_KEY when both keys are present',
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
apiKey: 'api-key',
|
|
255
|
+
mgmtKey: undefined,
|
|
256
|
+
expected: 'api-key',
|
|
257
|
+
desc: 'should return OPENROUTER_API_KEY when only API key is present',
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
apiKey: undefined,
|
|
261
|
+
mgmtKey: undefined,
|
|
262
|
+
expected: undefined,
|
|
263
|
+
desc: 'should return undefined when no keys are present',
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
apiKey: 'api-key',
|
|
267
|
+
mgmtKey: '',
|
|
268
|
+
expected: 'api-key',
|
|
269
|
+
desc: 'should treat empty strings as absent',
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
apiKey: ' ',
|
|
273
|
+
mgmtKey: 'mgmt-key',
|
|
274
|
+
expected: 'mgmt-key',
|
|
275
|
+
desc: 'should treat whitespace-only strings as absent',
|
|
276
|
+
},
|
|
277
|
+
])('$desc', ({ apiKey, mgmtKey, expected }) => {
|
|
278
|
+
setKeys(apiKey, mgmtKey);
|
|
279
|
+
expect(getUsageApiKey()).toBe(expected);
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
describe('isConfiguredForModelSync', () => {
|
|
284
|
+
beforeEach(() => {
|
|
285
|
+
restoreEnv();
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it.each([
|
|
289
|
+
{
|
|
290
|
+
apiKey: 'api-key',
|
|
291
|
+
mgmtKey: undefined,
|
|
292
|
+
expected: true,
|
|
293
|
+
desc: 'should return true when API key is set',
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
apiKey: undefined,
|
|
297
|
+
mgmtKey: 'mgmt-key',
|
|
298
|
+
expected: true,
|
|
299
|
+
desc: 'should return true when management key is set',
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
apiKey: undefined,
|
|
303
|
+
mgmtKey: undefined,
|
|
304
|
+
expected: false,
|
|
305
|
+
desc: 'should return false when no keys are set',
|
|
306
|
+
},
|
|
307
|
+
])('$desc', ({ apiKey, mgmtKey, expected }) => {
|
|
308
|
+
setKeys(apiKey, mgmtKey);
|
|
309
|
+
expect(isConfiguredForModelSync()).toBe(expected);
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
describe('isConfiguredForUsage', () => {
|
|
314
|
+
beforeEach(() => {
|
|
315
|
+
restoreEnv();
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
it.each([
|
|
319
|
+
{
|
|
320
|
+
apiKey: undefined,
|
|
321
|
+
mgmtKey: 'mgmt-key',
|
|
322
|
+
expected: true,
|
|
323
|
+
desc: 'should return true when management key is set',
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
apiKey: 'api-key',
|
|
327
|
+
mgmtKey: undefined,
|
|
328
|
+
expected: true,
|
|
329
|
+
desc: 'should return true when API key is set',
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
apiKey: undefined,
|
|
333
|
+
mgmtKey: undefined,
|
|
334
|
+
expected: false,
|
|
335
|
+
desc: 'should return false when no keys are set',
|
|
336
|
+
},
|
|
337
|
+
])('$desc', ({ apiKey, mgmtKey, expected }) => {
|
|
338
|
+
setKeys(apiKey, mgmtKey);
|
|
339
|
+
expect(isConfiguredForUsage()).toBe(expected);
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
describe('fetchUserModels with management key fallback', () => {
|
|
344
|
+
beforeEach(() => {
|
|
345
|
+
vi.resetAllMocks();
|
|
346
|
+
restoreEnv();
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it('should accept OPENROUTER_MANAGEMENT_KEY when only management key is present', async () => {
|
|
350
|
+
setKeys(undefined, 'mgmt-key');
|
|
351
|
+
|
|
352
|
+
const mockResponse = {
|
|
353
|
+
data: [
|
|
354
|
+
{
|
|
355
|
+
id: 'openai/gpt-4',
|
|
356
|
+
name: 'GPT-4',
|
|
357
|
+
context_length: 8192,
|
|
358
|
+
pricing: { prompt: '0.00003', completion: '0.00006' },
|
|
359
|
+
},
|
|
360
|
+
],
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
const MockOpenRouter = vi.mocked((await import('@openrouter/sdk/sdk/sdk.js')).OpenRouter);
|
|
364
|
+
const mockClient = createMockSDKClient({
|
|
365
|
+
listForUser: vi.fn().mockResolvedValue(mockResponse),
|
|
366
|
+
});
|
|
367
|
+
MockOpenRouter.mockImplementation(() => mockClient as any);
|
|
368
|
+
|
|
369
|
+
const result = await fetchUserModels();
|
|
370
|
+
expect(result!.data).toHaveLength(1);
|
|
371
|
+
expect(mockClient.models.listForUser).toHaveBeenCalled();
|
|
372
|
+
// Verify management key was used
|
|
373
|
+
expect(MockOpenRouter).toHaveBeenCalledWith(expect.objectContaining({ apiKey: 'mgmt-key' }));
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
it('should prefer OPENROUTER_API_KEY when both keys are present', async () => {
|
|
377
|
+
setKeys('api-key', 'mgmt-key');
|
|
378
|
+
|
|
379
|
+
const mockResponse = {
|
|
380
|
+
data: [
|
|
381
|
+
{
|
|
382
|
+
id: 'openai/gpt-4',
|
|
383
|
+
name: 'GPT-4',
|
|
384
|
+
context_length: 8192,
|
|
385
|
+
pricing: { prompt: '0.00003', completion: '0.00006' },
|
|
386
|
+
},
|
|
387
|
+
],
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
const MockOpenRouter = vi.mocked((await import('@openrouter/sdk/sdk/sdk.js')).OpenRouter);
|
|
391
|
+
const mockClient = createMockSDKClient({
|
|
392
|
+
listForUser: vi.fn().mockResolvedValue(mockResponse),
|
|
393
|
+
});
|
|
394
|
+
MockOpenRouter.mockImplementation(() => mockClient as any);
|
|
395
|
+
|
|
396
|
+
await fetchUserModels();
|
|
397
|
+
// Verify that the OpenRouter client was constructed with the API key, not management key
|
|
398
|
+
expect(MockOpenRouter).toHaveBeenCalledWith(expect.objectContaining({ apiKey: 'api-key' }));
|
|
399
|
+
});
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
describe('getCredits with usage API key selection', () => {
|
|
403
|
+
beforeEach(() => {
|
|
404
|
+
vi.resetAllMocks();
|
|
405
|
+
restoreEnv();
|
|
406
|
+
vi.resetModules(); // Clear module cache including client instance
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
it('should use OPENROUTER_MANAGEMENT_KEY when both keys are present', async () => {
|
|
410
|
+
setKeys('api-key', 'mgmt-key');
|
|
411
|
+
|
|
412
|
+
const mockResponse = {
|
|
413
|
+
data: {
|
|
414
|
+
total_granted: 10.0,
|
|
415
|
+
total_used: 2.5,
|
|
416
|
+
total_available: 7.5,
|
|
417
|
+
},
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
const MockOpenRouter = vi.mocked((await import('@openrouter/sdk/sdk/sdk.js')).OpenRouter);
|
|
421
|
+
const mockClient = createMockSDKClient({
|
|
422
|
+
getCredits: vi.fn().mockResolvedValue(mockResponse),
|
|
423
|
+
});
|
|
424
|
+
MockOpenRouter.mockImplementation(() => mockClient as any);
|
|
425
|
+
|
|
426
|
+
const { getCredits } = await import('../client.js');
|
|
427
|
+
await getCredits();
|
|
428
|
+
|
|
429
|
+
// Verify management key was used (not API key)
|
|
430
|
+
expect(MockOpenRouter).toHaveBeenCalledWith(expect.objectContaining({ apiKey: 'mgmt-key' }));
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
it('should fall back to OPENROUTER_API_KEY when only API key is present', async () => {
|
|
434
|
+
setKeys('api-key', undefined);
|
|
435
|
+
|
|
436
|
+
const mockResponse = {
|
|
437
|
+
data: {
|
|
438
|
+
total_granted: 10.0,
|
|
439
|
+
total_used: 2.5,
|
|
440
|
+
total_available: 7.5,
|
|
441
|
+
},
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
const MockOpenRouter = vi.mocked((await import('@openrouter/sdk/sdk/sdk.js')).OpenRouter);
|
|
445
|
+
const mockClient = createMockSDKClient({
|
|
446
|
+
getCredits: vi.fn().mockResolvedValue(mockResponse),
|
|
447
|
+
});
|
|
448
|
+
MockOpenRouter.mockImplementation(() => mockClient as any);
|
|
449
|
+
|
|
450
|
+
const { getCredits } = await import('../client.js');
|
|
451
|
+
await getCredits();
|
|
452
|
+
|
|
453
|
+
expect(MockOpenRouter).toHaveBeenCalledWith(expect.objectContaining({ apiKey: 'api-key' }));
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
it('should fall back to API key when management key is whitespace', async () => {
|
|
457
|
+
setKeys('api-key', ' ');
|
|
458
|
+
|
|
459
|
+
const mockResponse = {
|
|
460
|
+
data: {
|
|
461
|
+
total_granted: 10.0,
|
|
462
|
+
total_used: 2.5,
|
|
463
|
+
total_available: 7.5,
|
|
464
|
+
},
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
const MockOpenRouter = vi.mocked((await import('@openrouter/sdk/sdk/sdk.js')).OpenRouter);
|
|
468
|
+
const mockClient = createMockSDKClient({
|
|
469
|
+
getCredits: vi.fn().mockResolvedValue(mockResponse),
|
|
470
|
+
});
|
|
471
|
+
MockOpenRouter.mockImplementation(() => mockClient as any);
|
|
472
|
+
|
|
473
|
+
const { getCredits } = await import('../client.js');
|
|
474
|
+
await getCredits();
|
|
475
|
+
|
|
476
|
+
// Should use API key since management key is whitespace-only
|
|
477
|
+
expect(MockOpenRouter).toHaveBeenCalledWith(expect.objectContaining({ apiKey: 'api-key' }));
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
it('should return null when no keys are configured', async () => {
|
|
481
|
+
setKeys(undefined, undefined);
|
|
482
|
+
|
|
483
|
+
const { getCredits } = await import('../client.js');
|
|
484
|
+
const result = await getCredits();
|
|
485
|
+
expect(result).toBeNull();
|
|
486
|
+
});
|
|
487
|
+
});
|