From: Jérôme Benoit Date: Sat, 21 Mar 2026 18:41:46 +0000 (+0100) Subject: test(auth): strengthen assertion quality and edge case coverage X-Git-Tag: v3.2~13 X-Git-Url: https://git.piment-noir.org/?a=commitdiff_plain;h=4943d4f9b706c2f81ef845add2c4f7db72d83cdd;p=e-mobility-charging-stations-simulator.git test(auth): strengthen assertion quality and edge case coverage - G03.FR.01 cache tests now verify status and TTL, not just cache key - Add stats increment verification after success and failure auth - Add cache error resilience test (cache.set throws) - Add idTag boundary tests for exactly 19, 20, 21 characters - Rename misleading 'concurrent operations' test to 'sequential batch' --- diff --git a/tests/charging-station/ocpp/auth/cache/InMemoryAuthCache.test.ts b/tests/charging-station/ocpp/auth/cache/InMemoryAuthCache.test.ts index 706db7a1..85e67cf0 100644 --- a/tests/charging-station/ocpp/auth/cache/InMemoryAuthCache.test.ts +++ b/tests/charging-station/ocpp/auth/cache/InMemoryAuthCache.test.ts @@ -485,7 +485,7 @@ await describe('InMemoryAuthCache - G03.FR.01 Conformance', async () => { assert.notStrictEqual(result, undefined) }) - await it('should handle concurrent operations', () => { + await it('should handle sequential batch operations', () => { // Concurrent sets cache.set('token-1', mockResult) cache.set('token-2', mockResult) diff --git a/tests/charging-station/ocpp/auth/strategies/RemoteAuthStrategy.test.ts b/tests/charging-station/ocpp/auth/strategies/RemoteAuthStrategy.test.ts index 6d983106..9b0b2b89 100644 --- a/tests/charging-station/ocpp/auth/strategies/RemoteAuthStrategy.test.ts +++ b/tests/charging-station/ocpp/auth/strategies/RemoteAuthStrategy.test.ts @@ -129,8 +129,12 @@ await describe('RemoteAuthStrategy', async () => { await it('should cache successful authorization results', async () => { let cachedKey: string | undefined - mockAuthCache.set = (key: string) => { + let cachedValue: AuthorizationResult | undefined + let cachedTtl: number | undefined + mockAuthCache.set = (key: string, value: AuthorizationResult, ttl?: number) => { cachedKey = key + cachedValue = value + cachedTtl = ttl } const config = createTestAuthConfig({ @@ -143,12 +147,18 @@ await describe('RemoteAuthStrategy', async () => { await strategy.authenticate(request, config) assert.strictEqual(cachedKey, 'CACHE_TAG') + assert.strictEqual(cachedValue?.status, AuthorizationStatus.ACCEPTED) + assert.strictEqual(cachedTtl, 300) }) await it('G03.FR.01.T4.01 - should cache BLOCKED authorization status', async () => { let cachedKey: string | undefined - mockAuthCache.set = (key: string) => { + let cachedValue: AuthorizationResult | undefined + let cachedTtl: number | undefined + mockAuthCache.set = (key: string, value: AuthorizationResult, ttl?: number) => { cachedKey = key + cachedValue = value + cachedTtl = ttl } mockOCPP16Adapter.authorizeRemote = () => new Promise(resolve => { @@ -170,12 +180,18 @@ await describe('RemoteAuthStrategy', async () => { await strategy.authenticate(request, config) assert.strictEqual(cachedKey, 'BLOCKED_TAG') + assert.strictEqual(cachedValue?.status, AuthorizationStatus.BLOCKED) + assert.strictEqual(cachedTtl, 300) }) await it('G03.FR.01.T4.02 - should cache EXPIRED authorization status', async () => { let cachedKey: string | undefined - mockAuthCache.set = (key: string) => { + let cachedValue: AuthorizationResult | undefined + let cachedTtl: number | undefined + mockAuthCache.set = (key: string, value: AuthorizationResult, ttl?: number) => { cachedKey = key + cachedValue = value + cachedTtl = ttl } mockOCPP16Adapter.authorizeRemote = () => new Promise(resolve => { @@ -197,12 +213,18 @@ await describe('RemoteAuthStrategy', async () => { await strategy.authenticate(request, config) assert.strictEqual(cachedKey, 'EXPIRED_TAG') + assert.strictEqual(cachedValue?.status, AuthorizationStatus.EXPIRED) + assert.strictEqual(cachedTtl, 300) }) await it('G03.FR.01.T4.03 - should cache INVALID authorization status', async () => { let cachedKey: string | undefined - mockAuthCache.set = (key: string) => { + let cachedValue: AuthorizationResult | undefined + let cachedTtl: number | undefined + mockAuthCache.set = (key: string, value: AuthorizationResult, ttl?: number) => { cachedKey = key + cachedValue = value + cachedTtl = ttl } mockOCPP16Adapter.authorizeRemote = () => new Promise(resolve => { @@ -224,12 +246,18 @@ await describe('RemoteAuthStrategy', async () => { await strategy.authenticate(request, config) assert.strictEqual(cachedKey, 'INVALID_TAG') + assert.strictEqual(cachedValue?.status, AuthorizationStatus.INVALID) + assert.strictEqual(cachedTtl, 300) }) await it('G03.FR.01.T4.04 - should still cache ACCEPTED authorization status (regression)', async () => { let cachedKey: string | undefined - mockAuthCache.set = (key: string) => { + let cachedValue: AuthorizationResult | undefined + let cachedTtl: number | undefined + mockAuthCache.set = (key: string, value: AuthorizationResult, ttl?: number) => { cachedKey = key + cachedValue = value + cachedTtl = ttl } const config = createTestAuthConfig({ @@ -242,6 +270,8 @@ await describe('RemoteAuthStrategy', async () => { await strategy.authenticate(request, config) assert.strictEqual(cachedKey, 'ACCEPTED_TAG') + assert.strictEqual(cachedValue?.status, AuthorizationStatus.ACCEPTED) + assert.strictEqual(cachedTtl, 300) }) await it('should return undefined when remote is unavailable', async () => { @@ -286,6 +316,24 @@ await describe('RemoteAuthStrategy', async () => { assert.strictEqual(result, undefined) }) + await it('should still return result when cache.set throws', async () => { + mockAuthCache.set = () => { + throw new Error('Cache storage full') + } + + const config = createTestAuthConfig({ + authorizationCacheEnabled: true, + authorizationCacheLifetime: 300, + }) + const request = createMockAuthRequest({ + identifier: createMockIdentifier('CACHE_ERROR_TAG', IdentifierType.ID_TAG), + }) + + const result = await strategy.authenticate(request, config) + assert.notStrictEqual(result, undefined) + assert.strictEqual(result?.status, AuthorizationStatus.ACCEPTED) + }) + await it('G03.FR.01.T8.01 - should not cache identifier that is in local auth list', async () => { let cachedKey: string | undefined mockAuthCache.set = (key: string) => { @@ -405,6 +453,35 @@ await describe('RemoteAuthStrategy', async () => { const stats = await strategy.getStats() assert.strictEqual(typeof stats.adapterAvailable, 'boolean') }) + + await it('should update statistics after successful and failed authentications', async () => { + strategy.initialize(createTestAuthConfig()) + + // Successful auth + const successRequest = createMockAuthRequest({ + identifier: createMockIdentifier('SUCCESS_TAG', IdentifierType.ID_TAG), + }) + await strategy.authenticate(successRequest, createTestAuthConfig()) + + const statsAfterSuccess = await strategy.getStats() + assert.strictEqual(statsAfterSuccess.totalRequests, 1) + assert.strictEqual(statsAfterSuccess.successfulRemoteAuth, 1) + assert.strictEqual(statsAfterSuccess.failedRemoteAuth, 0) + + // Failed auth (adapter throws) + mockOCPP16Adapter.authorizeRemote = () => { + throw new Error('Network error') + } + const failRequest = createMockAuthRequest({ + identifier: createMockIdentifier('FAIL_TAG', IdentifierType.ID_TAG), + }) + await strategy.authenticate(failRequest, createTestAuthConfig()) + + const statsAfterFailure = await strategy.getStats() + assert.strictEqual(statsAfterFailure.totalRequests, 2) + assert.strictEqual(statsAfterFailure.successfulRemoteAuth, 1) + assert.strictEqual(statsAfterFailure.failedRemoteAuth, 1) + }) }) await describe('cleanup', async () => { diff --git a/tests/charging-station/ocpp/auth/utils/AuthValidators.test.ts b/tests/charging-station/ocpp/auth/utils/AuthValidators.test.ts index b41c195e..5396361e 100644 --- a/tests/charging-station/ocpp/auth/utils/AuthValidators.test.ts +++ b/tests/charging-station/ocpp/auth/utils/AuthValidators.test.ts @@ -112,6 +112,24 @@ await describe('AuthValidators', async () => { await it('should handle empty string', () => { assert.strictEqual(AuthValidators.sanitizeIdTag(''), '') }) + + await it('should not truncate exactly 19 characters', () => { + const tag19 = 'A'.repeat(19) + assert.strictEqual(AuthValidators.sanitizeIdTag(tag19).length, 19) + assert.strictEqual(AuthValidators.sanitizeIdTag(tag19), tag19) + }) + + await it('should not truncate exactly 20 characters', () => { + const tag20 = 'A'.repeat(20) + assert.strictEqual(AuthValidators.sanitizeIdTag(tag20).length, 20) + assert.strictEqual(AuthValidators.sanitizeIdTag(tag20), tag20) + }) + + await it('should truncate 21 characters to 20', () => { + const tag21 = 'A'.repeat(21) + assert.strictEqual(AuthValidators.sanitizeIdTag(tag21).length, 20) + assert.strictEqual(AuthValidators.sanitizeIdTag(tag21), 'A'.repeat(20)) + }) }) await describe('sanitizeIdToken', async () => {