@usageflow/core 0.4.1 → 0.4.3

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/test/base.test.ts CHANGED
@@ -162,9 +162,18 @@ describe('UsageFlowAPI', () => {
162
162
  method: 'GET',
163
163
  url: '/test',
164
164
  path: '/test',
165
+ app: {
166
+ _router: {
167
+ stack: []
168
+ }
169
+ } as any,
165
170
  route: { path: '/test/:id' },
166
171
  headers: {},
167
- app: {} as any
172
+ app: {
173
+ _router: {
174
+ stack: []
175
+ }
176
+ } as any
168
177
  };
169
178
  const pattern = api.getRoutePattern(request);
170
179
  assert.strictEqual(pattern, '/test/:id');
@@ -175,7 +184,8 @@ describe('UsageFlowAPI', () => {
175
184
  url: '/users/:id',
176
185
  method: 'GET',
177
186
  identityFieldName: 'id',
178
- identityFieldLocation: 'path_params'
187
+ identityFieldLocation: 'path_params',
188
+ hasRateLimit: false
179
189
  }];
180
190
 
181
191
  const request: UsageFlowRequest = {
@@ -183,11 +193,17 @@ describe('UsageFlowAPI', () => {
183
193
  url: '/users',
184
194
  path: '/users/123',
185
195
  params: { id: '123' },
186
- headers: {}
196
+ headers: {},
197
+ app: {
198
+ _router: {
199
+ stack: []
200
+ }
201
+ } as any
187
202
  };
188
203
 
189
- const ledgerId = api.guessLedgerId(request);
190
- assert.strictEqual(ledgerId, 'GET /users/123 123');
204
+ const result = api.guessLedgerId(request);
205
+ assert.strictEqual(result.ledgerId, 'GET /users/123 123');
206
+ assert.strictEqual(result.hasLimit, false);
191
207
  });
192
208
 
193
209
  test('should guess ledger ID from query params', () => {
@@ -195,18 +211,25 @@ describe('UsageFlowAPI', () => {
195
211
  url: '/users',
196
212
  method: 'GET',
197
213
  identityFieldName: 'userId',
198
- identityFieldLocation: 'query_params'
214
+ identityFieldLocation: 'query_params',
215
+ hasRateLimit: true
199
216
  }];
200
217
 
201
218
  const request: UsageFlowRequest = {
202
219
  method: 'GET',
203
220
  url: '/users',
204
221
  query: { userId: '456' },
205
- headers: {}
222
+ headers: {},
223
+ app: {
224
+ _router: {
225
+ stack: []
226
+ }
227
+ } as any
206
228
  };
207
229
 
208
- const ledgerId = api.guessLedgerId(request);
209
- assert.strictEqual(ledgerId, 'GET /users 456');
230
+ const result = api.guessLedgerId(request);
231
+ assert.strictEqual(result.ledgerId, 'GET /users 456');
232
+ assert.strictEqual(result.hasLimit, true);
210
233
  });
211
234
 
212
235
  test('should guess ledger ID from body', () => {
@@ -214,18 +237,25 @@ describe('UsageFlowAPI', () => {
214
237
  url: '/users',
215
238
  method: 'POST',
216
239
  identityFieldName: 'userId',
217
- identityFieldLocation: 'body'
240
+ identityFieldLocation: 'body',
241
+ hasRateLimit: false
218
242
  }];
219
243
 
220
244
  const request: UsageFlowRequest = {
221
245
  method: 'POST',
222
246
  url: '/users',
223
247
  body: { userId: '789' },
224
- headers: {}
248
+ headers: {},
249
+ app: {
250
+ _router: {
251
+ stack: []
252
+ }
253
+ } as any
225
254
  };
226
255
 
227
- const ledgerId = api.guessLedgerId(request);
228
- assert.strictEqual(ledgerId, 'POST /users 789');
256
+ const result = api.guessLedgerId(request);
257
+ assert.strictEqual(result.ledgerId, 'POST /users 789');
258
+ assert.strictEqual(result.hasLimit, false);
229
259
  });
230
260
 
231
261
  test('should guess ledger ID from bearer token', () => {
@@ -233,7 +263,8 @@ describe('UsageFlowAPI', () => {
233
263
  url: '/users',
234
264
  method: 'GET',
235
265
  identityFieldName: 'userId',
236
- identityFieldLocation: 'bearer_token'
266
+ identityFieldLocation: 'bearer_token',
267
+ hasRateLimit: true
237
268
  }];
238
269
 
239
270
  const header = Buffer.from(JSON.stringify({ alg: 'HS256' })).toString('base64');
@@ -243,11 +274,17 @@ describe('UsageFlowAPI', () => {
243
274
  const request: UsageFlowRequest = {
244
275
  method: 'GET',
245
276
  url: '/users',
246
- headers: { authorization: `Bearer ${token}` }
277
+ headers: { authorization: `Bearer ${token}` },
278
+ app: {
279
+ _router: {
280
+ stack: []
281
+ }
282
+ } as any
247
283
  };
248
284
 
249
- const ledgerId = api.guessLedgerId(request);
250
- assert.strictEqual(ledgerId, 'GET /users token-user');
285
+ const result = api.guessLedgerId(request);
286
+ assert.strictEqual(result.ledgerId, 'GET /users token-user');
287
+ assert.strictEqual(result.hasLimit, true);
251
288
  });
252
289
 
253
290
  test('should guess ledger ID from headers', () => {
@@ -255,17 +292,24 @@ describe('UsageFlowAPI', () => {
255
292
  url: '/users',
256
293
  method: 'GET',
257
294
  identityFieldName: 'x-user-id',
258
- identityFieldLocation: 'headers'
295
+ identityFieldLocation: 'headers',
296
+ hasRateLimit: false
259
297
  }];
260
298
 
261
299
  const request: UsageFlowRequest = {
262
300
  method: 'GET',
263
301
  url: '/users',
264
- headers: { 'x-user-id': 'header-user' }
302
+ headers: { 'x-user-id': 'header-user' },
303
+ app: {
304
+ _router: {
305
+ stack: []
306
+ }
307
+ } as any
265
308
  };
266
309
 
267
- const ledgerId = api.guessLedgerId(request);
268
- assert.strictEqual(ledgerId, 'GET /users header-user');
310
+ const result = api.guessLedgerId(request);
311
+ assert.strictEqual(result.ledgerId, 'GET /users header-user');
312
+ assert.strictEqual(result.hasLimit, false);
269
313
  });
270
314
 
271
315
  test('should return default ledger ID when no config matches', () => {
@@ -274,11 +318,526 @@ describe('UsageFlowAPI', () => {
274
318
  const request: UsageFlowRequest = {
275
319
  method: 'GET',
276
320
  url: '/users',
277
- headers: {}
321
+ headers: {},
322
+ app: {
323
+ _router: {
324
+ stack: []
325
+ }
326
+ } as any
278
327
  };
279
328
 
280
- const ledgerId = api.guessLedgerId(request);
281
- assert.strictEqual(ledgerId, 'GET /users');
329
+ const result = api.guessLedgerId(request);
330
+ assert.strictEqual(result.ledgerId, 'GET /users');
331
+ assert.strictEqual(result.hasLimit, false);
332
+ });
333
+
334
+ describe('guessLedgerId - Cookie scenarios', () => {
335
+ test('should guess ledger ID from standard cookie with cookie. prefix', () => {
336
+ (api as any).apiConfigs = [{
337
+ url: '/users',
338
+ method: 'GET',
339
+ identityFieldName: 'cookie.sessionId',
340
+ identityFieldLocation: 'cookie',
341
+ hasRateLimit: true
342
+ }];
343
+
344
+ const request: UsageFlowRequest = {
345
+ method: 'GET',
346
+ url: '/users',
347
+ headers: { cookie: 'sessionId=abc123; other=value' },
348
+ app: {
349
+ _router: {
350
+ stack: []
351
+ }
352
+ } as any
353
+ };
354
+
355
+ const result = api.guessLedgerId(request);
356
+ assert.strictEqual(result.ledgerId, 'GET /users abc123');
357
+ assert.strictEqual(result.hasLimit, true);
358
+ });
359
+
360
+ test('should guess ledger ID from standard cookie without prefix (dot notation)', () => {
361
+ (api as any).apiConfigs = [{
362
+ url: '/users',
363
+ method: 'GET',
364
+ identityFieldName: 'sessionId',
365
+ identityFieldLocation: 'cookie',
366
+ hasRateLimit: false
367
+ }];
368
+
369
+ const request: UsageFlowRequest = {
370
+ method: 'GET',
371
+ url: '/users',
372
+ headers: { cookie: 'sessionId=xyz789' },
373
+ app: {
374
+ _router: {
375
+ stack: []
376
+ }
377
+ } as any
378
+ };
379
+
380
+ const result = api.guessLedgerId(request);
381
+ assert.strictEqual(result.ledgerId, 'GET /users xyz789');
382
+ assert.strictEqual(result.hasLimit, false);
383
+ });
384
+
385
+ test('should guess ledger ID from JWT cookie with technique and pick', () => {
386
+ (api as any).apiConfigs = [{
387
+ url: '/users',
388
+ method: 'GET',
389
+ identityFieldName: '[technique=jwt]sess[pick=sub]',
390
+ identityFieldLocation: 'cookie',
391
+ hasRateLimit: true
392
+ }];
393
+
394
+ const header = Buffer.from(JSON.stringify({ alg: 'HS256' })).toString('base64');
395
+ const payload = Buffer.from(JSON.stringify({ sub: 'jwt-user-123', name: 'Test User' })).toString('base64');
396
+ const jwtToken = `${header}.${payload}.signature`;
397
+
398
+ const request: UsageFlowRequest = {
399
+ method: 'GET',
400
+ url: '/users',
401
+ headers: { cookie: `sess=${jwtToken}` },
402
+ app: {
403
+ _router: {
404
+ stack: []
405
+ }
406
+ } as any
407
+ };
408
+
409
+ const result = api.guessLedgerId(request);
410
+ assert.strictEqual(result.ledgerId, 'GET /users jwt-user-123');
411
+ assert.strictEqual(result.hasLimit, true);
412
+ });
413
+
414
+ test('should handle case-insensitive cookie header (Cookie vs cookie)', () => {
415
+ (api as any).apiConfigs = [{
416
+ url: '/users',
417
+ method: 'GET',
418
+ identityFieldName: 'cookie.sessionId',
419
+ identityFieldLocation: 'cookie',
420
+ hasRateLimit: false
421
+ }];
422
+
423
+ const request: UsageFlowRequest = {
424
+ method: 'GET',
425
+ url: '/users',
426
+ headers: { Cookie: 'sessionId=case-test-123' },
427
+ app: {
428
+ _router: {
429
+ stack: []
430
+ }
431
+ } as any
432
+ };
433
+
434
+ const result = api.guessLedgerId(request);
435
+ assert.strictEqual(result.ledgerId, 'GET /users case-test-123');
436
+ assert.strictEqual(result.hasLimit, false);
437
+ });
438
+
439
+ test('should return default when cookie not found', () => {
440
+ (api as any).apiConfigs = [{
441
+ url: '/users',
442
+ method: 'GET',
443
+ identityFieldName: 'cookie.missing',
444
+ identityFieldLocation: 'cookie',
445
+ hasRateLimit: false
446
+ }];
447
+
448
+ const request: UsageFlowRequest = {
449
+ method: 'GET',
450
+ url: '/users',
451
+ headers: { cookie: 'other=value' },
452
+ app: {
453
+ _router: {
454
+ stack: []
455
+ }
456
+ } as any
457
+ };
458
+
459
+ const result = api.guessLedgerId(request);
460
+ assert.strictEqual(result.ledgerId, 'GET /users');
461
+ assert.strictEqual(result.hasLimit, false);
462
+ });
463
+
464
+ test('should handle JWT cookie with missing claim', () => {
465
+ (api as any).apiConfigs = [{
466
+ url: '/users',
467
+ method: 'GET',
468
+ identityFieldName: '[technique=jwt]sess[pick=missingClaim]',
469
+ identityFieldLocation: 'cookie',
470
+ hasRateLimit: false
471
+ }];
472
+
473
+ const header = Buffer.from(JSON.stringify({ alg: 'HS256' })).toString('base64');
474
+ const payload = Buffer.from(JSON.stringify({ sub: 'user-123' })).toString('base64');
475
+ const jwtToken = `${header}.${payload}.signature`;
476
+
477
+ const request: UsageFlowRequest = {
478
+ method: 'GET',
479
+ url: '/users',
480
+ headers: { cookie: `sess=${jwtToken}` },
481
+ app: {
482
+ _router: {
483
+ stack: []
484
+ }
485
+ } as any
486
+ };
487
+
488
+ const result = api.guessLedgerId(request);
489
+ assert.strictEqual(result.ledgerId, 'GET /users');
490
+ assert.strictEqual(result.hasLimit, false);
491
+ });
492
+
493
+ test('should handle JWT cookie with invalid token', () => {
494
+ (api as any).apiConfigs = [{
495
+ url: '/users',
496
+ method: 'GET',
497
+ identityFieldName: '[technique=jwt]sess[pick=sub]',
498
+ identityFieldLocation: 'cookie',
499
+ hasRateLimit: false
500
+ }];
501
+
502
+ const request: UsageFlowRequest = {
503
+ method: 'GET',
504
+ url: '/users',
505
+ headers: { cookie: 'sess=invalid-token' },
506
+ app: {
507
+ _router: {
508
+ stack: []
509
+ }
510
+ } as any
511
+ };
512
+
513
+ const result = api.guessLedgerId(request);
514
+ assert.strictEqual(result.ledgerId, 'GET /users');
515
+ assert.strictEqual(result.hasLimit, false);
516
+ });
517
+ });
518
+
519
+ describe('guessLedgerId - Edge cases', () => {
520
+ test('should use first matching config when multiple configs exist', () => {
521
+ (api as any).apiConfigs = [
522
+ {
523
+ url: '/users',
524
+ method: 'GET',
525
+ identityFieldName: 'userId',
526
+ identityFieldLocation: 'query_params',
527
+ hasRateLimit: true
528
+ },
529
+ {
530
+ url: '/users',
531
+ method: 'GET',
532
+ identityFieldName: 'id',
533
+ identityFieldLocation: 'path_params',
534
+ hasRateLimit: false
535
+ }
536
+ ];
537
+
538
+ const request: UsageFlowRequest = {
539
+ method: 'GET',
540
+ url: '/users',
541
+ query: { userId: 'first-match' },
542
+ params: { id: 'second-match' },
543
+ headers: {},
544
+ app: {
545
+ _router: {
546
+ stack: []
547
+ }
548
+ } as any
549
+ };
550
+
551
+ const result = api.guessLedgerId(request);
552
+ assert.strictEqual(result.ledgerId, 'GET /users first-match');
553
+ assert.strictEqual(result.hasLimit, true);
554
+ });
555
+
556
+ test('should not match when method does not match', () => {
557
+ (api as any).apiConfigs = [{
558
+ url: '/users',
559
+ method: 'POST',
560
+ identityFieldName: 'userId',
561
+ identityFieldLocation: 'query_params',
562
+ hasRateLimit: false
563
+ }];
564
+
565
+ const request: UsageFlowRequest = {
566
+ method: 'GET',
567
+ url: '/users',
568
+ query: { userId: '123' },
569
+ headers: {},
570
+ app: {
571
+ _router: {
572
+ stack: []
573
+ }
574
+ } as any
575
+ };
576
+
577
+ const result = api.guessLedgerId(request);
578
+ assert.strictEqual(result.ledgerId, 'GET /users');
579
+ assert.strictEqual(result.hasLimit, false);
580
+ });
581
+
582
+ test('should not match when URL does not match', () => {
583
+ (api as any).apiConfigs = [{
584
+ url: '/posts',
585
+ method: 'GET',
586
+ identityFieldName: 'userId',
587
+ identityFieldLocation: 'query_params',
588
+ hasRateLimit: false
589
+ }];
590
+
591
+ const request: UsageFlowRequest = {
592
+ method: 'GET',
593
+ url: '/users',
594
+ query: { userId: '123' },
595
+ headers: {},
596
+ app: {
597
+ _router: {
598
+ stack: []
599
+ }
600
+ } as any
601
+ };
602
+
603
+ const result = api.guessLedgerId(request);
604
+ assert.strictEqual(result.ledgerId, 'GET /users');
605
+ assert.strictEqual(result.hasLimit, false);
606
+ });
607
+
608
+ test('should handle missing identity field in path params', () => {
609
+ (api as any).apiConfigs = [{
610
+ url: '/users/:id',
611
+ method: 'GET',
612
+ identityFieldName: 'id',
613
+ identityFieldLocation: 'path_params',
614
+ hasRateLimit: false
615
+ }];
616
+
617
+ const request: UsageFlowRequest = {
618
+ method: 'GET',
619
+ url: '/users',
620
+ params: { otherParam: '123' },
621
+ headers: {},
622
+ app: {
623
+ _router: {
624
+ stack: []
625
+ }
626
+ } as any
627
+ };
628
+
629
+ const result = api.guessLedgerId(request);
630
+ assert.strictEqual(result.ledgerId, 'GET /users');
631
+ assert.strictEqual(result.hasLimit, false);
632
+ });
633
+
634
+ test('should handle missing identity field in query params', () => {
635
+ (api as any).apiConfigs = [{
636
+ url: '/users',
637
+ method: 'GET',
638
+ identityFieldName: 'userId',
639
+ identityFieldLocation: 'query_params',
640
+ hasRateLimit: false
641
+ }];
642
+
643
+ const request: UsageFlowRequest = {
644
+ method: 'GET',
645
+ url: '/users',
646
+ query: { otherParam: '123' },
647
+ headers: {},
648
+ app: {
649
+ _router: {
650
+ stack: []
651
+ }
652
+ } as any
653
+ };
654
+
655
+ const result = api.guessLedgerId(request);
656
+ assert.strictEqual(result.ledgerId, 'GET /users');
657
+ assert.strictEqual(result.hasLimit, false);
658
+ });
659
+
660
+ test('should handle missing identity field in body', () => {
661
+ (api as any).apiConfigs = [{
662
+ url: '/users',
663
+ method: 'POST',
664
+ identityFieldName: 'userId',
665
+ identityFieldLocation: 'body',
666
+ hasRateLimit: false
667
+ }];
668
+
669
+ const request: UsageFlowRequest = {
670
+ method: 'POST',
671
+ url: '/users',
672
+ body: { otherField: '123' },
673
+ headers: {},
674
+ app: {
675
+ _router: {
676
+ stack: []
677
+ }
678
+ } as any
679
+ };
680
+
681
+ const result = api.guessLedgerId(request);
682
+ assert.strictEqual(result.ledgerId, 'POST /users');
683
+ assert.strictEqual(result.hasLimit, false);
684
+ });
685
+
686
+ test('should handle bearer token without authorization header', () => {
687
+ (api as any).apiConfigs = [{
688
+ url: '/users',
689
+ method: 'GET',
690
+ identityFieldName: 'userId',
691
+ identityFieldLocation: 'bearer_token',
692
+ hasRateLimit: false
693
+ }];
694
+
695
+ const request: UsageFlowRequest = {
696
+ method: 'GET',
697
+ url: '/users',
698
+ headers: {},
699
+ app: {
700
+ _router: {
701
+ stack: []
702
+ }
703
+ } as any
704
+ };
705
+
706
+ const result = api.guessLedgerId(request);
707
+ assert.strictEqual(result.ledgerId, 'GET /users');
708
+ assert.strictEqual(result.hasLimit, false);
709
+ });
710
+
711
+ test('should handle bearer token with invalid format', () => {
712
+ (api as any).apiConfigs = [{
713
+ url: '/users',
714
+ method: 'GET',
715
+ identityFieldName: 'userId',
716
+ identityFieldLocation: 'bearer_token',
717
+ hasRateLimit: false
718
+ }];
719
+
720
+ const request: UsageFlowRequest = {
721
+ method: 'GET',
722
+ url: '/users',
723
+ headers: { authorization: 'Invalid token' },
724
+ app: {
725
+ _router: {
726
+ stack: []
727
+ }
728
+ } as any
729
+ };
730
+
731
+ const result = api.guessLedgerId(request);
732
+ assert.strictEqual(result.ledgerId, 'GET /users');
733
+ assert.strictEqual(result.hasLimit, false);
734
+ });
735
+
736
+ test('should handle bearer token with missing claim', () => {
737
+ (api as any).apiConfigs = [{
738
+ url: '/users',
739
+ method: 'GET',
740
+ identityFieldName: 'userId',
741
+ identityFieldLocation: 'bearer_token',
742
+ hasRateLimit: false
743
+ }];
744
+
745
+ const header = Buffer.from(JSON.stringify({ alg: 'HS256' })).toString('base64');
746
+ const payload = Buffer.from(JSON.stringify({ otherClaim: 'value' })).toString('base64');
747
+ const token = `${header}.${payload}.signature`;
748
+
749
+ const request: UsageFlowRequest = {
750
+ method: 'GET',
751
+ url: '/users',
752
+ headers: { authorization: `Bearer ${token}` },
753
+ app: {
754
+ _router: {
755
+ stack: []
756
+ }
757
+ } as any
758
+ };
759
+
760
+ const result = api.guessLedgerId(request);
761
+ assert.strictEqual(result.ledgerId, 'GET /users');
762
+ assert.strictEqual(result.hasLimit, false);
763
+ });
764
+
765
+ test('should handle missing header field', () => {
766
+ (api as any).apiConfigs = [{
767
+ url: '/users',
768
+ method: 'GET',
769
+ identityFieldName: 'x-user-id',
770
+ identityFieldLocation: 'headers',
771
+ hasRateLimit: false
772
+ }];
773
+
774
+ const request: UsageFlowRequest = {
775
+ method: 'GET',
776
+ url: '/users',
777
+ headers: { 'other-header': 'value' },
778
+ app: {
779
+ _router: {
780
+ stack: []
781
+ }
782
+ } as any
783
+ };
784
+
785
+ const result = api.guessLedgerId(request);
786
+ assert.strictEqual(result.ledgerId, 'GET /users');
787
+ assert.strictEqual(result.hasLimit, false);
788
+ });
789
+
790
+ test('should use overrideUrl when provided', () => {
791
+ (api as any).apiConfigs = [{
792
+ url: '/custom/path',
793
+ method: 'GET',
794
+ identityFieldName: 'userId',
795
+ identityFieldLocation: 'query_params',
796
+ hasRateLimit: true
797
+ }];
798
+
799
+ const request: UsageFlowRequest = {
800
+ method: 'GET',
801
+ url: '/users',
802
+ query: { userId: 'override-test' },
803
+ headers: {},
804
+ app: {
805
+ _router: {
806
+ stack: []
807
+ }
808
+ } as any
809
+ };
810
+
811
+ const result = api.guessLedgerId(request, '/custom/path');
812
+ assert.strictEqual(result.ledgerId, 'GET /custom/path override-test');
813
+ assert.strictEqual(result.hasLimit, true);
814
+ });
815
+
816
+ test('should handle hasRateLimit as undefined (defaults to false)', () => {
817
+ (api as any).apiConfigs = [{
818
+ url: '/users',
819
+ method: 'GET',
820
+ identityFieldName: 'userId',
821
+ identityFieldLocation: 'query_params'
822
+ // hasRateLimit is undefined
823
+ }];
824
+
825
+ const request: UsageFlowRequest = {
826
+ method: 'GET',
827
+ url: '/users',
828
+ query: { userId: '123' },
829
+ headers: {},
830
+ app: {
831
+ _router: {
832
+ stack: []
833
+ }
834
+ } as any
835
+ };
836
+
837
+ const result = api.guessLedgerId(request);
838
+ assert.strictEqual(result.ledgerId, 'GET /users 123');
839
+ assert.strictEqual(result.hasLimit, false);
840
+ });
282
841
  });
283
842
 
284
843
  test('should destroy and clean up resources', () => {
@@ -286,5 +845,163 @@ describe('UsageFlowAPI', () => {
286
845
  // Should not throw
287
846
  assert.doesNotThrow(() => api.destroy());
288
847
  });
848
+
849
+ describe('getValueByPath', () => {
850
+ test('should extract simple property', () => {
851
+ const obj = { amount: 100 };
852
+ const result = api.getValueByPath(obj, 'amount');
853
+ assert.strictEqual(result, 100);
854
+ });
855
+
856
+ test('should extract nested property', () => {
857
+ const obj = { data: { amount: 200 } };
858
+ const result = api.getValueByPath(obj, 'data.amount');
859
+ assert.strictEqual(result, 200);
860
+ });
861
+
862
+ test('should extract deeply nested property', () => {
863
+ const obj = { response: { data: { result: { amount: 300 } } } };
864
+ const result = api.getValueByPath(obj, 'response.data.result.amount');
865
+ assert.strictEqual(result, 300);
866
+ });
867
+
868
+ test('should extract array element by index', () => {
869
+ const obj = { items: [{ id: 1 }, { id: 2 }, { id: 3 }] };
870
+ const result = api.getValueByPath(obj, 'items[0].id');
871
+ assert.strictEqual(result, 1);
872
+ });
873
+
874
+ test('should extract from array with wildcard notation', () => {
875
+ const obj = { items: [{ id: 1 }, { id: 2 }, { id: 3 }] };
876
+ const result = api.getValueByPath(obj, 'items[*].id');
877
+ assert.strictEqual(result, 1); // Should return first found
878
+ });
879
+
880
+ test('should extract from nested array with wildcard', () => {
881
+ const obj = {
882
+ data: [
883
+ { users: [{ id: 10 }, { id: 20 }] },
884
+ { users: [{ id: 30 }] }
885
+ ]
886
+ };
887
+ const result = api.getValueByPath(obj, 'data[*].users[*].id');
888
+ assert.strictEqual(result, 10); // Should return first found
889
+ });
890
+
891
+ test('should extract amount from response body structure', () => {
892
+ const responseBody = {
893
+ success: true,
894
+ data: {
895
+ transaction: {
896
+ amount: 1500
897
+ }
898
+ }
899
+ };
900
+ const result = api.getValueByPath(responseBody, 'data.transaction.amount');
901
+ assert.strictEqual(result, 1500);
902
+ });
903
+
904
+ test('should extract amount from array response', () => {
905
+ const responseBody = {
906
+ results: [
907
+ { id: 1, amount: 100 },
908
+ { id: 2, amount: 200 },
909
+ { id: 3, amount: 300 }
910
+ ]
911
+ };
912
+ const result = api.getValueByPath(responseBody, 'results[*].amount');
913
+ assert.strictEqual(result, 100); // First found
914
+ });
915
+
916
+ test('should return undefined for non-existent path', () => {
917
+ const obj = { data: { amount: 100 } };
918
+ const result = api.getValueByPath(obj, 'data.missing');
919
+ assert.strictEqual(result, undefined);
920
+ });
921
+
922
+ test('should return undefined for null object', () => {
923
+ const result = api.getValueByPath(null, 'amount');
924
+ assert.strictEqual(result, undefined);
925
+ });
926
+
927
+ test('should return undefined for undefined object', () => {
928
+ const result = api.getValueByPath(undefined, 'amount');
929
+ assert.strictEqual(result, undefined);
930
+ });
931
+
932
+ test('should return undefined for empty path', () => {
933
+ const obj = { amount: 100 };
934
+ const result = api.getValueByPath(obj, '');
935
+ assert.strictEqual(result, undefined);
936
+ });
937
+
938
+ test('should handle array with wildcard at root level', () => {
939
+ const obj = [{ id: 1, amount: 50 }, { id: 2, amount: 75 }];
940
+ const result = api.getValueByPath(obj, '[*].amount');
941
+ assert.strictEqual(result, 50); // First element's amount
942
+ });
943
+
944
+ test('should handle wildcard when array is empty', () => {
945
+ const obj = { items: [] };
946
+ const result = api.getValueByPath(obj, 'items[*].id');
947
+ assert.strictEqual(result, undefined);
948
+ });
949
+
950
+ test('should handle wildcard when property does not exist in array items', () => {
951
+ const obj = { items: [{ name: 'test' }, { name: 'test2' }] };
952
+ const result = api.getValueByPath(obj, 'items[*].missing');
953
+ assert.strictEqual(result, undefined);
954
+ });
955
+
956
+ test('should handle complex nested structure with multiple wildcards', () => {
957
+ const obj = {
958
+ orders: [
959
+ {
960
+ items: [
961
+ { product: { price: 10 } },
962
+ { product: { price: 20 } }
963
+ ]
964
+ },
965
+ {
966
+ items: [
967
+ { product: { price: 30 } }
968
+ ]
969
+ }
970
+ ]
971
+ };
972
+ const result = api.getValueByPath(obj, 'orders[*].items[*].product.price');
973
+ assert.strictEqual(result, 10); // First found
974
+ });
975
+
976
+ test('should handle numeric string indices', () => {
977
+ const obj = { items: ['first', 'second', 'third'] };
978
+ const result = api.getValueByPath(obj, 'items[1]');
979
+ assert.strictEqual(result, 'second');
980
+ });
981
+
982
+ test('should return first element when wildcard is last segment', () => {
983
+ const obj = { items: [{ id: 1 }, { id: 2 }] };
984
+ const result = api.getValueByPath(obj, 'items[*]');
985
+ assert.deepStrictEqual(result, { id: 1 }); // First element
986
+ });
987
+
988
+ test('should handle null values in path', () => {
989
+ const obj = { data: null };
990
+ const result = api.getValueByPath(obj, 'data.amount');
991
+ assert.strictEqual(result, undefined);
992
+ });
993
+
994
+ test('should extract from response body with amount field', () => {
995
+ const responseBody = {
996
+ status: 'success',
997
+ amount: 5000,
998
+ metadata: {
999
+ timestamp: '2024-01-01'
1000
+ }
1001
+ };
1002
+ const result = api.getValueByPath(responseBody, 'amount');
1003
+ assert.strictEqual(result, 5000);
1004
+ });
1005
+ });
289
1006
  });
290
1007