} from '../types/index.js'
import { WorkerProcessType } from '../worker/index.js'
import { CURRENT_CONFIGURATION_SCHEMA_VERSION } from './ConfigurationMigrations.js'
+import { Constants } from './Constants.js'
import { isHostLiteralWithoutPort } from './HostUtils.js'
import { has, isNotEmptyArray } from './Utils.js'
/**
* StorageConfiguration — performance storage configuration section.
* Legacy `URI` (uppercase) is accepted but not auto-migrated; canonical key is `uri`.
+ * `uri` is parsed with `new URL()` when the configuration is loaded for the
+ * `jsonfile` and `sqlite` types, and when the storage is opened at startup:
+ * the `mongodb`, `mysql` and `mariadb` backends are opened when `enabled` is
+ * true or omitted (an omitted `enabled` resolves to
+ * `Constants.DEFAULT_PERFORMANCE_STORAGE_ENABLED`, the same default the runtime
+ * storage configuration uses). It must then be an absolute URL: a relative file path would
+ * otherwise throw a bare `TypeError` at startup. Values the simulator never
+ * parses are left as they are.
*/
export const StorageConfigurationSchema = z
.object({
URI: z.string().optional().describe("@deprecated: use 'uri' instead"),
})
.strict()
+ .superRefine((value, ctx) => {
+ if (value.uri == null || URL.canParse(value.uri)) return
+ const parsedAtLoad = value.type === StorageType.JSON_FILE || value.type === StorageType.SQLITE
+ const parsedAtOpen =
+ (value.enabled ?? Constants.DEFAULT_PERFORMANCE_STORAGE_ENABLED) &&
+ value.type != null &&
+ value.type !== StorageType.NONE
+ if (parsedAtLoad || parsedAtOpen) {
+ ctx.addIssue({
+ code: 'custom',
+ message:
+ 'must be an absolute URL (e.g. file:///path/to/records.json or mongodb://host:27017/db)',
+ path: ['uri'],
+ })
+ }
+ })
/**
* UIServerAuthentication — credentials for the UI server. `username` is a
static readonly DEFAULT_PERFORMANCE_RECORDS_DB_NAME = 'e-mobility-charging-stations-simulator'
static readonly DEFAULT_PERFORMANCE_RECORDS_FILENAME = 'performanceRecords.json'
+ /**
+ * Whether the performance storage is enabled when `performanceStorage.enabled`
+ * is omitted from the configuration. Shared by the runtime default storage
+ * configuration and the configuration schema so both resolve an omitted
+ * `enabled` the same way.
+ */
+ static readonly DEFAULT_PERFORMANCE_STORAGE_ENABLED = true
+
/**
* Peak jitter fraction: consumers scale the base delay by a uniform draw in
* `[0, jitterPercent)`. See `computeExponentialBackOffDelay` (uni-directional
)
assert.ok(result.success)
})
+
+ await it('should accept performanceStorage.uri values the storage backends can parse', () => {
+ for (const uri of [
+ 'file:///tmp/perf.json',
+ 'file:///tmp/e-mobility-charging-stations-simulator.db',
+ 'mongodb://localhost:27017/e-mobility-charging-stations-simulator',
+ ]) {
+ const result = ConfigurationSchema.safeParse(
+ buildMinimalConfiguration({ performanceStorage: { type: 'jsonfile', uri } })
+ )
+ assert.ok(result.success, uri)
+ }
+ })
+
+ await it('should reject a performanceStorage.uri that is not an absolute URL where it is parsed', () => {
+ const parsedSections = [
+ { type: 'jsonfile' },
+ { type: 'sqlite' },
+ { enabled: false, type: 'jsonfile' },
+ { enabled: true, type: 'mongodb' },
+ { enabled: true, type: 'mysql' },
+ { enabled: true, type: 'mariadb' },
+ // `enabled` omitted: inherited as true for the remote backends.
+ { type: 'mongodb' },
+ { type: 'mysql' },
+ { type: 'mariadb' },
+ ]
+ for (const section of parsedSections) {
+ for (const uri of ['not a url', 'performance/records.json', '/tmp/perf.json', '']) {
+ assert.throws(() => new URL(uri), TypeError, uri)
+ const result = ConfigurationSchema.safeParse(
+ buildMinimalConfiguration({ performanceStorage: { ...section, uri } })
+ )
+ assert.ok(!result.success, `${JSON.stringify(section)} ${uri}`)
+ assert.ok(
+ result.error.issues.some(i => i.path.join('.') === 'performanceStorage.uri'),
+ `${JSON.stringify(section)} ${uri}`
+ )
+ }
+ }
+ })
+
+ await it('should leave a performanceStorage.uri the simulator never parses unchanged', () => {
+ for (const section of [
+ { type: 'none' },
+ { enabled: true, type: 'none' },
+ { enabled: false, type: 'mongodb' },
+ { enabled: false, type: 'mysql' },
+ { enabled: false, type: 'mariadb' },
+ {},
+ { enabled: true },
+ ]) {
+ const result = ConfigurationSchema.safeParse(
+ buildMinimalConfiguration({ performanceStorage: { ...section, uri: 'not a url' } })
+ )
+ assert.ok(result.success, JSON.stringify(section))
+ }
+ })
+
+ await it('should leave the unused performanceStorage.URI deprecated alias unchanged', () => {
+ const result = ConfigurationSchema.safeParse(
+ buildMinimalConfiguration({ performanceStorage: { type: 'jsonfile', URI: 'not a url' } })
+ )
+ assert.ok(result.success)
+ })
})
await describe('round-trip on real config-template.json', async () => {