Skip to content

Instantly share code, notes, and snippets.

@marekkrzynowek
Created December 4, 2019 15:50
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save marekkrzynowek/72d4f7bf66f61ef278dc9383688516be to your computer and use it in GitHub Desktop.
Save marekkrzynowek/72d4f7bf66f61ef278dc9383688516be to your computer and use it in GitHub Desktop.
const _ = require('lodash');
const createEvent = require('aws-event-mocks');
const expect = require('expect');
const yaml = require('js-yaml');
const fs = require('fs');
const dotenv = require('dotenv');
const diff = require('deep-diff');
const fns = {};
/**
* This function wraps the message object in SNS context so it simulates amazon SNS payload.
*
* @param {*} message: Payload to be included in th SNS
* @param {string} topicArn: Optional: topic the SNS was sent to
* @param {string} sourceArn: Optional: SNS source topic
* @returns SNS message
*/
fns.generateSNSFixture = function(message, topicArn = 'topic_Arn', sourceArn = 'source_arn') {
const body = {
Type: 'Notification',
MessageId: '694e2d00-afd6-5065-a525-7932afa3652e',
TopicArn: topicArn,
Message: JSON.stringify(message),
Timestamp: new Date().toISOString(),
SignatureVersion: '1',
Signature: 'K+szeNJUc/q8aQ4LcDzfwBJt1/Q==',
SigningCertURL: 'https://sns.us-east-1.amazonaws.com/url',
UnsubscribeURL: 'https://sns.us-east-1.amazonaws.com/?url'
};
const fixture = {
Records: [
{
messageId: 'eccfe821-0a8e-4ab8-aa8c-cb818c0dcc1a',
receiptHandle: 'N1254aOMj35YWpAVbUzzx25rvGL39CC3NbL9+ZDaSK43Rgafo3+LDJPjydxF0LkAQ1+e1giDiL0=',
body: JSON.stringify(body),
attributes: {},
messageAttributes: {},
md5OfBody: 'be61a9f98ddd5dd9ef53e5e0f62b67fe',
eventSource: 'aws:sqs',
eventSourceArn: sourceArn,
awsRegion: 'us-east-1'
}
]
};
return fixture;
};
/**
* This method is a wrapper fixing the aws-mock implementation which
* has a bug that causes modifications to be remembered between calls.
*
* @param {*} config event parameters
* @return {*} event mock
*/
fns.createAwsEvent = function(config) {
const event = createEvent({ template: config.template });
const eventClone = { ...event };
return _.has(config, 'merge') ? _.merge(eventClone, config.merge) : eventClone;
};
/**
* Test for existence of headers necessary for the endpoint to support CORS requests.
*
* @param {*} Response headers
*/
fns.checkCorsHeadersPresent = function(headers) {
expect(headers['Access-Control-Allow-Origin']).toBe('*');
expect(headers['Access-Control-Allow-Credentials']).toBe(true);
};
/**
* Test that there are no messages send to unknown SNS topics.
* if that test fails usually means your serverless.yml file is missing
* SNS topics defined as env variables. Use this test very often.
*
* @param {Sinon.spy} spy:Sinon spy
*/
fns.expectNoUnknownSNS = function(spy) {
for (const spyCall of spy.getCalls()) {
const params = spyCall.args[0];
expect(params.TopicArn).toBeDefined();
}
};
/**
* Test the exact number of sns send to a spy with the correlating topics and optionally payloads.
* You should construct the 'expected' array to contain all Messages that the test method will fire
* Once you pass this array the tests will ensure that all of these messages were fired and only these messages.
* It does not test the message firing order.
*
* @param {*} spy: Sinon spy
* @param {*} expected: Array of Messages to test for:
* [
* {topic: 'SNS_USER_CREATED', payload: { user }},
* {topic: 'SNS_USER_UPDATED' }
* ]
*/
fns.expectExactSNSMessages = function(spy, expected) {
const snss = spy.getCalls().map(call => {
const params = call.args[0];
return { topic: params.TopicArn, payload: JSON.parse(params.Message) };
});
expect(expected.length).toBe(snss.length);
for (const msg of expected) {
const toRemove = _findSNSIndex(snss, msg);
if (toRemove !== undefined && toRemove >= 0) {
snss.splice(toRemove, 1);
}
}
expect(snss.length).toBe(0);
};
/**
* This function parses the serverless.yml file and recreates the environment by setting
* all the env variables that will be used after the deployment.
*
* @param {*} serverlessPath: Path to the serverless.yml file
* @param {*} dotEnvPath : Path to the .env file. Any entries in this file will overwrite
* whatever values will bbe set in serverless.yml file handy for
* setting the DB_URI variable for testing.
*/
fns.prepareServerlessEnv = function(serverlessPath, dotEnvPath) {
const serverless = yaml.safeLoad(fs.readFileSync(serverlessPath, 'utf8'));
const envVars = Object.keys(serverless.provider.environment);
for (const envVar of envVars) {
process.env[envVar] = envVar.toLocaleLowerCase();
}
// Override from .env file
const overrides = dotenv.parse(fs.readFileSync(dotEnvPath));
for (const override of Object.keys(overrides)) {
process.env[override] = overrides[override];
}
};
/**
* Returns all the payloads for the topic
*
* @param {*} spy; Sinon spy
* @param {*} topic: SNS topic
*/
fns.getSNSPayloads = function(spy, topic) {
const payloads = [];
for (const spyCall of spy.getCalls()) {
const params = spyCall.args[0];
if (params.TopicArn === topic) {
payloads.push(JSON.parse(params.Message));
}
}
return payloads;
};
/**
* Test that messages was send to the topic.
*
* @param {*} spy: Sinon spy
* @param {string} topic: SNS topic name
* @param {int} count: Expected number of messages sent to the topic defaults to 1
*/
fns.expectSNSTopic = function(spy, topic, count = 1) {
let found = 0;
for (const spyCall of spy.getCalls()) {
const params = spyCall.args[0];
if (params.TopicArn === topic) {
found += 1;
}
}
expect(found).toBe(count);
};
/**
* Tests that the exact payload was sent to the topic.
* Prints a diff of payloads for all payloads sent to the topic for debugging.
*
* @param {*} spy: Sinon Spy.
* @param {string} topic: SNS topic name.
* @param {*} payload: Expected payload sent.
*/
fns.expectSNSWithPayload = function(spy, topic, payload) {
let found = 0;
for (const spyCall of spy.getCalls()) {
console.log('==');
const params = spyCall.args[0];
if (params.TopicArn === topic) {
const jsonSns = JSON.parse(params.Message);
console.log('Found a call for topic:', topic);
console.log('-');
console.log('Expected SNS Payload:', payload);
console.log('-');
console.log('Acutal Payload:', jsonSns);
console.log('-');
const differences = diff(jsonSns, JSON.parse(JSON.stringify(payload)));
if (!differences) {
console.log('Found a match');
found += 1;
} else {
console.log('Differences: ', differences);
}
console.log('==');
}
}
expect(found).toBe(1);
};
/**
* Tests that the topic had no messages sent to it.
*
* @param {*} spy: Sinon spy.
* @param {string} topic: SNS topic.
*/
fns.expectNoCallToSNSTopic = function(spy, topic) {
for (const spyCall of spy.getCalls()) {
const params = spyCall.args[0];
expect(params.TopicArn).not.toBe(topic);
}
};
function _findSNSIndex(snss, msg) {
for (let i = 0; i < snss.length; i++) {
const sns = snss[i];
if (msg.topic === sns.topic) {
if (msg.payload) {
const difference = diff(sns.payload, JSON.parse(JSON.stringify(msg.payload)));
if (difference) {
console.log('Payloads are different:', difference);
}
return i;
}
return i;
}
}
}
module.exports = fns;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment