]> Piment Noir Git Repositories - e-mobility-charging-stations-simulator.git/commitdiff
refactor(tests): restructure TEST_STYLE_GUIDE and use centralized constants
authorJérôme Benoit <jerome.benoit@sap.com>
Sat, 28 Feb 2026 22:39:20 +0000 (23:39 +0100)
committerJérôme Benoit <jerome.benoit@sap.com>
Sat, 28 Feb 2026 22:39:20 +0000 (23:39 +0100)
- 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

tests/TEST_STYLE_GUIDE.md
tests/charging-station/ChargingStation-Transactions.test.ts
tests/charging-station/ChargingStation.test.ts
tests/charging-station/ChargingStationTestConstants.ts
tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-RequestStartTransaction.test.ts

index 8dbfb6d94c8925df9a70ffad3bbd3442913d5db6..ec7bec7c0ce35a73e3a831286f13f8321edb6124 100644 (file)
@@ -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
index b69eed44a21358a76a31cb76450cd9dbfcad07a5..20e2ae575149cd7457549b3ee5d063a64fcd5554 100644 (file)
@@ -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
index e0b7557a223c994a8cfb8735daaef4f63512038b..47f09ac95874e88c6d0ede8f026e420bf13f2312 100644 (file)
@@ -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()
index b801a23350ff6624de6e102912dbccbd11dc5172..08608e4464e7f17333c75d4fbdf1f3a5911f0758 100644 (file)
@@ -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'
index 80c724d27e545360f05f106dec312930fe8cb089..85db043b7f4fa5afc30e1b1b4f3fcf9c4cc9d86e 100644 (file)
@@ -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)
   })
 })