Convention alignment refactor across the monorepo — no behavior change.
Helpers adoption:
- adopt isEmpty() helper for emptiness checks (9 sites); OCPPServiceUtils.ts:1113 preserved with anchor comment (already-trimmed input; isEmpty would redundantly re-trim)
- adopt Constants.UNIT_DIVIDER_KILO for kW→W conversion (4 sites)
- use JSONStringify for unknown MCP tool payload (Map/Set-safe replacer)
Constants promotion:
- promote inline literals to named constants (MAX_ITEMS_PER_REPORT_MESSAGE with OCPP 2.0.1 §2.1.16/§2.1.18 spec-anchor comment; AVG_ENTRY_SIZE_BYTES; DEFAULT_EXPONENTIAL_BACKOFF_BASE_DELAY_MS; DEFAULT_AUTH_CACHE_TTL_SECONDS; DEFAULT_PERSIST_STATE)
- coverage gap: isNotEmptyArray adoption at ConfigurationValidation.ts:124
Imports discipline:
- add .js extension to relative import in ui-web/vitest.config (ESM/NodeNext)
- use component barrels instead of deep imports in tests (10 sites)
Test literals:
- use DEFAULT_HOST/DEFAULT_PORT constants in ui-common tests (72 sites: 32 in config.test.ts + 40 in WebSocketClient.test.ts)
Documentation anchors:
- document Number.parseInt/parseFloat convention exceptions (3 comments covering 4 call sites where convertToInt/convertToFloat cannot substitute)
Quality gates: typecheck clean, lint clean, 2954/2960 tests pass, 0 fail.
Diff: +140/-113 across 28 files.
}
return this.stationInfo?.reconnectExponentialDelay === true
? computeExponentialBackOffDelay({
- baseDelayMs: 100,
+ baseDelayMs: Constants.DEFAULT_EXPONENTIAL_BACKOFF_BASE_DELAY_MS,
jitterPercent: 0.2,
retryNumber: this.wsConnectionRetryCount,
})
const powerArrayRandomIndex = Math.floor(secureRandom() * stationTemplate.power.length)
stationInfo.maximumPower =
stationTemplate.powerUnit === PowerUnits.KILO_WATT
- ? stationTemplate.power[powerArrayRandomIndex] * 1000
+ ? stationTemplate.power[powerArrayRandomIndex] * Constants.UNIT_DIVIDER_KILO
: stationTemplate.power[powerArrayRandomIndex]
} else if (typeof stationTemplate.power === 'number') {
stationInfo.maximumPower =
stationTemplate.powerUnit === PowerUnits.KILO_WATT
- ? stationTemplate.power * 1000
+ ? stationTemplate.power * Constants.UNIT_DIVIDER_KILO
: stationTemplate.power
}
stationInfo.maximumAmperage = this.getMaximumAmperage(stationInfo)
'hex'
)
const connectorsConfigChanged =
- this.connectors.size !== 0 && this.connectorsConfigurationHash !== connectorsConfigHash
+ !isEmpty(this.connectors) && this.connectorsConfigurationHash !== connectorsConfigHash
if (isEmpty(this.connectors) || connectorsConfigChanged) {
connectorsConfigChanged && this.connectors.clear()
this.connectorsConfigurationHash = connectorsConfigHash
'hex'
)
const evsesConfigChanged =
- this.evses.size !== 0 && this.evsesConfigurationHash !== evsesConfigHash
+ !isEmpty(this.evses) && this.evsesConfigurationHash !== evsesConfigHash
if (isEmpty(this.evses) || evsesConfigChanged) {
evsesConfigChanged && this.evses.clear()
this.evsesConfigurationHash = evsesConfigHash
// eslint-disable-next-line promise/no-promise-in-callback
sleep(
computeExponentialBackOffDelay({
- baseDelayMs: 100,
+ baseDelayMs: Constants.DEFAULT_EXPONENTIAL_BACKOFF_BASE_DELAY_MS,
jitterPercent: 0.2,
retryNumber: messageIdx ?? 0,
})
import type { ChargingStation } from './ChargingStation.js'
import { BaseError } from '../exception/index.js'
-import { logger } from '../utils/index.js'
+import { isEmpty, logger } from '../utils/index.js'
import { getEvProfilesFile } from './Helpers.js'
import { disposeCoherentSessionRuntime } from './meter-values/CoherentSampleComputer.js'
import {
if (this.chargingStation.stationInfo?.coherentMeterValues !== true) {
return undefined
}
- if (this.evProfiles == null || this.evProfiles.profiles.length === 0) {
+ if (this.evProfiles == null || isEmpty(this.evProfiles.profiles)) {
return undefined
}
const session = createCoherentSession(this.chargingStation, {
PowerUnits,
Voltage,
} from '../types/index.js'
-import { clone, isEmpty, isNotEmptyArray, logger, secureRandom } from '../utils/index.js'
+import { clone, Constants, isEmpty, isNotEmptyArray, logger, secureRandom } from '../utils/index.js'
const moduleName = 'HelpersConfig'
const powerArrayRandomIndex = Math.floor(secureRandom() * stationTemplate.power.length)
maximumPower =
stationTemplate.powerUnit === PowerUnits.KILO_WATT
- ? stationTemplate.power[powerArrayRandomIndex] * 1000
+ ? stationTemplate.power[powerArrayRandomIndex] * Constants.UNIT_DIVIDER_KILO
: stationTemplate.power[powerArrayRandomIndex]
} else if (typeof stationTemplate.power === 'number') {
maximumPower =
stationTemplate.powerUnit === PowerUnits.KILO_WATT
- ? stationTemplate.power * 1000
+ ? stationTemplate.power * Constants.UNIT_DIVIDER_KILO
: stationTemplate.power
}
if (maximumPower == null) {
MeterValuePhase,
MeterValueUnit,
} from '../../types/index.js'
-import { Constants, isNotEmptyArray, logger, roundTo } from '../../utils/index.js'
+import { Constants, isEmpty, isNotEmptyArray, logger, roundTo } from '../../utils/index.js'
import {
advanceEnergyRegister,
computeCoherentSample,
const surviving: SampledValueTemplate[] = []
for (const family of Map.groupBy(bucket, templateFamilyKey).values()) {
const perPhaseLN = family.filter(isLineToNeutralTemplate)
- if (perPhaseLN.length === 0) {
+ if (isEmpty(perPhaseLN)) {
surviving.push(...family)
continue
}
import type { CoherentSession, EvProfile, ICoherentContext } from './types.js'
import { CurrentType, Voltage } from '../../types/index.js'
-import { Constants, logger } from '../../utils/index.js'
+import { Constants, isEmpty, logger } from '../../utils/index.js'
import { selectEvProfile } from './EvProfiles.js'
import { createStreamPrng, hashLabel } from './PRNG.js'
context: ICoherentContext,
options: CreateSessionOptions
): CoherentSession | undefined => {
- if (options.profiles.length === 0) {
+ if (isEmpty(options.profiles)) {
return undefined
}
const now = options.now ?? Date.now()
import { z } from 'zod'
import { BaseError } from '../../exception/index.js'
-import { getErrorMessage, logger } from '../../utils/index.js'
+import { getErrorMessage, isEmpty, logger } from '../../utils/index.js'
import { type EvProfile, type EvProfilesFile, EvProfilesFileSchema } from './types.js'
const moduleName = 'EvProfiles'
curve: { powerFraction: number; socPercent: number }[],
socPercent: number
): number => {
- if (curve.length === 0) {
+ if (isEmpty(curve)) {
return 1
}
if (process.env.NODE_ENV !== 'production') {
const pemCertificates = pem.match(
/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g
)
- if (pemCertificates == null || pemCertificates.length === 0) {
+ if (pemCertificates == null || isEmpty(pemCertificates)) {
return { reason: 'No PEM certificate found', valid: false }
}
const moduleName = 'OCPP20IncomingRequestService'
+// GetBaseReport response chunking: conservative project default.
+// The actual per-device limits are reported at runtime by the Charging Station via
+// OCPP 2.0.1 §2.1.16 DeviceDataCtrlr.ItemsPerMessage(instance:GetReport) and
+// §2.1.18 DeviceDataCtrlr.BytesPerMessage(instance:GetReport); both are ReadOnly
+// device capability declarations with no spec-defined ceiling.
+const MAX_ITEMS_PER_REPORT_MESSAGE = 100 as const
+
interface StationInfoReportField {
property: 'chargePointModel' | 'chargePointSerialNumber' | 'chargePointVendor' | 'firmwareVersion'
variable: OCPP20DeviceInfoVariableName
meterValues.push(meterValue)
}
}
- if (meterValues.length === 0) {
+ if (isEmpty(meterValues)) {
meterValues.push({
sampledValue: [
{
const cached = stationState.reportDataCache.get(requestId)
const reportData = cached ?? this.buildReportData(chargingStation, reportBase)
- const maxItemsPerMessage = 100
const chunks = []
- for (let i = 0; i < reportData.length; i += maxItemsPerMessage) {
- chunks.push(reportData.slice(i, i + maxItemsPerMessage))
+ for (let i = 0; i < reportData.length; i += MAX_ITEMS_PER_REPORT_MESSAGE) {
+ chunks.push(reportData.slice(i, i + MAX_ITEMS_PER_REPORT_MESSAGE))
}
if (!isNotEmptyArray(chunks)) {
const enabled = new Set<MeterValueMeasurand>()
for (const entry of rawValue.split(',')) {
const trimmed = entry.trim()
+ // Kept as `.length === 0`: entry is already trimmed above; `isEmpty()` here would be
+ // semantically identical (its string branch is `value.trim().length === 0`) — direct
+ // check avoids a redundant re-trim.
if (trimmed.length === 0) {
continue
}
},
...options,
}
+ // Number.parseFloat preserved: NaN sentinel drives the POSITIVE_INFINITY fallback below;
+ // convertToFloat throws on NaN and would break the fallback branch.
const parsedValue = Number.parseFloat(value ?? '')
if (options.limitationEnabled) {
return max(
const moduleName = 'InMemoryAuthCache'
+// Rough heap-footprint estimate per cached entry — not measured; used only for stats display.
+const AVG_ENTRY_SIZE_BYTES = 500 as const
+
/**
* Cached authorization entry with expiration
*/
const hitRate = totalAccess > 0 ? (this.stats.hits / totalAccess) * 100 : 0
// Calculate memory usage estimate
- const avgEntrySize = 500 // Rough estimate: 500 bytes per entry
- const memoryUsage = this.cache.size * avgEntrySize
+ const memoryUsage = this.cache.size * AVG_ENTRY_SIZE_BYTES
// Clean expired rate limit entries
this.cleanupExpiredRateLimits()
allowOfflineTxForUnknownId: false,
authKeyManagementEnabled: false,
authorizationCacheEnabled: true,
- authorizationCacheLifetime: 3600,
+ authorizationCacheLifetime: Constants.DEFAULT_AUTH_CACHE_TTL_SECONDS,
authorizationTimeout: 30,
certificateAuthEnabled:
this.chargingStation.stationInfo?.ocppVersion !== OCPPVersion.VERSION_16,
getErrorMessage,
isEmpty,
isNotEmptyArray,
+ JSONStringify,
logger,
} from '../../utils/index.js'
import { AbstractUIServer } from './AbstractUIServer.js'
}
private static createToolResponse (payload: unknown): CallToolResult {
- return { content: [{ text: JSON.stringify(payload), type: 'text' as const }] }
+ return { content: [{ text: JSONStringify(payload), type: 'text' as const }] }
}
public override hasResponseHandler (uuid: UUIDv4): boolean {
if (hexMatch == null) {
return undefined
}
+ // radix-16 required for IPv4-mapped IPv6 hex octet parsing; convertToInt does not accept a radix
const high = Number.parseInt(hexMatch[1], 16)
const low = Number.parseInt(hexMatch[2], 16)
return [high >> 8, high & 0xff, low >> 8, low & 0xff].join('.')
if (groups.length !== 8) {
return undefined
}
+ // radix-16 required for IPv6 hex group normalization; convertToInt does not accept a radix
return groups.every(isIPv6Group)
? groups.map(group => Number.parseInt(group, 16).toString(16))
: undefined
startDelay: DEFAULT_WORKER_START_DELAY_MS,
}
-const defaultPersistState = true
-
-export const DEFAULT_PERSIST_STATE = defaultPersistState
+export const DEFAULT_PERSIST_STATE = true as const
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
export class Configuration {
}
public static getPersistState (): boolean {
- return Configuration.getConfigurationData()?.persistState ?? defaultPersistState
+ return Configuration.getConfigurationData()?.persistState ?? DEFAULT_PERSIST_STATE
}
public static getStationTemplateUrls (): StationTemplateUrl[] | undefined {
} from './ConfigurationMigrations.js'
import { ConfigurationSchema } from './ConfigurationSchema.js'
import { configurationLogPrefix } from './ConfigurationUtils.js'
-import { assertIsJsonObject, clone, isEmpty } from './Utils.js'
+import { assertIsJsonObject, clone, isEmpty, isNotEmptyArray } from './Utils.js'
const moduleName = 'ConfigurationValidation'
)}`
)
}
- if (remapErrors.length > 0) {
+ if (isNotEmptyArray(remapErrors)) {
throw new ConfigurationValidationError(remapErrors, {
filePath,
migratedFrom,
static readonly DEFAULT_EV_CONNECTION_TIMEOUT_SECONDS = 180
+ static readonly DEFAULT_EXPONENTIAL_BACKOFF_BASE_DELAY_MS = 100
+
static readonly DEFAULT_FLUCTUATION_PERCENT = 5
static readonly DEFAULT_HASH_ALGORITHM = 'sha384'
* @file Shared template fixtures for schema/migration/validation tests.
*/
-import { CURRENT_SCHEMA_VERSION } from '../../../src/charging-station/TemplateMigrations.js'
+import { CURRENT_SCHEMA_VERSION } from '../../../src/charging-station/index.js'
import {
TEST_CHARGE_POINT_MODEL,
TEST_CHARGE_POINT_VENDOR,
import type { ChargingStation } from '../../../../src/charging-station/index.js'
-import { buildConfigKey } from '../../../../src/charging-station/ConfigurationKeyUtils.js'
+import { buildConfigKey } from '../../../../src/charging-station/index.js'
import { OCPP20CertSigningRetryManager } from '../../../../src/charging-station/ocpp/2.0/OCPP20CertSigningRetryManager.js'
import { OCPP20VariableManager } from '../../../../src/charging-station/ocpp/2.0/OCPP20VariableManager.js'
import {
addConfigurationKey,
buildConfigKey,
setConfigurationKeyValue,
-} from '../../../../src/charging-station/ConfigurationKeyUtils.js'
+} from '../../../../src/charging-station/index.js'
import { createTestableIncomingRequestService } from '../../../../src/charging-station/ocpp/2.0/__testable__/index.js'
import { OCPP20IncomingRequestService } from '../../../../src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.js'
import { OCPP20VariableManager } from '../../../../src/charging-station/ocpp/2.0/OCPP20VariableManager.js'
import { afterEach, beforeEach, describe, it, mock } from 'node:test'
import type { ChargingStation } from '../../../../src/charging-station/index.js'
-import type { ConnectorStatus } from '../../../../src/types/ConnectorStatus.js'
-import type { EmptyObject } from '../../../../src/types/index.js'
+import type { ConnectorStatus, EmptyObject } from '../../../../src/types/index.js'
-import { addConfigurationKey } from '../../../../src/charging-station/ConfigurationKeyUtils.js'
+import { addConfigurationKey } from '../../../../src/charging-station/index.js'
import {
buildTransactionEvent,
OCPP20ServiceUtils,
buildConfigKey,
deleteConfigurationKey,
getConfigurationKey,
-} from '../../../../src/charging-station/ConfigurationKeyUtils.js'
+} from '../../../../src/charging-station/index.js'
import { createTestableVariableManager } from '../../../../src/charging-station/ocpp/2.0/__testable__/index.js'
import { OCPP20VariableManager } from '../../../../src/charging-station/ocpp/2.0/OCPP20VariableManager.js'
import { VARIABLE_REGISTRY } from '../../../../src/charging-station/ocpp/2.0/OCPP20VariableRegistry.js'
import { UIMCPServer } from '../../../src/charging-station/ui-server/UIMCPServer.js'
import { HttpMethod } from '../../../src/charging-station/ui-server/UIServerUtils.js'
import { ApplicationProtocol, ConfigurationSection } from '../../../src/types/index.js'
-import { Configuration } from '../../../src/utils/Configuration.js'
+import { Configuration } from '../../../src/utils/index.js'
import { standardCleanup } from '../../helpers/TestLifecycleHelpers.js'
import { createMockBootstrap, createMockUIServerConfiguration } from './UIServerTestUtils.js'
ProcedureName,
ResponseStatus,
} from '../../../src/types/index.js'
-import { logger } from '../../../src/utils/Logger.js'
+import { logger } from '../../../src/utils/index.js'
import {
createLoggerMocks,
standardCleanup,
ProcedureName,
ResponseStatus,
} from '../../../src/types/index.js'
-import { logger } from '../../../src/utils/Logger.js'
+import { logger } from '../../../src/utils/index.js'
import { createLoggerMocks, standardCleanup } from '../../helpers/TestLifecycleHelpers.js'
import { TEST_UUID } from './UIServerTestConstants.js'
import {
ResponseStatus,
UIRequestOrigin,
} from '../../../../src/types/index.js'
-import { logger } from '../../../../src/utils/Logger.js'
+import { logger } from '../../../../src/utils/index.js'
import { standardCleanup } from '../../../helpers/TestLifecycleHelpers.js'
import { TEST_HASH_ID, TEST_UUID } from '../UIServerTestConstants.js'
import {
import type { ResponsePayload } from '../src/types/UIProtocol.js'
import { ServerFailureError, WebSocketClient } from '../src/client/WebSocketClient.js'
+import { DEFAULT_HOST, DEFAULT_PORT } from '../src/constants.js'
import {
AuthenticationType,
ProcedureName,
const mockWs = createMockWebSocketLike()
const factory: WebSocketFactory = () => mockWs
const client = new WebSocketClient(factory, {
- host: 'localhost',
- port: 8080,
+ host: DEFAULT_HOST,
+ port: DEFAULT_PORT,
protocol: Protocol.UI,
version: ProtocolVersion['0.0.1'],
})
type: AuthenticationType.PROTOCOL_BASIC_AUTH,
username: 'admin',
},
- host: 'localhost',
- port: 8080,
+ host: DEFAULT_HOST,
+ port: DEFAULT_PORT,
protocol: Protocol.UI,
version: ProtocolVersion['0.0.1'],
})
const mockWs = createMockWebSocketLike()
const factory: WebSocketFactory = () => mockWs
const client = new WebSocketClient(factory, {
- host: 'localhost',
- port: 8080,
+ host: DEFAULT_HOST,
+ port: DEFAULT_PORT,
protocol: Protocol.UI,
version: ProtocolVersion['0.0.1'],
})
const mockWs = createMockWebSocketLike()
const factory: WebSocketFactory = () => mockWs
const client = new WebSocketClient(factory, {
- host: 'localhost',
- port: 8080,
+ host: DEFAULT_HOST,
+ port: DEFAULT_PORT,
protocol: Protocol.UI,
version: ProtocolVersion['0.0.1'],
})
const mockWs = createMockWebSocketLike()
const factory: WebSocketFactory = () => mockWs
const client = new WebSocketClient(factory, {
- host: 'localhost',
- port: 8080,
+ host: DEFAULT_HOST,
+ port: DEFAULT_PORT,
protocol: Protocol.UI,
version: ProtocolVersion['0.0.1'],
})
const mockWs = createMockWebSocketLike()
const factory: WebSocketFactory = () => mockWs
const client = new WebSocketClient(factory, {
- host: 'localhost',
- port: 8080,
+ host: DEFAULT_HOST,
+ port: DEFAULT_PORT,
protocol: Protocol.UI,
version: ProtocolVersion['0.0.1'],
})
const mockWs = createMockWebSocketLike()
const factory: WebSocketFactory = () => mockWs
const client = new WebSocketClient(factory, {
- host: 'localhost',
- port: 8080,
+ host: DEFAULT_HOST,
+ port: DEFAULT_PORT,
protocol: Protocol.UI,
version: ProtocolVersion['0.0.1'],
})
const mockWs = createMockWebSocketLike()
const factory: WebSocketFactory = () => mockWs
const client = new WebSocketClient(factory, {
- host: 'localhost',
- port: 8080,
+ host: DEFAULT_HOST,
+ port: DEFAULT_PORT,
protocol: Protocol.UI,
version: ProtocolVersion['0.0.1'],
})
const mockWs = createMockWebSocketLike()
const factory: WebSocketFactory = () => mockWs
const client = new WebSocketClient(factory, {
- host: 'localhost',
- port: 8080,
+ host: DEFAULT_HOST,
+ port: DEFAULT_PORT,
protocol: Protocol.UI,
version: ProtocolVersion['0.0.1'],
})
const mockWs = createMockWebSocketLike()
const factory: WebSocketFactory = () => mockWs
const client = new WebSocketClient(factory, {
- host: 'localhost',
- port: 8080,
+ host: DEFAULT_HOST,
+ port: DEFAULT_PORT,
protocol: Protocol.UI,
version: ProtocolVersion['0.0.1'],
})
const mockWs = createMockWebSocketLike()
const factory: WebSocketFactory = () => mockWs
const client = new WebSocketClient(factory, {
- host: 'localhost',
- port: 8080,
+ host: DEFAULT_HOST,
+ port: DEFAULT_PORT,
protocol: Protocol.UI,
version: ProtocolVersion['0.0.1'],
})
const mockWs = createMockWebSocketLike()
const factory: WebSocketFactory = () => mockWs
const client = new WebSocketClient(factory, {
- host: 'localhost',
- port: 8080,
+ host: DEFAULT_HOST,
+ port: DEFAULT_PORT,
protocol: Protocol.UI,
version: ProtocolVersion['0.0.1'],
})
const client = new WebSocketClient(
() => mockWs,
{
- host: 'localhost',
- port: 8080,
+ host: DEFAULT_HOST,
+ port: DEFAULT_PORT,
protocol: Protocol.UI,
version: ProtocolVersion['0.0.1'],
},
const client = new WebSocketClient(
() => mockWs,
{
- host: 'localhost',
- port: 8080,
+ host: DEFAULT_HOST,
+ port: DEFAULT_PORT,
protocol: Protocol.UI,
version: ProtocolVersion['0.0.1'],
},
const mockWs = createMockWebSocketLike()
const factory: WebSocketFactory = () => mockWs
const client = new WebSocketClient(factory, {
- host: 'localhost',
- port: 8080,
+ host: DEFAULT_HOST,
+ port: DEFAULT_PORT,
protocol: Protocol.UI,
version: ProtocolVersion['0.0.1'],
})
const mockWs = createMockWebSocketLike()
const factory: WebSocketFactory = () => mockWs
const client = new WebSocketClient(factory, {
- host: 'localhost',
- port: 8080,
+ host: DEFAULT_HOST,
+ port: DEFAULT_PORT,
protocol: Protocol.UI,
version: ProtocolVersion['0.0.1'],
})
const mockWs = createMockWebSocketLike()
const factory: WebSocketFactory = () => mockWs
const client = new WebSocketClient(factory, {
- host: 'localhost',
- port: 8080,
+ host: DEFAULT_HOST,
+ port: DEFAULT_PORT,
protocol: Protocol.UI,
version: ProtocolVersion['0.0.1'],
})
const mockWs = createMockWebSocketLike()
const client = new WebSocketClient(
() => mockWs,
- { host: 'localhost', port: 8080, protocol: Protocol.UI, version: ProtocolVersion['0.0.1'] },
+ {
+ host: DEFAULT_HOST,
+ port: DEFAULT_PORT,
+ protocol: Protocol.UI,
+ version: ProtocolVersion['0.0.1'],
+ },
undefined,
notification => {
notifications.push(notification)
const factory: WebSocketFactory = () => mockWs
const client = new WebSocketClient(
factory,
- { host: 'localhost', port: 8080, protocol: Protocol.UI, version: ProtocolVersion['0.0.1'] },
+ {
+ host: DEFAULT_HOST,
+ port: DEFAULT_PORT,
+ protocol: Protocol.UI,
+ version: ProtocolVersion['0.0.1'],
+ },
undefined,
notification => {
notifications.push(notification)
await it('should NOT fire onNotification when callback is undefined', async () => {
const mockWs = createMockWebSocketLike()
const client = new WebSocketClient(() => mockWs, {
- host: 'localhost',
- port: 8080,
+ host: DEFAULT_HOST,
+ port: DEFAULT_PORT,
protocol: Protocol.UI,
version: ProtocolVersion['0.0.1'],
})
import { describe, it } from 'node:test'
import { configurationSchema, uiServerConfigSchema } from '../src/config/schema.js'
+import { DEFAULT_HOST, DEFAULT_PORT } from '../src/constants.js'
await describe('config schema validation', async () => {
await it('should validate a minimal valid config', () => {
const result = uiServerConfigSchema.safeParse({
- host: 'localhost',
- port: 8080,
+ host: DEFAULT_HOST,
+ port: DEFAULT_PORT,
protocol: 'ui',
version: '0.0.1',
})
await it('should reject config with empty protocol', () => {
const result = uiServerConfigSchema.safeParse({
- host: 'localhost',
- port: 8080,
+ host: DEFAULT_HOST,
+ port: DEFAULT_PORT,
protocol: '',
version: '0.0.1',
})
await it('should reject config with protocol not in Protocol enum', () => {
const result = uiServerConfigSchema.safeParse({
- host: 'localhost',
- port: 8080,
+ host: DEFAULT_HOST,
+ port: DEFAULT_PORT,
protocol: 'ws',
version: '0.0.1',
})
await it('should reject config with version not in ProtocolVersion enum', () => {
const result = uiServerConfigSchema.safeParse({
- host: 'localhost',
- port: 8080,
+ host: DEFAULT_HOST,
+ port: DEFAULT_PORT,
protocol: 'ui',
version: '2.0',
})
await it('should reject missing required host field', () => {
const result = uiServerConfigSchema.safeParse({
- port: 8080,
+ port: DEFAULT_PORT,
protocol: 'ui',
version: '0.0.1',
})
await it('should reject invalid port number', () => {
const result = uiServerConfigSchema.safeParse({
- host: 'localhost',
+ host: DEFAULT_HOST,
port: 99999,
})
assert.strictEqual(result.success, false)
await it('should reject empty host string', () => {
const result = uiServerConfigSchema.safeParse({
host: '',
- port: 8080,
+ port: DEFAULT_PORT,
protocol: 'ui',
version: '0.0.1',
})
},
host: 'simulator.example.com',
name: 'My Simulator',
- port: 8080,
+ port: DEFAULT_PORT,
protocol: 'ui',
secure: true,
version: '0.0.1',
await it('should validate configuration with array of servers', () => {
const result = configurationSchema.safeParse({
uiServer: [
- { host: 'server1.example.com', port: 8080, protocol: 'ui', version: '0.0.1' },
- { host: 'server2.example.com', port: 8080, protocol: 'ui', version: '0.0.1' },
+ { host: 'server1.example.com', port: DEFAULT_PORT, protocol: 'ui', version: '0.0.1' },
+ { host: 'server2.example.com', port: DEFAULT_PORT, protocol: 'ui', version: '0.0.1' },
],
})
assert.strictEqual(result.success, true)
await it('should validate configuration with single server', () => {
const result = configurationSchema.safeParse({
- uiServer: { host: 'localhost', port: 8080, protocol: 'ui', version: '0.0.1' },
+ uiServer: { host: DEFAULT_HOST, port: DEFAULT_PORT, protocol: 'ui', version: '0.0.1' },
})
assert.strictEqual(result.success, true)
})
enabled: true,
type: 'protocol-basic-auth',
},
- host: 'localhost',
- port: 8080,
+ host: DEFAULT_HOST,
+ port: DEFAULT_PORT,
protocol: 'ui',
version: '0.0.1',
})
type: 'protocol-basic-auth',
username: '',
},
- host: 'localhost',
- port: 8080,
+ host: DEFAULT_HOST,
+ port: DEFAULT_PORT,
protocol: 'ui',
version: '0.0.1',
})
type: 'protocol-basic-auth',
username: 'admin',
},
- host: 'localhost',
- port: 8080,
+ host: DEFAULT_HOST,
+ port: DEFAULT_PORT,
protocol: 'ui',
version: '0.0.1',
})
type: 'protocol-basic-auth',
username: 'a:b',
},
- host: 'localhost',
- port: 8080,
+ host: DEFAULT_HOST,
+ port: DEFAULT_PORT,
protocol: 'ui',
version: '0.0.1',
})
password: 'admin',
type: 'protocol-basic-auth',
},
- host: 'localhost',
- port: 8080,
+ host: DEFAULT_HOST,
+ port: DEFAULT_PORT,
protocol: 'ui',
version: '0.0.1',
})
type: 'protocol-basic-auth',
username: 'admin',
},
- host: 'localhost',
- port: 8080,
+ host: DEFAULT_HOST,
+ port: DEFAULT_PORT,
protocol: 'ui',
version: '0.0.1',
})
type: 'protocol-basic-auth',
username: 'admin',
},
- host: 'localhost',
- port: 8080,
+ host: DEFAULT_HOST,
+ port: DEFAULT_PORT,
protocol: 'ui',
version: '0.0.1',
})
enabled: false,
type: 'protocol-basic-auth',
},
- host: 'localhost',
- port: 8080,
+ host: DEFAULT_HOST,
+ port: DEFAULT_PORT,
protocol: 'ui',
version: '0.0.1',
})
import { mergeConfig } from 'vite'
import { configDefaults, defineConfig } from 'vitest/config'
-import viteConfig from './vite.config'
+import viteConfig from './vite.config.js'
const nodeMajor = Number.parseInt(process.versions.node.split('.')[0], 10)