nosible 0.2.13 → 0.2.16

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.
@@ -0,0 +1,652 @@
1
+ import {describe, it, expect} from "bun:test";
2
+ import {createLimiters} from "./rateLimiters";
3
+ import type {LimitsResponse} from "../api/schemas";
4
+
5
+ describe("rateLimiters", () => {
6
+ describe("createLimiters", () => {
7
+ it("should create limiters from limits response", () => {
8
+ const mockLimitsResponse: LimitsResponse = {
9
+ api_key_id: "test-api-key-id",
10
+ subscription_id: "test-subscription-id",
11
+ limits: [
12
+ {
13
+ name: "fast_minute",
14
+ query_type: "fast",
15
+ duration_seconds: 60,
16
+ duration_string: "minute",
17
+ limit: 60,
18
+ limited: false,
19
+ remaining: 60,
20
+ period_start: null,
21
+ period_end: null,
22
+ },
23
+ {
24
+ name: "fast_four_week",
25
+ query_type: "fast",
26
+ duration_seconds: 2419200,
27
+ duration_string: "four_week",
28
+ limit: 3000,
29
+ limited: false,
30
+ remaining: 2925,
31
+ period_start: "2026-02-02T14:02:57.687557",
32
+ period_end: "2026-03-02T14:02:57.687557",
33
+ },
34
+ {
35
+ name: "slow_minute",
36
+ query_type: "slow",
37
+ duration_seconds: 60,
38
+ duration_string: "minute",
39
+ limit: 60,
40
+ limited: false,
41
+ remaining: 60,
42
+ period_start: null,
43
+ period_end: null,
44
+ },
45
+ {
46
+ name: "slow_four_week",
47
+ query_type: "slow",
48
+ duration_seconds: 2419200,
49
+ duration_string: "four_week",
50
+ limit: 300,
51
+ limited: false,
52
+ remaining: 296,
53
+ period_start: "2026-02-02T14:02:57.687557",
54
+ period_end: "2026-03-02T14:02:57.687557",
55
+ },
56
+ {
57
+ name: "visit_minute",
58
+ query_type: "visit",
59
+ duration_seconds: 60,
60
+ duration_string: "minute",
61
+ limit: 60,
62
+ limited: false,
63
+ remaining: 60,
64
+ period_start: null,
65
+ period_end: null,
66
+ },
67
+ {
68
+ name: "visit_four_week",
69
+ query_type: "visit",
70
+ duration_seconds: 2419200,
71
+ duration_string: "four_week",
72
+ limit: 300,
73
+ limited: false,
74
+ remaining: 298,
75
+ period_start: "2026-02-02T14:02:57.687557",
76
+ period_end: "2026-03-02T14:02:57.687557",
77
+ },
78
+ ],
79
+ };
80
+
81
+ const limiters = createLimiters(mockLimitsResponse);
82
+
83
+ expect(limiters).toBeDefined();
84
+ expect(limiters.scrapeUrl).toBeDefined();
85
+ expect(limiters.bulk).toBeDefined();
86
+ expect(limiters.fast).toBeDefined();
87
+ });
88
+
89
+ it("should handle partial limits data with fallback values", () => {
90
+ const mockLimitsResponse: LimitsResponse = {
91
+ api_key_id: "test-api-key-id",
92
+ subscription_id: "test-subscription-id",
93
+ limits: [
94
+ {
95
+ name: "fast_minute",
96
+ query_type: "fast",
97
+ duration_seconds: 60,
98
+ duration_string: "minute",
99
+ limit: 120,
100
+ limited: false,
101
+ remaining: 100,
102
+ period_start: null,
103
+ period_end: null,
104
+ },
105
+ ],
106
+ };
107
+
108
+ const limiters = createLimiters(mockLimitsResponse);
109
+
110
+ expect(limiters).toBeDefined();
111
+ expect(limiters.scrapeUrl).toBeDefined();
112
+ expect(limiters.bulk).toBeDefined();
113
+ expect(limiters.fast).toBeDefined();
114
+ });
115
+
116
+ it("should use remaining values when available", () => {
117
+ const mockLimitsResponse: LimitsResponse = {
118
+ api_key_id: "test-api-key-id",
119
+ subscription_id: "test-subscription-id",
120
+ limits: [
121
+ {
122
+ name: "fast_minute",
123
+ query_type: "fast",
124
+ duration_seconds: 60,
125
+ duration_string: "minute",
126
+ limit: 100,
127
+ limited: false,
128
+ remaining: 50,
129
+ period_start: null,
130
+ period_end: null,
131
+ },
132
+ {
133
+ name: "fast_four_week",
134
+ query_type: "fast",
135
+ duration_seconds: 2419200,
136
+ duration_string: "four_week",
137
+ limit: 5000,
138
+ limited: false,
139
+ remaining: 4800,
140
+ period_start: "2026-02-02T14:02:57.687557",
141
+ period_end: "2026-03-02T14:02:57.687557",
142
+ },
143
+ ],
144
+ };
145
+
146
+ const limiters = createLimiters(mockLimitsResponse);
147
+
148
+ expect(limiters.fast).toBeDefined();
149
+ });
150
+
151
+ it("should handle empty limits array with fallback values", () => {
152
+ const mockLimitsResponse: LimitsResponse = {
153
+ api_key_id: "test-api-key-id",
154
+ subscription_id: "test-subscription-id",
155
+ limits: [],
156
+ };
157
+
158
+ const limiters = createLimiters(mockLimitsResponse);
159
+
160
+ expect(limiters).toBeDefined();
161
+ expect(limiters.scrapeUrl).toBeDefined();
162
+ expect(limiters.bulk).toBeDefined();
163
+ expect(limiters.fast).toBeDefined();
164
+ });
165
+
166
+ it("should handle empty limits array with unlimited limiters", () => {
167
+ const mockLimitsResponse: LimitsResponse = {
168
+ api_key_id: "test-api-key-id",
169
+ subscription_id: "test-subscription-id",
170
+ limits: [],
171
+ };
172
+
173
+ const limiters = createLimiters(mockLimitsResponse);
174
+
175
+ expect(limiters.scrapeUrl).toBeDefined();
176
+ expect(limiters.bulk).toBeDefined();
177
+ expect(limiters.fast).toBeDefined();
178
+
179
+ // Limiters should be unlimited (no reservoir configuration)
180
+ // This is indicated by the absence of reservoir constraints
181
+ });
182
+
183
+ it("should create separate limiters for each query type", () => {
184
+ const mockLimitsResponse: LimitsResponse = {
185
+ api_key_id: "test-api-key-id",
186
+ subscription_id: "test-subscription-id",
187
+ limits: [
188
+ {
189
+ name: "fast_minute",
190
+ query_type: "fast",
191
+ duration_seconds: 60,
192
+ duration_string: "minute",
193
+ limit: 360,
194
+ limited: false,
195
+ remaining: 360,
196
+ period_start: null,
197
+ period_end: null,
198
+ },
199
+ {
200
+ name: "slow_minute",
201
+ query_type: "slow",
202
+ duration_seconds: 60,
203
+ duration_string: "minute",
204
+ limit: 60,
205
+ limited: false,
206
+ remaining: 60,
207
+ period_start: null,
208
+ period_end: null,
209
+ },
210
+ {
211
+ name: "visit_minute",
212
+ query_type: "visit",
213
+ duration_seconds: 60,
214
+ duration_string: "minute",
215
+ limit: 60,
216
+ limited: false,
217
+ remaining: 60,
218
+ period_start: null,
219
+ period_end: null,
220
+ },
221
+ ],
222
+ };
223
+
224
+ const limiters = createLimiters(mockLimitsResponse);
225
+
226
+ expect(limiters.fast).not.toBe(limiters.bulk);
227
+ expect(limiters.bulk).not.toBe(limiters.scrapeUrl);
228
+ expect(limiters.scrapeUrl).not.toBe(limiters.fast);
229
+ });
230
+
231
+ it("should handle rate-limited scenarios", () => {
232
+ const mockLimitsResponse: LimitsResponse = {
233
+ api_key_id: "test-api-key-id",
234
+ subscription_id: "test-subscription-id",
235
+ limits: [
236
+ {
237
+ name: "fast_minute",
238
+ query_type: "fast",
239
+ duration_seconds: 60,
240
+ duration_string: "minute",
241
+ limit: 60,
242
+ limited: true,
243
+ remaining: 0,
244
+ period_start: "2026-02-04T10:00:00.000000",
245
+ period_end: "2026-02-04T10:01:00.000000",
246
+ },
247
+ {
248
+ name: "fast_four_week",
249
+ query_type: "fast",
250
+ duration_seconds: 2419200,
251
+ duration_string: "four_week",
252
+ limit: 3000,
253
+ limited: false,
254
+ remaining: 2500,
255
+ period_start: "2026-02-02T14:02:57.687557",
256
+ period_end: "2026-03-02T14:02:57.687557",
257
+ },
258
+ ],
259
+ };
260
+
261
+ const limiters = createLimiters(mockLimitsResponse);
262
+
263
+ expect(limiters.fast).toBeDefined();
264
+ });
265
+
266
+ it("should handle all three query types with four_week limits", () => {
267
+ const mockLimitsResponse: LimitsResponse = {
268
+ api_key_id: "test-api-key-id",
269
+ subscription_id: "test-subscription-id",
270
+ limits: [
271
+ {
272
+ name: "fast_minute",
273
+ query_type: "fast",
274
+ duration_seconds: 60,
275
+ duration_string: "minute",
276
+ limit: 360,
277
+ limited: false,
278
+ remaining: 350,
279
+ period_start: null,
280
+ period_end: null,
281
+ },
282
+ {
283
+ name: "fast_four_week",
284
+ query_type: "fast",
285
+ duration_seconds: 2419200,
286
+ duration_string: "four_week",
287
+ limit: 3000000,
288
+ limited: false,
289
+ remaining: 2999000,
290
+ period_start: "2026-01-05T00:00:00.000000",
291
+ period_end: "2026-02-02T00:00:00.000000",
292
+ },
293
+ {
294
+ name: "slow_minute",
295
+ query_type: "slow",
296
+ duration_seconds: 60,
297
+ duration_string: "minute",
298
+ limit: 360,
299
+ limited: false,
300
+ remaining: 360,
301
+ period_start: null,
302
+ period_end: null,
303
+ },
304
+ {
305
+ name: "slow_four_week",
306
+ query_type: "slow",
307
+ duration_seconds: 2419200,
308
+ duration_string: "four_week",
309
+ limit: 300000,
310
+ limited: false,
311
+ remaining: 299500,
312
+ period_start: "2026-01-05T00:00:00.000000",
313
+ period_end: "2026-02-02T00:00:00.000000",
314
+ },
315
+ {
316
+ name: "visit_minute",
317
+ query_type: "visit",
318
+ duration_seconds: 60,
319
+ duration_string: "minute",
320
+ limit: 360,
321
+ limited: false,
322
+ remaining: 360,
323
+ period_start: null,
324
+ period_end: null,
325
+ },
326
+ {
327
+ name: "visit_four_week",
328
+ query_type: "visit",
329
+ duration_seconds: 2419200,
330
+ duration_string: "four_week",
331
+ limit: 300000,
332
+ limited: false,
333
+ remaining: 299800,
334
+ period_start: "2026-01-05T00:00:00.000000",
335
+ period_end: "2026-02-02T00:00:00.000000",
336
+ },
337
+ ],
338
+ };
339
+
340
+ const limiters = createLimiters(mockLimitsResponse);
341
+
342
+ expect(limiters.scrapeUrl).toBeDefined();
343
+ expect(limiters.bulk).toBeDefined();
344
+ expect(limiters.fast).toBeDefined();
345
+ });
346
+
347
+ it("should handle API keys with only minute limits (no four_week limits)", () => {
348
+ const mockLimitsResponse: LimitsResponse = {
349
+ api_key_id: "test-api-key-id",
350
+ subscription_id: "test-subscription-id",
351
+ limits: [
352
+ {
353
+ name: "fast_minute",
354
+ query_type: "fast",
355
+ duration_seconds: 60,
356
+ duration_string: "minute",
357
+ limit: 120,
358
+ limited: false,
359
+ remaining: 115,
360
+ period_start: null,
361
+ period_end: null,
362
+ },
363
+ {
364
+ name: "slow_minute",
365
+ query_type: "slow",
366
+ duration_seconds: 60,
367
+ duration_string: "minute",
368
+ limit: 120,
369
+ limited: false,
370
+ remaining: 118,
371
+ period_start: null,
372
+ period_end: null,
373
+ },
374
+ {
375
+ name: "visit_minute",
376
+ query_type: "visit",
377
+ duration_seconds: 60,
378
+ duration_string: "minute",
379
+ limit: 120,
380
+ limited: false,
381
+ remaining: 120,
382
+ period_start: null,
383
+ period_end: null,
384
+ },
385
+ ],
386
+ };
387
+
388
+ const limiters = createLimiters(mockLimitsResponse);
389
+
390
+ expect(limiters.scrapeUrl).toBeDefined();
391
+ expect(limiters.bulk).toBeDefined();
392
+ expect(limiters.fast).toBeDefined();
393
+ });
394
+
395
+ it("should handle mixed scenarios - some query types with four_week, some without", () => {
396
+ const mockLimitsResponse: LimitsResponse = {
397
+ api_key_id: "test-api-key-id",
398
+ subscription_id: "test-subscription-id",
399
+ limits: [
400
+ {
401
+ name: "fast_minute",
402
+ query_type: "fast",
403
+ duration_seconds: 60,
404
+ duration_string: "minute",
405
+ limit: 360,
406
+ limited: false,
407
+ remaining: 360,
408
+ period_start: null,
409
+ period_end: null,
410
+ },
411
+ {
412
+ name: "fast_four_week",
413
+ query_type: "fast",
414
+ duration_seconds: 2419200,
415
+ duration_string: "four_week",
416
+ limit: 3000000,
417
+ limited: false,
418
+ remaining: 2999000,
419
+ period_start: "2026-01-05T00:00:00.000000",
420
+ period_end: "2026-02-02T00:00:00.000000",
421
+ },
422
+ {
423
+ name: "slow_minute",
424
+ query_type: "slow",
425
+ duration_seconds: 60,
426
+ duration_string: "minute",
427
+ limit: 60,
428
+ limited: false,
429
+ remaining: 60,
430
+ period_start: null,
431
+ period_end: null,
432
+ },
433
+ {
434
+ name: "visit_minute",
435
+ query_type: "visit",
436
+ duration_seconds: 60,
437
+ duration_string: "minute",
438
+ limit: 60,
439
+ limited: false,
440
+ remaining: 60,
441
+ period_start: null,
442
+ period_end: null,
443
+ },
444
+ ],
445
+ };
446
+
447
+ const limiters = createLimiters(mockLimitsResponse);
448
+
449
+ expect(limiters.scrapeUrl).toBeDefined();
450
+ expect(limiters.bulk).toBeDefined();
451
+ expect(limiters.fast).toBeDefined();
452
+ });
453
+
454
+ it("should work with only minute limits for a single query type", () => {
455
+ const mockLimitsResponse: LimitsResponse = {
456
+ api_key_id: "test-api-key-id",
457
+ subscription_id: "test-subscription-id",
458
+ limits: [
459
+ {
460
+ name: "fast_minute",
461
+ query_type: "fast",
462
+ duration_seconds: 60,
463
+ duration_string: "minute",
464
+ limit: 10,
465
+ limited: false,
466
+ remaining: 5,
467
+ period_start: null,
468
+ period_end: null,
469
+ },
470
+ ],
471
+ };
472
+
473
+ const limiters = createLimiters(mockLimitsResponse);
474
+
475
+ expect(limiters.scrapeUrl).toBeDefined();
476
+ expect(limiters.bulk).toBeDefined();
477
+ expect(limiters.fast).toBeDefined();
478
+ });
479
+
480
+ it("should handle dynamic durations like 5-minute limits", () => {
481
+ const mockLimitsResponse: LimitsResponse = {
482
+ api_key_id: "test-api-key-id",
483
+ subscription_id: "test-subscription-id",
484
+ limits: [
485
+ {
486
+ name: "fast_5min",
487
+ query_type: "fast",
488
+ duration_seconds: 300, // 5 minutes
489
+ duration_string: "five_minute",
490
+ limit: 500,
491
+ limited: false,
492
+ remaining: 450,
493
+ period_start: null,
494
+ period_end: null,
495
+ },
496
+ {
497
+ name: "slow_5min",
498
+ query_type: "slow",
499
+ duration_seconds: 300,
500
+ duration_string: "five_minute",
501
+ limit: 100,
502
+ limited: false,
503
+ remaining: 95,
504
+ period_start: null,
505
+ period_end: null,
506
+ },
507
+ ],
508
+ };
509
+
510
+ const limiters = createLimiters(mockLimitsResponse);
511
+
512
+ expect(limiters.scrapeUrl).toBeDefined();
513
+ expect(limiters.bulk).toBeDefined();
514
+ expect(limiters.fast).toBeDefined();
515
+ });
516
+
517
+ it("should handle multiple custom duration limits for a single query type", () => {
518
+ const mockLimitsResponse: LimitsResponse = {
519
+ api_key_id: "test-api-key-id",
520
+ subscription_id: "test-subscription-id",
521
+ limits: [
522
+ {
523
+ name: "fast_minute",
524
+ query_type: "fast",
525
+ duration_seconds: 60,
526
+ duration_string: "minute",
527
+ limit: 120,
528
+ limited: false,
529
+ remaining: 120,
530
+ period_start: null,
531
+ period_end: null,
532
+ },
533
+ {
534
+ name: "fast_5min",
535
+ query_type: "fast",
536
+ duration_seconds: 300,
537
+ duration_string: "five_minute",
538
+ limit: 500,
539
+ limited: false,
540
+ remaining: 500,
541
+ period_start: null,
542
+ period_end: null,
543
+ },
544
+ {
545
+ name: "fast_hour",
546
+ query_type: "fast",
547
+ duration_seconds: 3600,
548
+ duration_string: "hour",
549
+ limit: 5000,
550
+ limited: false,
551
+ remaining: 5000,
552
+ period_start: null,
553
+ period_end: null,
554
+ },
555
+ {
556
+ name: "fast_day",
557
+ query_type: "fast",
558
+ duration_seconds: 86400,
559
+ duration_string: "day",
560
+ limit: 100000,
561
+ limited: false,
562
+ remaining: 100000,
563
+ period_start: "2026-02-04T00:00:00.000000",
564
+ period_end: "2026-02-05T00:00:00.000000",
565
+ },
566
+ ],
567
+ };
568
+
569
+ const limiters = createLimiters(mockLimitsResponse);
570
+
571
+ expect(limiters.fast).toBeDefined();
572
+ expect(limiters.scrapeUrl).toBeDefined();
573
+ expect(limiters.bulk).toBeDefined();
574
+ });
575
+
576
+ it("should sort and chain limiters by duration (shortest first)", () => {
577
+ const mockLimitsResponse: LimitsResponse = {
578
+ api_key_id: "test-api-key-id",
579
+ subscription_id: "test-subscription-id",
580
+ limits: [
581
+ {
582
+ name: "fast_four_week",
583
+ query_type: "fast",
584
+ duration_seconds: 2419200, // Listed first but longest
585
+ duration_string: "four_week",
586
+ limit: 3000000,
587
+ limited: false,
588
+ remaining: 3000000,
589
+ period_start: "2026-01-05T00:00:00.000000",
590
+ period_end: "2026-02-02T00:00:00.000000",
591
+ },
592
+ {
593
+ name: "fast_minute",
594
+ query_type: "fast",
595
+ duration_seconds: 60, // Should be sorted first
596
+ duration_string: "minute",
597
+ limit: 120,
598
+ limited: false,
599
+ remaining: 120,
600
+ period_start: null,
601
+ period_end: null,
602
+ },
603
+ {
604
+ name: "fast_hour",
605
+ query_type: "fast",
606
+ duration_seconds: 3600, // Should be in middle
607
+ duration_string: "hour",
608
+ limit: 5000,
609
+ limited: false,
610
+ remaining: 5000,
611
+ period_start: null,
612
+ period_end: null,
613
+ },
614
+ ],
615
+ };
616
+
617
+ const limiters = createLimiters(mockLimitsResponse);
618
+
619
+ expect(limiters.fast).toBeDefined();
620
+ });
621
+
622
+ it("should create unlimited limiters for query types without limits", () => {
623
+ const mockLimitsResponse: LimitsResponse = {
624
+ api_key_id: "test-api-key-id",
625
+ subscription_id: "test-subscription-id",
626
+ limits: [
627
+ {
628
+ name: "fast_minute",
629
+ query_type: "fast",
630
+ duration_seconds: 60,
631
+ duration_string: "minute",
632
+ limit: 120,
633
+ limited: false,
634
+ remaining: 120,
635
+ period_start: null,
636
+ period_end: null,
637
+ },
638
+ // No slow/visit limits defined
639
+ ],
640
+ };
641
+
642
+ const limiters = createLimiters(mockLimitsResponse);
643
+
644
+ // Fast should have limits
645
+ expect(limiters.fast).toBeDefined();
646
+
647
+ // Slow and visit should be unlimited (no limits defined)
648
+ expect(limiters.bulk).toBeDefined();
649
+ expect(limiters.scrapeUrl).toBeDefined();
650
+ });
651
+ });
652
+ });