Implement some loopback mock routes, simplify client usage

This commit is contained in:
Les Orchard 2018-05-14 14:47:35 -04:00
Родитель 137983d44d
Коммит 99b6c4d1b0
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 6DA657B1385DFCAE
9 изменённых файлов: 179 добавлений и 19 удалений

Просмотреть файл

@ -83,7 +83,23 @@ If everything was successful, you should now have a running stack with an HTTPS
To send your first request, use the `client` script with the GET endpoint URL:
```
npm run client -- https://30r00qsyhf.execute-api.us-east-1.amazonaws.com/lmorchard/accept
npm run client
```
With no options, this command should attempt to auto-detect the endpoint URL for your deployed stack. You can check to see the results of this request working its way through the stack with the following log commands:
```
# Client request is accepted into the queue
npm run logs -- -f accept
# Client request is received from the queue
npm run logs -- -f pollQueue
# Queued job is processed
npm run logs -- -f processQueueItem
# Upstream service receives a request
npm run logs -- -f mockUpstream
# Client callback service receives a negative result
npm run logs -- -f mockClientNegative
# Client callback service receives a positive result
npm run logs -- -f mockClientPositive
```
If you want to remove this stack from AWS and delete everything, run `npm run remove`

Просмотреть файл

@ -7,10 +7,13 @@ const program = require("commander");
const { devCredentials } = require("../lib/constants");
const packageData = require("../package.json");
let endpointURL = null;
async function main() {
program
.version(packageData.version)
.usage("[options] <url>")
.usage("[options]")
.option("-U, --url <url>", "Service URL (auto-detected by default)")
.option("-u, --id <id>", "User id")
.option("-k, --key <key>", "User key")
.option("-i, --image <path>", "Image path")
@ -20,9 +23,26 @@ async function main() {
.option("-e, --email <email>", "Email for positives")
.parse(process.argv);
const [url] = program.args;
let url = program.url;
if (!url) {
console.error("URL required");
const endpointURL = await discoverEndpointURL();
url = `${endpointURL}/accept`;
}
let negative_uri = program.negative;
if (!negative_uri) {
const endpointURL = await discoverEndpointURL();
negative_uri = `${endpointURL}/mock/client/negative`;
}
let positive_uri = program.positive;
if (!positive_uri) {
const endpointURL = await discoverEndpointURL();
positive_uri = `${endpointURL}/mock/client/positive`;
}
if (!url || !negative_uri || !positive_uri) {
console.error("Missing required URL");
program.outputHelp();
return "";
}
@ -36,9 +56,9 @@ async function main() {
});
const formData = {
negative_uri: program.negative || "https://example.com/negative",
positive_uri: program.positive || "https://example.com/positive",
image: fs.createReadStream(program.image)
image: program.image ? fs.createReadStream(program.image) : DEFAULT_IMAGE,
negative_uri,
positive_uri
};
if (program.email) {
formData.positive_email = program.email;
@ -55,6 +75,39 @@ async function main() {
});
}
async function discoverEndpointURL() {
if (!endpointURL) {
// Attempt to discover the current stack's accept URL if missing option
const Serverless = require("serverless");
const serverless = new Serverless({ interactive: false });
await serverless.init();
await serverless.variables.populateService();
const stackName = serverless.providers.aws.naming.getStackName();
const stackInfo = await serverless.providers.aws.request(
"CloudFormation",
"describeStacks",
{ StackName: stackName }
);
stackInfo.Stacks[0].Outputs.forEach(({ OutputKey, OutputValue }) => {
if (OutputKey === "ServiceEndpoint") {
endpointURL = OutputValue;
console.log(`Discovered endpoint URL: ${endpointURL}`);
}
});
}
if (!endpointURL) {
throw "Could not discover endpoint URL";
}
return endpointURL;
}
// NOTE: This is a tiny image of lmorchard's face used if --image not supplied
// (this is probably overkill)
const DEFAULT_IMAGE = Buffer.from(
"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",
"base64"
);
main()
.then(console.log)
.catch(err => console.error("ERROR", err.message));

Просмотреть файл

