Skip to content

Commit

Permalink
Merge branch 'main' into slackRoles-validation-fix
Browse files Browse the repository at this point in the history
  • Loading branch information
solaris007 authored Oct 23, 2024
2 parents 8c40da4 + 6579a26 commit 485abc4
Show file tree
Hide file tree
Showing 7 changed files with 233 additions and 2 deletions.
4 changes: 2 additions & 2 deletions packages/spacecat-shared-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
"@adobe/fetch": "4.1.9",
"@aws-sdk/client-s3": "3.675.0",
"@aws-sdk/client-sqs": "3.675.0",
"aws-xray-sdk": "3.10.1",
"@json2csv/plainjs": "7.0.6"
"@json2csv/plainjs": "7.0.6",
"aws-xray-sdk": "3.10.1"
}
}
14 changes: 14 additions & 0 deletions packages/spacecat-shared-utils/src/adobe-fetch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* Copyright 2024 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
import { context as h2, h1 } from '@adobe/fetch';

export const { fetch } = process.env.HELIX_FETCH_FORCE_HTTP1 ? h1() : h2();
6 changes: 6 additions & 0 deletions packages/spacecat-shared-utils/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
* governing permissions and limitations under the License.
*/

import { Request, RequestOptions, Response } from '@adobe/fetch';

/** UTILITY FUNCTIONS */
export function arrayEquals<T>(a: T[], b: T[]): boolean;

Expand Down Expand Up @@ -159,3 +161,7 @@ export function storeMetrics(content: object, config: object, context: object):

export function s3Wrapper(fn: (request: object, context: object) => Promise<Response>):
(request: object, context: object) => Promise<Response>;

export function fetch(url: string|Request, options?: RequestOptions): Promise<Response>;

