Skip to content

Commit 66faeca

Browse files
CLOUDP-290416: Testing for ipa s3 dataDump
1 parent d24bdaa commit 66faeca

File tree

9 files changed

+301
-83
lines changed

9 files changed

+301
-83
lines changed

package-lock.json

Lines changed: 146 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"@eslint/js": "^9.18.0",
3636
"@jest/globals": "^29.7.0",
3737
"@stoplight/types": "^14.1.1",
38+
"aws-sdk-client-mock": "^4.1.0",
3839
"babel-plugin-transform-import-meta": "^2.3.2",
3940
"eslint": "^9.18.0",
4041
"eslint-plugin-require-extensions": "^0.1.3",
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { beforeEach, describe, expect, it, jest } from '@jest/globals';
2+
import { uploadMetricCollectionDataToS3 } from '../../metrics/metricS3Upload.js';
3+
import path from 'path';
4+
import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
5+
import { mockClient } from 'aws-sdk-client-mock';
6+
7+
const testPrefix = 's3://bucket-name/path/to/file';
8+
const testTime = new Date(2020, 3, 1);
9+
const testMetricResultFilePath = path.join(__dirname, 'data', 'expected-metric-results.json');
10+
11+
const clientMock = mockClient(S3Client);
12+
const filePathMock = { bucketName: 'bucket-name', key: 'path/to/file' };
13+
14+
jest.mock('../../metrics/utils/dataDumpUtils.js', () => ({
15+
...jest.requireActual('../../metrics/utils/dataDumpUtils.js'),
16+
getS3FilePath: jest.fn().mockReturnValue(filePathMock),
17+
}));
18+
19+
describe('tools/spectral/ipa/metrics/metricS3Upload.js uploadMetricCollectionDataToS3', () => {
20+
beforeEach(() => {
21+
jest.useFakeTimers();
22+
jest.setSystemTime(testTime);
23+
jest.clearAllMocks();
24+
clientMock.reset();
25+
jest.resetModules();
26+
process.env = {
27+
AWS_ACCESS_KEY_ID: 'id',
28+
AWS_SECRET_ACCESS_KEY: 'secret',
29+
S3_BUCKET_PREFIX: testPrefix,
30+
};
31+
});
32+
33+
it('Outputs the expected metrics collection results', async () => {
34+
await uploadMetricCollectionDataToS3(testMetricResultFilePath);
35+
36+
const clientCalls = clientMock.calls();
37+
expect(clientCalls.length).toEqual(1);
38+
39+
const putObjectCommand = clientMock.commandCalls(PutObjectCommand);
40+
expect(putObjectCommand.length).toEqual(1);
41+
42+
const input = putObjectCommand.at(0).args.at(0).input;
43+
expect(input['Bucket']).toEqual(filePathMock.bucketName);
44+
expect(input['Key']).toEqual(
45+
path.join(filePathMock.key, testTime.toISOString().split('T')[0], 'metric-collection-results.parquet')
46+
);
47+
expect(input['Body']).not.toBe(undefined);
48+
});
49+
});
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { beforeEach, describe, expect, it, jest } from '@jest/globals';
2+
import { getS3FilePath } from '../../../metrics/utils/dataDumpUtils.js';
3+
4+
describe('tools/spectral/ipa/metrics/utils/dataDumpUtils.js getS3FilePath', () => {
5+
beforeEach(() => {
6+
jest.clearAllMocks();
7+
jest.resetModules();
8+
process.env = {
9+
AWS_ACCESS_KEY_ID: 'id',
10+
AWS_SECRET_ACCESS_KEY: 'secret',
11+
S3_BUCKET_PREFIX: 's3://bucket-name/path/to/file',
12+
};
13+
});
14+
15+
it('Parses the S3 file path correctly', () => {
16+
const result = getS3FilePath();
17+
expect(result['bucketName']).toEqual('bucket-name');
18+
expect(result['key']).toEqual('path/to/file');
19+
});
20+
});

tools/spectral/ipa/metrics/metricCollection.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
loadOpenAPIFile,
77
loadRuleset,
88
merge,
9-
} from './utils.js';
9+
} from './utils/metricCollectionUtils.js';
1010

1111
export async function runMetricCollectionJob(
1212
{
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { PutObjectCommand, S3ServiceException } from '@aws-sdk/client-s3';
2+
import config from './config.js';
3+
import path from 'path';
4+
import fs from 'node:fs';
5+
import { tableFromJSON, tableToIPC } from 'apache-arrow';
6+
import { getS3Client, getS3FilePath } from './utils/dataDumpUtils.js';
7+
8+
/**
9+
* Upload IPA product metrics to Data Warehouse S3
10+
* @param filePath file path to the metrics collection results, uses config.js by default
11+
*/
12+
export async function uploadMetricCollectionDataToS3(filePath = config.defaultMetricCollectionResultsFilePath) {
13+
const client = getS3Client();
14+
const formattedDate = new Date().toISOString().split('T')[0];
15+
const metricsCollectionData = JSON.parse(fs.readFileSync(filePath, 'utf8'));
16+
const table = tableFromJSON(metricsCollectionData);
17+
18+
const s3fileProps = getS3FilePath();
19+
const command = new PutObjectCommand({
20+
Bucket: s3fileProps.bucketName,
21+
Key: path.join(s3fileProps.key, formattedDate, 'metric-collection-results.parquet'),
22+
Body: tableToIPC(table, 'stream'),
23+
});
24+
25+
try {
26+
const response = await client.send(command);
27+
console.log(response);
28+
} catch (caught) {
29+
if (caught instanceof S3ServiceException && caught.name === 'EntityTooLarge') {
30+
console.error(
31+
`Error from S3 while uploading object. The object was too large. \
32+
To upload objects larger than 5GB, use the S3 console (160GB max) or the multipart upload API (5TB max).`
33+
);
34+
throw caught;
35+
} else if (caught instanceof S3ServiceException) {
36+
console.error(`Error from S3 while uploading object. ${caught.name}: ${caught.message}`);
37+
throw caught;
38+
} else {
39+
throw caught;
40+
}
41+
}
42+
}

0 commit comments

Comments
 (0)