Skip to content
This repository was archived by the owner on May 8, 2025. It is now read-only.
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,6 @@ typings/
# dotenv environment variables file
.env

# IDE
.idea/

1 change: 0 additions & 1 deletion build/index.js

This file was deleted.

25 changes: 25 additions & 0 deletions integration/__snapshots__/index.test.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`fetches google.com 1`] = `
Object {
"status": 200,
"statusText": "OK",
"url": "https://www.google.com/",
}
`;

exports[`returns error when href is invalid 1`] = `
Object {
"error": Object {
"message": "Only absolute URLs are supported",
},
}
`;

exports[`returns error when href is not passed 1`] = `
Object {
"error": Object {
"message": "Cannot read property 'href' of null",
},
}
`;
79 changes: 79 additions & 0 deletions integration/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
const childProcess = require('child_process');
const fetch = require('node-fetch');

let samProcess;
const port = 5000;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this port is used only for local development, but does it make sense to come from an environment variable, from .env file or something?

let lambdaUrl = `http://127.0.0.1:${port}/`;
function localApiStarted(processOutput) {
// Example output when local function started - "Running on http://127.0.0.1:5000/"
Copy link
Contributor Author

@D-Andreev D-Andreev Dec 28, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

beforeAll hook starts the function locally and waits for output that sam has started, so the tests can begin. Probably not ideal way of doing it, I'm open to suggestions how to do this better :)

const urlRegex = /(Running on )(https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*))/gm;
const matches = urlRegex.exec(processOutput);
return matches && matches.length >= 3;
}

function startLambda(done) {
samProcess = childProcess.spawn(
'sam',
['local', 'start-api', '--port', port]
);
// The runtime output (for example, logs) is output to stderr, and the Lambda function result is output to stdout.
// https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-local-invoke.html
samProcess.stderr.on('data', data => {
if (localApiStarted(data.toString())) {
done();
}
});
samProcess.on('error', error => {
done(new Error(`SAM invoke error: ${error}`));
});
}

function stopLambda() {
samProcess.kill();
}

beforeAll((done) => {
startLambda(done);
});

afterAll(() => {
stopLambda();
});

test('returns error when href is not passed', (done) => {
fetch(lambdaUrl)
.then(res => {
expect(res.status).toEqual(500);
return res.json();
})
.then(res => {
expect(res).toMatchSnapshot();
done();
});
});

test('returns error when href is invalid', (done) => {
const qs = new URLSearchParams({ href: 'google.com' });
fetch(`${lambdaUrl}?${qs}`)
.then(res => {
expect(res.status).toEqual(500);
return res.json();
})
.then(res => {
expect(res).toMatchSnapshot();
done();
});
});

test('fetches google.com', (done) => {
const qs = new URLSearchParams({ href: 'https://google.com' });
fetch(`${lambdaUrl}?${qs}`)
.then(res => {
expect(res.status).toEqual(200);
return res.json();
})
.then(res => {
expect(res).toMatchSnapshot();
done();
});
});
7 changes: 7 additions & 0 deletions jest.config.integration.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module.exports = {
testEnvironment: 'node',
testMatch: [
'**/integration/**/*.test.js'
],
"rootDir": "./"
};
8 changes: 6 additions & 2 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,9 @@ module.exports = {
'/node_modules/(?!@)/'
],

"rootDir": "./"
}
"rootDir": "./",

"modulePathIgnorePatterns": [
'integration'
]
};
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
"test:debug": "echo '/*' && echo ' Navigate to about:inspect in Chrome' && echo '*/' && node --inspect node_modules/.bin/jest --runInBand",
"test:ci": "CI=true jest --verbose=false --config ./jest.config.js",
"test:watch": "jest --watch --verbose=false --config ./jest.config.js",
"start": "sam local start-api --port 5000",
"test:integration": "npm run build && jest --verbose=false --config ./jest.config.integration.js",
"test:watch:integration": "npm run build && jest --watch --verbose=false --config ./jest.config.integration.js",
"start": "sam local start-api --port 5000 2>&1 | tr \"\\r\" \"\\n\"",
Copy link
Contributor Author

@D-Andreev D-Andreev Dec 28, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added this part 2>&1 | tr \"\\r\" \"\\n\" because the console.logs from inside the function were not shown. A workaround, but couldn't find anything better - #1359

"dist": "rm -f dist.zip && zip -jq dist.zip build/index.js",
"update": "aws lambda update-function-code --function-name FooBarExample --zip-file fileb://dist.zip --publish",
"deploy": "npm run build && npm run test:ci && npm run dist && npm run update"
Expand Down
38 changes: 18 additions & 20 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,27 @@
import fetch from 'node-fetch';

exports.handler = async (event, context, callback) => {
// Request headers for a mock 200 response
const request = fetch(decodeURIComponent(event.queryStringParameters.href), {
method: 'HEAD'
});
function createResponse(statusCode, body) {
return {
statusCode: statusCode,
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(body)
}
}

let data;
exports.handler = async ({queryStringParameters}, context) => {
// TODO: Validate body or query params with some kind of json schema. AJV?
try {
const response = await request;
// Request headers for a mock 200 response
const response = await fetch(decodeURIComponent(queryStringParameters.href), {
method: 'HEAD'
});

data = {
return createResponse(200, {
url: response.url,
status: response.status,
statusText: response.statusText
};
} catch (e) {
console.log(e);
});
} catch (error) {
return createResponse(500, {error: {message: error.message}});
}

return callback(null, {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When function is async the callback is not needed, you can just return result.

statusCode: 200,
header: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
}
};
22 changes: 18 additions & 4 deletions src/index.test.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import event from '../event.json';
import { handler } from './index.js';

function callback (error, response) { return error || response }

beforeEach(() => {
// reset event.json
jest.resetModules();
Expand All @@ -12,10 +10,26 @@ test('Handler does not explode on import', () => {
expect(true).toBe(true);
});

test('returns error when href is not provided', async () => {
event.queryStringParameters.href = null;

const response = await handler(event, null);

expect(response.statusCode).toBe(500);
});

test('returns error when href is invalid', async () => {
event.queryStringParameters.href = 'google.com';

const response = await handler(event, null);

expect(response.statusCode).toBe(500);
});

test('Fetch google.com', async () => {
event.queryStringParameters.href = encodeURIComponent('https://google.com');

const response = await handler(event, null, callback)
const response = await handler(event, null);

expect(response.statusCode).toBe(200);
});
});