]> Piment Noir Git Repositories - e-mobility-charging-stations-simulator.git/commitdiff
test(auth): strengthen assertion quality and edge case coverage
authorJérôme Benoit <jerome.benoit@sap.com>
Sat, 21 Mar 2026 18:41:46 +0000 (19:41 +0100)
committerJérôme Benoit <jerome.benoit@sap.com>
Sat, 21 Mar 2026 18:41:46 +0000 (19:41 +0100)
- 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'

tests/charging-station/ocpp/auth/cache/InMemoryAuthCache.test.ts
tests/charging-station/ocpp/auth/strategies/RemoteAuthStrategy.test.ts
tests/charging-station/ocpp/auth/utils/AuthValidators.test.ts

index 706db7a1ff1f4247a9fc9f3b5125e7800082b01c..85e67cf01c1672e2348a31246a5ce65a57c29532 100644 (file)
@@ -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)
index 6d983106f0e90ff970a67a9378effb5f0be225eb..9b0b2b89a3587a7d2ba2a4255e2c66fa83def2e8 100644 (file)
@@ -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<AuthorizationResult>(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<AuthorizationResult>(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<AuthorizationResult>(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 () => {
index b41c195e8dce264a308fb54666fffafca02b3109..5396361e080ba2a684f4c176961caf83a11fd9da 100644 (file)
@@ -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 () => {