diff --git a/keeper/.env.example b/keeper/.env.example index 98e0004..e43199e 100644 --- a/keeper/.env.example +++ b/keeper/.env.example @@ -47,6 +47,10 @@ RETRY_STORAGE_PATH=./data/retries.json MAX_RETRY_QUEUE_SIZE=100 MAX_RETRIES_PER_CYCLE=2 +# Simulation Cache Configuration +SIMULATION_CACHE_TTL=30 +SIMULATION_CACHE_MAX_SIZE=1000 + # Logging Configuration LOG_LEVEL=info NODE_ENV=production diff --git a/keeper/__tests__/simulationCache.test.js b/keeper/__tests__/simulationCache.test.js new file mode 100644 index 0000000..43b49b9 --- /dev/null +++ b/keeper/__tests__/simulationCache.test.js @@ -0,0 +1,155 @@ +const { SimulationCache } = require('../src/simulationCache'); + +describe('SimulationCache', () => { + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + describe('get and set', () => { + it('should return null for missing entries', () => { + const cache = new SimulationCache(); + expect(cache.get(123)).toBeNull(); + }); + + it('should store and retrieve values', () => { + const cache = new SimulationCache(); + const taskConfig = { last_run: 100, interval: 60, gas_balance: 1000 }; + cache.set(123, taskConfig); + expect(cache.get(123)).toEqual(taskConfig); + }); + }); + + describe('cache invalidation', () => { + it('should invalidate single entry', () => { + const cache = new SimulationCache(); + cache.set(123, { last_run: 100 }); + expect(cache.invalidate(123)).toBe(true); + expect(cache.get(123)).toBeNull(); + }); + + it('should return false when invalidating non-existent entry', () => { + const cache = new SimulationCache(); + expect(cache.invalidate(999)).toBe(false); + }); + + it('should bulk invalidate entries', () => { + const cache = new SimulationCache(); + cache.set(1, { a: 1 }); + cache.set(2, { b: 2 }); + cache.set(3, { c: 3 }); + expect(cache.invalidateAll([1, 2])).toBe(2); + expect(cache.get(1)).toBeNull(); + expect(cache.get(2)).toBeNull(); + expect(cache.get(3)).not.toBeNull(); + }); + }); + + describe('TTL expiration', () => { + it('should expire entries after TTL', () => { + const cache = new SimulationCache({ ttlSeconds: 5 }); + cache.set(123, { last_run: 100 }); + + // Before TTL expires + jest.advanceTimersByTime(4000); + expect(cache.get(123)).not.toBeNull(); + + // After TTL expires + jest.advanceTimersByTime(2000); + expect(cache.get(123)).toBeNull(); + }); + + it('should track misses for expired entries', () => { + const cache = new SimulationCache({ ttlSeconds: 1 }); + cache.set(123, { data: 'test' }); + + cache.get(123); // hit + jest.advanceTimersByTime(2000); + cache.get(123); // miss (expired) + + const stats = cache.getStats(); + expect(stats.hits).toBe(1); + expect(stats.misses).toBe(1); + }); + }); + + describe('hit rate tracking', () => { + it('should calculate hit rate correctly', () => { + const cache = new SimulationCache(); + cache.set(1, { data: 1 }); + cache.set(2, { data: 2 }); + + cache.get(1); // hit + cache.get(999); // miss (not found) + jest.advanceTimersByTime(100000); // expire all + cache.get(2); // miss (expired) + + const stats = cache.getStats(); + expect(stats.hits).toBe(1); + expect(stats.misses).toBe(2); + expect(stats.hitRatePercent).toBe(33.3); + }); + }); + + describe('cache size limits', () => { + it('should evict oldest entry when at max size', () => { + const cache = new SimulationCache({ maxSize: 2, ttlSeconds: 60 }); + + cache.set(1, { data: 1 }); + jest.advanceTimersByTime(1000); + cache.set(2, { data: 2 }); + jest.advanceTimersByTime(1000); + cache.set(3, { data: 3 }); // should evict entry 1 + + expect(cache.get(1)).toBeNull(); + expect(cache.get(2)).not.toBeNull(); + expect(cache.get(3)).not.toBeNull(); + }); + }); + + describe('clear and cleanup', () => { + it('should clear all entries', () => { + const cache = new SimulationCache(); + cache.set(1, { a: 1 }); + cache.set(2, { b: 2 }); + cache.get(1); + + cache.clear(); + + expect(cache.get(1)).toBeNull(); + expect(cache.get(2)).toBeNull(); + const stats = cache.getStats(); + expect(stats.hits).toBe(0); + expect(stats.misses).toBe(0); + }); + + it('should cleanup remove expired entries', () => { + const cache = new SimulationCache({ ttlSeconds: 5 }); + cache.set(1, { a: 1 }); + cache.set(2, { b: 2 }); + + jest.advanceTimersByTime(6000); + const removed = cache.cleanup(); + + expect(removed).toBe(2); + expect(cache.get(1)).toBeNull(); + expect(cache.get(2)).toBeNull(); + }); + }); + + describe('key generation', () => { + it('should handle different task ID types', () => { + const cache = new SimulationCache(); + const config = { last_run: 100 }; + + cache.set(123, config); + cache.set('123', config); + cache.set(123n, config); + + expect(cache.cache.size).toBe(3); + }); + }); +}); \ No newline at end of file diff --git a/keeper/coverage/coverage-summary.json b/keeper/coverage/coverage-summary.json index dd5276f..ef1ae37 100644 --- a/keeper/coverage/coverage-summary.json +++ b/keeper/coverage/coverage-summary.json @@ -1,8 +1,2 @@ -{"total": {"lines":{"total":384,"covered":350,"skipped":0,"pct":91.14},"statements":{"total":393,"covered":358,"skipped":0,"pct":91.09},"functions":{"total":67,"covered":66,"skipped":0,"pct":98.5},"branches":{"total":235,"covered":196,"skipped":0,"pct":83.4},"branchesTrue":{"total":0,"covered":0,"skipped":0,"pct":"Unknown"}} -,"C:\\Users\\c-christopher\\Desktop\\soro\\SoroTask\\keeper\\src\\concurrency.js": {"lines":{"total":22,"covered":22,"skipped":0,"pct":100},"functions":{"total":6,"covered":6,"skipped":0,"pct":100},"statements":{"total":22,"covered":22,"skipped":0,"pct":100},"branches":{"total":4,"covered":4,"skipped":0,"pct":100}} -,"C:\\Users\\c-christopher\\Desktop\\soro\\SoroTask\\keeper\\src\\logger.js": {"lines":{"total":33,"covered":33,"skipped":0,"pct":100},"functions":{"total":18,"covered":18,"skipped":0,"pct":100},"statements":{"total":33,"covered":33,"skipped":0,"pct":100},"branches":{"total":20,"covered":15,"skipped":0,"pct":75}} -,"C:\\Users\\c-christopher\\Desktop\\soro\\SoroTask\\keeper\\src\\poller.js": {"lines":{"total":117,"covered":95,"skipped":0,"pct":81.19},"functions":{"total":11,"covered":11,"skipped":0,"pct":100},"statements":{"total":118,"covered":96,"skipped":0,"pct":81.35},"branches":{"total":57,"covered":44,"skipped":0,"pct":77.19}} -,"C:\\Users\\c-christopher\\Desktop\\soro\\SoroTask\\keeper\\src\\queue.js": {"lines":{"total":69,"covered":66,"skipped":0,"pct":95.65},"functions":{"total":7,"covered":6,"skipped":0,"pct":85.71},"statements":{"total":71,"covered":67,"skipped":0,"pct":94.36},"branches":{"total":32,"covered":28,"skipped":0,"pct":87.5}} -,"C:\\Users\\c-christopher\\Desktop\\soro\\SoroTask\\keeper\\src\\registry.js": {"lines":{"total":72,"covered":64,"skipped":0,"pct":88.88},"functions":{"total":12,"covered":12,"skipped":0,"pct":100},"statements":{"total":74,"covered":66,"skipped":0,"pct":89.18},"branches":{"total":44,"covered":34,"skipped":0,"pct":77.27}} -,"C:\\Users\\c-christopher\\Desktop\\soro\\SoroTask\\keeper\\src\\retry.js": {"lines":{"total":71,"covered":70,"skipped":0,"pct":98.59},"functions":{"total":13,"covered":13,"skipped":0,"pct":100},"statements":{"total":75,"covered":74,"skipped":0,"pct":98.66},"branches":{"total":78,"covered":71,"skipped":0,"pct":91.02}} +{"total": {"lines":{"total":0,"covered":0,"skipped":0,"pct":"Unknown"},"statements":{"total":0,"covered":0,"skipped":0,"pct":"Unknown"},"functions":{"total":0,"covered":0,"skipped":0,"pct":"Unknown"},"branches":{"total":0,"covered":0,"skipped":0,"pct":"Unknown"},"branchesTrue":{"total":0,"covered":0,"skipped":0,"pct":"Unknown"}} } diff --git a/keeper/coverage/lcov-report/index.html b/keeper/coverage/lcov-report/index.html index dc7fdf7..6d0fa42 100644 --- a/keeper/coverage/lcov-report/index.html +++ b/keeper/coverage/lcov-report/index.html @@ -23,30 +23,30 @@
| - | |||||||||
|---|---|---|---|---|---|---|---|---|---|
| concurrency.js | -
-
- |
- 0% | -0/51 | -0% | -0/26 | -0% | -0/8 | -0% | -0/50 | -
| logger.js | -
-
- |
- 0% | -0/33 | -0% | -0/20 | -0% | -0/18 | -0% | -0/33 | -
| poller.js | -
-
- |
- 0% | -0/122 | -0% | -0/62 | -0% | -0/12 | -0% | -0/121 | -
| queue.js | -
-
- |
- 94.36% | -67/71 | -87.5% | -28/32 | -85.71% | -6/7 | -95.65% | -66/69 | -
| registry.js | -
-
- |
- 0% | -0/74 | -0% | -0/44 | -0% | -0/12 | -0% | -0/72 | -
| retry.js | -
-
- |
- 98.66% | -74/75 | -91.02% | -71/78 | -100% | -13/13 | -98.59% | -70/71 | -