From: Jérôme Benoit Date: Sat, 28 Feb 2026 22:39:20 +0000 (+0100) Subject: refactor(tests): restructure TEST_STYLE_GUIDE and use centralized constants X-Git-Tag: ocpp-server@v3.0.0~57 X-Git-Url: https://git.piment-noir.org/?a=commitdiff_plain;h=b12eb5bdbe95cbc9d7a3b157f2464e3cb4a322e1;p=e-mobility-charging-stations-simulator.git refactor(tests): restructure TEST_STYLE_GUIDE and use centralized constants - Rewrite TEST_STYLE_GUIDE.md: 691 → 295 lines, logical sections, no redundancy - Fix toBeTruthy() → toBeDefined() for strict assertions - Add test constants (TEST_ID_TAG, TEST_TRANSACTION_ID, TEST_TRANSACTION_ENERGY_WH) - Update test files to use centralized constants instead of inline values --- diff --git a/tests/TEST_STYLE_GUIDE.md b/tests/TEST_STYLE_GUIDE.md index 8dbfb6d9..ec7bec7c 100644 --- a/tests/TEST_STYLE_GUIDE.md +++ b/tests/TEST_STYLE_GUIDE.md @@ -1,85 +1,54 @@ # Test Style Guide -This document establishes conventions for writing maintainable, consistent tests in the e-mobility charging stations simulator project. +Conventions for writing maintainable, consistent tests in the e-mobility charging stations simulator. -## Testing Philosophy +## Core Principles -Core principles guiding test implementation: - -- **Test behavior, not implementation**: Focus on what code does, not how it does it -- **Isolation is mandatory**: Each test must run independently with fresh state +- **Test behavior, not implementation**: Focus on what code does, not how +- **Isolation is mandatory**: Each test runs independently with fresh state - **Determinism required**: Tests must produce identical results on every run -- **Coverage target**: Aim for 80%+ code coverage on new code -- **Strict assertions**: Use strict equality (`toBe`, `toStrictEqual`) to prevent false positives +- **Strict assertions**: Use `toBe`, `toStrictEqual` — never `toEqual`, `toBeTruthy` +- **Coverage target**: 80%+ on new code -## Naming Conventions +--- -### Test Case Naming (MANDATORY) +## 1. Naming Conventions -Use consistent `should [verb]` pattern in **lowercase**: +### Test Cases -✅ **Good:** +Pattern: `should [verb]` in **lowercase** ```typescript +// ✅ Good it('should start successfully with valid configuration', async () => {}) it('should reject invalid identifier', () => {}) -it('should handle Reset request with Immediate type', async () => {}) -``` - -❌ **Bad:** -```typescript -// Inconsistent capitalization +// ❌ Bad it('Should start successfully', () => {}) // Capital 'S' - -// Imperative style -it('Verify generateUUID()', () => {}) // Not declarative - -// Missing 'should' -it('starts successfully', () => {}) // No 'should' +it('Verify generateUUID()', () => {}) // Imperative +it('starts successfully', () => {}) // Missing 'should' ``` ### Files & Suites -- **Files**: Use descriptive names matching the module under test: `ModuleName.test.ts` -- **Test suites**: Use `describe()` with the module name only - NO "test suite" suffix -- **OCPP tests**: Use requirement codes: `describe('B11 - Reset', () => {})` -- **Auth tests**: Reference spec sections: `describe('G03.FR.01 - AuthCache Conformance', () => {})` -- **Variables**: Use camelCase for variables, functions, and test helpers -- **Constants**: Use SCREAMING_SNAKE_CASE for test constants - -**describe() Naming:** - -✅ **Good:** - -```typescript -describe('ChargingStation', () => {}) // Module name only -describe('B11 - Reset', () => {}) // OCPP spec code + description -describe('Utils', () => {}) // Simple and clear -``` - -❌ **Bad:** +| Element | Convention | Example | +| ---------- | ----------------------- | --------------------------------------- | +| Files | `ModuleName.test.ts` | `ChargingStation.test.ts` | +| Suites | Module name only | `describe('ChargingStation', () => {})` | +| OCPP tests | Spec code + description | `describe('B11 - Reset', () => {})` | +| Variables | camelCase | `mockStation`, `requestService` | +| Constants | SCREAMING_SNAKE_CASE | `TEST_HEARTBEAT_INTERVAL` | ```typescript -describe('ChargingStation test suite', () => {}) // Don't add "test suite" -describe('B11 & B12 - Reset', () => {}) // Split into separate describes -describe('WorkerUtils test suite', () => {}) // Redundant suffix +// ❌ Never add "test suite" suffix +describe('ChargingStation test suite', () => {}) ``` -## Test Structure (AAA Pattern) +--- -Follow the Arrange-Act-Assert pattern for clarity: +## 2. Test Structure -1. **Arrange**: Set up test data, mocks, and preconditions -2. **Act**: Execute the code under test -3. **Assert**: Verify the expected outcome - -### When to Use AAA Comments - -- **Required**: Tests with 3+ setup steps or complex assertions -- **Optional**: Simple single-assertion tests where intent is obvious - -**Complex test (comments required):** +### AAA Pattern (Arrange-Act-Assert) ```typescript it('should calculate total power correctly', () => { @@ -95,131 +64,12 @@ it('should calculate total power correctly', () => { }) ``` -**Simple test (comments optional):** - -```typescript -it('should return true for valid identifier', () => { - expect(isValidIdentifier('ABC123')).toBe(true) -}) -``` - -## Async Testing Patterns - -Most tests in this project are asynchronous. Follow these patterns: - -### Async/Await (Preferred) - -✅ **Good:** - -```typescript -it('should start charging session successfully', async () => { - // Arrange - const { station } = createMockChargingStation({ connectorsCount: 2 }) - const connectorId = 1 - - // Act - const result = await station.startTransaction(connectorId, 'VALID_TAG') - - // Assert - expect(result.status).toBe('Accepted') - expect(station.getConnectorStatus(connectorId)?.transactionStarted).toBe(true) -}) -``` - -### Promise Rejection Testing - -```typescript -it('should reject invalid connector ID', async () => { - const { station } = createMockChargingStation({ connectorsCount: 1 }) - - await expect(station.startTransaction(99, 'TAG')).rejects.toThrow('Invalid connector') -}) -``` - -### Timeout Handling - -```typescript -it('should timeout when server does not respond', async () => { - mock.timers.enable({ apis: ['setTimeout'] }) - const { station } = createMockChargingStation() - - const responsePromise = station.sendHeartbeat() - mock.timers.tick(30000) // Advance past timeout +**When to use AAA comments:** - await expect(responsePromise).rejects.toThrow('Timeout') - mock.timers.reset() -}) -``` +- Required: Tests with 3+ setup steps +- Optional: Simple single-assertion tests -❌ **Bad (Mixing callbacks and Promises):** - -```typescript -// WRONG: Never mix callback and Promise patterns -it('broken test', done => { - someAsyncOp().then(() => { - done() // Confusing - use async/await instead - }) -}) -``` - -## Error & Exception Testing - -Error handling is critical. Test both expected errors and edge cases: - -### Testing Expected Errors - -```typescript -it('should throw on invalid configuration', () => { - expect(() => new ChargingStation(null)).toThrow('Configuration required') -}) - -it('should reject unauthorized tag', async () => { - const { station } = createMockChargingStation() - - await expect(station.authorize('INVALID_TAG')).rejects.toThrow(OCPPError) -}) -``` - -### Testing Error Properties - -```typescript -it('should include error code in OCPPError', async () => { - const { station } = createMockChargingStation() - - try { - await station.sendInvalidCommand() - expect.fail('Should have thrown') - } catch (error) { - expect(error).toBeInstanceOf(OCPPError) - expect((error as OCPPError).code).toBe('GenericError') - } -}) -``` - -### Testing Error Recovery - -```typescript -it('should recover after transient error', async () => { - const { station } = createMockChargingStation() - mock.method(station, 'sendMessage', () => { - throw new Error('Network error') - }) - - // First call fails - await expect(station.sendHeartbeat()).rejects.toThrow('Network') - - // Restore and retry succeeds - mock.restoreAll() - const result = await station.sendHeartbeat() - expect(result).toBeDefined() -}) -``` - -## Comments & JSDoc - -### File Headers - -Every test file MUST include a JSDoc header: +### File Headers (MANDATORY) ```typescript /** @@ -228,348 +78,227 @@ Every test file MUST include a JSDoc header: */ ``` -### Inline Comments - -- Use comments sparingly - prefer self-documenting test names -- Comment WHY, not WHAT (the code shows what) -- Document non-obvious setup or complex assertions - -## Constants - -**ALWAYS use consolidated test constants from the canonical source:** - -- ✅ Import from: `tests/charging-station/ChargingStationTestConstants.ts` -- ❌ NEVER duplicate constants in individual test files -- ❌ NEVER create inline magic values - -**Good:** - -```typescript -import { TEST_CHARGING_STATION_BASE_NAME } from '../ChargingStationTestConstants.js' -``` - -**Bad:** - -```typescript -// Don't do this! -const TEST_STATION_NAME = 'CS-TEST-001' // Duplicate constant -``` - -## Mocks & Factories +--- -### When to Use Mock Factories +## 3. Test Isolation (CRITICAL) -Use centralized mock factories for complex objects: - -- `createMockChargingStation()` - From `ChargingStationTestUtils.ts` (returns `{ station, mocks }`) -- Auth mocks - From `tests/charging-station/ocpp/auth/helpers/MockFactories.ts` - -**Example:** - -```typescript -import { createMockChargingStation } from './ChargingStationTestUtils.js' - -const { station } = createMockChargingStation({ - ocppVersion: OCPPVersion.VERSION_20, - numberOfConnectors: 2, -}) -``` - -### Shared Test Utilities - -The following utilities are available for reuse across test files: - -| Utility | Location | Purpose | -| --------------------------------------- | ------------------------------------ | --------------------------------------------- | -| `createMockChargingStation()` | `ChargingStationTestUtils.ts` | Full test station with OCPP services + mocks | -| `createConnectorStatus()` | `helpers/StationHelpers.ts` | ConnectorStatus factory with defaults | -| `cleanupChargingStation()` | `helpers/StationHelpers.ts` | Proper station cleanup for afterEach | -| `resetChargingStationState()` | `helpers/StationHelpers.ts` | Reset station state between tests | -| `createMockChargingStationTemplate()` | `helpers/StationHelpers.ts` | Minimal charging station template | -| `MockWebSocket` | `mocks/MockWebSocket.ts` | WebSocket simulation with message capture | -| `MockIdTagsCache` | `mocks/MockCaches.ts` | In-memory IdTags cache mock | -| `MockSharedLRUCache` | `mocks/MockCaches.ts` | In-memory LRU cache mock | -| `createStationWithCertificateManager()` | `ocpp/2.0/OCPP20TestUtils.ts` | Station with certificate manager (type-safe) | -| `createMockCertificateManager()` | `ocpp/2.0/OCPP20TestUtils.ts` | Mock certificate manager factory | -| `createTestableOCPP20RequestService()` | `ocpp/2.0/OCPP20TestUtils.ts` | Testable wrapper for OCPP 2.0 request service | -| Auth factories | `ocpp/auth/helpers/MockFactories.ts` | Auth-specific mock creation | -| UI server utilities | `ui-server/UIServerTestUtils.ts` | UI WebSocket server testing utilities | - -**DO NOT duplicate these utilities.** Import and reuse them. +### Fresh Instances Per Test ```typescript -// Good: Import shared utilities -import { createMockChargingStation, cleanupChargingStation } from './ChargingStationTestUtils.js' -import { resetChargingStationState } from './helpers/StationHelpers.js' -``` - -### Mocking Best Practices - -- Use `mock.method()` for function mocking (Node.js native) -- Use `mock.timers` for time-dependent tests -- Keep mocks focused - mock only what's necessary -- Verify mock calls when behavior depends on them +// ✅ Good - Fresh instances in beforeEach +describe('My Test Suite', () => { + let station: ChargingStation -## Test Isolation (CRITICAL) - -**NEVER define mock instances at module level inside describe blocks.** Each test must get fresh instances. - -❌ **Bad (Module-Level State Sharing):** + beforeEach(() => { + const { station: s } = createMockChargingStation() + station = s + }) -```typescript -await describe('My Test Suite', async () => { afterEach(() => { - mock.restoreAll() + standardCleanup() }) - // WRONG: These instances are SHARED across all tests! - const mockResponseService = new OCPP20ResponseService() - const requestService = new OCPP20RequestService(mockResponseService) - const { station: mockChargingStation } = createMockChargingStation({...}) - - await it('test 1', () => { /* uses shared state */ }) - await it('test 2', () => { /* uses same shared state! Test pollution risk! */ }) + it('test 1', () => { + /* clean state */ + }) + it('test 2', () => { + /* clean state */ + }) }) -``` - -✅ **Good (Fresh Instances Per Test):** -```typescript -await describe('My Test Suite', async () => { - let mockResponseService: OCPP20ResponseService - let requestService: OCPP20RequestService - let mockChargingStation: TestChargingStation +// ❌ Bad - Shared state at module level +describe('My Test Suite', () => { + const { station } = createMockChargingStation() // SHARED! - beforeEach(() => { - // Fresh instances for every test - proper isolation - mockResponseService = new OCPP20ResponseService() - requestService = new OCPP20RequestService(mockResponseService) - const { station } = createMockChargingStation({...}) - mockChargingStation = station + it('test 1', () => { + /* polluted state */ }) - - afterEach(() => { - mock.restoreAll() + it('test 2', () => { + /* same polluted state */ }) - - await it('test 1', () => { /* clean state */ }) - await it('test 2', () => { /* clean state */ }) }) ``` -**Why:** Module-level state causes: - -- Test pollution (state leaks between tests) -- Flaky tests (order-dependent results) -- False positives/negatives -- Difficult debugging - -**Exception:** Static constants (strings, numbers, frozen objects) CAN be at module level since they don't change. - -## Cleanup Hooks - -**ALWAYS include `afterEach()` cleanup to prevent test pollution:** +### Mandatory Cleanup ```typescript import { standardCleanup } from '../helpers/TestLifecycleHelpers.js' afterEach(() => { - standardCleanup() - mock.restoreAll() + standardCleanup() // ALWAYS call this }) ``` -### standardCleanup() (MANDATORY) - -The `standardCleanup()` function from `tests/helpers/TestLifecycleHelpers.ts` MUST be called in every test file's `afterEach()` hook. It: - -- Clears shared caches (MockIdTagsCache, MockSharedLRUCache) -- Resets any singleton state -- Ensures test isolation - -### What to Clean Up - -- Mock timers: `mock.timers.reset()` -- Mock functions: `mock.restoreAll()` -- Charging stations: `await chargingStation.stop()` -- File handles, network connections, database connections -- Any global state modifications +`standardCleanup()` clears caches, resets singletons, ensures isolation. -**Missing cleanup causes flaky tests and false positives.** +--- -## Anti-Patterns to Avoid +## 4. Async & Timers -### 1. Inline `as any` Casts - -❌ **Bad:** +### Async/Await (Preferred) ```typescript -;(incomingRequestService as any).handleRequestReset(station, request) +it('should start charging session', async () => { + const { station } = createMockChargingStation() + const result = await station.startTransaction(1, 'VALID_TAG') + expect(result.status).toBe('Accepted') +}) + +it('should reject invalid connector', async () => { + const { station } = createMockChargingStation() + await expect(station.startTransaction(99, 'TAG')).rejects.toThrow('Invalid') +}) ``` -✅ **Good:** +### Mock Timers (Never Use Real Delays) ```typescript -import { createTestableIncomingRequestService } from '../__testable__/index.js' +// ✅ Good - Instant execution +it('should timeout', async t => { + await withMockTimers(t, ['setTimeout'], async () => { + const promise = station.sendHeartbeat() + t.mock.timers.tick(30000) + await expect(promise).rejects.toThrow('Timeout') + }) +}) -const testable = createTestableIncomingRequestService(incomingRequestService) -await testable.handleRequestReset(station, request) +// ❌ Bad - Real delay (slow, flaky) +it('should timeout', async () => { + await new Promise(r => setTimeout(r, 5000)) // NEVER +}) ``` -**Why:** Type safety prevents bugs. Use testable interfaces instead of breaking the type system. +--- -### 2. Duplicate Constants +## 5. Constants & Imports -❌ **Bad:** +### Single Source of Truth ```typescript -// In multiple test files: -const TEST_STATION_NAME = 'CS-TEST-001' -``` - -✅ **Good:** +// ✅ Good - Import from canonical source +import { TEST_CHARGING_STATION_BASE_NAME, TEST_ID_TAG } from '../ChargingStationTestConstants.js' -```typescript -import { TEST_CHARGING_STATION_BASE_NAME } from '../ChargingStationTestConstants.js' +// ❌ Bad - Duplicated constant +const TEST_STATION_NAME = 'CS-TEST-001' ``` -**Why:** Single source of truth. Changes propagate automatically, reduces maintenance burden. - -### 3. Missing Cleanup +Available constants: `tests/charging-station/ChargingStationTestConstants.ts` -❌ **Bad:** +--- -```typescript -describe('Tests', () => { - it('test 1', () => { - /* ... */ - }) - it('test 2', () => { - /* ... */ - }) - // No afterEach cleanup! -}) -``` +## 6. Assertions -✅ **Good:** +### Strict Only ```typescript -describe('Tests', () => { - afterEach(() => { - mock.restoreAll() - // Clean up resources - }) +// ✅ Good +expect(result).toStrictEqual({ status: 'ok' }) // Exact match +expect(count).toBe(5) // Primitive +expect(value).toBe(true) // Explicit boolean +expect(item).toBeDefined() // Existence check - it('test 1', () => { - /* ... */ - }) - it('test 2', () => { - /* ... */ - }) -}) +// ❌ Bad +expect(result).toEqual({ status: 'ok' }) // Ignores extra properties +expect(value).toBeTruthy() // Too vague +expect(count == '5').toBe(true) // Type coercion ``` -**Why:** Test isolation. Each test should run independently without side effects. +--- -### 4. Probabilistic Assertions +## 7. Type Safety -❌ **Bad:** +### No `as any` Casts ```typescript -const successRate = calculateSuccessRate() -expect(successRate).toBeGreaterThan(50) // Flaky! -``` +// ✅ Good - Use testable interfaces +const testable = createTestableOCPP20RequestService(requestService) +testable.buildRequestPayload(station, command) -✅ **Good:** - -```typescript -const result = await authenticateUser(mockCredentials) -expect(result.success).toBe(true) -expect(result.token).toBeDefined() +// ❌ Bad - Breaks type safety +;(requestService as any).buildRequestPayload(station, command) ``` -**Why:** Tests must be deterministic. Use mocks to control behavior, not probabilistic thresholds. - -### 5. Over-Use of `eslint-disable` - -❌ **Bad:** +### Exception: Runtime Type Validation Tests ```typescript -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-explicit-any */ +// Acceptable when testing defensive code +// eslint-disable-next-line @typescript-eslint/no-explicit-any +expect(AuthValidators.isValidIdentifierValue(123 as any)).toBe(false) ``` -✅ **Good:** +--- -```typescript -// Use proper types and testable interfaces - no disables needed -``` +## 8. Mock Factories -**Why:** Disabling linting rules hides real problems. Fix the underlying type issues instead. +### Choose the Right Factory -**Exception - Legitimate Uses of eslint-disable:** +| Factory | Use Case | Location | +| ---------------------------------------- | -------------------------------- | ------------------------------------ | +| `createMockChargingStation()` | Full OCPP protocol testing | `ChargingStationTestUtils.ts` | +| `createMockAuthServiceTestStation()` | Auth service tests (lightweight) | `ocpp/auth/helpers/MockFactories.ts` | +| `createMockStationWithRequestTracking()` | Verify sent OCPP requests | `ocpp/2.0/OCPP20TestUtils.ts` | -Some eslint-disable comments are acceptable when testing defensive code that validates inputs at runtime: +### Usage ```typescript -// Testing that validators handle invalid types gracefully -// This is legitimate because the function is designed to handle runtime type errors -await it('should return false for non-string input', () => { - // Testing runtime type validation - intentionally passing wrong type - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any - expect(AuthValidators.isValidIdentifierValue(123 as any)).toBe(false) +// Full station with mocks +const { station, mocks } = createMockChargingStation({ + connectorsCount: 2, + ocppVersion: OCPPVersion.VERSION_20, }) -// Testing async function detection requires empty function expressions -// eslint-disable-next-line @typescript-eslint/no-empty-function -expect(isAsyncFunction(() => {})).toBe(false) +// Verify sent messages +expect(mocks.webSocket.sentMessages).toContain(expectedMessage) ``` -**Acceptable Rules to Disable (with justification):** +--- -- `@typescript-eslint/no-empty-function` - When testing function type detection -- `@typescript-eslint/no-explicit-any` - When testing runtime type validation -- `@typescript-eslint/unbound-method` - When testing method type detection -- `@cspell/spellchecker` - For intentional misspellings in test data +## 9. Utility Reference -**Still NOT Acceptable:** +### Lifecycle Helpers (`helpers/TestLifecycleHelpers.ts`) -- File-level disables (`/* eslint-disable ... */` at top of file) -- Disabling rules to bypass type safety in test setup -- Disabling rules because proper interfaces haven't been created +| Utility | Purpose | +| --------------------------------- | ------------------------------------ | +| `standardCleanup()` | **MANDATORY** afterEach cleanup | +| `withMockTimers()` | Execute test with timer mocking | +| `createTimerScope()` | Manual timer control | +| `setupConnectorWithTransaction()` | Setup connector in transaction state | +| `clearConnectorTransaction()` | Clear connector transaction state | -### 6. Non-Strict Assertions +### Mock Classes (`mocks/`) -❌ **Bad:** +| Class | Purpose | +| -------------------- | ----------------------------------------- | +| `MockWebSocket` | WebSocket simulation with message capture | +| `MockIdTagsCache` | In-memory IdTags cache | +| `MockSharedLRUCache` | In-memory LRU cache | -```typescript -// Loose equality - can cause false positives -expect(result).toEqual({ status: 'ok' }) // Ignores extra properties -expect(count == '5').toBe(true) // Type coercion -expect(value).toBeTruthy() // Too vague -``` +### OCPP 2.0 (`ocpp/2.0/OCPP20TestUtils.ts`) -✅ **Good:** +| Utility | Purpose | +| -------------------------------------- | ------------------------------- | +| `createTestableOCPP20RequestService()` | Type-safe private method access | +| `createMockCertificateManager()` | Certificate operations mock | +| `IdTokenFixtures` | Pre-built IdToken fixtures | +| `TransactionContextFixtures` | Transaction context fixtures | -```typescript -// Strict equality - catches more bugs -expect(result).toStrictEqual({ status: 'ok' }) // Exact match -expect(count).toBe(5) // Type-safe -expect(value).toBe(true) // Explicit -``` +### Auth (`ocpp/auth/helpers/MockFactories.ts`) -**Why:** Strict assertions catch type mismatches and unexpected properties. Use `toBe()` for primitives, `toStrictEqual()` for objects. +| Utility | Purpose | +| --------------------------------- | --------------------------- | +| `createMockIdentifier()` | UnifiedIdentifier factory | +| `createMockAuthRequest()` | AuthRequest factory | +| `createMockAuthorizationResult()` | AuthorizationResult factory | +| `expectAcceptedAuthorization()` | Assert accepted result | -## Summary +--- -- **Name clearly**: Descriptive names for files, suites, and test cases -- **Structure with AAA**: Arrange, Act, Assert -- **Document minimally**: JSDoc headers required, inline comments only when necessary -- **Use canonical constants**: Single source of truth -- **Leverage mock factories**: Centralized, reusable mocks -- **Clean up always**: `afterEach()` hooks prevent test pollution -- **Avoid anti-patterns**: No `as any`, no duplication, no probabilistic tests +## Summary -Following these guidelines ensures tests are maintainable, reliable, and easy to understand. +1. **Name**: `should [verb]` lowercase +2. **Structure**: AAA pattern, JSDoc headers +3. **Isolate**: Fresh instances in `beforeEach`, `standardCleanup()` in `afterEach` +4. **Async**: Use `async/await`, mock timers +5. **Constants**: Import from `ChargingStationTestConstants.ts` +6. **Assert**: Strict only (`toBe`, `toStrictEqual`) +7. **Types**: No `as any`, use testable interfaces diff --git a/tests/charging-station/ChargingStation-Transactions.test.ts b/tests/charging-station/ChargingStation-Transactions.test.ts index b69eed44..20e2ae57 100644 --- a/tests/charging-station/ChargingStation-Transactions.test.ts +++ b/tests/charging-station/ChargingStation-Transactions.test.ts @@ -8,6 +8,7 @@ import { afterEach, beforeEach, describe, it } from 'node:test' import type { ChargingStation } from '../../src/charging-station/ChargingStation.js' import { standardCleanup, withMockTimers } from '../helpers/TestLifecycleHelpers.js' +import { TEST_ID_TAG } from './ChargingStationTestConstants.js' import { cleanupChargingStation, createMockChargingStation } from './ChargingStationTestUtils.js' await describe('ChargingStation Transaction Management', async () => { @@ -44,7 +45,7 @@ await describe('ChargingStation Transaction Management', async () => { if (connector1 != null) { connector1.transactionStarted = true connector1.transactionId = 100 - connector1.transactionIdTag = 'TEST-TAG-001' + connector1.transactionIdTag = TEST_ID_TAG } // Act @@ -98,7 +99,7 @@ await describe('ChargingStation Transaction Management', async () => { if (connector1 != null) { connector1.transactionStarted = true connector1.transactionId = 200 - connector1.transactionIdTag = 'TEST-TAG-002' + connector1.transactionIdTag = TEST_ID_TAG } // Act diff --git a/tests/charging-station/ChargingStation.test.ts b/tests/charging-station/ChargingStation.test.ts index e0b7557a..47f09ac9 100644 --- a/tests/charging-station/ChargingStation.test.ts +++ b/tests/charging-station/ChargingStation.test.ts @@ -15,6 +15,11 @@ import type { ChargingStation } from '../../src/charging-station/ChargingStation import { RegistrationStatusEnumType } from '../../src/types/index.js' import { standardCleanup } from '../helpers/TestLifecycleHelpers.js' +import { + TEST_ID_TAG, + TEST_TRANSACTION_ENERGY_WH, + TEST_TRANSACTION_ID, +} from './ChargingStationTestConstants.js' import { cleanupChargingStation, createMockChargingStation, @@ -133,15 +138,17 @@ await describe('ChargingStation Integration Tests', async () => { const connector1 = station.getConnectorStatus(1) if (connector1 != null) { connector1.transactionStarted = true - connector1.transactionId = 1 - connector1.transactionIdTag = 'TEST-TAG' - connector1.transactionEnergyActiveImportRegisterValue = 5000 + connector1.transactionId = TEST_TRANSACTION_ID + connector1.transactionIdTag = TEST_ID_TAG + connector1.transactionEnergyActiveImportRegisterValue = TEST_TRANSACTION_ENERGY_WH } // Verify transaction expect(station.getNumberOfRunningTransactions()).toBe(1) - expect(station.getTransactionIdTag(1)).toBe('TEST-TAG') - expect(station.getEnergyActiveImportRegisterByTransactionId(1)).toBe(5000) + expect(station.getTransactionIdTag(TEST_TRANSACTION_ID)).toBe(TEST_ID_TAG) + expect(station.getEnergyActiveImportRegisterByTransactionId(TEST_TRANSACTION_ID)).toBe( + TEST_TRANSACTION_ENERGY_WH + ) // Stop station await station.stop() diff --git a/tests/charging-station/ChargingStationTestConstants.ts b/tests/charging-station/ChargingStationTestConstants.ts index b801a233..08608e44 100644 --- a/tests/charging-station/ChargingStationTestConstants.ts +++ b/tests/charging-station/ChargingStationTestConstants.ts @@ -44,3 +44,27 @@ export const TEST_STATUS_CHARGE_POINT_VENDOR = 'Test Status Vendor' * Test values for connector addressing */ export const TEST_CONNECTOR_ID_VALID_INSTANCE = '1' + +/** + * Test ID Tags + * Test values for authentication and authorization + */ +export const TEST_ID_TAG = 'TEST-TAG-001' +export const TEST_ID_TAG_VALID = 'VALID_TAG' +export const TEST_ID_TAG_INVALID = 'INVALID_TAG' +export const TEST_ID_TAG_BLOCKED = 'BLOCKED_TAG' + +/** + * Test Transaction Values + * Test values for transaction-related operations + */ +export const TEST_TRANSACTION_ID = 1 +export const TEST_TRANSACTION_ENERGY_WH = 5000 + +/** + * Test Token Values (OCPP 2.0) + * Test values for IdToken-based authentication + */ +export const TEST_TOKEN_ISO14443 = 'TEST_RFID_TOKEN_001' +export const TEST_TOKEN_EMAID = 'DE*ABC*E123456*1' +export const TEST_TOKEN_CENTRAL = 'CENTRAL_TOKEN_001' diff --git a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-RequestStartTransaction.test.ts b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-RequestStartTransaction.test.ts index 80c724d2..85db043b 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-RequestStartTransaction.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-RequestStartTransaction.test.ts @@ -364,7 +364,7 @@ await describe('F01 & F02 - Remote Start Transaction', async () => { // Verify transactionId is a string (UUID format in OCPP 2.0) expect(typeof response.transactionId).toBe('string') - expect(response.transactionId).toBeTruthy() + expect(response.transactionId).toBeDefined() expect(response.transactionId?.length).toBeGreaterThan(0) }) })