Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,18 @@
}
},
"vscode": {
"settings": {
"cucumber.features": [
"tests/ctst/features/**/*.feature"
],
"cucumber.glue": [
"tests/ctst/common/*.ts",
"tests/ctst/steps/**/*.ts"
]
},
"extensions": [
"ms-kubernetes-tools.vscode-kubernetes-tools",
"cucumberopen.cucumber-official"
]
}
},
Expand Down
3 changes: 2 additions & 1 deletion tests/ctst/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@
},
"devDependencies": {
"@aws-sdk/client-iam": "^3.901.0",
"@aws-sdk/client-s3": "^3.901.0",
"@aws-sdk/client-s3": "^3.931.0",
"@aws-sdk/client-sts": "^3.901.0",
"@eslint/compat": "^1.1.1",
"@scality/cloudserverclient": "1.0.5",
"eslint": "^9.9.1",
"eslint-config-scality": "scality/Guidelines#8.3.0",
"typescript-eslint": "^8.4.0"
Expand Down
49 changes: 7 additions & 42 deletions tests/ctst/steps/user-defined-metadata/get-object-attributes.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import assert from 'assert';
import { Then, When } from '@cucumber/cucumber';
import { parseStringPromise } from 'xml2js';
import { Identity } from 'cli-testing';
import { GetObjectAttributesExtendedCommand } from '@scality/cloudserverclient';
import Zenko from '../../world/Zenko';
import { safeJsonParse } from '../../common/utils';

Expand All @@ -15,47 +14,13 @@ async function getObjectAttributes(

const bucketName = world.getSaved<string>('bucketName');
const attributesList = attributes.split(',').map(attr => attr.trim());
const credentials = Identity.getCurrentCredentials();

let path = `/${bucketName}/${objectName}?attributes`;
if (versionId) {
path += `&versionId=${versionId}`;
}

const result = await world.awsS3Request(
'GET',
path,
{ accessKeyId: credentials.accessKeyId, secretAccessKey: credentials.secretAccessKey },
{ 'x-amz-object-attributes': attributesList.join(',') },
);

if (result.err) {
world.setResult({
stdout: '',
err: result.err,
statusCode: result.statusCode,
});
return;
}

const rawXml = result.data as string;
const parsed: Record<string, unknown> = {};

if (rawXml) {
const parsedXml = await parseStringPromise(rawXml) as Record<string, unknown>;
const parsedData = parsedXml?.GetObjectAttributesResponse;
if (parsedData && typeof parsedData === 'object') {
for (const k of Object.keys(parsedData)) {
parsed[k] = (parsedData as Record<string, string[]>)[k][0];
}
}
}

world.setResult({
stdout: JSON.stringify(parsed),
err: null,
statusCode: result.statusCode,
});
await world.sendS3Command(new GetObjectAttributesExtendedCommand({
Bucket: bucketName,
Key: objectName,
VersionId: versionId,
ObjectAttributes: attributesList,
}));
}

When('the user calls GetObjectAttributes for {string} requesting {string}', async function (
Expand Down
63 changes: 16 additions & 47 deletions tests/ctst/steps/user-defined-metadata/list-objects-v2.ts
Original file line number Diff line number Diff line change
@@ -1,60 +1,29 @@
import assert from 'assert';
import { Then, When } from '@cucumber/cucumber';
import { parseStringPromise } from 'xml2js';
import { Identity } from 'cli-testing';
import { ListObjectsV2ExtendedCommand } from '@scality/cloudserverclient';
import Zenko from '../../world/Zenko';
import { safeJsonParse } from '../../common/utils';

When('the user calls ListObjectsV2 on the bucket with optional attributes {string}', async function (
this: Zenko,
async function listObjectsV2WithOptionalAttributes(
world: Zenko,
optionalAttributes: string,
) {
this.resetCommand();

const bucketName = this.getSaved<string>('bucketName');
const attributesList = optionalAttributes.split(',').map(attr => attr.trim());
const credentials = Identity.getCurrentCredentials();

const path = `/${bucketName}?list-type=2`;
world.resetCommand();

const result = await this.awsS3Request(
'GET',
path,
{ accessKeyId: credentials.accessKeyId, secretAccessKey: credentials.secretAccessKey },
{ 'x-amz-optional-object-attributes': attributesList.join(',') },
);

if (result.err) {
this.setResult({
stdout: '',
err: result.err,
statusCode: result.statusCode,
});
return;
}
const bucketName = world.getSaved<string>('bucketName');
const optionalAttrsList = optionalAttributes.split(',').map(attr => attr.trim());

const rawXml = result.data as string;
const contents: Record<string, unknown>[] = [];
await world.sendS3Command(new ListObjectsV2ExtendedCommand({
Bucket: bucketName,
ObjectAttributes: optionalAttrsList,
}));
}

if (rawXml) {
const parsedXml = await parseStringPromise(rawXml) as Record<string, unknown>;
const listResult = parsedXml?.ListBucketResult as Record<string, unknown> | undefined;
if (listResult?.Contents) {
for (const item of listResult.Contents as Record<string, unknown[]>[]) {
const obj: Record<string, unknown> = {};
for (const k of Object.keys(item)) {
obj[k] = item[k][0];
}
contents.push(obj);
}
}
}

this.setResult({
stdout: JSON.stringify({ Contents: contents }),
err: null,
statusCode: result.statusCode,
});
When('the user calls ListObjectsV2 on the bucket with optional attributes {string}', async function (
this: Zenko,
optionalAttributes: string,
) {
await listObjectsV2WithOptionalAttributes(this, optionalAttributes);
});

Then('the ListObjectsV2 response should contain {string}', function (
Expand Down
3 changes: 0 additions & 3 deletions tests/ctst/types/xml2js.d.ts

This file was deleted.

33 changes: 32 additions & 1 deletion tests/ctst/world/Zenko.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { World, IWorldOptions, setWorldConstructor } from '@cucumber/cucumber';
import axios, { AxiosRequestConfig, AxiosResponse, Method } from 'axios';
import { AccessKey } from '@aws-sdk/client-iam';
import { S3Client, S3ServiceException } from '@aws-sdk/client-s3';
import { Credentials } from '@aws-sdk/client-sts';
import { aws4Interceptor } from 'aws4-axios';
import qs from 'qs';
Expand Down Expand Up @@ -924,8 +925,38 @@ export default class Zenko extends World<ZenkoWorldParameters> {
}
}

createS3Client(): S3Client {
const credentials = Identity.getCurrentCredentials();
const protocol = this.parameters.ssl === false ? 'http' : 'https';
const subdomain = this.parameters.subdomain || Constants.DEFAULT_SUBDOMAIN;

return new S3Client({
region: 'us-east-1',
endpoint: `${protocol}://s3.${subdomain}`,
credentials: {
accessKeyId: credentials.accessKeyId,
secretAccessKey: credentials.secretAccessKey,
},
forcePathStyle: true,
});
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
async sendS3Command(command: any): Promise<void> {
try {
const client = this.createS3Client();
const result = await client.send(command);
this.setResult({ stdout: JSON.stringify(result), err: null, statusCode: 200 });
} catch (err: unknown) {
if (err instanceof S3ServiceException) {
this.setResult({ stdout: '', err: err.name, statusCode: err.$metadata.httpStatusCode || 403 });
} else {
throw err;
}
}
}

/**
*
* @param {Method} method HTTP Method
* @param {string} path Path to the API endpoint
* @param {AxiosRequestHeaders} headers Headers to the request
Expand Down
Loading
Loading