Is this even possible?
Hello friends.
I'm in the process of building an application using AWS AppSync + DynamoDB and I'm starting to have quite a large pile of resolver mapping templates, all which are written using the Apache Velocity Template Language (VTL).
The concern I'm starting to have is that these vtl files are quite critical to the application (since they define how data is retrieved) and a bug in one of the could wreak havoc. So like any critical part of a system... I would like to write some automated unit tests for them. But I haven't found much about others doing this.
If you're using VTL (with AppSync or API Gateway), how do you test them?
Is it even possible to write automated tests to velocity templates?
Or am I going down the total wrong path and I should just be using Lambdas as my resolvers?
Thanks in advance!
It took me a while to figure this out myself, but I found a good way to write unit tests for my VTL request and response templates. I used the amplify-appsync-simulator npm package's VelocityTemplate class. The only caveat I have seen so far is that you need to use $context in your VTL, the abbreviated $ctx is not recognized by the simulators VTL renderer. Check it out:
My VTL:
#set( $timeNow = $util.time.nowEpochMilliSeconds() )
{
"version" : "2018-05-29",
"operation" : "PutItem",
"key": {
"pk" : { "S" : "game#${util.autoId()}" },
"sk" : { "S" : "meta#${timeNow}" }
},
"attributeValues" : {
"players": { "L" : [
{ "M" : {
## num and color added at start game
"player": $util.dynamodb.toDynamoDBJson($context.args.input.player)
}}
]},
"createdBy": { "S": "${context.identity.sub}"},
"gsipk": { "S": "${context.args.input.status}"},
"gsisk": { "S": "${context.args.input.level}#${context.args.input.maxPlayers}"},
"gsipk2": {"S": "game"},
"turns": { "L": [] },
"nextPlayerNum": { "N": 1 },
"createdAt": { "N": ${timeNow} },
"updatedAt": { "N": ${timeNow} }
}
}
My test:
import { AmplifyAppSyncSimulator } from 'amplify-appsync-simulator'
import { VelocityTemplate } from "amplify-appsync-simulator/lib/velocity"
import { readFileSync } from 'fs'
import path from 'path';
const vtl = readFileSync(path.join(__dirname, '..', 'addGame-request-mapping-template.vtl'))
const template = {
content: vtl
}
const velocity = new VelocityTemplate(template, new AmplifyAppSyncSimulator)
describe('valid user and request', () => {
// This is the graphql input
const validContext = {
arguments: {
input: {
player: 'player#1234',
maxPlayers: 4,
status: 'new',
level: 7
}
},
source: {}
}
// This is a logged in user with a JWT
const requestContext = {
requestAuthorizationMode: 'OPENID_CONNECT',
jwt: {
sub: 'abcd1234'
}
}
const info = {
fieldNodes: []
}
it('works', () => {
const result = velocity.render(validContext, requestContext, info)
expect(result).toEqual({
result: {
version: "2018-05-29",
operation: "PutItem",
key: {
pk: { S: expect.stringMatching(/^game#[a-f0-9-]*$/) },
sk: { S: expect.stringMatching(/^meta#[0-9]*$/)}
},
attributeValues: {
players: {
L: [
{ M: { player: { S: validContext.arguments.input.player }}}
]
},
createdBy: { S: requestContext.jwt.sub },
gsipk: { S: validContext.arguments.input.status },
gsisk: { S: `${validContext.arguments.input.level}#${validContext.arguments.input.maxPlayers}`},
gsipk2: { S: 'game' },
turns: { L: [] },
nextPlayerNum: { N: 1 },
createdAt: { N: expect.any(Number) },
updatedAt: { N: expect.any(Number) }
}
},
stash: {},
errors: [],
isReturn: false
})
})
})
Found this project https://github.com/ToQoz/api-gateway-mapping-template which is a bit old but still works.
It is designed to test API Gateway mapping templates so it is missing all the special $util functions you get with AppSync resolvers, but I think one can incrementally add missing utils.
Amplify just released the ability to locally test your AppSync apis, including VTL resolvers. You can check out their blog post https://aws.amazon.com/blogs/aws/new-local-mocking-and-testing-with-the-amplify-cli/ which contains a how-to for the local API Mocking functionality; Look for where it says "When I edit a VTL template, the Amplify CLI recognizes that immediately, and generates the updated code for the resolver." You could then build this into a CI or other testing pipeline of your choosing.
Related
Is there a way of making transactWriteItem return the document it updated?
const transactionParams = {
ReturnConsumedCapacity: "INDEXES",
TransactItems: [
{
Delete: {
TableName: reactionTableName,
Key: {
"SOME_PK_",
"SOME_SK_",
},
ReturnValues: 'ALL_OLD',
},
},
{
Update: {
TableName: reviewTableName,
Key: { PK: "SOME_PK", SK: "SOME_SK" },
ReturnValues: 'ALL_OLD',
},
},
],
};
try {
const result = await docClient.transactWrite(transactionParams).promise();
} catch (error) {
context.done(error, null);
}
For example in the above code get the documents that were touched (before or after update)?
No, TransactWriteItems API does not provide the ability to return values of a modified item, however, you could obtain those values using DynamoDB Streams, otherwise you would need to default to the singleton APIs UpdateItem/DeleteItem which are not ACID compliant together.
I use a Dialogflow API as NLP and the interface that we use is Whatsapp API.
my problem is, when I want to bypass Text and Whatsapp client number to Dialogflow (my reference), I didn't found document to explain that. for comparison, the Telegram official integration dialogflow, from the body request we can extract that data like name and Telegram user ID.
const sessionId = phone_number_id; //session ID get from phone number
const sessionPath = sessionClient.projectAgentSessionPath(projectId, sessionId);
const request = {
session: sessionPath,
queryInput: {
text: {
text: msg_body,
languageCode: "id-ID"
},
},
payload: {
data: "testing",
phoneNumber : phone_number_id
}
};
console.log("request", request);
await sessionClient.detectIntent(request).then(responses => {
console.log("DetectIntent", JSON.stringify(responses));
}).catch(err => {
console.error("ERROR:", err);
})
I tried it with request variable like that but in request body in dialogflow fulfillment, it never showed up
{
"responseId": "censored",
"queryResult": {
"queryText": "halo",
"action": "input.welcome",
"parameters": {},
"allRequiredParamsPresent": true,
"fulfillmentText": "error",
"fulfillmentMessages": [
{
"text": {
"text": [
"error"
]
}
}
],
"outputContexts": [
{
"name": "censored",
"parameters": {
"no-input": 0,
"no-match": 0
}
}
],
"intent": {
"name": "censored",
"displayName": "Default Welcome Intent"
},
"intentDetectionConfidence": 1,
"languageCode": "id"
},
"originalDetectIntentRequest": {
"payload": {}
},
"session": "censored"
}
#Maulana ahmad, As you have mentioned in the comment below example code can be referred to extract data from the body request.
const dialogflow = require('dialogflow');
// Import the JSON to gRPC struct converter
const structjson = require('./structjson.js');
// Instantiates a sessison client
const sessionClient = new dialogflow.SessionsClient();
// The path to identify the agent that owns the created intent.
const sessionPath = sessionClient.sessionPath(projectId, sessionId);
// The text query request.
const request = {
session: sessionPath,
queryInput: {
event: {
name: eventName,
parameters: structjson.jsonToStructProto({foo: 'bar'}),
languageCode: languageCode,
},
},
};
sessionClient
.detectIntent(request)
.then(responses => {
console.log('Detected intent');
logQueryResult(sessionClient, responses[0].queryResult);
})
.catch(err => {
console.error('ERROR:', err);
});
This Stack Overflow link can be referred for more information.
Posting the answer as community wiki for the benefit of the community that might encounter this use case in the future.
Feel free to edit this answer for additional information.
I have been trying for hours to perform a DynamoDB DeleteRequest using BatchWriteItemCommand but I keep getting the following error:
Error ValidationException: 1 validation error detected: Value null at 'requestItems.td_notes_sdk.member.1.member.deleteRequest.key' failed to satisfy constraint: Member must not be null
This is what my table looks like:
Partition key: user_id (string)
Sort key: timestamp (number)
DynamoDB Screenshot
This is what my code looks like:
// Import required AWS SDK clients and commands for Node.js
import {
DynamoDBClient,
BatchWriteItemCommand,
} from "#aws-sdk/client-dynamodb";
// Set the parameters
export const params = {
RequestItems: {
"td_notes_sdk": [
{
DeleteRequest: {
Item: {
Key: {
user_id: { S : "bb" },
timestamp: { N : 2 },
},
},
},
},
],
},
};
export const run = async () => {
const ddbClient = new DynamoDBClient({ region: "us-east-2" });
try {
const data = await ddbClient.send(new BatchWriteItemCommand(params));
console.log("Success, items inserted", data);
return data;
} catch (err) {
console.log("Error", err);
}
};
run();
Here are some resources that I've been trying to follow along with:
Resource 1: Writing items in Batch Example
Resource 2: AWS Javascript SDK v3 Documentation
Update: BatchWrite PutRequest work with the code below, so I know that the structure of my keys/attributes is closer to being correct. Still does not work for DeleteRequest.
export const params = {
RequestItems: {
"td_notes_sdk": [
{
PutRequest: {
Item: {
user_id: { "S": "bb" },
timestamp: { "N": "5" },
},
},
},
],
},
};
You don't supply an Item when deleting an item. You supply a Key.
Here is a working example:
const params_delete = {
RequestItems: {
"td_notes_sdk": [
{
DeleteRequest: {
Key: {
user_id: { S: "bb" },
timestamp: { N: "2" },
},
},
},
],
},
};
const delete_batch = async () => {
const ddbClient = new DynamoDBClient({ region: "us-east-2" });
try {
const data = await ddbClient.send(new BatchWriteItemCommand(params_delete));
console.log("Success, item deleted");
return data;
} catch (err) {
console.log("Error", err);
}
};
delete_batch();
Sample Code from the VueJs components. Note that I'm trying to cover the watcher as well as the method
computed: {
...mapGetters({
resourceLinks: `tools/${storeGetters.resourceLinks}`,
}),
},
methods: {
formatData(data) {
this.setSocialLinks();
},
},
},
watch: {
resourceLinks(data) {
this.formatData(data);
},
},
expect(wrapper.vm.$options.watch.resourceLinks.call(wrapper.vm, resourceLinks1));
I have WSO2 AM (1.10.0) installed on a production server within my organization and this server is allowed to be accessed in public by removing firewall. However it is only available in organization's intranet like browsing to the URL https://puaki.mpi.govt.nz/api/mrldatabase/v1/countries and will get following data,
[
{
"countryCode":"AUS",
"countryName":"Australia"
},
{
"countryCode":"CAN",
"countryName":"Canada"
},
{
"countryCode":"CHN",
"countryName":"China"
},
{
"countryCode":"CDX",
"countryName":"Codex"
},
{
"countryCode":"EEC",
"countryName":"European Union"
},
{
"countryCode":"HKG",
"countryName":"Hong Kong"
},
{
"countryCode":"IND",
"countryName":"India"
},
{
"countryCode":"IDN",
"countryName":"Indonesia"
},
{
"countryCode":"ISR",
"countryName":"Israel"
},
{
"countryCode":"JPN",
"countryName":"Japan"
},
{
"countryCode":"KOR",
"countryName":"Korea"
},
{
"countryCode":"MAL",
"countryName":"Malaysia"
},
{
"countryCode":"NCL",
"countryName":"New Caledonia"
},
{
"countryCode":"NZL",
"countryName":"New Zealand"
},
{
"countryCode":"RUS",
"countryName":"Russian Federation"
},
{
"countryCode":"SGP",
"countryName":"Singapore"
},
{
"countryCode":"SAF",
"countryName":"South Africa"
},
{
"countryCode":"CHE",
"countryName":"Switzerland"
},
{
"countryCode":"TWN",
"countryName":"Taiwan"
},
{
"countryCode":"THA",
"countryName":"Thailand"
},
{
"countryCode":"USA",
"countryName":"United States"
},
{
"countryCode":"VNM",
"countryName":"Vietnam"
}
]
When trying to exacly the same thing from extranet, I got the following error
ERR_CONNECTION_RESET
I was wondering if there are filters on the API Manager to prevent extenal connections (as the traffic is getting to the server but being rejected)?
I was wondering if there are filters on the API Manager to prevent
extenal connections (as the traffic is getting to the server but being
rejected)?
There aren't any such filters in API Manager. You should check your firewall policies and ports.