export function tracingFetch(url: string|Request, options?: RequestOptions): Promise<Response>;
3 changes: 3 additions & 0 deletions packages/spacecat-shared-utils/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,6 @@ export {
export { getStoredMetrics, storeMetrics } from './metrics-store.js';

export { s3Wrapper } from './s3.js';

export { fetch } from './adobe-fetch.js';
export { tracingFetch } from './tracing-fetch.js';
43 changes: 43 additions & 0 deletions packages/spacecat-shared-utils/src/tracing-fetch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2024 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
import AWSXRay from 'aws-xray-sdk';

import { fetch as adobeFetch } from './adobe-fetch.js';

export async function tracingFetch(url, options = {}) {
const parentSegment = AWSXRay.getSegment();

if (!parentSegment) {
return adobeFetch(url, options);
}

const method = options.method || 'GET';
const subsegment = parentSegment.addNewSubsegment(`HTTP ${method} ${url}`);

try {
subsegment.addAnnotation('url', url);
subsegment.addAnnotation('method', method);

const response = await adobeFetch(url, options);

subsegment.addMetadata('statusCode', response.status);

subsegment.close();

return response;
} catch (error) {
subsegment.addError(error);
subsegment.close();

throw error;
}
}
2 changes: 2 additions & 0 deletions packages/spacecat-shared-utils/test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ describe('Index Exports', () => {
'stripTrailingSlash',
'stripWWW',
'toBoolean',
'fetch',
'tracingFetch',
];

it('exports all expected functions', () => {
Expand Down
163 changes: 163 additions & 0 deletions packages/spacecat-shared-utils/test/tracing-fetch.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/*
* Copyright 2024 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
/* eslint-env mocha */

import { expect } from 'chai';
import { AbortController } from '@adobe/fetch';
import sinon from 'sinon';
import nock from 'nock';
import AWSXRay from 'aws-xray-sdk';
import { tracingFetch } from '../src/index.js';

describe('tracing fetch function', () => {
let sandbox;
let getSegmentStub;
let parentSegment;
let subsegment;
let log;

beforeEach(() => {
sandbox = sinon.createSandbox();

getSegmentStub = sandbox.stub(AWSXRay, 'getSegment');

subsegment = {
addAnnotation: sandbox.spy(),
addMetadata: sandbox.spy(),
addError: sandbox.spy(),
close: sandbox.spy(),
};

parentSegment = {
addNewSubsegment: sandbox.stub().returns(subsegment),
};

log = {
warn: sandbox.spy(),
};
});

afterEach(() => {
sandbox.restore();
nock.cleanAll();
});

it('calls adobeFetch and return response when there is no parent segment', async () => {
getSegmentStub.returns(null);

const url = 'https://example.com/api/data';
nock('https://example.com')
.get('/api/data')
.reply(200, 'OK');

const response = await tracingFetch(url);

expect(response.status).to.equal(200);
const responseBody = await response.text();
expect(responseBody).to.equal('OK');
});

it('creates subsegment, add annotations, call adobeFetch, and close subsegment', async () => {
getSegmentStub.returns(parentSegment);

const url = 'https://example.com/api/data';

nock('https://example.com')
.get('/api/data')
.reply(200, 'OK');

const options = { method: 'GET' };

const response = await tracingFetch(url, options);

expect(parentSegment.addNewSubsegment.calledOnceWithExactly(`HTTP GET ${url}`)).to.be.true;
expect(subsegment.addAnnotation.calledWith('url', url)).to.be.true;
expect(subsegment.addAnnotation.calledWith('method', 'GET')).to.be.true;

expect(response.status).to.equal(200);
const responseBody = await response.text();
expect(responseBody).to.equal('OK');

expect(subsegment.addMetadata.calledWith('statusCode', response.status)).to.be.true;

expect(subsegment.close.calledOnce).to.be.true;
});

it('handles fetch error, adds error to subsegment, closes subsegment, and rethrows', async () => {
getSegmentStub.returns(parentSegment);

const url = 'https://example.com/api/data';

nock('https://example.com')
.get('/api/data')
.replyWithError('Network Error');

try {
await tracingFetch(url);
throw new Error('Expected fetch to throw an error');
} catch (error) {
expect(error.message).to.equal('Network Error');

expect(parentSegment.addNewSubsegment.calledOnceWithExactly(`HTTP GET ${url}`)).to.be.true;
expect(subsegment.addAnnotation.calledWith('url', url)).to.be.true;

expect(subsegment.addError.calledOnce).to.be.true;
expect(subsegment.addError.getCall(0).args[0].message).to.equal('Network Error');

expect(subsegment.close.calledOnce).to.be.true;
}
});

it('handles timeout and returns 408 status with tracing', async () => {
const fetchWithTimeout = async (url, timeout, logger) => {
const controller = new AbortController();
const { signal } = controller;
const id = setTimeout(() => controller.abort(), timeout);

try {
const response = await tracingFetch(url, { signal });
clearTimeout(id);
return response;
} catch (error) {
clearTimeout(id);
if (error.name === 'AbortError') {
logger.warn(`Request to ${url} timed out after ${timeout}ms`);
return { ok: false, status: 408 };
} else {
throw error;
}
}
};

getSegmentStub.returns(parentSegment);

const url = 'https://example.com/api/data';
nock('https://example.com')
.get('/api/data')
.delay(1000) // Delay longer than timeout
.reply(200, 'OK');

const timeout = 500; // Timeout shorter than response delay

const response = await fetchWithTimeout(url, timeout, log);

expect(response.ok).to.be.false;
expect(response.status).to.equal(408);

expect(parentSegment.addNewSubsegment.calledOnceWithExactly(`HTTP GET ${url}`)).to.be.true;
expect(subsegment.addAnnotation.calledWith('url', url)).to.be.true;
expect(subsegment.addError.calledOnce).to.be.true;
expect(subsegment.close.calledOnce).to.be.true;

expect(log.warn.calledOnceWithExactly(`Request to ${url} timed out after ${timeout}ms`)).to.be.true;
});
});

0 comments on commit 485abc4

Please sign in to comment.