MeterValueUnit,
type SampledValue,
} from './ocpp/MeterValues.js'
+export { OCPPProtocol } from './ocpp/OCPPProtocol.js'
export { OCPPVersion } from './ocpp/OCPPVersion.js'
export {
AvailabilityType,
type AutomaticTransactionGeneratorConfiguration,
type ChargingStationInfo,
CurrentType,
+ OCPPProtocol,
OCPPVersion,
VendorParametersKey,
} from '../types/index.js'
static readonly DEFAULT_STATION_INFO: Readonly<Partial<ChargingStationInfo>> = Object.freeze({
automaticTransactionGeneratorPersistentConfiguration: true,
autoReconnectMaxRetries: -1,
+ autoRegister: false,
autoStart: true,
beginEndMeterValues: false,
currentOutType: CurrentType.AC,
mainVoltageMeterValues: true,
meteringPerTransaction: true,
ocppPersistentConfiguration: true,
+ ocppProtocol: OCPPProtocol.JSON,
ocppStrictCompliance: true,
ocppVersion: OCPPVersion.VERSION_16,
outOfOrderEndMeterValues: false,
parsing) MUST build a real station from a template file via
`helpers/StationHelpers.realStation.ts` — never re-implement the temp-dir scaffolding:
-| Helper | Purpose |
-| ----------------------------- | ----------------------------------------------------------- |
-| `writeStationTemplate(obj)` | Write an inline template object into an isolated temp dir |
-| `copyStationTemplate(ovr?)` | Copy a bundled asset template, optionally merging overrides |
-| `createStationFromTemplate()` | Construct a real `ChargingStation` from a template file |
-| `cleanupStationTemplates()` | Remove temp template dirs (call in `afterEach`) |
+| Helper | Purpose |
+| ----------------------------------------- | ----------------------------------------------------------- |
+| `writeStationTemplate(obj)` | Write an inline template object into an isolated temp dir |
+| `copyStationTemplate(ovr?)` | Copy a bundled asset template, optionally merging overrides |
+| `cleanupStationTemplates()` | Remove temp template dirs (call in `afterEach`) |
+| `createStationFromTemplate()` | Construct a real `ChargingStation` from a template file |
+| `persistedConfigurationDir(file)` | Resolve the sibling `configurations` dir of a template file |
+| `resolvePersistedConfigurationFile(file)` | Resolve the persisted config file (throws if none yet) |
```typescript
afterEach(() => {
--- /dev/null
+/**
+ * @file Tests for `autoRegister` and `ocppProtocol` default seeding into `stationInfo`.
+ * @description Both fields are static defaults with no derivation, so they live in
+ * `DEFAULT_STATION_INFO` (`autoRegister: false`, `ocppProtocol: OCPPProtocol.JSON`)
+ * and are applied by `getStationInfo` via `mergeDeepRight(DEFAULT_STATION_INFO, stationInfo)`.
+ * A template omitting them must yield the seeded values in `stationInfo` and in the UI
+ * data payload (`buildAddedMessage`) instead of `undefined` (the empty Web UI placeholder).
+ * An explicit template value is preserved (idempotent, no clobber), and a persisted
+ * configuration predating the fields is backfilled on reload. `ocppProtocol` preservation
+ * is not tested: the enum has a single value, so an explicit value is indistinguishable
+ * from the default (non-discriminant).
+ */
+import assert from 'node:assert/strict'
+import { readFileSync, writeFileSync } from 'node:fs'
+import { afterEach, describe, it } from 'node:test'
+
+import type { ChargingStation } from '../../src/charging-station/ChargingStation.js'
+
+import { OCPPProtocol } from '../../src/types/index.js'
+import { buildAddedMessage } from '../../src/utils/MessageChannelUtils.js'
+import { flushMicrotasks, standardCleanup } from '../helpers/TestLifecycleHelpers.js'
+import {
+ cleanupStationTemplates,
+ createStationFromTemplate,
+ resolvePersistedConfigurationFile,
+ writeStationTemplate,
+} from './helpers/StationHelpers.realStation.js'
+
+const POWER_W = 22000
+
+interface TemplateOverrides {
+ autoRegister?: boolean
+}
+
+const buildTemplate = (overrides: TemplateOverrides = {}): Record<string, unknown> => ({
+ $schemaVersion: 1,
+ baseName: 'TEST-AUTO-REGISTER-OCPP-PROTOCOL',
+ chargePointModel: 'Simulator simple',
+ chargePointVendor: 'Simulator',
+ Connectors: {
+ 0: {},
+ 1: { bootStatus: 'Available' },
+ },
+ currentOutType: 'AC',
+ numberOfConnectors: 1,
+ power: POWER_W,
+ powerUnit: 'W',
+ randomConnectors: false,
+ ...(overrides.autoRegister != null ? { autoRegister: overrides.autoRegister } : {}),
+})
+
+const makeStation = (templateFile: string, persistentConfiguration = false): ChargingStation =>
+ createStationFromTemplate(templateFile, {
+ baseName: 'TEST-AUTO-REGISTER-OCPP-PROTOCOL',
+ fixedName: true,
+ persistentConfiguration,
+ })
+
+const newStation = (overrides: TemplateOverrides = {}): ChargingStation =>
+ makeStation(writeStationTemplate(buildTemplate(overrides)))
+
+await describe('ChargingStation autoRegister/ocppProtocol seeding', async () => {
+ afterEach(() => {
+ standardCleanup()
+ cleanupStationTemplates()
+ })
+
+ await it('should seed autoRegister to false for a template omitting the field', () => {
+ const station = newStation()
+ assert.strictEqual(station.stationInfo?.autoRegister, false)
+ })
+
+ await it('should seed ocppProtocol to json for a template omitting the field', () => {
+ const station = newStation()
+ assert.strictEqual(station.stationInfo?.ocppProtocol, OCPPProtocol.JSON)
+ })
+
+ await it('should transmit the seeded defaults in the UI data payload', () => {
+ const station = newStation()
+ const { stationInfo } = buildAddedMessage(station).data
+ assert.strictEqual(stationInfo.autoRegister, false)
+ assert.strictEqual(stationInfo.ocppProtocol, OCPPProtocol.JSON)
+ })
+
+ await it('should preserve an explicit autoRegister template value', () => {
+ const station = newStation({ autoRegister: true })
+ assert.strictEqual(station.stationInfo?.autoRegister, true)
+ })
+
+ await it('should backfill the defaults into a persisted config that predates the fields', async () => {
+ const templateFile = writeStationTemplate(buildTemplate())
+ // The persisted config write runs under an async lock; flush before reading it back.
+ makeStation(templateFile, true)
+ await flushMicrotasks()
+ // Simulate a legacy configuration written before the fields were seeded.
+ const configurationFile = resolvePersistedConfigurationFile(templateFile)
+ const configuration = JSON.parse(readFileSync(configurationFile, 'utf8')) as {
+ stationInfo: { autoRegister?: boolean; ocppProtocol?: string }
+ }
+ assert.strictEqual(configuration.stationInfo.autoRegister, false)
+ assert.strictEqual(configuration.stationInfo.ocppProtocol, OCPPProtocol.JSON)
+ delete configuration.stationInfo.autoRegister
+ delete configuration.stationInfo.ocppProtocol
+ writeFileSync(configurationFile, JSON.stringify(configuration), 'utf8')
+ // A fresh station starts with an empty configurationFileHash, so getConfigurationFromFile
+ // bypasses the shared cache and reads the file from disk; the file-sourced stationInfo
+ // is backfilled by mergeDeepRight(DEFAULT_STATION_INFO, stationInfo) in getStationInfo.
+ const reloaded = makeStation(templateFile, true)
+ assert.ok(reloaded.stationInfo)
+ assert.strictEqual(reloaded.stationInfo.autoRegister, false)
+ assert.strictEqual(reloaded.stationInfo.ocppProtocol, OCPPProtocol.JSON)
+ })
+})
* backfilled on reload.
*/
import assert from 'node:assert/strict'
-import { readdirSync, readFileSync, writeFileSync } from 'node:fs'
-import { dirname, join } from 'node:path'
+import { readFileSync, writeFileSync } from 'node:fs'
import { afterEach, describe, it } from 'node:test'
import type { ChargingStation } from '../../src/charging-station/ChargingStation.js'
import {
cleanupStationTemplates,
createStationFromTemplate,
+ resolvePersistedConfigurationFile,
writeStationTemplate,
} from './helpers/StationHelpers.realStation.js'
makeStation(templateFile, true)
await flushMicrotasks()
// Simulate a legacy configuration written before numberOfPhases was seeded.
- const configurationDir = join(dirname(dirname(templateFile)), 'configurations')
- const configurationFile = join(
- configurationDir,
- readdirSync(configurationDir).find(entry => entry.endsWith('.json')) ?? ''
- )
+ const configurationFile = resolvePersistedConfigurationFile(templateFile)
const configuration = JSON.parse(readFileSync(configurationFile, 'utf8')) as {
stationInfo: { numberOfPhases?: number }
}
*/
import assert from 'node:assert/strict'
import { existsSync, readdirSync, readFileSync } from 'node:fs'
-import { dirname, join } from 'node:path'
+import { join } from 'node:path'
import { afterEach, describe, it } from 'node:test'
import { setTimeout as sleep } from 'node:timers/promises'
cleanupStationTemplates,
copyStationTemplate,
createStationFromTemplate,
+ persistedConfigurationDir,
} from './helpers/StationHelpers.realStation.js'
// The identity logic lives in initialize(); the identity tests call it directly
templateFile: string,
chargingStationId: string
): Promise<boolean> => {
- const configurationsDir = dirname(templateFile.replace('station-templates', 'configurations'))
+ const configurationsDir = persistedConfigurationDir(templateFile)
for (let attempt = 0; attempt < 100; attempt++) {
if (existsSync(configurationsDir)) {
for (const file of readdirSync(configurationsDir)) {
* @file Helpers to build a real ChargingStation from a template file.
* @description Tests that exercise the real construction pipeline
* (`initialize()`/`getStationInfo()`, persistence, reset, reconnect) cannot use
- * the `createMockChargingStation` stub, which bypasses it. These helpers write a
- * template into an isolated temp `station-templates` dir and construct a real
- * `ChargingStation` from it, tracking temp roots for a single `afterEach`
- * cleanup. Two template sources are supported: an inline object
- * (`writeStationTemplate`) and a bundled asset (`copyStationTemplate`).
+ * the `createMockChargingStation` stub, which bypasses it. Grouped by concern:
+ * (1) temp `station-templates` dir lifecycle — write an inline template
+ * (`writeStationTemplate`) or copy a bundled asset (`copyStationTemplate`),
+ * removed by a single `afterEach` `cleanupStationTemplates`; (2) real
+ * `ChargingStation` construction (`createStationFromTemplate`); (3) resolution of
+ * the persisted configuration the station writes (`persistedConfigurationDir`,
+ * `resolvePersistedConfigurationFile`).
*/
-import { copyFileSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
+import {
+ copyFileSync,
+ mkdirSync,
+ mkdtempSync,
+ readdirSync,
+ readFileSync,
+ rmSync,
+ writeFileSync,
+} from 'node:fs'
import { tmpdir } from 'node:os'
-import { join } from 'node:path'
+import { dirname, join } from 'node:path'
import type { ChargingStationOptions } from '../../../src/types/index.js'
return root
}
+// ---------------------------------------------------------------
+// Temp template dir lifecycle
+// ---------------------------------------------------------------
+
/**
* Writes an inline template object into a fresh isolated `station-templates` dir.
* @param template - Template object to serialize.
return file
}
+/**
+ * Removes every temp template dir created by `writeStationTemplate`/`copyStationTemplate`.
+ * Call in `afterEach`, alongside `standardCleanup()`.
+ */
+export const cleanupStationTemplates = (): void => {
+ for (const root of templateRoots.splice(0)) {
+ rmSync(root, { force: true, recursive: true })
+ }
+}
+
+// ---------------------------------------------------------------
+// Real station construction
+// ---------------------------------------------------------------
+
/**
* Constructs a real ChargingStation from a template file with test defaults
* (`autoStart` off, local supervision URL); caller options take precedence.
...options,
})
+// ---------------------------------------------------------------
+// Persisted configuration resolution
+// ---------------------------------------------------------------
+
/**
- * Removes every temp template dir created by `writeStationTemplate`/`copyStationTemplate`.
- * Call in `afterEach`, alongside `standardCleanup()`.
+ * Resolves the isolated `configurations` dir sitting beside the
+ * `station-templates` dir of a template file — where a persisted
+ * ChargingStation writes its configuration.
+ * @param templateFile - Path returned by `writeStationTemplate`/`copyStationTemplate`.
+ * @returns Absolute path to the sibling `configurations` dir.
*/
-export const cleanupStationTemplates = (): void => {
- for (const root of templateRoots.splice(0)) {
- rmSync(root, { force: true, recursive: true })
+export const persistedConfigurationDir = (templateFile: string): string =>
+ join(dirname(dirname(templateFile)), 'configurations')
+
+/**
+ * Resolves the single persisted configuration file written by a station built
+ * from `templateFile`. Throws when none exists yet (flush the async write first).
+ * @param templateFile - Path returned by `writeStationTemplate`/`copyStationTemplate`.
+ * @returns Absolute path to the persisted configuration `.json` file.
+ */
+export const resolvePersistedConfigurationFile = (templateFile: string): string => {
+ const configurationDir = persistedConfigurationDir(templateFile)
+ const configurationFile = readdirSync(configurationDir).find(entry => entry.endsWith('.json'))
+ if (configurationFile == null) {
+ throw new Error(`No persisted configuration file found in ${configurationDir}`)
}
+ return join(configurationDir, configurationFile)
}