|
2 | 2 |
|
3 | 3 | ## Overview |
4 | 4 |
|
5 | | -The `cache.test.js` file provides comprehensive **unit tests** for the RERUM API caching layer, verifying that all read endpoints have functioning cache middleware. |
| 5 | +The cache testing suite includes two test files that provide comprehensive coverage of the RERUM API caching layer: |
| 6 | + |
| 7 | +1. **`cache.test.js`** - Middleware functionality tests (48 tests) |
| 8 | +2. **`cache-limits.test.js`** - Limit enforcement tests (12 tests) |
6 | 9 |
|
7 | 10 | ## Test Execution |
8 | 11 |
|
9 | | -### Run Cache Tests |
| 12 | +### Run All Cache Tests |
10 | 13 | ```bash |
11 | | -npm run runtest -- cache/cache.test.js |
| 14 | +npm run runtest -- cache/__tests__/ |
| 15 | +``` |
| 16 | + |
| 17 | +### Run Individual Test Files |
| 18 | +```bash |
| 19 | +# Middleware tests |
| 20 | +npm run runtest -- cache/__tests__/cache.test.js |
| 21 | + |
| 22 | +# Limit enforcement tests |
| 23 | +npm run runtest -- cache/__tests__/cache-limits.test.js |
12 | 24 | ``` |
13 | 25 |
|
14 | 26 | ### Expected Results |
15 | 27 | ``` |
16 | | -✅ Test Suites: 1 passed, 1 total |
17 | | -✅ Tests: 36 passed, 36 total |
18 | | -⚡ Time: ~0.33s |
| 28 | +✅ Test Suites: 2 passed, 2 total |
| 29 | +✅ Tests: 60 passed, 60 total |
| 30 | +⚡ Time: ~1.2s |
19 | 31 | ``` |
20 | 32 |
|
21 | 33 | --- |
22 | 34 |
|
23 | | -## What cache.test.js DOES Test |
| 35 | +## cache.test.js - Middleware Functionality (48 tests) |
24 | 36 |
|
25 | 37 | ### ✅ Read Endpoint Caching (30 tests) |
26 | 38 |
|
@@ -411,10 +423,145 @@ Tests verify cache statistics are accurately tracked: |
411 | 423 | - ⚠️ Cache invalidation on write operations |
412 | 424 | - ⚠️ Actual MongoDB interactions |
413 | 425 | - ⚠️ TTL expiration (requires time-based testing) |
414 | | -- ⚠️ Cache eviction under max size limit |
415 | 426 | - ⚠️ Concurrent request handling |
416 | 427 | - ⚠️ Memory pressure scenarios |
417 | 428 |
|
| 429 | +--- |
| 430 | + |
| 431 | +## cache-limits.test.js - Limit Enforcement (12 tests) |
| 432 | + |
| 433 | +### What This Tests |
| 434 | + |
| 435 | +Comprehensive validation of cache limit enforcement to ensure memory safety and proper eviction behavior. |
| 436 | + |
| 437 | +### ✅ Length Limit Tests (3 tests) |
| 438 | + |
| 439 | +#### 1. Max Length Enforcement |
| 440 | +- ✅ Cache never exceeds maxLength when adding entries |
| 441 | +- ✅ Automatically evicts least recently used (LRU) entries at limit |
| 442 | +- ✅ Eviction counter accurately tracked |
| 443 | + |
| 444 | +#### 2. LRU Eviction Order |
| 445 | +- ✅ Least recently used entries evicted first |
| 446 | +- ✅ Recently accessed entries preserved |
| 447 | +- ✅ Proper head/tail management in linked list |
| 448 | + |
| 449 | +#### 3. LRU Order Preservation |
| 450 | +- ✅ Accessing entries moves them to head (most recent) |
| 451 | +- ✅ Unaccessed entries move toward tail (least recent) |
| 452 | +- ✅ Eviction targets correct (tail) entry |
| 453 | + |
| 454 | +### ✅ Byte Size Limit Tests (3 tests) |
| 455 | + |
| 456 | +#### 1. Max Bytes Enforcement |
| 457 | +- ✅ Cache never exceeds maxBytes when adding entries |
| 458 | +- ✅ Byte size calculated accurately using `calculateByteSize()` |
| 459 | +- ✅ Multiple evictions triggered if necessary |
| 460 | + |
| 461 | +**Critical Fix Verified**: Previously, byte limit was NOT enforced due to `JSON.stringify(Map)` bug. Tests confirm the fix works correctly. |
| 462 | + |
| 463 | +#### 2. Multiple Entry Eviction |
| 464 | +- ✅ Evicts multiple entries to stay under byte limit |
| 465 | +- ✅ Continues eviction until bytes < maxBytes |
| 466 | +- ✅ Handles large entries requiring multiple LRU removals |
| 467 | + |
| 468 | +#### 3. Realistic Entry Sizes |
| 469 | +- ✅ Handles typical RERUM query results (~27KB for 100 items) |
| 470 | +- ✅ Properly calculates byte size for complex objects |
| 471 | +- ✅ Byte limit enforced with production-like data |
| 472 | + |
| 473 | +### ✅ Combined Limits Tests (2 tests) |
| 474 | + |
| 475 | +#### 1. Dual Limit Enforcement |
| 476 | +- ✅ Both length and byte limits enforced simultaneously |
| 477 | +- ✅ Neither limit can be exceeded |
| 478 | +- ✅ Proper interaction between both limits |
| 479 | + |
| 480 | +#### 2. Limit Prioritization |
| 481 | +- ✅ Byte limit takes precedence when entries are large |
| 482 | +- ✅ Length limit takes precedence for typical entries |
| 483 | +- ✅ Defense-in-depth protection verified |
| 484 | + |
| 485 | +### ✅ Edge Cases (3 tests) |
| 486 | + |
| 487 | +#### 1. Updating Existing Entries |
| 488 | +- ✅ Updates don't trigger unnecessary evictions |
| 489 | +- ✅ Cache size remains constant on updates |
| 490 | +- ✅ Entry values properly replaced |
| 491 | + |
| 492 | +#### 2. Large Single Entries |
| 493 | +- ✅ Single large entry can be cached if within limits |
| 494 | +- ✅ Proper handling of entries near byte limit |
| 495 | +- ✅ No infinite eviction loops |
| 496 | + |
| 497 | +#### 3. Empty Cache |
| 498 | +- ✅ Statistics accurate with empty cache |
| 499 | +- ✅ Limits properly reported |
| 500 | +- ✅ No errors accessing empty cache |
| 501 | + |
| 502 | +### ✅ Real-World Simulation (1 test) |
| 503 | + |
| 504 | +#### Production-Like Usage Patterns |
| 505 | +- ✅ 2000 cache operations with realistic RERUM data |
| 506 | +- ✅ Proper handling of pagination (creates duplicate keys with updates) |
| 507 | +- ✅ Statistics accurately tracked across many operations |
| 508 | +- ✅ Verifies limits are well-balanced for typical usage |
| 509 | + |
| 510 | +**Key Finding**: With default limits (1000 entries, 1GB), typical RERUM queries (100 items) only use ~26 MB (2.7% of byte limit). Length limit is reached first in normal operation. |
| 511 | + |
| 512 | +### Test Implementation Details |
| 513 | + |
| 514 | +```javascript |
| 515 | +// Helper functions for testing with custom limits |
| 516 | +function setupTestCache(maxLength, maxBytes, ttl) { |
| 517 | + cache.clear() |
| 518 | + cache.maxLength = maxLength |
| 519 | + cache.maxBytes = maxBytes |
| 520 | + cache.ttl = ttl |
| 521 | + // Reset stats |
| 522 | + return cache |
| 523 | +} |
| 524 | + |
| 525 | +function restoreDefaultCache() { |
| 526 | + cache.clear() |
| 527 | + cache.maxLength = parseInt(process.env.CACHE_MAX_LENGTH ?? 1000) |
| 528 | + cache.maxBytes = parseInt(process.env.CACHE_MAX_BYTES ?? 1000000000) |
| 529 | + cache.ttl = parseInt(process.env.CACHE_TTL ?? 300000) |
| 530 | +} |
| 531 | +``` |
| 532 | +
|
| 533 | +### Byte Size Calculation Verification |
| 534 | +
|
| 535 | +Tests verify the fix for the critical bug where `JSON.stringify(Map)` returned `{}`: |
| 536 | +
|
| 537 | +```javascript |
| 538 | +// Before (broken): JSON.stringify(this.cache) → "{}" → 2 bytes |
| 539 | +// After (fixed): Proper iteration through Map entries |
| 540 | +calculateByteSize() { |
| 541 | + let totalBytes = 0 |
| 542 | + for (const [key, node] of this.cache.entries()) { |
| 543 | + totalBytes += Buffer.byteLength(key, 'utf8') |
| 544 | + totalBytes += Buffer.byteLength(JSON.stringify(node.value), 'utf8') |
| 545 | + } |
| 546 | + return totalBytes |
| 547 | +} |
| 548 | +``` |
| 549 | +
|
| 550 | +### Limit Balance Findings |
| 551 | +
|
| 552 | +| Entry Type | Entries for 1000 Limit | Bytes Used | % of 1GB | |
| 553 | +|-----------|------------------------|------------|----------| |
| 554 | +| ID lookups | 1000 | 0.17 MB | 0.02% | |
| 555 | +| Query (10 items) | 1000 | 2.61 MB | 0.27% | |
| 556 | +| Query (100 items) | 1000 | 25.7 MB | 2.70% | |
| 557 | +| GOG (50 items) | 1000 | 12.9 MB | 1.35% | |
| 558 | +
|
| 559 | +**Conclusion**: Limits are well-balanced. Length limit (1000) will be reached first in 99%+ of scenarios. Byte limit (1GB) serves as safety net for edge cases. |
| 560 | +
|
| 561 | +--- |
| 562 | +
|
| 563 | +## What Tests Do NOT Cover |
| 564 | +
|
418 | 565 | ## Extending the Tests |
419 | 566 |
|
420 | 567 | ### Adding Tests for New Endpoints |
@@ -516,7 +663,8 @@ Before merging cache changes: |
516 | 663 |
|
517 | 664 | --- |
518 | 665 |
|
519 | | -**Test Suite**: cache.test.js |
520 | | -**Tests**: 25 |
521 | | -**Status**: ✅ All Passing |
522 | | -**Last Updated**: October 20, 2025 |
| 666 | +**Test Coverage Summary**: |
| 667 | +- **cache.test.js**: 48 tests covering middleware functionality |
| 668 | +- **cache-limits.test.js**: 12 tests covering limit enforcement |
| 669 | +- **Total**: 60 tests, all passing ✅ |
| 670 | +- **Last Updated**: October 21, 2025 |
0 commit comments