Skip to content

Commit bb3bd7a

Browse files
committed
Add CTST tests for storage usage reporting API
Issue: ZENKO-5202
1 parent 49ee65c commit bb3bd7a

File tree

5 files changed

+201
-5
lines changed

5 files changed

+201
-5
lines changed

tests/ctst/common/hooks.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ import Zenko from '../world/Zenko';
99
import { CacheHelper, Identity } from 'cli-testing';
1010
import { prepareQuotaScenarios, teardownQuotaScenarios } from 'steps/quotas/quotas';
1111
import { prepareUtilizationScenarios } from 'steps/utilization/utilizationAPI';
12+
import {
13+
prepareStorageUsageReportingScenarios,
14+
} from 'steps/reporting/storageUsageReporting';
1215
import { cleanS3Bucket } from './common';
1316
import { cleanAzureContainer, cleanZenkoLocation } from 'steps/azureArchive';
1417
import { displayDebuggingInformation, preparePRA } from 'steps/pra';
@@ -60,6 +63,10 @@ Before({ tags: '@UtilizationAPI', timeout: 1200000 }, async function (scenarioOp
6063
await prepareUtilizationScenarios(this as Zenko, scenarioOptions);
6164
});
6265

66+
Before({ tags: '@StorageUsageReporting and @CronJob', timeout: 1200000 }, async function (scenarioOptions) {
67+
await prepareStorageUsageReportingScenarios(this as Zenko, scenarioOptions);
68+
});
69+
6370
After(async function (this: Zenko, results) {
6471
// Reset any configuration set on the endpoint (ssl, port)
6572
CacheHelper.parameters.ssl = this.parameters.ssl;
@@ -97,6 +104,16 @@ After({ tags: '@AzureArchive' }, async function (this: Zenko) {
97104
);
98105
});
99106

107+
After({ tags: '@StorageUsageReporting' }, async function (this: Zenko, results) {
108+
const additionalAccountNames = this.getSaved<string[]>('additionalAccountNames');
109+
if (results.result?.status === 'FAILED' || !additionalAccountNames) {
110+
return;
111+
}
112+
for (const accountName of additionalAccountNames) {
113+
await cleanupAccount(this, accountName);
114+
}
115+
});
116+
100117
After({ tags: '@BP-ASSUME_ROLE_USER_CROSS_ACCOUNT'}, async function (this: Zenko, results) {
101118
const crossAccountName = this.getSaved<string>('crossAccountName');
102119

tests/ctst/common/utils.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -304,10 +304,12 @@ export async function cleanupAccount(world: Zenko, accountName: string) {
304304
}
305305
}
306306

307-
interface PrepareScenarioOptions {
307+
export interface PrepareScenarioOptions {
308308
versioning?: string;
309309
jobNamespace?: string;
310310
jobName?: string;
311+
objectSize?: number;
312+
objectCount?: number;
311313
}
312314

313315
/**
@@ -330,7 +332,9 @@ export async function prepareMetricsScenarios(
330332
const {
331333
versioning = '',
332334
jobName = 'end2end-ops-count-items',
333-
jobNamespace = `${featureName}-setup`
335+
jobNamespace = `${featureName}-setup`,
336+
objectSize = 0,
337+
objectCount = 1,
334338
} = options;
335339

336340
if (!fs.existsSync(filePath)) {
@@ -365,7 +369,12 @@ export async function prepareMetricsScenarios(
365369
for (const scenarioId of scenarioIds) {
366370
await world.createAccount(scenarioId, true);
367371
await createBucketWithConfiguration(world, scenarioId, versioning);
368-
await putObject(world);
372+
for (let i = 0; i < objectCount; i++) {
373+
if (objectSize > 0) {
374+
world.addToSaved('objectSize', objectSize);
375+
}
376+
await putObject(world);
377+
}
369378
output[scenarioId] = Identity.getCurrentCredentials()!;
370379
}
371380

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
Feature: Storage Usage Reporting API
2+
The storage usage reporting API allows authorized Keycloak users to retrieve
3+
aggregated storage usage metrics across all accounts and locations.
4+
5+
@2.14.0
6+
@PreMerge
7+
@StorageUsageReporting
8+
Scenario: StorageManager can retrieve the storage usage report
9+
Given a STORAGE_MANAGER type
10+
When the user retrieves the storage usage report
11+
Then the storage usage report response status is 200
12+
And the storage usage report response has a valid structure
13+
14+
@2.14.0
15+
@PreMerge
16+
@StorageUsageReporting
17+
Scenario: No-rights Keycloak user cannot retrieve the storage usage report
18+
When the user retrieves the storage usage report as a no-rights user
19+
Then the storage usage report response status is 403
20+
21+
@2.14.0
22+
@PreMerge
23+
@StorageUsageReporting
24+
Scenario: Storage usage report contains multiple accounts
25+
Given two additional accounts
26+
And a STORAGE_MANAGER type
27+
When the user retrieves the storage usage report
28+
Then the storage usage report response status is 200
29+
And the storage usage report contains the additional accounts
30+
31+
@2.14.0
32+
@PreMerge
33+
@StorageUsageReporting
34+
@CronJob
35+
Scenario Outline: Storage usage report returns accurate metrics
36+
Given the environment is set up with bucket created, test data uploaded, and count-items ran
37+
And a STORAGE_MANAGER type
38+
When the user retrieves the storage usage report
39+
Then the storage usage report response status is 200
40+
And the storage usage report contains the test account with location "<locationName>"
41+
And the location metrics show 3 objects and 600 bytes
42+
43+
Examples:
44+
| locationName |
45+
| us-east-1 |
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import { Given, When, Then, ITestCaseHookParameter } from '@cucumber/cucumber';
2+
import { strict as assert } from 'assert';
3+
import Zenko from '../../world/Zenko';
4+
import { SuperAdmin } from 'cli-testing';
5+
import { prepareMetricsScenarios } from '../../common/utils';
6+
7+
interface LocationUsage {
8+
bytesTotal: number;
9+
objectsTotal: number;
10+
}
11+
12+
interface ReportingUsageResponse {
13+
isTruncated: boolean;
14+
marker: string;
15+
accounts: Record<string, Record<string, LocationUsage>>;
16+
}
17+
18+
export async function prepareStorageUsageReportingScenarios(
19+
world: Zenko, scenarioConfiguration: ITestCaseHookParameter,
20+
) {
21+
await prepareMetricsScenarios(world, scenarioConfiguration, {
22+
versioning: '',
23+
jobNamespace: 'storage-usage-reporting-setup',
24+
jobName: 'end2end-ops-count-items',
25+
objectSize: 200,
26+
objectCount: 3,
27+
});
28+
}
29+
30+
Given('two additional accounts', async function (this: Zenko) {
31+
const accountNames: string[] = [];
32+
const accountIds: string[] = [];
33+
for (let i = 0; i < 2; i++) {
34+
const name = `reporting-test-${Date.now()}-${i}`;
35+
await this.createAccount(name, true);
36+
const account = await SuperAdmin.getAccount({ accountName: name });
37+
accountNames.push(name);
38+
accountIds.push(account.id);
39+
}
40+
this.addToSaved('additionalAccountNames', accountNames);
41+
this.addToSaved('additionalAccountIds', accountIds);
42+
});
43+
44+
When('the user retrieves the storage usage report',
45+
async function (this: Zenko) {
46+
const result = await this.managementAPIRequest(
47+
'GET',
48+
`/instance/${this.parameters.InstanceID}/reporting/usage`,
49+
);
50+
this.addToSaved('reportingResponse', result);
51+
});
52+
53+
When('the user retrieves the storage usage report as a no-rights user',
54+
async function (this: Zenko) {
55+
const noRightsUsername =
56+
`${this.parameters.KeycloakUsername || 'storage_manager'}-norights`;
57+
const result = await this.managementAPIRequest(
58+
'GET',
59+
`/instance/${this.parameters.InstanceID}/reporting/usage`,
60+
{},
61+
{},
62+
noRightsUsername,
63+
this.parameters.KeycloakTestPassword || '123',
64+
);
65+
this.addToSaved('reportingResponse', result);
66+
});
67+
68+
Then('the storage usage report response status is {int}',
69+
function (this: Zenko, expectedStatus: number) {
70+
const response = this.getSaved<{ statusCode: number }>('reportingResponse');
71+
assert.strictEqual(response.statusCode, expectedStatus,
72+
`Expected status ${expectedStatus} but got ${response.statusCode}`);
73+
});
74+
75+
Then('the storage usage report response has a valid structure',
76+
function (this: Zenko) {
77+
const response = this.getSaved<{ statusCode: number; data: ReportingUsageResponse }>(
78+
'reportingResponse');
79+
const data = response.data;
80+
assert.strictEqual(typeof data.isTruncated, 'boolean',
81+
'isTruncated should be a boolean');
82+
assert.strictEqual(typeof data.marker, 'string',
83+
'marker should be a string');
84+
assert.strictEqual(typeof data.accounts, 'object',
85+
'accounts should be an object');
86+
});
87+
88+
Then('the storage usage report contains the additional accounts',
89+
async function (this: Zenko) {
90+
const response = this.getSaved<{ statusCode: number; data: ReportingUsageResponse }>(
91+
'reportingResponse');
92+
const accountIds = this.getSaved<string[]>('additionalAccountIds');
93+
for (const accountId of accountIds) {
94+
assert.ok(accountId in response.data.accounts,
95+
`Account ${accountId} should be present in the report`);
96+
}
97+
});
98+
99+
Then('the storage usage report contains the test account with location {string}',
100+
async function (this: Zenko, locationName: string) {
101+
const response = this.getSaved<{ statusCode: number; data: ReportingUsageResponse }>(
102+
'reportingResponse');
103+
const accountName = this.getSaved<string>('accountName');
104+
const account = await SuperAdmin.getAccount({ accountName });
105+
106+
assert.ok(account.id in response.data.accounts,
107+
`Account ${account.id} (${accountName}) should be present in the report`);
108+
109+
const accountData = response.data.accounts[account.id];
110+
assert.ok(locationName in accountData,
111+
`Location ${locationName} should be present for account ${account.id}`);
112+
113+
this.addToSaved('reportedLocationUsage', accountData[locationName]);
114+
});
115+
116+
Then('the location metrics show {int} objects and {int} bytes',
117+
function (this: Zenko, expectedObjects: number, expectedBytes: number) {
118+
const usage = this.getSaved<LocationUsage>('reportedLocationUsage');
119+
assert.strictEqual(usage.objectsTotal, expectedObjects,
120+
`Expected ${expectedObjects} objects but got ${usage.objectsTotal}`);
121+
assert.strictEqual(usage.bytesTotal, expectedBytes,
122+
`Expected ${expectedBytes} bytes but got ${usage.bytesTotal}`);
123+
});

tests/ctst/world/Zenko.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -937,10 +937,12 @@ export default class Zenko extends World<ZenkoWorldParameters> {
937937
path: string,
938938
headers: object = {},
939939
payload: object | string = {},
940+
username?: string,
941+
password?: string,
940942
): Promise<{ statusCode: number; data: object } | { statusCode: number; err: unknown }> {
941943
const token = await this.getWebIdentityToken(
942-
this.parameters.KeycloakUsername || 'storage_manager',
943-
this.parameters.KeycloakPassword || '123',
944+
username || this.parameters.KeycloakUsername || 'storage_manager',
945+
password || this.parameters.KeycloakPassword || '123',
944946
this.parameters.KeycloakHost || 'keycloak.zenko.local',
945947
this.parameters.KeycloakPort || '80',
946948
`/auth/realms/${this.parameters.KeycloakRealm || 'zenko'}/protocol/openid-connect/token`,

0 commit comments

Comments
 (0)