Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions keeper/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -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
155 changes: 155 additions & 0 deletions keeper/__tests__/simulationCache.test.js
Original file line number Diff line number Diff line change
@@ -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);
});
});
});
8 changes: 1 addition & 7 deletions keeper/coverage/coverage-summary.json
Original file line number Diff line number Diff line change
@@ -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"}}
}
112 changes: 11 additions & 101 deletions keeper/coverage/lcov-report/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,30 +23,30 @@ <h1>All files</h1>
<div class='clearfix'>

<div class='fl pad1y space-right2'>
<span class="strong">91.09% </span>
<span class="strong">Unknown% </span>
<span class="quiet">Statements</span>
<span class='fraction'>358/393</span>
<span class='fraction'>0/0</span>
</div>


<div class='fl pad1y space-right2'>
<span class="strong">83.4% </span>
<span class="strong">Unknown% </span>
<span class="quiet">Branches</span>
<span class='fraction'>196/235</span>
<span class='fraction'>0/0</span>
</div>


<div class='fl pad1y space-right2'>
<span class="strong">0% </span>
<span class="strong">Unknown% </span>
<span class="quiet">Functions</span>
<span class='fraction'>0/79</span>
<span class='fraction'>0/0</span>
</div>


<div class='fl pad1y space-right2'>
<span class="strong">91.14% </span>
<span class="strong">Unknown% </span>
<span class="quiet">Lines</span>
<span class='fraction'>350/384</span>
<span class='fraction'>0/0</span>
</div>


Expand All @@ -61,7 +61,7 @@ <h1>All files</h1>
</div>
</template>
</div>
<div class='status-line low'></div>
<div class='status-line medium'></div>
<div class="pad1">
<table class="coverage-summary">
<thead>
Expand All @@ -78,105 +78,15 @@ <h1>All files</h1>
<th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
</tr>
</thead>
<tbody><tr>
<td class="file low" data-value="concurrency.js"><a href="concurrency.js.html">concurrency.js</a></td>
<td data-value="0" class="pic low">
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
</td>
<td data-value="0" class="pct low">0%</td>
<td data-value="51" class="abs low">0/51</td>
<td data-value="0" class="pct low">0%</td>
<td data-value="26" class="abs low">0/26</td>
<td data-value="0" class="pct low">0%</td>
<td data-value="8" class="abs low">0/8</td>
<td data-value="0" class="pct low">0%</td>
<td data-value="50" class="abs low">0/50</td>
</tr>

<tr>
<td class="file low" data-value="logger.js"><a href="logger.js.html">logger.js</a></td>
<td data-value="0" class="pic low">
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
</td>
<td data-value="0" class="pct low">0%</td>
<td data-value="33" class="abs low">0/33</td>
<td data-value="0" class="pct low">0%</td>
<td data-value="20" class="abs low">0/20</td>
<td data-value="0" class="pct low">0%</td>
<td data-value="18" class="abs low">0/18</td>
<td data-value="0" class="pct low">0%</td>
<td data-value="33" class="abs low">0/33</td>
</tr>

<tr>
<td class="file low" data-value="poller.js"><a href="poller.js.html">poller.js</a></td>
<td data-value="0" class="pic low">
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
</td>
<td data-value="0" class="pct low">0%</td>
<td data-value="122" class="abs low">0/122</td>
<td data-value="0" class="pct low">0%</td>
<td data-value="62" class="abs low">0/62</td>
<td data-value="0" class="pct low">0%</td>
<td data-value="12" class="abs low">0/12</td>
<td data-value="0" class="pct low">0%</td>
<td data-value="121" class="abs low">0/121</td>
</tr>

<tr>
<td class="file high" data-value="queue.js"><a href="queue.js.html">queue.js</a></td>
<td data-value="94.36" class="pic high">
<div class="chart"><div class="cover-fill" style="width: 94%"></div><div class="cover-empty" style="width: 6%"></div></div>
</td>
<td data-value="94.36" class="pct high">94.36%</td>
<td data-value="71" class="abs high">67/71</td>
<td data-value="87.5" class="pct high">87.5%</td>
<td data-value="32" class="abs high">28/32</td>
<td data-value="85.71" class="pct high">85.71%</td>
<td data-value="7" class="abs high">6/7</td>
<td data-value="95.65" class="pct high">95.65%</td>
<td data-value="69" class="abs high">66/69</td>
</tr>

<tr>
<td class="file low" data-value="registry.js"><a href="registry.js.html">registry.js</a></td>
<td data-value="0" class="pic low">
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
</td>
<td data-value="0" class="pct low">0%</td>
<td data-value="74" class="abs low">0/74</td>
<td data-value="0" class="pct low">0%</td>
<td data-value="44" class="abs low">0/44</td>
<td data-value="0" class="pct low">0%</td>
<td data-value="12" class="abs low">0/12</td>
<td data-value="0" class="pct low">0%</td>
<td data-value="72" class="abs low">0/72</td>
</tr>

<tr>
<td class="file high" data-value="retry.js"><a href="retry.js.html">retry.js</a></td>
<td data-value="98.66" class="pic high">
<div class="chart"><div class="cover-fill" style="width: 98%"></div><div class="cover-empty" style="width: 2%"></div></div>
</td>
<td data-value="98.66" class="pct high">98.66%</td>
<td data-value="75" class="abs high">74/75</td>
<td data-value="91.02" class="pct high">91.02%</td>
<td data-value="78" class="abs high">71/78</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="13" class="abs high">13/13</td>
<td data-value="98.59" class="pct high">98.59%</td>
<td data-value="71" class="abs high">70/71</td>
</tr>

</tbody>
<tbody></tbody>
</table>
</div>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-04-26T18:30:56.429Z
at 2026-04-29T03:30:00.602Z
</div>
<script src="prettify.js"></script>
<script>
Expand Down
Loading
Loading