130 lines
4.5 KiB
JavaScript

'use strict';
const path = require('path');
const AWS = require('aws-sdk');
const WebSocket = require('ws');
const _ = require('lodash');
const { expect } = require('chai');
const { getTmpDirPath, readYamlFile, writeYamlFile } = require('../../utils/fs');
const { region, confirmCloudWatchLogs, wait } = require('../../utils/misc');
const { createTestService, deployService, removeService } = require('../../utils/integration');
const {
createApi,
deleteApi,
getRoutes,
createStage,
deleteStage,
} = require('../../utils/websocket');
const CF = new AWS.CloudFormation({ region });
describe('AWS - API Gateway Websocket Integration Test', function() {
this.timeout(1000 * 60 * 10); // Involves time-taking deploys
let serviceName;
let stackName;
let tmpDirPath;
let serverlessFilePath;
const stage = 'dev';
before(async () => {
tmpDirPath = getTmpDirPath();
console.info(`Temporary path: ${tmpDirPath}`);
serverlessFilePath = path.join(tmpDirPath, 'serverless.yml');
const serverlessConfig = await createTestService(tmpDirPath, {
templateDir: path.join(__dirname, 'service'),
});
serviceName = serverlessConfig.service;
stackName = `${serviceName}-${stage}`;
console.info(`Deploying "${stackName}" service...`);
return deployService(tmpDirPath);
});
after(() => {
console.info('Removing service...');
return removeService(tmpDirPath);
});
describe('Minimal Setup', () => {
it('should expose an accessible websocket endpoint', async () => {
const result = await CF.describeStacks({ StackName: stackName }).promise();
const webSocketServerUrl = _.find(result.Stacks[0].Outputs, {
OutputKey: 'ServiceEndpointWebsocket',
}).OutputValue;
console.info('WebSocket Server URL', webSocketServerUrl);
expect(webSocketServerUrl).to.match(/wss:\/\/.+\.execute-api\..+\.amazonaws\.com.+/);
return new Promise((resolve, reject) => {
const ws = new WebSocket(webSocketServerUrl);
let isRejected = false;
reject = (promiseReject => error => {
isRejected = true;
promiseReject(error);
try {
ws.close();
} catch (closeError) {
// safe to ignore
}
})(reject);
ws.on('error', reject);
ws.on('open', () => {
confirmCloudWatchLogs(`/aws/websocket/${stackName}`, () => {
if (isRejected) throw new Error('Stop propagation');
ws.send('test message');
return wait(500);
}).then(events => {
expect(events.length > 0).to.equal(true);
ws.close();
}, reject);
});
ws.on('close', resolve);
ws.on('message', event => {
console.info('Unexpected WebSocket message', event);
reject(new Error('Unexpected message'));
});
});
});
// NOTE: this test should be at the very end because we're using an external REST API here
describe('when using an existing websocket API', () => {
let websocketApiId;
before(async () => {
// create an external websocket API
const externalWebsocketApiName = `${stage}-${serviceName}-ext-api`;
const wsApiMeta = await createApi(externalWebsocketApiName);
websocketApiId = wsApiMeta.ApiId;
await createStage(websocketApiId, 'dev');
const serverless = readYamlFile(serverlessFilePath);
_.merge(serverless.provider, {
apiGateway: {
websocketApiId,
},
});
writeYamlFile(serverlessFilePath, serverless);
return deployService(tmpDirPath);
});
after(async () => {
// NOTE: deleting the references to the old, external websocket API
const serverless = readYamlFile(serverlessFilePath);
delete serverless.provider.apiGateway.websocketApiId;
writeYamlFile(serverlessFilePath, serverless);
// NOTE: we need to delete the stage before deleting the stack
// otherwise CF will refuse to delete the deployment because a stage refers to that
await deleteStage(websocketApiId, 'dev');
// NOTE: deploying once again to get the stack into the original state
console.info('Redeploying service...');
await deployService(tmpDirPath);
console.info('Deleting external websocket API...');
await deleteApi(websocketApiId);
});
it('should add the routes to the referenced API', async () => {
const routes = await getRoutes(websocketApiId);
expect(routes.length).to.equal(3);
});
});
});
});