From 338dad9a877ce89cb0456254b206ac6f0b84f3e8 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Wed, 1 Oct 2025 11:29:46 -0400 Subject: [PATCH] add unit tests for CacheManagementService --- .../test/unit/core/CacheManagementService.ts | 132 ++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 packages/backend/test/unit/core/CacheManagementService.ts diff --git a/packages/backend/test/unit/core/CacheManagementService.ts b/packages/backend/test/unit/core/CacheManagementService.ts new file mode 100644 index 0000000000..477e45fa8a --- /dev/null +++ b/packages/backend/test/unit/core/CacheManagementService.ts @@ -0,0 +1,132 @@ +/* + * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { jest } from '@jest/globals'; +import { MockRedis } from '../../misc/MockRedis.js'; +import { GodOfTimeService } from '../../misc/GodOfTimeService.js'; +import { CacheManagementService, type Manager } from '@/core/CacheManagementService.js'; +import { InternalEventService } from '@/core/InternalEventService.js'; +import { MemoryKVCache } from '@/misc/cache.js'; + +describe(CacheManagementService, () => { + let timeService: GodOfTimeService; + let redisClient: MockRedis; + let internalEventService: InternalEventService; + + let serviceUnderTest: CacheManagementService; + let internalsUnderTest: { managedCaches: Set }; + + beforeEach(() => { + timeService = new GodOfTimeService(); + timeService.resetToNow(); + redisClient = new MockRedis(timeService); + internalEventService = new InternalEventService(redisClient, redisClient, { host: 'example.com' }); + + serviceUnderTest = new CacheManagementService(redisClient, timeService, internalEventService); + internalsUnderTest = { managedCaches: Reflect.get(serviceUnderTest, 'managedCaches') }; + }); + + afterEach(() => { + timeService.reset(); + serviceUnderTest.dispose(); + internalEventService.dispose(); + redisClient.disconnect(); + }); + + describe('createMemoryKVCache', () => testCreate('createMemoryKVCache', Infinity)); + describe('createMemorySingleCache', () => testCreate('createMemorySingleCache', Infinity)); + describe('createRedisKVCache', () => testCreate('createRedisKVCache', 'redis', { lifetime: Infinity, memoryCacheLifetime: Infinity })); + describe('createRedisSingleCache', () => testCreate('createRedisSingleCache', 'single', { lifetime: Infinity, memoryCacheLifetime: Infinity })); + describe('createQuantumKVCache', () => testCreate('createQuantumKVCache', 'quantum', { lifetime: Infinity, fetcher: () => { throw new Error('not implement'); } })); + + describe('clear', () => testClear('clear', false)); + describe('dispose', () => testClear('dispose', true)); + describe('onApplicationShutdown', () => testClear('onApplicationShutdown', true)); + + function testCreate(func: Func, ...args: Parameters) { + // @ts-expect-error TypeScript bug: https://github.com/microsoft/TypeScript/issues/57322 + const act = () => serviceUnderTest[func](...args); + + it('should construct a cache', () => { + const cache = act(); + + expect(cache).not.toBeNull(); + }); + + it('should track reference', () => { + const cache = act(); + + expect(internalsUnderTest.managedCaches).toContain(cache); + }); + } + + function testClear(func: 'clear' | 'dispose' | 'onApplicationShutdown', shouldDispose: boolean) { + function act() { + serviceUnderTest[func](); + } + + it('should clear managed caches', () => { + const cache = serviceUnderTest.createMemoryKVCache(Infinity); + const clear = jest.spyOn(cache, 'clear'); + + act(); + + expect(clear).toHaveBeenCalled(); + }); + + it(`should${shouldDispose ? ' ' : ' not '}dispose managed caches`, () => { + const cache = serviceUnderTest.createMemoryKVCache(Infinity); + const dispose = jest.spyOn(cache as MemoryKVCache, 'dispose'); + + act(); + + if (shouldDispose) { + expect(dispose).toHaveBeenCalled(); + } else { + expect(dispose).not.toHaveBeenCalled(); + } + }); + + it('should not error with nothing to do', () => { + act(); + }); + + it('should be callable multiple times', () => { + const cache = serviceUnderTest.createMemoryKVCache(Infinity); + const clear = jest.spyOn(cache, 'clear'); + + act(); + act(); + act(); + + const expected = shouldDispose ? 1 : 3; + expect(clear).toHaveBeenCalledTimes(expected); + }); + + it(`should${shouldDispose ? ' ' : ' not '}deref caches`, () => { + const cache = serviceUnderTest.createMemoryKVCache(Infinity); + + act(); + + if (shouldDispose) { + expect(internalsUnderTest.managedCaches).not.toContain(cache); + } else { + expect(internalsUnderTest.managedCaches).toContain(cache); + } + }); + + it(`should${shouldDispose ? ' ' : ' not '}reset cache list`, () => { + serviceUnderTest.createMemoryKVCache(Infinity); + + act(); + + if (shouldDispose) { + expect(internalsUnderTest.managedCaches.size).toBe(0); + } else { + expect(internalsUnderTest.managedCaches.size).not.toBe(0); + } + }); + } +});