| 419 | expect(() => getRequestURL(42)).toThrow(TypeError); | 427 | expect(() => getRequestURL(42)).toThrow(TypeError); |
| 420 | }); | 428 | }); |
| 421 | }); | 429 | }); |
| | 430 | |
| | 431 | describe('delay', () => { |
| | 432 | test('should resolve after the specified time', async () => { |
| | 433 | jest.useFakeTimers(); |
| | 434 | let resolved = false; |
| | 435 | delay(50).then(() => { resolved = true; }); |
| | 436 | expect(resolved).toBe(false); |
| | 437 | jest.advanceTimersByTime(50); |
| | 438 | await Promise.resolve(); |
| | 439 | expect(resolved).toBe(true); |
| | 440 | jest.useRealTimers(); |
| | 441 | }); |
| | 442 | |
| | 443 | test('should return a promise', () => { |
| | 444 | jest.useFakeTimers(); |
| | 445 | const result = delay(0); |
| | 446 | expect(result).toBeInstanceOf(Promise); |
| | 447 | jest.useRealTimers(); |
| | 448 | }); |
| | 449 | }); |
| | 450 | |
| | 451 | describe('formatBytes', () => { |
| | 452 | test('should format bytes to human-readable string', () => { |
| | 453 | expect(formatBytes(0)).toBe('0B'); |
| | 454 | expect(formatBytes(1024)).toBe('1KB'); |
| | 455 | expect(formatBytes(1048576)).toBe('1MB'); |
| | 456 | }); |
| | 457 | |
| | 458 | test('should return empty string for null/undefined', () => { |
| | 459 | expect(formatBytes(null)).toBe(''); |
| | 460 | expect(formatBytes(undefined)).toBe(''); |
| | 461 | }); |
| | 462 | }); |
| | 463 | |
| | 464 | describe('sanitizeSafeCharacterReplacements', () => { |
| | 465 | test('should always return underscore', () => { |
| | 466 | expect(sanitizeSafeCharacterReplacements('/')).toBe('_'); |
| | 467 | expect(sanitizeSafeCharacterReplacements('\\')).toBe('_'); |
| | 468 | expect(sanitizeSafeCharacterReplacements(':')).toBe('_'); |
| | 469 | expect(sanitizeSafeCharacterReplacements('')).toBe('_'); |
| | 470 | }); |
| | 471 | }); |
| | 472 | |
| | 473 | describe('generateTimestamp', () => { |
| | 474 | test('should return YYYYMMDD-HHMMSS format for a known date', () => { |
| | 475 | jest.useFakeTimers(); |
| | 476 | jest.setSystemTime(new Date('2025-07-15T09:30:45')); |
| | 477 | expect(generateTimestamp()).toBe('20250715-093045'); |
| | 478 | jest.useRealTimers(); |
| | 479 | }); |
| | 480 | }); |
| | 481 | |
| | 482 | describe('mergeObjectWithYaml', () => { |
| | 483 | test('should merge a YAML object into the target', () => { |
| | 484 | const obj = { a: 1 }; |
| | 485 | mergeObjectWithYaml(obj, 'b: 2\nc: 3'); |
| | 486 | expect(obj).toEqual({ a: 1, b: 2, c: 3 }); |
| | 487 | }); |
| | 488 | |
| | 489 | test('should merge a YAML array of objects into the target', () => { |
| | 490 | const obj = { a: 1 }; |
| | 491 | mergeObjectWithYaml(obj, '- b: 2\n- c: 3'); |
| | 492 | expect(obj).toEqual({ a: 1, b: 2, c: 3 }); |
| | 493 | }); |
| | 494 | |
| | 495 | test('should override existing keys', () => { |
| | 496 | const obj = { a: 1 }; |
| | 497 | mergeObjectWithYaml(obj, 'a: 99'); |
| | 498 | expect(obj.a).toBe(99); |
| | 499 | }); |
| | 500 | |
| | 501 | test('should do nothing for empty/falsy yamlString', () => { |
| | 502 | const obj = { a: 1 }; |
| | 503 | mergeObjectWithYaml(obj, ''); |
| | 504 | mergeObjectWithYaml(obj, null); |
| | 505 | mergeObjectWithYaml(obj, undefined); |
| | 506 | expect(obj).toEqual({ a: 1 }); |
| | 507 | }); |
| | 508 | |
| | 509 | test('should not throw on invalid YAML', () => { |
| | 510 | const obj = { a: 1 }; |
| | 511 | expect(() => mergeObjectWithYaml(obj, '{{{')).not.toThrow(); |
| | 512 | expect(obj).toEqual({ a: 1 }); |
| | 513 | }); |
| | 514 | |
| | 515 | test('should skip non-object items in YAML array', () => { |
| | 516 | const obj = { a: 1 }; |
| | 517 | mergeObjectWithYaml(obj, '- hello\n- b: 2'); |
| | 518 | expect(obj).toEqual({ a: 1, b: 2 }); |
| | 519 | }); |
| | 520 | }); |
| | 521 | |
| | 522 | describe('excludeKeysByYaml', () => { |
| | 523 | test('should delete keys listed in a YAML array', () => { |
| | 524 | const obj = { a: 1, b: 2, c: 3 }; |
| | 525 | excludeKeysByYaml(obj, '- a\n- c'); |
| | 526 | expect(obj).toEqual({ b: 2 }); |
| | 527 | }); |
| | 528 | |
| | 529 | test('should delete keys from a YAML object', () => { |
| | 530 | const obj = { a: 1, b: 2 }; |
| | 531 | excludeKeysByYaml(obj, 'a: whatever\nb: whatever'); |
| | 532 | expect(obj).toEqual({}); |
| | 533 | }); |
| | 534 | |
| | 535 | test('should delete a single string key', () => { |
| | 536 | const obj = { a: 1, b: 2 }; |
| | 537 | excludeKeysByYaml(obj, 'a'); |
| | 538 | expect(obj).toEqual({ b: 2 }); |
| | 539 | }); |
| | 540 | |
| | 541 | test('should do nothing for empty/falsy yamlString', () => { |
| | 542 | const obj = { a: 1 }; |
| | 543 | excludeKeysByYaml(obj, ''); |
| | 544 | excludeKeysByYaml(obj, null); |
| | 545 | expect(obj).toEqual({ a: 1 }); |
| | 546 | }); |
| | 547 | |
| | 548 | test('should not throw on invalid YAML', () => { |
| | 549 | const obj = { a: 1 }; |
| | 550 | expect(() => excludeKeysByYaml(obj, '{{{')).not.toThrow(); |
| | 551 | expect(obj).toEqual({ a: 1 }); |
| | 552 | }); |
| | 553 | }); |
| | 554 | |
| | 555 | describe('Cache', () => { |
| | 556 | test('should store and retrieve values', () => { |
| | 557 | const cache = new Cache(1000); |
| | 558 | cache.set('key', 'value'); |
| | 559 | expect(cache.get('key')).toBe('value'); |
| | 560 | }); |
| | 561 | |
| | 562 | test('should return null for missing keys', () => { |
| | 563 | const cache = new Cache(1000); |
| | 564 | expect(cache.get('missing')).toBeNull(); |
| | 565 | }); |
| | 566 | |
| | 567 | test('should return null for expired entries', () => { |
| | 568 | jest.useFakeTimers(); |
| | 569 | const cache = new Cache(10); |
| | 570 | cache.set('key', 'value'); |
| | 571 | jest.advanceTimersByTime(20); |
| | 572 | expect(cache.get('key')).toBeNull(); |
| | 573 | jest.useRealTimers(); |
| | 574 | }); |
| | 575 | |
| | 576 | test('should remove entries', () => { |
| | 577 | const cache = new Cache(1000); |
| | 578 | cache.set('key', 'value'); |
| | 579 | cache.remove('key'); |
| | 580 | expect(cache.get('key')).toBeNull(); |
| | 581 | }); |
| | 582 | |
| | 583 | test('should clear all entries', () => { |
| | 584 | const cache = new Cache(1000); |
| | 585 | cache.set('a', 1); |
| | 586 | cache.set('b', 2); |
| | 587 | cache.clear(); |
| | 588 | expect(cache.get('a')).toBeNull(); |
| | 589 | expect(cache.get('b')).toBeNull(); |
| | 590 | }); |
| | 591 | }); |
| | 592 | |
| | 593 | describe('MemoryLimitedMap', () => { |
| | 594 | test('should store and retrieve values', () => { |
| | 595 | const map = new MemoryLimitedMap('1 MB'); |
| | 596 | map.set('key', 'value'); |
| | 597 | expect(map.get('key')).toBe('value'); |
| | 598 | expect(map.has('key')).toBe(true); |
| | 599 | }); |
| | 600 | |
| | 601 | test('should reject non-string keys and values', () => { |
| | 602 | const map = new MemoryLimitedMap('1 MB'); |
| | 603 | map.set(123, 'value'); |
| | 604 | map.set('key', 123); |
| | 605 | expect(map.size()).toBe(0); |
| | 606 | }); |
| | 607 | |
| | 608 | test('should evict oldest entries when memory limit is reached', () => { |
| | 609 | // 20 bytes capacity = 10 chars (2 bytes per char) |
| | 610 | const map = new MemoryLimitedMap('20B'); |
| | 611 | map.set('a', '12345'); // 10 bytes |
| | 612 | map.set('b', '12345'); // 10 bytes, fills capacity |
| | 613 | map.set('c', '12345'); // 10 bytes, should evict 'a' |
| | 614 | expect(map.has('a')).toBe(false); |
| | 615 | expect(map.has('b')).toBe(true); |
| | 616 | expect(map.has('c')).toBe(true); |
| | 617 | }); |
| | 618 | |
| | 619 | test('should reject values larger than max memory', () => { |
| | 620 | const map = new MemoryLimitedMap('10B'); |
| | 621 | map.set('key', '123456'); // 12 bytes > 10 byte limit |
| | 622 | expect(map.has('key')).toBe(false); |
| | 623 | }); |
| | 624 | |
| | 625 | test('should do nothing when maxMemory is 0', () => { |
| | 626 | const map = new MemoryLimitedMap('0B'); |
| | 627 | map.set('key', 'value'); |
| | 628 | expect(map.size()).toBe(0); |
| | 629 | }); |
| | 630 | |
| | 631 | test('should track memory usage', () => { |
| | 632 | const map = new MemoryLimitedMap('1 MB'); |
| | 633 | map.set('key', 'hello'); // 10 bytes |
| | 634 | expect(map.totalMemory()).toBe(10); |
| | 635 | }); |
| | 636 | |
| | 637 | test('should update memory when overwriting a key', () => { |
| | 638 | const map = new MemoryLimitedMap('1 MB'); |
| | 639 | map.set('key', 'hi'); // 4 bytes |
| | 640 | map.set('key', 'hello'); // 10 bytes |
| | 641 | expect(map.totalMemory()).toBe(10); |
| | 642 | expect(map.get('key')).toBe('hello'); |
| | 643 | }); |
| | 644 | |
| | 645 | test('should delete entries and free memory', () => { |
| | 646 | const map = new MemoryLimitedMap('1 MB'); |
| | 647 | map.set('key', 'hello'); |
| | 648 | expect(map.delete('key')).toBe(true); |
| | 649 | expect(map.totalMemory()).toBe(0); |
| | 650 | expect(map.has('key')).toBe(false); |
| | 651 | }); |
| | 652 | |
| | 653 | test('should return false when deleting non-existent key', () => { |
| | 654 | const map = new MemoryLimitedMap('1 MB'); |
| | 655 | expect(map.delete('nope')).toBe(false); |
| | 656 | }); |
| | 657 | |
| | 658 | test('should clear all entries and reset memory', () => { |
| | 659 | const map = new MemoryLimitedMap('1 MB'); |
| | 660 | map.set('a', 'hello'); |
| | 661 | map.set('b', 'world'); |
| | 662 | map.clear(); |
| | 663 | expect(map.size()).toBe(0); |
| | 664 | expect(map.totalMemory()).toBe(0); |
| | 665 | }); |
| | 666 | |
| | 667 | test('should iterate with forEach', () => { |
| | 668 | const map = new MemoryLimitedMap('1 MB'); |
| | 669 | map.set('a', '1'); |
| | 670 | map.set('b', '2'); |
| | 671 | const entries = []; |
| | 672 | map.forEach((value, key) => entries.push([key, value])); |
| | 673 | expect(entries).toEqual([['a', '1'], ['b', '2']]); |
| | 674 | }); |
| | 675 | |
| | 676 | test('should expose keys and values iterators', () => { |
| | 677 | const map = new MemoryLimitedMap('1 MB'); |
| | 678 | map.set('a', '1'); |
| | 679 | map.set('b', '2'); |
| | 680 | expect([...map.keys()]).toEqual(['a', 'b']); |
| | 681 | expect([...map.values()]).toEqual(['1', '2']); |
| | 682 | }); |
| | 683 | |
| | 684 | test('estimateStringSize should return 2 bytes per character', () => { |
| | 685 | expect(MemoryLimitedMap.estimateStringSize('hello')).toBe(10); |
| | 686 | expect(MemoryLimitedMap.estimateStringSize('')).toBe(0); |
| | 687 | expect(MemoryLimitedMap.estimateStringSize(null)).toBe(0); |
| | 688 | }); |
| | 689 | }); |