Skip to content

Commit 837dca2

Browse files
committed
S3UTILS-211: ft tests for listObjectsByReplicationStatus
1 parent 202a560 commit 837dca2

File tree

3 files changed

+521
-0
lines changed

3 files changed

+521
-0
lines changed
Lines changed: 312 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,312 @@
1+
const vaultclient = require('vaultclient');
2+
const { Logger } = require('werelogs');
3+
const { listObjectsByReplicationStatus } = require('../../listObjectsByReplicationStatus');
4+
5+
const {
6+
iamHost,
7+
iamPort,
8+
s3Host,
9+
s3Port,
10+
adminAccessKeyId,
11+
adminSecretAccessKey,
12+
createTestAccount,
13+
deleteTestAccount,
14+
} = require('../utils/S3Setup');
15+
16+
const log = new Logger('listObjectsByReplicationStatus:test');
17+
18+
async function configureCrr(accountSource, accountDest) {
19+
// activate bucket versionning on source and destination buckets
20+
log.info('Enabling bucket versioning on source and destination buckets');
21+
await accountSource.s3Client.putBucketVersioning({
22+
Bucket: accountSource.bucketName,
23+
VersioningConfiguration: {
24+
Status: 'Enabled',
25+
},
26+
}).promise();
27+
28+
await accountDest.s3Client.putBucketVersioning({
29+
Bucket: accountDest.bucketName,
30+
VersioningConfiguration: {
31+
Status: 'Enabled',
32+
},
33+
}).promise();
34+
35+
log.info('Creating IAM policies and roles for CRR');
36+
// create policy
37+
const policy = {
38+
Version:'2012-10-17',
39+
Statement:[
40+
{
41+
Effect:'Allow',
42+
Action:[
43+
's3:GetObjectVersion',
44+
's3:GetObjectVersionAcl',
45+
's3:ReplicateObject'
46+
],
47+
Resource:[
48+
`arn:aws:s3:::${accountSource.bucketName}/*`
49+
]
50+
},
51+
{
52+
Effect:'Allow',
53+
Action:[
54+
's3:ListBucket',
55+
's3:GetReplicationConfiguration'
56+
],
57+
Resource:[
58+
'arn:aws:s3:::source'
59+
]
60+
},
61+
{
62+
Effect:'Allow',
63+
Action:[
64+
's3:ReplicateObject',
65+
's3:ReplicateDelete'
66+
],
67+
Resource:`arn:aws:s3:::${accountDest.bucketName}/*`
68+
}
69+
]
70+
};
71+
await accountSource.iamClient.createPolicy({
72+
PolicyName: 'crr-policy',
73+
PolicyDocument: JSON.stringify(policy),
74+
}).promise();
75+
76+
await accountDest.iamClient.createPolicy({
77+
PolicyName: 'crr-policy',
78+
PolicyDocument: JSON.stringify(policy),
79+
}).promise();
80+
81+
log.info('Creating IAM roles');
82+
// create trust
83+
const trust = {
84+
Version:'2012-10-17',
85+
Statement:[
86+
{
87+
Effect:'Allow',
88+
Principal:{
89+
Service:'backbeat'
90+
},
91+
Action:'sts:AssumeRole'
92+
}
93+
]
94+
};
95+
await accountSource.iamClient.createRole({
96+
RoleName: 'crr-trust-role',
97+
AssumeRolePolicyDocument: JSON.stringify(trust),
98+
}).promise();
99+
await accountDest.iamClient.createRole({
100+
RoleName: 'crr-trust-role',
101+
AssumeRolePolicyDocument: JSON.stringify(trust),
102+
}).promise();
103+
104+
log.info('Attaching policies to roles');
105+
// attach role to policy
106+
await accountSource.iamClient.attachRolePolicy({
107+
RoleName: 'crr-trust-role',
108+
PolicyArn: `arn:aws:iam::${accountSource.accountId}:policy/crr-policy`,
109+
}).promise();
110+
await accountDest.iamClient.attachRolePolicy({
111+
RoleName: 'crr-trust-role',
112+
PolicyArn: `arn:aws:iam::${accountDest.accountId}:policy/crr-policy`,
113+
}).promise();
114+
115+
log.info('Setting bucket replication configuration on source bucket');
116+
const replication = {
117+
Role: `arn:aws:iam::${accountSource.accountId}:role/crr-trust-role,arn:aws:iam::${accountDest.accountId}:role/crr-trust-role`,
118+
Rules: [
119+
{
120+
Prefix: '',
121+
Status: 'Enabled',
122+
Destination: {
123+
Bucket: `arn:aws:s3:::${accountDest.bucketName}`
124+
}
125+
}
126+
]
127+
};
128+
await accountSource.s3Client.putBucketReplication({
129+
Bucket: accountSource.bucketName,
130+
ReplicationConfiguration: replication
131+
}).promise();
132+
133+
}
134+
135+
async function removeCrrConfiguration(account) {
136+
log.info('Removing bucket crr configuration', { bucket: account.bucketName });
137+
try {
138+
await account.s3Client.deleteBucketReplication({ Bucket: account.bucketName }).promise();
139+
} catch (err) {
140+
log.error('Error removing bucket replication configuration', {
141+
bucket: account.bucketName,
142+
error: err.message,
143+
});
144+
}
145+
// remove versioning
146+
await account.s3Client.putBucketVersioning({
147+
Bucket: account.bucketName,
148+
VersioningConfiguration: {
149+
Status: 'Suspended',
150+
},
151+
}).promise();
152+
153+
// Clean up IAM resources first (roles and policies must be deleted before account)
154+
log.info('Cleaning up IAM resources', { account: account.accountName });
155+
try {
156+
// Detach policy from role
157+
await account.iamClient.detachRolePolicy({
158+
RoleName: 'crr-trust-role',
159+
PolicyArn: `arn:aws:iam::${account.accountId}:policy/crr-policy`,
160+
}).promise();
161+
log.info('Detached policy from role');
162+
} catch (err) {
163+
log.error('Error detaching policy from role', { error: err.message });
164+
}
165+
166+
try {
167+
// Delete role
168+
await account.iamClient.deleteRole({ RoleName: 'crr-trust-role' }).promise();
169+
log.info('Deleted IAM role');
170+
} catch (err) {
171+
log.error('Error deleting role', { error: err.message });
172+
}
173+
174+
try {
175+
// Delete policy
176+
await account.iamClient.deletePolicy({
177+
PolicyArn: `arn:aws:iam::${account.accountId}:policy/crr-policy`,
178+
}).promise();
179+
log.info('Deleted IAM policy');
180+
} catch (err) {
181+
log.error('Error deleting policy', { error: err.message });
182+
}
183+
184+
try {
185+
// Delete IAM user
186+
await account.iamClient.deleteUser({ UserName: account.iamUser }).promise();
187+
log.info('Deleted IAM user');
188+
} catch (err) {
189+
log.error('Error deleting IAM user', { error: err.message });
190+
}
191+
}
192+
193+
194+
describe('listObjectsByReplicationStatus', () => {
195+
let vaultClient;
196+
let accountSource;
197+
let accountDest;
198+
199+
beforeAll(async () => {
200+
vaultClient = new vaultclient.Client(
201+
iamHost,
202+
iamPort,
203+
false,
204+
undefined,
205+
undefined,
206+
undefined,
207+
undefined,
208+
adminAccessKeyId,
209+
adminSecretAccessKey
210+
);
211+
});
212+
213+
beforeEach(async () => {
214+
log.info('Setting up test accounts and buckets');
215+
accountSource = await createTestAccount(vaultClient);
216+
accountDest = await createTestAccount(vaultClient);
217+
log.info(`Account source: ${accountSource.accountName} and dest: ${accountDest.accountName} were created`);
218+
log.info('Configuring CRR between source and destination buckets');
219+
await configureCrr(accountSource, accountDest);
220+
});
221+
222+
afterEach(async () => {
223+
log.info('Cleaning up test accounts');
224+
await removeCrrConfiguration(accountSource);
225+
await removeCrrConfiguration(accountDest);
226+
await deleteTestAccount(vaultClient, accountSource);
227+
await deleteTestAccount(vaultClient, accountDest);
228+
log.info('Test accounts deleted');
229+
});
230+
231+
it('should list objects by replication status', async () => {
232+
// Add data to source bucket
233+
log.info('Uploading test objects to source bucket');
234+
const testObjects = [
235+
{ Key: 'test-object-1', Body: 'data to replicate 1' },
236+
{ Key: 'test-object-2', Body: 'data to replicate 2' },
237+
{ Key: 'test-object-3', Body: 'data to replicate 3' },
238+
];
239+
240+
for (const obj of testObjects) {
241+
await accountSource.s3Client.putObject({
242+
Bucket: accountSource.bucketName,
243+
Key: obj.Key,
244+
Body: obj.Body,
245+
}).promise();
246+
log.info('Uploaded object', { key: obj.Key });
247+
}
248+
249+
// Verify objects have replication status
250+
log.info('Verifying objects have replication status');
251+
for (const obj of testObjects) {
252+
const headResult = await accountSource.s3Client.headObject({
253+
Bucket: accountSource.bucketName,
254+
Key: obj.Key,
255+
}).promise();
256+
log.info('Object metadata', {
257+
key: obj.Key,
258+
replicationStatus: headResult.ReplicationStatus,
259+
versionId: headResult.VersionId
260+
});
261+
}
262+
263+
// Create a custom logger to capture log entries
264+
const capturedLogs = [];
265+
const captureLogger = new Logger('s3utils:listObjectsByReplicationStatus:test');
266+
const originalInfo = captureLogger.info.bind(captureLogger);
267+
captureLogger.info = function (message, data) {
268+
capturedLogs.push({ message, data });
269+
return originalInfo(message, data);
270+
};
271+
272+
// Execute the listObjectsByReplicationStatus function directly
273+
log.info('Executing listObjectsByReplicationStatus function');
274+
const endpoint = `http://${s3Host}:${s3Port}`;
275+
276+
// Call the function directly for coverage
277+
await listObjectsByReplicationStatus({
278+
buckets: accountSource.bucketName,
279+
accessKey: accountSource.accountAccessKey,
280+
secretKey: accountSource.accountSecretKey,
281+
endpoint,
282+
replicationStatus: 'PENDING,FAILED,COMPLETED',
283+
logger: captureLogger,
284+
});
285+
286+
log.info('Function execution completed successfully');
287+
288+
// Verify that objects were found and logged
289+
const foundObjectLogs = capturedLogs.filter(entry =>
290+
entry.message === 'object with matching replication status found'
291+
);
292+
293+
log.info('Found object logs', { count: foundObjectLogs.length });
294+
expect(foundObjectLogs.length).toBe(testObjects.length);
295+
296+
// Verify that all test objects were found
297+
const foundKeys = foundObjectLogs.map(entry => entry.data.Key);
298+
for (const testObj of testObjects) {
299+
expect(foundKeys).toContain(testObj.Key);
300+
log.info('Verified object was listed', { key: testObj.Key });
301+
}
302+
303+
// Verify that the objects have the expected replication status
304+
for (const logEntry of foundObjectLogs) {
305+
expect(['PENDING', 'FAILED', 'COMPLETED']).toContain(logEntry.data.ReplicationStatus);
306+
log.info('Verified replication status', {
307+
key: logEntry.data.Key,
308+
status: logEntry.data.ReplicationStatus
309+
});
310+
}
311+
}, 30000);
312+
});

0 commit comments

Comments
 (0)