From 5abd5a4ce49cb09fded09549e1ff5dce1dfd56e3 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Tue, 3 Mar 2026 20:50:20 +0100 Subject: [PATCH] test: add SharedLRUCache tests Add tests for the LRU cache singleton covering template and configuration CRUD operations, cacheability validation for configurations with empty fields, and cache clear behavior. --- tests/charging-station/SharedLRUCache.test.ts | 207 ++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100644 tests/charging-station/SharedLRUCache.test.ts diff --git a/tests/charging-station/SharedLRUCache.test.ts b/tests/charging-station/SharedLRUCache.test.ts new file mode 100644 index 00000000..5e12cabe --- /dev/null +++ b/tests/charging-station/SharedLRUCache.test.ts @@ -0,0 +1,207 @@ +/** + * @file Tests for SharedLRUCache singleton + * @description Verifies the LRU cache for charging station templates and configurations + * + * Covers: + * - Singleton pattern (getInstance) + * - Template CRUD (set/get/has/delete) + * - Configuration CRUD (set/get/has/delete with cacheability validation) + * - Cache clear + */ + +import { expect } from '@std/expect' +import { afterEach, beforeEach, describe, it } from 'node:test' + +import type { + ChargingStationConfiguration, + ChargingStationTemplate, +} from '../../src/types/index.js' + +import { Bootstrap } from '../../src/charging-station/Bootstrap.js' +import { SharedLRUCache } from '../../src/charging-station/SharedLRUCache.js' +import { standardCleanup } from '../helpers/TestLifecycleHelpers.js' + +interface BootstrapStatic { + instance: Bootstrap | null +} + +/** + * + * @param hash + */ +function createCacheableConfiguration (hash: string): ChargingStationConfiguration { + return { + automaticTransactionGenerator: { enable: false, maxDuration: 120, minDuration: 60 }, + configurationHash: hash, + configurationKey: [{ key: 'HeartbeatInterval', readonly: false, value: '60' }], + stationInfo: { chargingStationId: 'test-station' }, + } as unknown as ChargingStationConfiguration +} + +/** + * + * @param hash + */ +function createTemplate (hash: string): ChargingStationTemplate { + return { + baseName: 'test-template', + chargePointModel: 'test-model', + chargePointVendor: 'test-vendor', + templateHash: hash, + } as ChargingStationTemplate +} + +// Inject a mock Bootstrap singleton so that SharedLRUCache constructor reads numeric getters +// instead of triggering a real Bootstrap construction. +/** + * + */ +function installMockBootstrap (): void { + ;(Bootstrap as unknown as BootstrapStatic).instance = { + numberOfChargingStationTemplates: 10, + numberOfConfiguredChargingStations: 20, + numberOfProvisionedChargingStations: 5, + } as unknown as Bootstrap +} + +/** + * + */ +function resetSharedLRUCache (): void { + ;(SharedLRUCache as unknown as { instance: null }).instance = null +} + +await describe('SharedLRUCache', async () => { + beforeEach(() => { + installMockBootstrap() + }) + + afterEach(() => { + standardCleanup() + resetSharedLRUCache() + ;(Bootstrap as unknown as BootstrapStatic).instance = null + }) + + await describe('getInstance', async () => { + await it('should return the same instance on multiple calls', () => { + const instance1 = SharedLRUCache.getInstance() + const instance2 = SharedLRUCache.getInstance() + + expect(instance1).toBe(instance2) + }) + + await it('should create new instance after reset', () => { + const instance1 = SharedLRUCache.getInstance() + resetSharedLRUCache() + const instance2 = SharedLRUCache.getInstance() + + expect(instance1).not.toBe(instance2) + }) + }) + + await describe('template operations', async () => { + await it('should store and retrieve a charging station template', () => { + const cache = SharedLRUCache.getInstance() + const template = createTemplate('tmpl-hash-1') + + cache.setChargingStationTemplate(template) + const retrieved = cache.getChargingStationTemplate('tmpl-hash-1') + + expect(retrieved).toStrictEqual(template) + }) + + await it('should return undefined for non-existent template', () => { + const cache = SharedLRUCache.getInstance() + + const result = cache.getChargingStationTemplate('unknown-hash') + + expect(result).toBeUndefined() + }) + + await it('should report has correctly for templates', () => { + const cache = SharedLRUCache.getInstance() + const template = createTemplate('tmpl-hash-2') + + cache.setChargingStationTemplate(template) + + expect(cache.hasChargingStationTemplate('tmpl-hash-2')).toBe(true) + expect(cache.hasChargingStationTemplate('unknown-hash')).toBe(false) + }) + + await it('should delete a charging station template', () => { + const cache = SharedLRUCache.getInstance() + const template = createTemplate('tmpl-hash-3') + + cache.setChargingStationTemplate(template) + cache.deleteChargingStationTemplate('tmpl-hash-3') + + expect(cache.hasChargingStationTemplate('tmpl-hash-3')).toBe(false) + }) + }) + + await describe('configuration operations', async () => { + await it('should store and retrieve a cacheable configuration', () => { + const cache = SharedLRUCache.getInstance() + const config = createCacheableConfiguration('config-hash-1') + + cache.setChargingStationConfiguration(config) + const retrieved = cache.getChargingStationConfiguration('config-hash-1') + + expect(retrieved).toStrictEqual(config) + }) + + await it('should not cache configuration with empty configurationKey', () => { + const cache = SharedLRUCache.getInstance() + const config = createCacheableConfiguration('config-hash-empty-key') + ;(config as unknown as { configurationKey: never[] }).configurationKey = [] + + cache.setChargingStationConfiguration(config) + + expect(cache.hasChargingStationConfiguration('config-hash-empty-key')).toBe(false) + }) + + await it('should not cache configuration with null stationInfo', () => { + const cache = SharedLRUCache.getInstance() + const config = createCacheableConfiguration('config-hash-no-info') + ;(config as unknown as { stationInfo: undefined }).stationInfo = undefined + + cache.setChargingStationConfiguration(config) + + expect(cache.hasChargingStationConfiguration('config-hash-no-info')).toBe(false) + }) + + await it('should not cache configuration with empty configurationHash', () => { + const cache = SharedLRUCache.getInstance() + const config = createCacheableConfiguration('') + + cache.setChargingStationConfiguration(config) + + expect(cache.hasChargingStationConfiguration('')).toBe(false) + }) + + await it('should delete a charging station configuration', () => { + const cache = SharedLRUCache.getInstance() + const config = createCacheableConfiguration('config-hash-del') + + cache.setChargingStationConfiguration(config) + cache.deleteChargingStationConfiguration('config-hash-del') + + expect(cache.hasChargingStationConfiguration('config-hash-del')).toBe(false) + }) + }) + + await describe('clear', async () => { + await it('should clear all cached entries', () => { + const cache = SharedLRUCache.getInstance() + const template = createTemplate('tmpl-clear') + const config = createCacheableConfiguration('config-clear') + + cache.setChargingStationTemplate(template) + cache.setChargingStationConfiguration(config) + cache.clear() + + expect(cache.hasChargingStationTemplate('tmpl-clear')).toBe(false) + expect(cache.hasChargingStationConfiguration('config-clear')).toBe(false) + }) + }) +}) -- 2.53.0