From 98026ec03694e816202daff997af46e5c1c6aba1 Mon Sep 17 00:00:00 2001 From: sylvain senechal Date: Thu, 1 Oct 2026 18:53:24 +0200 Subject: [PATCH 1/5] Move constants from cli-testing into the local ctst constants file Issue: ZENKO-5372 --- tests/functional/ctst/common/common.ts | 12 ++++---- tests/functional/ctst/common/constants.ts | 30 +++++++++++++++++++ tests/functional/ctst/steps/azureArchive.ts | 5 ++-- .../functional/ctst/steps/cloudserverAuth.ts | 7 +++-- .../ctst/steps/iam-policies/IAMUser.ts | 5 ++-- tests/functional/ctst/steps/pra.ts | 5 ++-- tests/functional/ctst/steps/utils/utils.ts | 5 ++-- tests/functional/ctst/world/Zenko.ts | 30 +++++++++---------- 8 files changed, 66 insertions(+), 33 deletions(-) diff --git a/tests/functional/ctst/common/common.ts b/tests/functional/ctst/common/common.ts index 6bc0bc81de..0dc284ded6 100644 --- a/tests/functional/ctst/common/common.ts +++ b/tests/functional/ctst/common/common.ts @@ -1,6 +1,6 @@ import { ListObjectVersionsOutput } from '@aws-sdk/client-s3'; import { Given, setDefaultTimeout, Then, When } from '@cucumber/cucumber'; -import { CacheHelper, Constants, Identity, IdentityEnum, S3, Utils } from 'cli-testing'; +import { CacheHelper, Identity, IdentityEnum, S3, Utils } from 'cli-testing'; import Zenko from 'world/Zenko'; import { parseGoDuration, safeJsonParse } from './utils'; import assert from 'assert'; @@ -20,7 +20,7 @@ import { import { ActionPermissionsType } from 'steps/bucket-policies/utils'; import constants from './constants'; -setDefaultTimeout(Constants.DEFAULT_TIMEOUT); +setDefaultTimeout(constants.DEFAULT_TIMEOUT); /** * Cleans the created test bucket @@ -144,16 +144,16 @@ async function createBucket(world: Zenko, versioning: string, bucketName: string Given('a {string} bucket with dot', async function (this: Zenko, versioning: string) { const preName = this.getSaved('accountName') || - this.parameters.AccountName || Constants.ACCOUNT_NAME; + this.parameters.AccountName || constants.ACCOUNT_NAME; await createBucket(this, versioning, - `${preName}.${Constants.BUCKET_NAME_TEST}${Utils.randomString()}`.toLocaleLowerCase()); + `${preName}.${constants.BUCKET_NAME_TEST}${Utils.randomString()}`.toLocaleLowerCase()); }); Given('a {string} bucket', async function (this: Zenko, versioning: string) { const preName = this.getSaved('accountName') || - this.parameters.AccountName || Constants.ACCOUNT_NAME; + this.parameters.AccountName || constants.ACCOUNT_NAME; await createBucket(this, versioning, - `${preName}${Constants.BUCKET_NAME_TEST}${Utils.randomString()}`.toLocaleLowerCase()); + `${preName}${constants.BUCKET_NAME_TEST}${Utils.randomString()}`.toLocaleLowerCase()); }); Given('an existing bucket {string} {string} versioning, {string} ObjectLock {string} retention mode', async function diff --git a/tests/functional/ctst/common/constants.ts b/tests/functional/ctst/common/constants.ts index d6d07f45b4..14a035c45d 100644 --- a/tests/functional/ctst/common/constants.ts +++ b/tests/functional/ctst/common/constants.ts @@ -1,4 +1,34 @@ export default { complianceRetention: 'COMPLIANCE', governanceRetention: 'GOVERNANCE', + DEFAULT_SUBDOMAIN: 'my-company.com', + DEFAULT_TIMEOUT: 100000, + ACCOUNT_NAME: 'AccountTest', + INTERNAL_SERVICES_ACCOUNT_ID: '000000000000', + USER_NAME_TEST: 'userNameForTest', + POLICY_NAME_TEST: 'policyNameForTest', + ROLE_NAME_TEST: 'roleNameForTest', + BUCKET_NAME_TEST: 'bucketForTest', + K_CLIENT: 'zenko-ui', + MAX_ACCOUNT_CHECK_RETRIES: 100, + // Must match the file name cli-testing's S3.getObject writes downloaded objects to + OUTFILE_NAME: 'out.loc', + assumeRolePolicy: JSON.stringify({ + Version: '2012-10-17', + Statement: [ + { + Effect: 'Allow', + Action: ['iam:ListRoles', 'sts:AssumeRole'], + Resource: '*', + }, + ], + }), + assumeRoleTrustPolicy: JSON.stringify({ + Version: '2012-10-17', + Statement: { + Effect: 'Allow', + Principal: '*', + Action: 'sts:AssumeRole', + }, + }), }; diff --git a/tests/functional/ctst/steps/azureArchive.ts b/tests/functional/ctst/steps/azureArchive.ts index ad92b2aca2..2da28375a1 100644 --- a/tests/functional/ctst/steps/azureArchive.ts +++ b/tests/functional/ctst/steps/azureArchive.ts @@ -3,7 +3,8 @@ import path from 'path'; import assert from 'assert'; import { safeJsonParse, request } from '../common/utils'; import { Given, Then, When } from '@cucumber/cucumber'; -import { S3, Constants, Utils } from 'cli-testing'; +import { S3, Utils } from 'cli-testing'; +import constants from 'common/constants'; import util from 'util'; import { exec } from 'child_process'; import Zenko from 'world/Zenko'; @@ -230,7 +231,7 @@ Then('object {string} should have the same data', async function (this: Zenko, o const res = await S3.getObject(this.getCommandParameters()); assert.ifError(res.err); const cliTestingPath = path.dirname(require.resolve('cli-testing')); - const objectPath = path.join(cliTestingPath, 'utils', 'api', Constants.OUTFILE_NAME); + const objectPath = path.join(cliTestingPath, 'utils', 'api', constants.OUTFILE_NAME); const objectBuffer = fs.readFileSync(objectPath); fs.rmSync(objectPath); const expectedContent = Buffer.alloc(Buffer.byteLength(objectBuffer), 'a'); diff --git a/tests/functional/ctst/steps/cloudserverAuth.ts b/tests/functional/ctst/steps/cloudserverAuth.ts index a75a104be4..060c559ed7 100644 --- a/tests/functional/ctst/steps/cloudserverAuth.ts +++ b/tests/functional/ctst/steps/cloudserverAuth.ts @@ -1,6 +1,7 @@ import { When, Then } from '@cucumber/cucumber'; import Zenko from '../world/Zenko'; -import { Utils, S3, Constants } from 'cli-testing'; +import { Utils, S3 } from 'cli-testing'; +import constants from 'common/constants'; import { strict as assert } from 'assert'; interface DeleteObjectsResult { @@ -36,8 +37,8 @@ When('the user tries to perform CreateBucket', async function (this: Zenko) { this.resetCommand(); this.useSavedIdentity(); const preName = this.getSaved('accountName') || - this.parameters.AccountName || Constants.ACCOUNT_NAME; - const usedBucketName = `${preName}${Constants.BUCKET_NAME_TEST}${Utils.randomString()}`.toLocaleLowerCase(); + this.parameters.AccountName || constants.ACCOUNT_NAME; + const usedBucketName = `${preName}${constants.BUCKET_NAME_TEST}${Utils.randomString()}`.toLocaleLowerCase(); this.addToSaved('bucketName', usedBucketName); this.addCommandParameter({ bucket: usedBucketName }); // Empty strings are used to pass parameters that are used as a flag and do not require a value diff --git a/tests/functional/ctst/steps/iam-policies/IAMUser.ts b/tests/functional/ctst/steps/iam-policies/IAMUser.ts index 037bb53c8f..2be3ac7462 100644 --- a/tests/functional/ctst/steps/iam-policies/IAMUser.ts +++ b/tests/functional/ctst/steps/iam-policies/IAMUser.ts @@ -1,5 +1,6 @@ import { Given } from '@cucumber/cucumber'; -import { Constants, IAM, Identity, Utils } from 'cli-testing'; +import { IAM, Identity, Utils } from 'cli-testing'; +import constants from 'common/constants'; import { extractPropertyFromResults } from '../../common/utils'; import Zenko from 'world/Zenko'; @@ -9,7 +10,7 @@ Given('an IAM policy attached to the entity {string} with {string} effect to per this.resetCommand(); this.addToSaved('action', action); // create the IAM policy - this.addCommandParameter({ policyName: `${Constants.POLICY_NAME_TEST}${Utils.randomString()}` }); + this.addCommandParameter({ policyName: `${constants.POLICY_NAME_TEST}${Utils.randomString()}` }); this.addCommandParameter({ policyDocument: JSON.stringify({ Version: '2012-10-17', diff --git a/tests/functional/ctst/steps/pra.ts b/tests/functional/ctst/steps/pra.ts index dd3a5bfff5..dd9815c3ce 100644 --- a/tests/functional/ctst/steps/pra.ts +++ b/tests/functional/ctst/steps/pra.ts @@ -13,7 +13,8 @@ import { restoreObject, verifyObjectLocation, } from 'steps/utils/utils'; -import { CacheHelper, Constants, Identity, IdentityEnum, SuperAdmin, Utils } from 'cli-testing'; +import { CacheHelper, Identity, IdentityEnum, SuperAdmin, Utils } from 'cli-testing'; +import constants from 'common/constants'; import { safeJsonParse } from 'common/utils'; import { PrometheusDriver } from 'prometheus-query'; import assert from 'assert'; @@ -405,7 +406,7 @@ Given('access keys for the replicated account', { timeout: 360000 }, async () => const targetAccount = Zenko.sites['source'].accountName; let account; - let remaining = Constants.MAX_ACCOUNT_CHECK_RETRIES; + let remaining = constants.MAX_ACCOUNT_CHECK_RETRIES; account = await SuperAdmin.getAccount({ accountName: targetAccount, }); diff --git a/tests/functional/ctst/steps/utils/utils.ts b/tests/functional/ctst/steps/utils/utils.ts index f603d44ccd..fa07ac91ea 100644 --- a/tests/functional/ctst/steps/utils/utils.ts +++ b/tests/functional/ctst/steps/utils/utils.ts @@ -3,7 +3,6 @@ import { promises as fsp } from 'fs'; import { join } from 'path'; import { CacheHelper, - Constants, Identity, S3, Utils, @@ -179,9 +178,9 @@ async function createBucketWithConfiguration( retentionMode?: string) { world.resetCommand(); const preName = world.getSaved('accountName') || - world.parameters.AccountName || Constants.ACCOUNT_NAME; + world.parameters.AccountName || constants.ACCOUNT_NAME; const usedBucketName = bucketName - || `${preName}${Constants.BUCKET_NAME_TEST}${Utils.randomString()}`.toLocaleLowerCase(); + || `${preName}${constants.BUCKET_NAME_TEST}${Utils.randomString()}`.toLocaleLowerCase(); world.addToSaved('bucketName', usedBucketName); world.addCommandParameter({ bucket: usedBucketName }); if (withObjectLock === 'with') { diff --git a/tests/functional/ctst/world/Zenko.ts b/tests/functional/ctst/world/Zenko.ts index ec2f5a04ad..cd38ab5379 100644 --- a/tests/functional/ctst/world/Zenko.ts +++ b/tests/functional/ctst/world/Zenko.ts @@ -11,7 +11,6 @@ import { CacheHelper, ClientOptions, Command, - Constants, IAM, Identity, IdentityEnum, @@ -23,6 +22,7 @@ import { } from 'cli-testing'; import { extractPropertyFromResults } from '../common/utils'; +import constants from '../common/constants'; import AzureClient from 'clients/azure'; import ZenkoDrctl from 'steps/dr/drctl'; import assert from 'assert'; @@ -341,7 +341,7 @@ export default class Zenko extends World { this.parameters.KeycloakHost || 'keycloak.zenko.local', this.parameters.KeycloakPort || '80', `/auth/realms/${this.parameters.KeycloakRealm || 'zenko'}/protocol/openid-connect/token`, - this.parameters.KeycloakClientId || Constants.K_CLIENT, + this.parameters.KeycloakClientId || constants.K_CLIENT, this.parameters.KeycloakGrantType || 'password', ); if (!webIdentityToken) { @@ -470,7 +470,7 @@ export default class Zenko extends World { async createAccount(name?: string, force?: boolean, adminClientName?: string): Promise { Identity.resetIdentity(); const accountName = name || this.getSaved('accountName') || - `${Constants.ACCOUNT_NAME}${Utils.randomString()}`; + `${constants.ACCOUNT_NAME}${Utils.randomString()}`; if (Identity.hasIdentity(IdentityEnum.ACCOUNT, accountName) && !force) { Identity.useIdentity(IdentityEnum.ACCOUNT, accountName); return accountName; @@ -508,10 +508,10 @@ export default class Zenko extends World { const accountName = Identity.getCurrentAccountName(); // Creating a role to assume - const roleName = `${accountName}${Constants.ROLE_NAME_TEST}${Utils.randomString()}`; + const roleName = `${accountName}${constants.ROLE_NAME_TEST}${Utils.randomString()}`; this.addToSaved('roleName', roleName); this.addCommandParameter({ roleName }); - this.addCommandParameter({ assumeRolePolicyDocument: Constants.assumeRoleTrustPolicy }); + this.addCommandParameter({ assumeRolePolicyDocument: constants.assumeRoleTrustPolicy }); const roleArnToAssume = extractPropertyFromResults(await IAM.createRole( this.getCommandParameters()), 'Role', 'Arn'); @@ -521,7 +521,7 @@ export default class Zenko extends World { if (crossAccount) { // Creating a second account if its Cross-Account AssumeRole const account2 = await SuperAdmin.createAccount({ - accountName: `${Constants.ACCOUNT_NAME}${Utils.randomString()}`, + accountName: `${constants.ACCOUNT_NAME}${Utils.randomString()}`, }); // Creating credentials for the second account @@ -537,7 +537,7 @@ export default class Zenko extends World { // Creating a user in the account to be assumed from this.resetCommand(); - const userName = `${accountToBeAssumedFrom}${Constants.USER_NAME_TEST}${Utils.randomString()}`; + const userName = `${accountToBeAssumedFrom}${constants.USER_NAME_TEST}${Utils.randomString()}`; this.addCommandParameter({ userName }); await IAM.createUser(this.getCommandParameters()); @@ -545,10 +545,10 @@ export default class Zenko extends World { this.resetCommand(); this.addCommandParameter({ policyName: `${accountToBeAssumedFrom}` + - `${Constants.POLICY_NAME_TEST}` + + `${constants.POLICY_NAME_TEST}` + `${Utils.randomString()}`, }); - this.addCommandParameter({ policyDocument: Constants.assumeRolePolicy }); + this.addCommandParameter({ policyDocument: constants.assumeRolePolicy }); const assumeRolePolicyArn = extractPropertyFromResults(await IAM.createPolicy( this.getCommandParameters()), 'Policy', 'Arn'); @@ -610,7 +610,7 @@ export default class Zenko extends World { this.addCommandParameter({ roleName }); if (internal) { roleArnToAssume = - `arn:aws:iam::${Constants.INTERNAL_SERVICES_ACCOUNT_ID}:role/scality-internal/${roleName}`; + `arn:aws:iam::${constants.INTERNAL_SERVICES_ACCOUNT_ID}:role/scality-internal/${roleName}`; } else { const role = await IAM.getRole(this.getCommandParameters()); if (role.err) { @@ -683,7 +683,7 @@ export default class Zenko extends World { ); let account = null; // Waiting until the account exists, in case of parallel mode. - let remaining = Constants.MAX_ACCOUNT_CHECK_RETRIES; + let remaining = constants.MAX_ACCOUNT_CHECK_RETRIES; account = await SuperAdmin.getAccount({ accountName }); while (!account && remaining > 0) { await Utils.sleep(500); @@ -918,7 +918,7 @@ export default class Zenko extends World { const axiosConfig: AxiosRequestConfig = { method, url: `${protocol}s3.${this.parameters.subdomain - || Constants.DEFAULT_SUBDOMAIN}${path}`, + || constants.DEFAULT_SUBDOMAIN}${path}`, headers, data: payload, }; @@ -943,7 +943,7 @@ export default class Zenko extends World { createS3Client(): S3Client { const credentials = Identity.getCurrentCredentials(); const protocol = this.parameters.ssl === false ? 'http' : 'https'; - const subdomain = this.parameters.subdomain || Constants.DEFAULT_SUBDOMAIN; + const subdomain = this.parameters.subdomain || constants.DEFAULT_SUBDOMAIN; return new S3Client({ region: 'us-east-1', @@ -992,7 +992,7 @@ export default class Zenko extends World { this.parameters.KeycloakHost || 'keycloak.zenko.local', this.parameters.KeycloakPort || '80', `/auth/realms/${this.parameters.KeycloakRealm || 'zenko'}/protocol/openid-connect/token`, - this.parameters.KeycloakClientId || Constants.K_CLIENT, + this.parameters.KeycloakClientId || constants.K_CLIENT, this.parameters.KeycloakGrantType || 'password', ); const axiosInstance = axios.create(); @@ -1004,7 +1004,7 @@ export default class Zenko extends World { }; const axiosConfig: AxiosRequestConfig = { method, - url: `${protocol}management.${this.parameters.subdomain || Constants.DEFAULT_SUBDOMAIN}/api/v1${path}`, + url: `${protocol}management.${this.parameters.subdomain || constants.DEFAULT_SUBDOMAIN}/api/v1${path}`, headers, data: payload, }; From 3d8d3c01b89f0e9b1532157796f8732ef00c900b Mon Sep 17 00:00:00 2001 From: sylvain senechal Date: Thu, 1 Oct 2026 18:55:37 +0200 Subject: [PATCH 2/5] Replace cli-testing Utils with local sleep, randomString and isAccessKeys helpers Issue: ZENKO-5372 --- tests/functional/ctst/common/common.ts | 20 ++++++++-------- tests/functional/ctst/common/utils.ts | 20 +++++++++++++--- tests/functional/ctst/steps/azureArchive.ts | 10 ++++---- .../ctst/steps/bucket-policies/common.ts | 18 +++++++------- .../functional/ctst/steps/cloudserverAuth.ts | 5 ++-- tests/functional/ctst/steps/crrCascade.ts | 24 +++++++++---------- .../ctst/steps/iam-policies/IAMUser.ts | 6 ++--- .../ctst/steps/lifecycleExpiration.ts | 6 ++--- .../functional/ctst/steps/mongodbExporter.ts | 7 +++--- tests/functional/ctst/steps/notifications.ts | 9 +++---- tests/functional/ctst/steps/otel-tracing.ts | 5 ++-- tests/functional/ctst/steps/pra.ts | 14 +++++------ tests/functional/ctst/steps/quotas/quotas.ts | 6 ++--- tests/functional/ctst/steps/replication.ts | 6 ++--- tests/functional/ctst/steps/sosapi.ts | 5 ++-- tests/functional/ctst/steps/utils/kafka.ts | 9 +++---- .../functional/ctst/steps/utils/kubernetes.ts | 13 +++++----- tests/functional/ctst/steps/utils/utils.ts | 23 ++++++++---------- .../functional/ctst/steps/website/website.ts | 7 +++--- tests/functional/ctst/world/Zenko.ts | 24 +++++++++---------- 20 files changed, 127 insertions(+), 110 deletions(-) diff --git a/tests/functional/ctst/common/common.ts b/tests/functional/ctst/common/common.ts index 0dc284ded6..715060f5ac 100644 --- a/tests/functional/ctst/common/common.ts +++ b/tests/functional/ctst/common/common.ts @@ -1,8 +1,8 @@ import { ListObjectVersionsOutput } from '@aws-sdk/client-s3'; import { Given, setDefaultTimeout, Then, When } from '@cucumber/cucumber'; -import { CacheHelper, Identity, IdentityEnum, S3, Utils } from 'cli-testing'; +import { CacheHelper, Identity, IdentityEnum, S3 } from 'cli-testing'; import Zenko from 'world/Zenko'; -import { parseGoDuration, safeJsonParse } from './utils'; +import { parseGoDuration, safeJsonParse, sleep, randomString } from './utils'; import assert from 'assert'; import { Admin } from '@platformatic/kafka'; import { @@ -71,7 +71,7 @@ async function addMultipleObjects(this: Zenko, numberObjects: number, for (let i = 1; i <= numberObjects; i++) { this.resetCommand(); const objectNameFinal = getObjectNameWithBackendFlakiness.call(this, `${objectName}-${i}`) || - Utils.randomString(); + randomString(); if (sizeBytes > 0) { this.addToSaved('objectSize', sizeBytes); } @@ -146,14 +146,14 @@ Given('a {string} bucket with dot', async function (this: Zenko, versioning: str const preName = this.getSaved('accountName') || this.parameters.AccountName || constants.ACCOUNT_NAME; await createBucket(this, versioning, - `${preName}.${constants.BUCKET_NAME_TEST}${Utils.randomString()}`.toLocaleLowerCase()); + `${preName}.${constants.BUCKET_NAME_TEST}${randomString()}`.toLocaleLowerCase()); }); Given('a {string} bucket', async function (this: Zenko, versioning: string) { const preName = this.getSaved('accountName') || this.parameters.AccountName || constants.ACCOUNT_NAME; await createBucket(this, versioning, - `${preName}${constants.BUCKET_NAME_TEST}${Utils.randomString()}`.toLocaleLowerCase()); + `${preName}${constants.BUCKET_NAME_TEST}${randomString()}`.toLocaleLowerCase()); }); Given('an existing bucket {string} {string} versioning, {string} ObjectLock {string} retention mode', async function @@ -381,7 +381,7 @@ Then('kafka consumed messages should not take too much place on disk', { timeout break; } remainingTopics = nextTopics; - await Utils.sleep(5000); + await sleep(5000); } } finally { await kafkaAdmin.close(); @@ -431,16 +431,16 @@ When('the user tries to perform the current S3 action on the bucket {int} times for (let i = 0; i < numberOfRuns; i++) { // For repeated WRITE actions, we want to change the object name if (action.action === 'PutObject') { - this.addToSaved('objectName', `objectrepeat-${Utils.randomString()}`); + this.addToSaved('objectName', `objectrepeat-${randomString()}`); } else if (action.action === 'CopyObject') { - this.addToSaved('copyObject', `objectrepeatcopy-${Utils.randomString()}`); + this.addToSaved('copyObject', `objectrepeatcopy-${randomString()}`); } await runActionAgainstBucket(this, this.getSaved('currentAction').action); if (this.getResult().err && this.getResult().retryable?.throttling !== true) { this.logger.debug('Error during repeated action', { error: this.getResult().err }); break; } - await Utils.sleep(delay); + await sleep(delay); } }); @@ -492,6 +492,6 @@ When('I PUT an object with size {int}', async function (this: Zenko, size: numbe this.addToSaved('objectSize', size); } const result = await addMultipleObjects.call( - this, 1, `object-${Utils.randomString()}`, size); + this, 1, `object-${randomString()}`, size); this.setResult(result!); }); diff --git a/tests/functional/ctst/common/utils.ts b/tests/functional/ctst/common/utils.ts index ef3ad2f7e7..2bc8a20ef1 100644 --- a/tests/functional/ctst/common/utils.ts +++ b/tests/functional/ctst/common/utils.ts @@ -1,7 +1,8 @@ import assert from 'assert'; import { exec } from 'child_process'; import http from 'http'; -import { createHash } from 'crypto'; +import { createHash, randomBytes } from 'crypto'; +import { setTimeout as sleep } from 'timers/promises'; import { Command, IAM, Identity, IdentityEnum } from 'cli-testing'; import { AttachedPolicy, @@ -15,10 +16,23 @@ import Zenko from 'world/Zenko'; import fs from 'fs'; import { runOnceAcrossWorkers } from 'common/WorkerCoordination'; import { ITestCaseHookParameter } from '@cucumber/cucumber'; -import { AWSCredentials, Utils } from 'cli-testing'; +import { AWSCredentials } from 'cli-testing'; import { createBucketWithConfiguration, putObject } from '../steps/utils/utils'; import { createJobAndWaitForCompletion } from '../steps/utils/kubernetes'; +export { sleep }; + +export function randomString(): string { + return randomBytes(5).toString('hex'); +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function isAccessKeys(object: any): object is AWSCredentials { + const isValidCredentials = + object.AccessKeyId && object.SecretAccessKey && object.AccessKeyId !== '' && object.SecretAccessKey !== ''; + return isValidCredentials && object.SessionToken ? object.SessionToken !== '' : true; +} + export function assertBudgetFitsStepTimeout(budgetSeconds: number, stepTimeoutMs: number): void { assert.ok( budgetSeconds * 1000 < stepTimeoutMs, @@ -417,7 +431,7 @@ export async function prepareMetricsScenarios( } await createJobAndWaitForCompletion(world, jobName, jobNamespace); - await Utils.sleep(2000); + await sleep(2000); fs.writeFileSync(dataFile, JSON.stringify(output)); }, ); diff --git a/tests/functional/ctst/steps/azureArchive.ts b/tests/functional/ctst/steps/azureArchive.ts index 2da28375a1..44f44cb7c2 100644 --- a/tests/functional/ctst/steps/azureArchive.ts +++ b/tests/functional/ctst/steps/azureArchive.ts @@ -1,9 +1,9 @@ import fs from 'fs'; import path from 'path'; import assert from 'assert'; -import { safeJsonParse, request } from '../common/utils'; +import { safeJsonParse, request, sleep } from '../common/utils'; import { Given, Then, When } from '@cucumber/cucumber'; -import { S3, Utils } from 'cli-testing'; +import { S3 } from 'cli-testing'; import constants from 'common/constants'; import util from 'util'; import { exec } from 'child_process'; @@ -57,7 +57,7 @@ async function isObjectRehydrated(zenko: Zenko, objectName: string) { if (found) { return tarName; } - await Utils.sleep(1000); + await sleep(1000); } return undefined; } @@ -332,7 +332,7 @@ Then('the storage class of object {string} must stay {string} for {int} seconds' if (head?.StorageClass !== expectedClass) { break; } - await Utils.sleep(1000); + await sleep(1000); secondsPassed++; } assert(secondsPassed === seconds); @@ -361,7 +361,7 @@ When('i run sorbetctl to retry failed restore for {string} location', When('i wait for {int} days', { timeout: 10 * 60 * 1000 }, async function (this: Zenko, days: number) { const realTimeDay = days * 24 * 60 * 60 * 1000 / (this.parameters.TimeProgressionFactor > 1 ? this.parameters.TimeProgressionFactor : 1); - await Utils.sleep(realTimeDay); + await sleep(realTimeDay); }); Then('object {string} should expire in {int} days', async function (this: Zenko, objectName: string, days: number) { diff --git a/tests/functional/ctst/steps/bucket-policies/common.ts b/tests/functional/ctst/steps/bucket-policies/common.ts index 6ed5524a95..e499f83865 100644 --- a/tests/functional/ctst/steps/bucket-policies/common.ts +++ b/tests/functional/ctst/steps/bucket-policies/common.ts @@ -19,8 +19,8 @@ import { runActionAgainstBucket, } from 'steps/utils/utils'; import assert from 'assert'; -import { IAM, Identity, S3, Utils } from 'cli-testing'; -import { extractPropertyFromResults } from 'common/utils'; +import { IAM, Identity, S3 } from 'cli-testing'; +import { extractPropertyFromResults, randomString } from 'common/utils'; Given('an action {string}', function (this: Zenko, apiName: string) { // dynamically know the config based on the action @@ -52,7 +52,7 @@ Given('an existing bucket prepared for the action', async function (this: Zenko) this.getSaved('withObjectLock'), this.getSaved('retentionMode')); if (this.getSaved('preExistingObject')) { - await putObject(this, `objectforbptests-${Utils.randomString()}`); + await putObject(this, `objectforbptests-${randomString()}`); } }); @@ -139,7 +139,7 @@ Given('an {string} IAM Policy that {string} with {string} effect for the current } const createdPolicy = await IAM.createPolicy({ policyDocument: JSON.stringify(basePolicy), - policyName: `policyforauthz-${Utils.randomString()}`, + policyName: `policyforauthz-${randomString()}`, }); const policyArn = extractPropertyFromResults(createdPolicy, 'Policy', 'Arn'); @@ -347,7 +347,7 @@ Given('an environment setup for the API', async function (this: Zenko) { }; const createdPolicy = await IAM.createPolicy({ policyDocument: JSON.stringify(basePolicy), - policyName: `policyforauthz-${Utils.randomString()}`, + policyName: `policyforauthz-${randomString()}`, }); const policyArn = extractPropertyFromResults(createdPolicy, 'Policy', 'Arn'); @@ -378,7 +378,7 @@ Given('an environment setup for the API', async function (this: Zenko) { case 'CompleteMultipartUpload': case 'AbortMultipartUpload': case 'UploadPart': - const objectKey = `multipartUpload-${Utils.randomString()}`; + const objectKey = `multipartUpload-${randomString()}`; const initiateMPUResult = await S3.createMultipartUpload({ bucket: this.getSaved('bucketName'), key: objectKey, @@ -389,11 +389,11 @@ Given('an environment setup for the API', async function (this: Zenko) { break; case 'UploadPartCopy': // create an object to copy from - const copyObjectKey = `objectforcopy-${Utils.randomString()}`; + const copyObjectKey = `objectforcopy-${randomString()}`; await putObject(this, copyObjectKey); this.addToSaved('objectName', copyObjectKey); // create an object for the MPU as copyObject - const objectKeyCopy = `multipartUpload-${Utils.randomString()}`; + const objectKeyCopy = `multipartUpload-${randomString()}`; const initiateMPUResultCopy = await S3.createMultipartUpload({ bucket: this.getSaved('bucketName'), key: objectKeyCopy, @@ -423,7 +423,7 @@ Given('an environment setup for the API', async function (this: Zenko) { this.addCommandParameter({ bypassGovernanceRetention: 'true' }); break; case 'CreateMultipartUpload': - this.addToSaved('objectName', `objectforbptests-${Utils.randomString()}`); + this.addToSaved('objectName', `objectforbptests-${randomString()}`); this.addCommandParameter({ key: this.getSaved('objectName') }); break; default: diff --git a/tests/functional/ctst/steps/cloudserverAuth.ts b/tests/functional/ctst/steps/cloudserverAuth.ts index 060c559ed7..4fa9ae8e02 100644 --- a/tests/functional/ctst/steps/cloudserverAuth.ts +++ b/tests/functional/ctst/steps/cloudserverAuth.ts @@ -1,6 +1,7 @@ import { When, Then } from '@cucumber/cucumber'; import Zenko from '../world/Zenko'; -import { Utils, S3 } from 'cli-testing'; +import { S3 } from 'cli-testing'; +import { randomString } from 'common/utils'; import constants from 'common/constants'; import { strict as assert } from 'assert'; @@ -38,7 +39,7 @@ When('the user tries to perform CreateBucket', async function (this: Zenko) { this.useSavedIdentity(); const preName = this.getSaved('accountName') || this.parameters.AccountName || constants.ACCOUNT_NAME; - const usedBucketName = `${preName}${constants.BUCKET_NAME_TEST}${Utils.randomString()}`.toLocaleLowerCase(); + const usedBucketName = `${preName}${constants.BUCKET_NAME_TEST}${randomString()}`.toLocaleLowerCase(); this.addToSaved('bucketName', usedBucketName); this.addCommandParameter({ bucket: usedBucketName }); // Empty strings are used to pass parameters that are used as a flag and do not require a value diff --git a/tests/functional/ctst/steps/crrCascade.ts b/tests/functional/ctst/steps/crrCascade.ts index 38810a7efc..dbc706a0af 100644 --- a/tests/functional/ctst/steps/crrCascade.ts +++ b/tests/functional/ctst/steps/crrCascade.ts @@ -10,8 +10,8 @@ import { StorageClass, } from '@aws-sdk/client-s3'; import assert from 'assert'; -import { assertBudgetFitsStepTimeout } from 'common/utils'; -import { Identity, IdentityEnum, Utils } from 'cli-testing'; +import { assertBudgetFitsStepTimeout, sleep, randomString } from 'common/utils'; +import { Identity, IdentityEnum } from 'cli-testing'; import Zenko from 'world/Zenko'; const REPLICATE_STEP_TIMEOUT_MS = 300_000; @@ -30,7 +30,7 @@ export interface CRRAccountInfo { } Given('a versioned bucket exists in location {string}', async function (this: Zenko, location: string) { - const bucket = `cascade-${Utils.randomString().toLowerCase()}`; + const bucket = `cascade-${randomString().toLowerCase()}`; Identity.useIdentity(IdentityEnum.ACCOUNT, location); // Persist the identity so the default After-hook cleanup targets the // same account that owns the buckets created here (and by later steps). @@ -80,7 +80,7 @@ async function putCascadeObject( key: string, bodySize = 0, ): Promise { - const marker = Utils.randomString().toLowerCase(); + const marker = randomString().toLowerCase(); await client.send(new PutObjectCommand({ Bucket: bucket, Key: key, @@ -104,7 +104,7 @@ When('an object {string} of {int} bytes is put in location {string}', When('tags are put on the object {string} in location {string}', async function (this: Zenko, objectName: string, location: string) { const cascadeBuckets = this.getSaved>('cascadeBuckets'); - const tagValue = Utils.randomString().toLowerCase(); + const tagValue = randomString().toLowerCase(); Identity.useIdentity(IdentityEnum.ACCOUNT, location); await this.createS3Client().send(new PutObjectTaggingCommand({ Bucket: cascadeBuckets[location], @@ -135,7 +135,7 @@ Then( if (found) { return; } - await Utils.sleep(POLL_MS); + await sleep(POLL_MS); } assert.fail( `Timeout: tag 'cascade-test-tag=${tagValue}' not found at '${location}' after ${timeoutSeconds}s`, @@ -162,7 +162,7 @@ Then( ); lastStatus = res.ReplicationStatus ?? 'unset'; if (res.ReplicationStatus === 'PENDING') { - await Utils.sleep(POLL_MS); + await sleep(POLL_MS); continue; } assert.strictEqual( @@ -180,7 +180,7 @@ Then( throw err; } lastStatus = 'not found'; - await Utils.sleep(POLL_MS); + await sleep(POLL_MS); } } assert.fail( @@ -211,7 +211,7 @@ Then( `Object at '${location}' was found with ReplicationStatus=PENDING, ` + 'indicating the cascade loop wrote back to the source.', ); - await Utils.sleep(STABILITY_INTERVAL_MS); + await sleep(STABILITY_INTERVAL_MS); } }, ); @@ -257,7 +257,7 @@ Then( if (unsettled === undefined) { return; } - await Utils.sleep(POLL_MS); + await sleep(POLL_MS); } assert.fail(`cascade states did not settle within ${settleSeconds}s. ${unsettled}`); }, @@ -270,7 +270,7 @@ Then( assertBudgetFitsStepTimeout(holdSeconds, HOLD_STEP_TIMEOUT_MS); const deadline = Date.now() + holdSeconds * 1000; while (Date.now() < deadline) { - await Utils.sleep(STABILITY_INTERVAL_MS); + await sleep(STABILITY_INTERVAL_MS); const changed = await cascadeUnsettledReason(this); assert.strictEqual( changed, undefined, @@ -347,7 +347,7 @@ Then( ); return; } - await Utils.sleep(POLL_MS); + await sleep(POLL_MS); } assert.fail(`Cascade locations did not converge to the same marker within ${timeoutSeconds}s`); diff --git a/tests/functional/ctst/steps/iam-policies/IAMUser.ts b/tests/functional/ctst/steps/iam-policies/IAMUser.ts index 2be3ac7462..cda6825e76 100644 --- a/tests/functional/ctst/steps/iam-policies/IAMUser.ts +++ b/tests/functional/ctst/steps/iam-policies/IAMUser.ts @@ -1,7 +1,7 @@ import { Given } from '@cucumber/cucumber'; -import { IAM, Identity, Utils } from 'cli-testing'; +import { IAM, Identity } from 'cli-testing'; import constants from 'common/constants'; -import { extractPropertyFromResults } from '../../common/utils'; +import { extractPropertyFromResults, randomString } from '../../common/utils'; import Zenko from 'world/Zenko'; Given('an IAM policy attached to the entity {string} with {string} effect to perform {string} {string} on {string}', @@ -10,7 +10,7 @@ Given('an IAM policy attached to the entity {string} with {string} effect to per this.resetCommand(); this.addToSaved('action', action); // create the IAM policy - this.addCommandParameter({ policyName: `${constants.POLICY_NAME_TEST}${Utils.randomString()}` }); + this.addCommandParameter({ policyName: `${constants.POLICY_NAME_TEST}${randomString()}` }); this.addCommandParameter({ policyDocument: JSON.stringify({ Version: '2012-10-17', diff --git a/tests/functional/ctst/steps/lifecycleExpiration.ts b/tests/functional/ctst/steps/lifecycleExpiration.ts index 09f8c82494..c733a2032f 100644 --- a/tests/functional/ctst/steps/lifecycleExpiration.ts +++ b/tests/functional/ctst/steps/lifecycleExpiration.ts @@ -1,9 +1,9 @@ import { Then, When } from '@cucumber/cucumber'; -import { S3, Utils } from 'cli-testing'; +import { S3 } from 'cli-testing'; import { ListObjectVersionsOutput } from '@aws-sdk/client-s3'; import assert from 'assert'; import Zenko from 'world/Zenko'; -import { safeJsonParse } from 'common/utils'; +import { safeJsonParse, sleep } from 'common/utils'; import { addExpirationWorkflow } from './utils/utils'; When('i set a lifecycle expiration of {int} days for the {string}', @@ -31,7 +31,7 @@ Then('the bucket should contain {int} objects within {int} seconds', { timeout: if (count === expectedCount) { return; } - await Utils.sleep(2000); + await sleep(2000); } while (Date.now() < deadline); assert.fail( `Bucket ${bucketName} has ${count} versions/delete markers, ` + diff --git a/tests/functional/ctst/steps/mongodbExporter.ts b/tests/functional/ctst/steps/mongodbExporter.ts index 40f9c16f73..59823f04db 100644 --- a/tests/functional/ctst/steps/mongodbExporter.ts +++ b/tests/functional/ctst/steps/mongodbExporter.ts @@ -1,5 +1,6 @@ import { Then } from '@cucumber/cucumber'; -import { Utils } from 'cli-testing'; +import { sleep } from 'common/utils'; + import { PrometheusDriver } from 'prometheus-query'; import assert from 'assert'; import Zenko from 'world/Zenko'; @@ -36,7 +37,7 @@ Then( `Metric ${metric} never appeared (no series in /api/v1/query)`, ); } - await Utils.sleep(POLL_MS); + await sleep(POLL_MS); } }, ); @@ -54,7 +55,7 @@ Then( `Expected ${metric} to be absent, got ${res.result.length} series`, ); if (i < STABILITY_CHECKS - 1) { - await Utils.sleep(STABILITY_INTERVAL_MS); + await sleep(STABILITY_INTERVAL_MS); } } }, diff --git a/tests/functional/ctst/steps/notifications.ts b/tests/functional/ctst/steps/notifications.ts index 4c94c9eea0..ff4a5ff558 100644 --- a/tests/functional/ctst/steps/notifications.ts +++ b/tests/functional/ctst/steps/notifications.ts @@ -1,6 +1,7 @@ import { Then, Given, When } from '@cucumber/cucumber'; import { strict as assert } from 'assert'; -import { S3, Utils, AWSVersionObject, NotificationDestination } from 'cli-testing'; +import { S3, AWSVersionObject, NotificationDestination } from 'cli-testing'; +import { randomString } from 'common/utils'; import { Consumer, stringDeserializers } from '@platformatic/kafka'; import Zenko from 'world/Zenko'; import { putObject } from './utils/utils'; @@ -41,7 +42,7 @@ interface QueueConfiguration { async function copyObject(world: Zenko, sourceObject: string) { await putObject(world, sourceObject); world.resetCommand(); - let objName = `notif-s3:objectcreated:copy-target-${Utils.randomString()}`.toLocaleLowerCase(); + let objName = `notif-s3:objectcreated:copy-target-${randomString()}`.toLocaleLowerCase(); if (world.getSaved('filterType')) { objName = world.getSaved('filterType') === 'prefix' ? `${world.getSaved('objectNamePrefix') }${objName}` : @@ -269,7 +270,7 @@ When('a {string} event is triggered {string} {string}', async function (this: Zenko, notificationType: string, enable: string, filterType: string) { this.resetCommand(); this.addToSaved('notificationEventType', notificationType); - let objName = `notif-${notificationType}-${enable}-${filterType}-${Utils.randomString()}`.toLocaleLowerCase(); + let objName = `notif-${notificationType}-${enable}-${filterType}-${randomString()}`.toLocaleLowerCase(); if (enable === 'with') { this.addToSaved('filterType', filterType); objName = filterType === 'prefix' ? `${this.getSaved('objectNamePrefix')}${objName}` : @@ -322,7 +323,7 @@ Then('notifications should be enabled for {string} event in destination {int}', Then('i should {string} a notification for {string} event in destination {int}', async function (this: Zenko, receive: string, notificationType: string, destination: number) { const { topic, hosts } = this.getSaved('notificationDestinations')[destination]; - const groupId = `ctst_kafka_consumer_group_${Utils.randomString()}`; + const groupId = `ctst_kafka_consumer_group_${randomString()}`; const consumer = new Consumer({ clientId: groupId, diff --git a/tests/functional/ctst/steps/otel-tracing.ts b/tests/functional/ctst/steps/otel-tracing.ts index ceb42329e6..dd8d5d56be 100644 --- a/tests/functional/ctst/steps/otel-tracing.ts +++ b/tests/functional/ctst/steps/otel-tracing.ts @@ -1,8 +1,9 @@ import { Then, When } from '@cucumber/cucumber'; +import { sleep } from 'common/utils'; import { strict as assert } from 'assert'; import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3'; import { randomBytes } from 'crypto'; -import { Utils } from 'cli-testing'; + import Zenko from 'world/Zenko'; const JAEGER_POLL_TIMEOUT = 30000; @@ -66,7 +67,7 @@ async function pollJaegerForTrace( } catch (err) { lastError = err as Error; } - await Utils.sleep(intervalMs); + await sleep(intervalMs); } throw new Error( diff --git a/tests/functional/ctst/steps/pra.ts b/tests/functional/ctst/steps/pra.ts index dd9815c3ce..df94db44b6 100644 --- a/tests/functional/ctst/steps/pra.ts +++ b/tests/functional/ctst/steps/pra.ts @@ -13,9 +13,9 @@ import { restoreObject, verifyObjectLocation, } from 'steps/utils/utils'; -import { CacheHelper, Identity, IdentityEnum, SuperAdmin, Utils } from 'cli-testing'; +import { CacheHelper, Identity, IdentityEnum, SuperAdmin } from 'cli-testing'; import constants from 'common/constants'; -import { safeJsonParse } from 'common/utils'; +import { safeJsonParse, sleep } from 'common/utils'; import { PrometheusDriver } from 'prometheus-query'; import assert from 'assert'; import { EntityType } from 'world/Zenko'; @@ -142,7 +142,7 @@ async function waitForPhase( world.logger.debug('Failed to parse DR status, retrying', { parsedStatus, }); - await Utils.sleep(1000); + await sleep(1000); continue; } @@ -160,7 +160,7 @@ async function waitForPhase( if (phase === state) { return true; } - await Utils.sleep(1000); + await sleep(1000); } return false; @@ -293,7 +293,7 @@ When('the DATA_ACCESSOR user tries to perform PutObject on {string} site', { tim this.logger.error('Failed to setup entity', { err }); } if (!conditionOk) { - await Utils.sleep(1000); + await sleep(1000); } } @@ -331,7 +331,7 @@ Then('prometheus should scrap federated metrics from DR sink', { timeout: 180000 break; } - await Utils.sleep(1000); + await sleep(1000); } }); @@ -411,7 +411,7 @@ Given('access keys for the replicated account', { timeout: 360000 }, async () => accountName: targetAccount, }); while (!account && remaining > 0) { - await Utils.sleep(500); + await sleep(500); account = await SuperAdmin.getAccount({ accountName: targetAccount, }); diff --git a/tests/functional/ctst/steps/quotas/quotas.ts b/tests/functional/ctst/steps/quotas/quotas.ts index 00c05522f6..29df76b5d4 100644 --- a/tests/functional/ctst/steps/quotas/quotas.ts +++ b/tests/functional/ctst/steps/quotas/quotas.ts @@ -1,7 +1,7 @@ import { Given, When, ITestCaseHookParameter } from '@cucumber/cucumber'; import Zenko, { EntityType } from '../../world/Zenko'; -import { Scality, Command, Utils } from 'cli-testing'; -import { prepareMetricsScenarios } from '../../common/utils'; +import { Scality, Command } from 'cli-testing'; +import { prepareMetricsScenarios, sleep } from '../../common/utils'; import assert from 'assert'; export async function prepareQuotaScenarios(world: Zenko, scenarioConfiguration: ITestCaseHookParameter) { @@ -112,5 +112,5 @@ Given('an account quota set to {int} B', async function (this: Zenko, quota: num }); When('I wait {int} seconds', async (seconds: number) => { - await Utils.sleep(seconds * 1000); + await sleep(seconds * 1000); }); diff --git a/tests/functional/ctst/steps/replication.ts b/tests/functional/ctst/steps/replication.ts index 30950c2580..46c73337a6 100644 --- a/tests/functional/ctst/steps/replication.ts +++ b/tests/functional/ctst/steps/replication.ts @@ -6,12 +6,12 @@ import { PutBucketVersioningCommand, S3Client, } from '@aws-sdk/client-s3'; -import { IdentityEnum, Identity, S3, Utils } from 'cli-testing'; +import { IdentityEnum, Identity, S3 } from 'cli-testing'; import assert from 'assert'; import Zenko from '../world/Zenko'; import { createAndRunPod, getLocationConfigs, getZenkoVersion } from 'steps/utils/kubernetes'; import { getObject, headObject, putBucketReplicationRaw } from 'steps/utils/utils'; -import { assertBudgetFitsStepTimeout, safeJsonParse } from 'common/utils'; +import { assertBudgetFitsStepTimeout, safeJsonParse, randomString } from 'common/utils'; import { replicationLockTags } from 'common/hooks'; import { CRRAccountInfo } from './crrCascade'; @@ -314,7 +314,7 @@ When('the job to replicate existing objects with status {string} is executed', apiVersion: 'v1', kind: 'Pod', metadata: { - name: `s3utils-crr-existing-${Utils.randomString().toLowerCase()}`, + name: `s3utils-crr-existing-${randomString().toLowerCase()}`, namespace: 'default', labels: { app: 's3utils', diff --git a/tests/functional/ctst/steps/sosapi.ts b/tests/functional/ctst/steps/sosapi.ts index bc4e223e24..dd115de58a 100644 --- a/tests/functional/ctst/steps/sosapi.ts +++ b/tests/functional/ctst/steps/sosapi.ts @@ -1,7 +1,8 @@ import Zenko from 'world/Zenko'; import { Then, When } from '@cucumber/cucumber'; import { strict as assert } from 'assert'; -import { CacheHelper, S3, Utils } from 'cli-testing'; +import { CacheHelper, S3 } from 'cli-testing'; +import { randomString } from 'common/utils'; import { deleteFile, saveAsFile } from './utils/utils'; const validSystemXml = ` @@ -58,7 +59,7 @@ When('I PUT the {string} {string} XML file', } else { objectBody = (isValidObject === 'valid') ? validCapacityXml : invalidCapacityXml; } - const tempFileName = `${Utils.randomString()}_${objectKey}`; + const tempFileName = `${randomString()}_${objectKey}`; this.addToSaved('tempFileName', `/tmp/${tempFileName}`); await saveAsFile(tempFileName, objectBody); this.addCommandParameter({ body: this.getSaved('tempFileName') }); diff --git a/tests/functional/ctst/steps/utils/kafka.ts b/tests/functional/ctst/steps/utils/kafka.ts index 64548ee351..e9200e79ec 100644 --- a/tests/functional/ctst/steps/utils/kafka.ts +++ b/tests/functional/ctst/steps/utils/kafka.ts @@ -1,5 +1,6 @@ -import { Utils } from 'cli-testing'; + import { Consumer, stringDeserializers } from '@platformatic/kafka'; +import { sleep, randomString } from 'common/utils'; export interface DLQMessage { op: string; @@ -29,8 +30,8 @@ export async function startDLQConsumer( onMessage: (msg: DLQMessage) => void, ): Promise { const consumer = new Consumer({ - clientId: `zenko-e2e-dlq-${Utils.randomString()}`, - groupId: `zenko-e2e-dlq-${Utils.randomString()}`, + clientId: `zenko-e2e-dlq-${randomString()}`, + groupId: `zenko-e2e-dlq-${randomString()}`, bootstrapBrokers: [kafkaHosts], deserializers: stringDeserializers, }); @@ -88,7 +89,7 @@ export async function waitForDLQMessage( seenMessages.add(msg.requestId); return msg; } - await Utils.sleep(500); + await sleep(500); } throw new Error( `DLQ: no "${op}" message for object "${objectName}" in bucket "${bucketName}" ` + diff --git a/tests/functional/ctst/steps/utils/kubernetes.ts b/tests/functional/ctst/steps/utils/kubernetes.ts index 902b77e70d..7f6158e261 100644 --- a/tests/functional/ctst/steps/utils/kubernetes.ts +++ b/tests/functional/ctst/steps/utils/kubernetes.ts @@ -1,6 +1,7 @@ import fs from 'fs'; import * as path from 'path'; -import { KubernetesHelper, Utils } from 'cli-testing'; +import { KubernetesHelper } from 'cli-testing'; +import { sleep, randomString } from 'common/utils'; import Zenko from 'world/Zenko'; import { V1Job, @@ -201,7 +202,7 @@ export async function createJobAndWaitForCompletion( waitedSec: Math.round((Date.now() - lockStart) / 1000), }); } - await Utils.sleep(1000); + await sleep(1000); } } @@ -226,7 +227,7 @@ export async function createJobAndWaitForCompletion( job.apiVersion = 'batch/v1'; job.kind = 'Job'; job.spec = cronJobSpec; - metadata.name = `${jobName}-${Utils.randomString().toLowerCase()}`; + metadata.name = `${jobName}-${randomString().toLowerCase()}`; metadata.annotations = { 'cronjob.kubernetes.io/instantiate': 'ctst', }; @@ -412,7 +413,7 @@ export async function waitForZenkoToStabilize( }); if (!zenkoCR) { - await Utils.sleep(1000); + await sleep(1000); continue; } @@ -455,7 +456,7 @@ export async function waitForZenkoToStabilize( return; } - await Utils.sleep(1000); + await sleep(1000); } throw new Error('Zenko did not stabilize'); @@ -524,7 +525,7 @@ export async function waitForDataServicesToStabilize(world: Zenko, timeout = 15 if (allRunning) { return true; } - await Utils.sleep(1000); + await sleep(1000); } throw new Error('Data services did not stabilize'); diff --git a/tests/functional/ctst/steps/utils/utils.ts b/tests/functional/ctst/steps/utils/utils.ts index fa07ac91ea..b12bec4d26 100644 --- a/tests/functional/ctst/steps/utils/utils.ts +++ b/tests/functional/ctst/steps/utils/utils.ts @@ -5,11 +5,10 @@ import { CacheHelper, Identity, S3, - Utils, AWSVersionObject, Command, } from 'cli-testing'; -import { extractPropertyFromResults, s3FunctionExtraParams, safeJsonParse } from 'common/utils'; +import { extractPropertyFromResults, s3FunctionExtraParams, safeJsonParse, sleep, randomString } from 'common/utils'; import Zenko from 'world/Zenko'; import assert from 'assert'; import constants from 'common/constants'; @@ -41,7 +40,7 @@ export async function uploadSetup(world: Zenko, action: string, body?: string, s } const objectSize = (size ?? world.getSaved('objectSize')) || 0; if (body || objectSize > 0) { - const tempFileName = `${Utils.randomString()}_${world.getSaved('objectName')}`; + const tempFileName = `${randomString()}_${world.getSaved('objectName')}`; world.addToSaved('tempFileName', `/tmp/${tempFileName}`); const objectBody = body || 'a'.repeat(objectSize); await saveAsFile(tempFileName, objectBody); @@ -180,7 +179,7 @@ async function createBucketWithConfiguration( const preName = world.getSaved('accountName') || world.parameters.AccountName || constants.ACCOUNT_NAME; const usedBucketName = bucketName - || `${preName}${constants.BUCKET_NAME_TEST}${Utils.randomString()}`.toLocaleLowerCase(); + || `${preName}${constants.BUCKET_NAME_TEST}${randomString()}`.toLocaleLowerCase(); world.addToSaved('bucketName', usedBucketName); world.addCommandParameter({ bucket: usedBucketName }); if (withObjectLock === 'with') { @@ -210,7 +209,7 @@ async function createBucketWithConfiguration( } async function putMpuObject(world: Zenko, parts: number = 2, objectName: string, content?: string) { - const key = objectName || `${Utils.randomString()}`; + const key = objectName || `${randomString()}`; const bucket = world.getSaved('bucketName'); world.resetCommand(); @@ -291,7 +290,7 @@ async function putObject(world: Zenko, objectName?: string, content?: string, ob world.resetCommand(); let finalObjectName = objectName; if (!finalObjectName) { - finalObjectName = `${Utils.randomString()}`; + finalObjectName = `${randomString()}`; } world.addToSaved('objectName', finalObjectName); world.logger.debug('Adding object', { objectName: finalObjectName }); @@ -314,7 +313,7 @@ async function putObject(world: Zenko, objectName?: string, content?: string, ob return result; } -async function getObject(world: Zenko, objectKey: string, bucketName: string): Promise { +async function getObject(world: Zenko, objectKey: string, bucketName: string): Promise { const result = await S3.getObject({ key: objectKey, bucket: bucketName, @@ -323,7 +322,7 @@ async function getObject(world: Zenko, objectKey: string, bucketName: string): P return result; } -async function headObject(world: Zenko, objectKey: string, bucketName: string): Promise { +async function headObject(world: Zenko, objectKey: string, bucketName: string): Promise { const result = await S3.headObject({ key: objectKey, bucket: bucketName, @@ -341,7 +340,6 @@ function getAuthorizationConfiguration(world: Zenko): AuthorizationConfiguration }; } - async function emptyNonVersionedBucket(world: Zenko) { world.resetCommand(); world.addCommandParameter({ bucket: world.getSaved('bucketName') }); @@ -387,7 +385,7 @@ async function putBucketLifecycleConfigurationWithRetry(world: Zenko, rules: Rec const res = await S3.putBucketLifecycleConfiguration(commandParameters); conditionOk = res.err === null; // Wait for the configuration to be accepted because the deployment of the location's pods can take some time - await Utils.sleep(5000); + await sleep(5000); } } @@ -458,7 +456,7 @@ async function verifyObjectLocation(this: Zenko, objectName: string, const res = await S3.headObject(this.getCommandParameters()); if (res.err?.includes('NotFound')) { - await Utils.sleep(1000); + await sleep(1000); continue; } else if (res.err) { throw new Error(`HeadObject error for "${objName}": ${res.err}`); @@ -479,7 +477,7 @@ async function verifyObjectLocation(this: Zenko, objectName: string, } if (conditionOk) return; - await Utils.sleep(1000); + await sleep(1000); } throw new Error( @@ -573,7 +571,6 @@ function getObjectNameWithBackendFlakiness(this: Zenko, objectName: string) { return objectNameFinal; } - export { AuthorizationType, AuthorizationConfiguration, diff --git a/tests/functional/ctst/steps/website/website.ts b/tests/functional/ctst/steps/website/website.ts index 68c7826dc9..01da16a4e9 100644 --- a/tests/functional/ctst/steps/website/website.ts +++ b/tests/functional/ctst/steps/website/website.ts @@ -3,9 +3,10 @@ import { Given, When, Then } from '@cucumber/cucumber'; import Zenko from '../../world/Zenko'; import { putObject } from '../utils/utils'; import { waitForZenkoToStabilize, waitForDataServicesToStabilize } from '../utils/kubernetes'; -import { S3, Utils } from 'cli-testing'; +import { S3 } from 'cli-testing'; +import { sleep, randomString } from 'common/utils'; -const pageMessage = Utils.randomString(); +const pageMessage = randomString(); Given('an index html file', async function (this: Zenko) { // push a file with a basic html content named index.html in the bucket @@ -83,7 +84,7 @@ Then('the user should be able to load the index.html file from the {string} endp response, content, }); - await Utils.sleep(1000); + await sleep(1000); } } assert.fail('Failed to fetch the bucket website after 60 tries'); diff --git a/tests/functional/ctst/world/Zenko.ts b/tests/functional/ctst/world/Zenko.ts index cd38ab5379..7f00f2f82f 100644 --- a/tests/functional/ctst/world/Zenko.ts +++ b/tests/functional/ctst/world/Zenko.ts @@ -16,12 +16,11 @@ import { IdentityEnum, STS, SuperAdmin, - Utils, AWSCredentials, Logger, } from 'cli-testing'; -import { extractPropertyFromResults } from '../common/utils'; +import { extractPropertyFromResults, sleep, randomString, isAccessKeys } from '../common/utils'; import constants from '../common/constants'; import AzureClient from 'clients/azure'; import ZenkoDrctl from 'steps/dr/drctl'; @@ -187,7 +186,6 @@ export default class Zenko extends World { CacheHelper.savedAcrossTests[Zenko.PRA_INSTALL_COUNT_KEY] = 0; - if (this.parameters.AccountName && !Identity.hasIdentity(IdentityEnum.ACCOUNT, this.parameters.AccountName)) { Identity.addIdentity(IdentityEnum.ACCOUNT, this.parameters.AccountName, { accessKeyId: this.parameters.AccountAccessKey, @@ -379,7 +377,7 @@ export default class Zenko extends World { nextMarker = GRFWIResponse.data.IsTruncated ? GRFWIResponse.data.Marker : undefined; callNumber++; - await Utils.sleep(500); + await sleep(500); } while (callNumber < 100); // Ensure we can assume at least one role @@ -470,7 +468,7 @@ export default class Zenko extends World { async createAccount(name?: string, force?: boolean, adminClientName?: string): Promise { Identity.resetIdentity(); const accountName = name || this.getSaved('accountName') || - `${constants.ACCOUNT_NAME}${Utils.randomString()}`; + `${constants.ACCOUNT_NAME}${randomString()}`; if (Identity.hasIdentity(IdentityEnum.ACCOUNT, accountName) && !force) { Identity.useIdentity(IdentityEnum.ACCOUNT, accountName); return accountName; @@ -508,7 +506,7 @@ export default class Zenko extends World { const accountName = Identity.getCurrentAccountName(); // Creating a role to assume - const roleName = `${accountName}${constants.ROLE_NAME_TEST}${Utils.randomString()}`; + const roleName = `${accountName}${constants.ROLE_NAME_TEST}${randomString()}`; this.addToSaved('roleName', roleName); this.addCommandParameter({ roleName }); this.addCommandParameter({ assumeRolePolicyDocument: constants.assumeRoleTrustPolicy }); @@ -521,7 +519,7 @@ export default class Zenko extends World { if (crossAccount) { // Creating a second account if its Cross-Account AssumeRole const account2 = await SuperAdmin.createAccount({ - accountName: `${constants.ACCOUNT_NAME}${Utils.randomString()}`, + accountName: `${constants.ACCOUNT_NAME}${randomString()}`, }); // Creating credentials for the second account @@ -537,7 +535,7 @@ export default class Zenko extends World { // Creating a user in the account to be assumed from this.resetCommand(); - const userName = `${accountToBeAssumedFrom}${constants.USER_NAME_TEST}${Utils.randomString()}`; + const userName = `${accountToBeAssumedFrom}${constants.USER_NAME_TEST}${randomString()}`; this.addCommandParameter({ userName }); await IAM.createUser(this.getCommandParameters()); @@ -546,7 +544,7 @@ export default class Zenko extends World { this.addCommandParameter({ policyName: `${accountToBeAssumedFrom}` + `${constants.POLICY_NAME_TEST}` + - `${Utils.randomString()}`, + `${randomString()}`, }); this.addCommandParameter({ policyDocument: constants.assumeRolePolicy }); const assumeRolePolicyArn = @@ -686,7 +684,7 @@ export default class Zenko extends World { let remaining = constants.MAX_ACCOUNT_CHECK_RETRIES; account = await SuperAdmin.getAccount({ accountName }); while (!account && remaining > 0) { - await Utils.sleep(500); + await sleep(500); account = await SuperAdmin.getAccount({ accountName }); remaining--; } @@ -703,7 +701,7 @@ export default class Zenko extends World { if (!accountAccessKeys.accessKeyId || !accountAccessKeys.secretAccessKey) { const accessKeys = await SuperAdmin.generateAccountAccessKey({ accountName }); - if (!Utils.isAccessKeys(accessKeys)) { + if (!isAccessKeys(accessKeys)) { throw new Error('Failed to generate account access keys for site ${siteKey}'); } accountAccessKeys.accessKeyId = accessKeys.accessKeyId; @@ -728,7 +726,7 @@ export default class Zenko extends World { if (!accountAccessKeys.accessKeyId || !accountAccessKeys.secretAccessKey) { const accessKeys = await SuperAdmin.generateAccountAccessKey({ accountName }); - if (!Utils.isAccessKeys(accessKeys)) { + if (!isAccessKeys(accessKeys)) { throw new Error('Failed to generate account access keys for site ${siteKey}'); } accountAccessKeys.accessKeyId = accessKeys.accessKeyId; @@ -747,7 +745,7 @@ export default class Zenko extends World { * @returns {undefined} */ async prepareIamUser() { - const userName = `iamusertest${Utils.randomString()}`; + const userName = `iamusertest${randomString()}`; Identity.resetIdentity(); this.addToSaved('userName', userName); // Create IAM user From 39db8c9ead53751f372b3c1beddc7e5716f8cabd Mon Sep 17 00:00:00 2001 From: sylvain senechal Date: Thu, 1 Oct 2026 19:01:16 +0200 Subject: [PATCH 3/5] Fix isAccessKeys always returning true Issue: ZENKO-5372 --- tests/functional/ctst/common/utils.test.ts | 19 ++++++++++++++++++- tests/functional/ctst/common/utils.ts | 7 ++----- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/tests/functional/ctst/common/utils.test.ts b/tests/functional/ctst/common/utils.test.ts index f44245e4c4..05e0d0ab2b 100644 --- a/tests/functional/ctst/common/utils.test.ts +++ b/tests/functional/ctst/common/utils.test.ts @@ -1,5 +1,5 @@ import assert from 'assert'; -import { paramToCli, parseGoDuration } from './utils'; +import { isAccessKeys, paramToCli, parseGoDuration } from './utils'; const durations: [string, number][] = [ ['1m', 60], @@ -58,3 +58,20 @@ for (const [params, expected] of cliParams) { `paramToCli(${JSON.stringify(params)}) = "${result}", expected "${expected}"`, ); } + +const accessKeysCases: [Record, boolean][] = [ + [{ accessKeyId: 'AK', secretAccessKey: 'SK' }, true], + [{ accessKeyId: 'AK', secretAccessKey: 'SK', sessionToken: 'ST' }, true], + [{ accessKeyId: 'AK', secretAccessKey: 'SK', sessionToken: '' }, false], + [{ accessKeyId: '', secretAccessKey: 'SK' }, false], + [{ accessKeyId: 'AK' }, false], + [{}, false], +]; + +for (const [creds, expected] of accessKeysCases) { + const result = isAccessKeys(creds); + assert.strictEqual( + result, expected, + `isAccessKeys(${JSON.stringify(creds)}) = ${result}, expected ${expected}`, + ); +} diff --git a/tests/functional/ctst/common/utils.ts b/tests/functional/ctst/common/utils.ts index 2bc8a20ef1..256c8c3c92 100644 --- a/tests/functional/ctst/common/utils.ts +++ b/tests/functional/ctst/common/utils.ts @@ -26,11 +26,8 @@ export function randomString(): string { return randomBytes(5).toString('hex'); } -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function isAccessKeys(object: any): object is AWSCredentials { - const isValidCredentials = - object.AccessKeyId && object.SecretAccessKey && object.AccessKeyId !== '' && object.SecretAccessKey !== ''; - return isValidCredentials && object.SessionToken ? object.SessionToken !== '' : true; +export function isAccessKeys(object: Partial): object is AWSCredentials { + return Boolean(object.accessKeyId && object.secretAccessKey && object.sessionToken !== ''); } export function assertBudgetFitsStepTimeout(budgetSeconds: number, stepTimeoutMs: number): void { From 42df3fed42f3395dd7c68561ab78c00971574874 Mon Sep 17 00:00:00 2001 From: sylvain senechal Date: Fri, 23 Oct 2026 15:26:21 +0200 Subject: [PATCH 4/5] Deduplicate requireEnv into clients/env.ts Issue: ZENKO-5372 --- .github/scripts/end2end/setup-e2e-env.sh | 2 +- tests/functional/clients/azure.ts | 9 +-------- tests/functional/clients/env.ts | 7 +++++++ tests/functional/testResourcesSetup/config.ts | 9 +-------- 4 files changed, 10 insertions(+), 17 deletions(-) create mode 100644 tests/functional/clients/env.ts diff --git a/.github/scripts/end2end/setup-e2e-env.sh b/.github/scripts/end2end/setup-e2e-env.sh index 879ed7fadf..87a5d90986 100755 --- a/.github/scripts/end2end/setup-e2e-env.sh +++ b/.github/scripts/end2end/setup-e2e-env.sh @@ -157,7 +157,7 @@ else export STORAGE_ACCOUNT_OWNER_USER_NAME="storage_account_owner" export DATA_CONSUMER_USER_NAME="data_consumer" export DATA_ACCESSOR_USER_NAME="data_accessor" - # env vars used by cli-testing's Keycloak.ts seeder + # env vars read by tests/functional/ctst/steps/utils/keycloak.ts export ACCOUNT="${ZENKO_ACCOUNT_NAME}" export STORAGE_MANAGER="${STORAGE_MANAGER_USER_NAME}" export STORAGE_ACCOUNT_OWNER="${STORAGE_ACCOUNT_OWNER_USER_NAME}" diff --git a/tests/functional/clients/azure.ts b/tests/functional/clients/azure.ts index 11d9c4202e..026bbdd8c9 100644 --- a/tests/functional/clients/azure.ts +++ b/tests/functional/clients/azure.ts @@ -9,20 +9,13 @@ import { QueueServiceClient, StorageSharedKeyCredential as StorageQueueSharedKeyCredential, } from '@azure/storage-queue'; +import { requireEnv } from './env'; export type AzureCreds = { accountName: string; accountKey: string; }; -function requireEnv(name: string): string { - const value = process.env[name]; - if (!value) { - throw new Error(`Missing required environment variable: ${name}`); - } - return value; -} - export default class AzureClient { private readonly blobClient: BlobServiceClient; private readonly queueClient: QueueServiceClient; diff --git a/tests/functional/clients/env.ts b/tests/functional/clients/env.ts new file mode 100644 index 0000000000..8447d0ddd5 --- /dev/null +++ b/tests/functional/clients/env.ts @@ -0,0 +1,7 @@ +export function requireEnv(name: string): string { + const value = process.env[name]; + if (!value) { + throw new Error(`Missing required environment variable: ${name}`); + } + return value; +} diff --git a/tests/functional/testResourcesSetup/config.ts b/tests/functional/testResourcesSetup/config.ts index b09f820ebb..de055377ac 100644 --- a/tests/functional/testResourcesSetup/config.ts +++ b/tests/functional/testResourcesSetup/config.ts @@ -1,5 +1,6 @@ import fs from 'fs'; import { load } from 'js-yaml'; +import { requireEnv } from 'clients/env'; export interface EndpointConfig { hostname: string; @@ -43,14 +44,6 @@ export interface E2EConfig { locations: LocationConfig[]; } -function requireEnv(name: string): string { - const value = process.env[name]; - if (!value) { - throw new Error(`Missing required environment variable: ${name}`); - } - return value; -} - export interface Env { TOKEN: string; UUID: string; From 4ca47addd47884f1f93a7f89e68bdca9d958dbe6 Mon Sep 17 00:00:00 2001 From: sylvain senechal Date: Fri, 23 Oct 2026 16:09:37 +0200 Subject: [PATCH 5/5] Fix uninterpolated template strings in account access key errors Issue: ZENKO-5372 --- tests/functional/ctst/world/Zenko.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/functional/ctst/world/Zenko.ts b/tests/functional/ctst/world/Zenko.ts index 7f00f2f82f..fa050d2381 100644 --- a/tests/functional/ctst/world/Zenko.ts +++ b/tests/functional/ctst/world/Zenko.ts @@ -702,7 +702,7 @@ export default class Zenko extends World { if (!accountAccessKeys.accessKeyId || !accountAccessKeys.secretAccessKey) { const accessKeys = await SuperAdmin.generateAccountAccessKey({ accountName }); if (!isAccessKeys(accessKeys)) { - throw new Error('Failed to generate account access keys for site ${siteKey}'); + throw new Error(`Failed to generate account access keys for site ${siteKey}`); } accountAccessKeys.accessKeyId = accessKeys.accessKeyId; accountAccessKeys.secretAccessKey = accessKeys.secretAccessKey; @@ -727,7 +727,7 @@ export default class Zenko extends World { if (!accountAccessKeys.accessKeyId || !accountAccessKeys.secretAccessKey) { const accessKeys = await SuperAdmin.generateAccountAccessKey({ accountName }); if (!isAccessKeys(accessKeys)) { - throw new Error('Failed to generate account access keys for site ${siteKey}'); + throw new Error(`Failed to generate account access keys for account ${accountName}`); } accountAccessKeys.accessKeyId = accessKeys.accessKeyId; accountAccessKeys.secretAccessKey = accessKeys.secretAccessKey;