diff --git a/tests/functional/ctst/common/parseGoDuration.test.ts b/tests/functional/ctst/common/parseGoDuration.test.ts deleted file mode 100644 index 195ec91934..0000000000 --- a/tests/functional/ctst/common/parseGoDuration.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -import assert from 'assert'; -import { parseGoDuration } from './utils'; - -const cases: [string, number][] = [ - ['1m', 60], - ['2h', 7200], - ['30s', 30], - ['2h45m', 9900], - ['500ms', 0.5], - ['1.5s', 1.5], - ['1h30m10s', 5410], - ['100ns', 1e-7], - ['10us', 1e-5], - ['10µs', 1e-5], - ['0s', 0], -]; - -for (const [input, expected] of cases) { - const result = parseGoDuration(input); - assert.strictEqual( - Math.abs(result - expected) < 1e-12, true, - `parseGoDuration("${input}") = ${result}, expected ${expected}`, - ); -} - -const invalid = ['', 'abc', '1x', '5h 3m', '1', 'm', ' 1m']; -for (const input of invalid) { - assert.throws( - () => parseGoDuration(input), - { message: /Invalid duration/ }, - `parseGoDuration("${input}") should throw`, - ); -} - diff --git a/tests/functional/ctst/common/utils.test.ts b/tests/functional/ctst/common/utils.test.ts new file mode 100644 index 0000000000..f44245e4c4 --- /dev/null +++ b/tests/functional/ctst/common/utils.test.ts @@ -0,0 +1,60 @@ +import assert from 'assert'; +import { paramToCli, parseGoDuration } from './utils'; + +const durations: [string, number][] = [ + ['1m', 60], + ['2h', 7200], + ['30s', 30], + ['2h45m', 9900], + ['500ms', 0.5], + ['1.5s', 1.5], + ['1h30m10s', 5410], + ['100ns', 1e-7], + ['10us', 1e-5], + ['10µs', 1e-5], + ['0s', 0], +]; + +for (const [input, expected] of durations) { + const result = parseGoDuration(input); + assert.strictEqual( + Math.abs(result - expected) < 1e-12, true, + `parseGoDuration("${input}") = ${result}, expected ${expected}`, + ); +} + +const invalid = ['', 'abc', '1x', '5h 3m', '1', 'm', ' 1m']; +for (const input of invalid) { + assert.throws( + () => parseGoDuration(input), + { message: /Invalid duration/ }, + `parseGoDuration("${input}") should throw`, + ); +} + +const cliParams: [Record, string][] = [ + // pflag boolean flags must use the "=" form: "--flag false" would set the + // flag to true and drop "false" as a positional argument. + [{ forceRotateServiceCredentials: false }, '--force-rotate-service-credentials=false'], + [{ forceRotateServiceCredentials: true }, '--force-rotate-service-credentials=true'], + [{ wait: true }, '--wait=true'], + [{ sinkZenkoInstance: 'end2end-pra' }, '--sink-zenko-instance end2end-pra'], + [{ kafkaExternalPort: 9092 }, '--kafka-external-port 9092'], + [{ kafkaPersistenceSelector: 'app=kafka-dr-sink' }, '--kafka-persistence-selector app=kafka-dr-sink'], + [{ mongodbHosts: ['host-a', 'host-b'] }, '--mongodb-hosts host-a,host-b'], + [{ timeout: undefined }, ''], + [{ timeout: null }, ''], + [{}, ''], + [ + { sinkZenkoInstance: 'end2end-pra', wait: false, timeout: '30m' }, + '--sink-zenko-instance end2end-pra --wait=false --timeout 30m', + ], +]; + +for (const [params, expected] of cliParams) { + const result = paramToCli(params); + assert.strictEqual( + result, expected, + `paramToCli(${JSON.stringify(params)}) = "${result}", expected "${expected}"`, + ); +} diff --git a/tests/functional/ctst/common/utils.ts b/tests/functional/ctst/common/utils.ts index 3b1f036344..ef3ad2f7e7 100644 --- a/tests/functional/ctst/common/utils.ts +++ b/tests/functional/ctst/common/utils.ts @@ -135,6 +135,30 @@ export function parseGoDuration(duration: string): number { return totalSeconds; } +/** + * Serialises an options object into arguments for a Go CLI built with cobra/pflag. + * @param {Record} params - the options to serialise + * @return {string} - the arguments, space-separated + */ +export function paramToCli(params: Record): string { + const command: string[] = []; + Object.keys(params).forEach(key => { + const value = params[key]; + if (value == null) { + return; + } + const flag = `--${key.replace(/([A-Z])/g, '-$1').toLowerCase()}`; + if (typeof value === 'boolean') { + // pflag boolean flags do not consume the next argument: "--flag false" + // sets the flag to true and drops "false" as a positional argument. + command.push(`${flag}=${String(value)}`); + } else { + command.push(flag, String(value)); + } + }); + return command.join(' '); +} + export function safeJsonParse(jsonString: string): { ok: boolean, result: T | null, error?: Error | null } { let result: T; try { diff --git a/tests/functional/ctst/steps/dr/drctl.ts b/tests/functional/ctst/steps/dr/drctl.ts index 67338d329b..a6af84ca51 100644 --- a/tests/functional/ctst/steps/dr/drctl.ts +++ b/tests/functional/ctst/steps/dr/drctl.ts @@ -2,6 +2,7 @@ import util from 'util'; import { exec } from 'child_process'; import Zenko from 'world/Zenko'; +import { paramToCli } from 'common/utils'; type InstallConfig = { sourceZenkoDrInstance?: string; @@ -241,54 +242,42 @@ export default class ZenkoDrctl { } async install(config: InstallConfig) { - return this.runCommand('install', this.paramToCli(this.withSourceSinkKubeconfig(config)), true); + return this.runCommand('install', paramToCli(this.withSourceSinkKubeconfig(config)), true); } async uninstall(config: UninstallConfig) { - return this.runCommand('uninstall', this.paramToCli(this.withSourceSinkKubeconfig(config)), true); + return this.runCommand('uninstall', paramToCli(this.withSourceSinkKubeconfig(config)), true); } async bootstrapDump(config: BootstrapDumpConfig) { - return this.runCommand('bootstrap dump', this.paramToCli(config)); + return this.runCommand('bootstrap dump', paramToCli(config)); } async bootstrapLoad(config: BootstrapLoadConfig) { - return this.runCommand('bootstrap load', this.paramToCli(config)); + return this.runCommand('bootstrap load', paramToCli(config)); } async failover(config: FailoverConfig) { - return this.runCommand('failover', this.paramToCli(this.withSinkKubeconfig(config))); + return this.runCommand('failover', paramToCli(this.withSinkKubeconfig(config))); } async failback(config: FailbackConfig) { - return this.runCommand('failback', this.paramToCli(this.withSinkKubeconfig(config))); + return this.runCommand('failback', paramToCli(this.withSinkKubeconfig(config))); } async status(config: StatusConfig) { - return this.runCommand('status', this.paramToCli(this.withSourceSinkKubeconfig(config))); + return this.runCommand('status', paramToCli(this.withSourceSinkKubeconfig(config))); } async volumeGet(config: VolumeGetConfig) { - return this.runCommand('volume get', this.paramToCli(this.withTargetKubeconfig(config))); + return this.runCommand('volume get', paramToCli(this.withTargetKubeconfig(config))); } async replicationPause(config: ReplicationPauseConfig) { - return this.runCommand('replication pause', this.paramToCli(this.withSourceSinkKubeconfig(config))); + return this.runCommand('replication pause', paramToCli(this.withSourceSinkKubeconfig(config))); } async replicationResume(config: ReplicationResumeConfig) { - return this.runCommand('replication resume', this.paramToCli(this.withSourceSinkKubeconfig(config))); - } - - paramToCli(params: Record): string { - const command: string[] = []; - Object.keys(params).forEach(key => { - const value = params[key]; - if (value !== undefined && value !== null) { - command.push(`--${key.replace(/([A-Z])/g, '-$1').toLowerCase()}`); - command.push(String(value)); - } - }); - return command.join(' '); + return this.runCommand('replication resume', paramToCli(this.withSourceSinkKubeconfig(config))); } }