@ -11,7 +11,11 @@ const { DEV_CREDENTIALS, DEFAULT_HAWK_ALGORITHM } = require("../lib/constants");
const REQUIRED_FIELDS = ["image", "negative_uri", "positive_uri"];
module.exports.post = async function(event, context) {
const { QUEUE_NAME: QueueName, CONTENT_BUCKET: Bucket } = process.env;
const {
UPSTREAM_SERVICE_URL,
QUEUE_NAME: QueueName,
CONTENT_BUCKET: Bucket
} = process.env;
const {
headers,
@ -69,10 +73,20 @@ module.exports.post = async function(event, context) {
Body: image.data
}).promise();
const upstreamServiceUrl =
UPSTREAM_SERVICE_URL !== "__MOCK__"
? UPSTREAM_SERVICE_URL
: "https://" +
event.headers.Host +
"/" +
event.requestContext.stage +
"/mock/upstream";
const { QueueUrl } = await SQS.getQueueUrl({ QueueName }).promise();
await SQS.sendMessage({
QueueUrl,
MessageBody: JSON.stringify({
upstreamServiceUrl,
id: requestId,
user: credentials.id,
negative_uri,

Просмотреть файл

@ -0,0 +1,50 @@
"use strict";
module.exports.upstreamPost = async (event, context) => {
console.log("upstream", event.body);
return response(
200,
Object.assign(
{},
baseMatchResponse,
// TODO: Find a more deterministic way to simulate pos/neg match
{ IsMatch: Math.random() < 0.5 }
)
);
};
module.exports.clientNegativePost = async (event, context) => {
console.log("negative", event.body);
return response(200, { status: "OK" });
};
module.exports.clientPositivePost = async (event, context) => {
console.log("positive", event.body);
return response(200, { status: "OK" });
};
function response(statusCode, body, headers = {}) {
return {
statusCode,
headers: Object.assign({ "Content-Type": "application/json" }, headers),
body: JSON.stringify(body)
};
}
const baseMatchResponse = {
Status: {
Code: 3000,
Description: "OK",
Exception: null
},
ContentId: null,
IsMatch: false,
MatchDetails: {
AdvancedInfo: [],
MatchFlags: []
},
XPartnerCustomerId: null,
TrackingId:
"WUS_418b5903425346a1b1451821c5cd06ee_57c7457ae3a97812ecf8bde9_ddba296dab39454aa00cf0b17e0eb7bf",
EvaluateResponse: null
};

Просмотреть файл

@ -137,6 +137,7 @@ const positiveMatchResponse = {
};
const defaultMessage = {
upstreamServiceUrl: UPSTREAM_SERVICE_URL,
id: "8675309",
user: "devuser",
negative_uri: "https://example.com/negative?id=123",

Просмотреть файл

@ -6,14 +6,10 @@ const SQS = new AWS.SQS({ apiVersion: "2012-11-05" });
const request = require("request-promise-native");
module.exports.handler = async function({ ReceiptHandle, Body }) {
const {
QUEUE_NAME,
CONTENT_BUCKET,
UPSTREAM_SERVICE_URL,
UPSTREAM_SERVICE_KEY
} = process.env;
const { QUEUE_NAME, CONTENT_BUCKET, UPSTREAM_SERVICE_KEY } = process.env;
const {
upstreamServiceUrl,
id,
// user,
negative_uri,
@ -30,7 +26,7 @@ module.exports.handler = async function({ ReceiptHandle, Body }) {
});
const upstreamServiceResponse = await request.post({
url: `${UPSTREAM_SERVICE_URL}?enhance`,
url: `${upstreamServiceUrl}?enhance`,
headers: {
"Content-Type": "application/json",
"Ocp-Apim-Subscription-Key": UPSTREAM_SERVICE_KEY

Просмотреть файл

@ -9,7 +9,7 @@
"info": "serverless info",
"lint": "npm-run-all lint:*",
"lint:js": "eslint functions lib test",
"prettier": "prettier --write \"{functions,lib}/**/*.js\"",
"prettier": "prettier --write \"{functions,lib,bin}/**/*.js\"",
"logs": "serverless logs",
"postlint": "nsp check -o summary",
"nsp": "nsp check",

Просмотреть файл

@ -1,7 +1,8 @@
# Change this to a name that's unique to you
stage: your-name-here-dev
# Point this at a debugging URL to watch your stack sending requests
# Using __MOCK__ values here causes the stack to use its own internal mock
# endpoints. Otherwise, you can configure a real upstream service URL and key
upstreamService:
url: https://webhook.site/c0a8dd46-1405-4172-a99a-0646663f3dc2
key: 8675309
url: __MOCK__
key: __MOCK__

Просмотреть файл

@ -170,3 +170,32 @@ functions:
handler: functions/processQueueItem.handler
name: ${self:custom.process}
environment: ${self:custom.fnEnv}
# TODO: Find a way to exclude these functions from prod deployments
# See https://stackoverflow.com/questions/47718004/exclude-lambda-function-from-deploy-to-a-particular-stage
mockUpstream:
handler: functions/mockEndpoints.upstreamPost
name: ${self:custom.prefix}-mockUpstream
environment: ${self:custom.fnEnv}
events:
- http:
path: mock/upstream
method: post
mockClientNegative:
handler: functions/mockEndpoints.clientNegativePost
name: ${self:custom.prefix}-mockClientNegative
environment: ${self:custom.fnEnv}
events:
- http:
path: mock/client/negative
method: post
mockClientPositive:
handler: functions/mockEndpoints.clientPositivePost
name: ${self:custom.prefix}-mockClientPositive
environment: ${self:custom.fnEnv}
events:
- http:
path: mock/client/positive
method: post