Sinon Stub for AWS X-Ray calls - sinon

I'm trying to set stubs for AWS X-Ray calls to unit-test NodeJS Lambdas outside of AWS.
I have the code below in my lambda. How can I use sinon to stub out the calls to the AWSXRay API shown below? The getSegment() call has to be stubbed, then "chained" so I can then call addNewSubSegment(parm)?
const AWSXRay = require('aws-xray-sdk-core');
...
const rootSubSegment = AWSXRay.getSegment().addNewSubsegment("RootSubSegment");
rootSubSegment.addAnnotation("MyAnnotationKey", "MyAnnotationData");
rootSubSegment.addMetadata("MyMetaDataKey", "MyMetaData");
const s3SubSegment = AWSXRay.getSegment().addNewSubsegment("Do S3 Stuff");
...
s3SubSegment.close();
rootSubSegment.close();

You just need to stub getSegment to return an object that has a stubbed addNewSubsegment property.
It looks like your code runs as soon as it is required, so you need to make sure your stub is in place before requiring your code in the test.
Here is a working example test to get you started:
const AWSXRay = require('aws-xray-sdk-core');
const sinon = require('sinon');
describe('code', () => {
it('should add subsegments', () => {
const getSegmentStub = sinon.stub(AWSXRay, 'getSegment');
const addNewSubsegmentStub = sinon.stub();
getSegmentStub.returns({ addNewSubsegment: addNewSubsegmentStub });
const rootSubSegmentMock = {
addAnnotation: sinon.spy(),
addMetadata: sinon.spy(),
close: sinon.spy()
}
const s3SubSegmentMock = {
close: sinon.spy()
}
addNewSubsegmentStub.onFirstCall().returns(rootSubSegmentMock);
addNewSubsegmentStub.onSecondCall().returns(s3SubSegmentMock);
require('[path to your code]'); // <= now require the code to run it
sinon.assert.calledWithExactly(addNewSubsegmentStub.firstCall, 'RootSubSegment'); // Success!
sinon.assert.calledWithExactly(addNewSubsegmentStub.secondCall, 'Do S3 Stuff'); // Success!
sinon.assert.calledWithExactly(rootSubSegmentMock.addAnnotation, 'MyAnnotationKey', 'MyAnnotationData'); // Success!
sinon.assert.calledWithExactly(rootSubSegmentMock.addMetadata, 'MyMetaDataKey', 'MyMetaData'); // Success!
sinon.assert.called(s3SubSegmentMock.close); // Success!
sinon.assert.called(rootSubSegmentMock.close); // Success!
})
})

Related

How to use runtime config in composable?

I want to do this
composables/apiFetch.ts
import { $fetch } from 'ohmyfetch'
export const useApiFetch = $fetch.create({ baseURL: useRuntimeConfig().apiUrl })
And use it within Pinia so I don't repeat myself writing $fetch.create over and over again for every single API call.
somewhere_in_pinia.ts
...TRIM...
actions: {
async doSomething(payload: SomeNicePayload): Promise<void> {
const response = await useApiFetch('/something', { method: 'POST', body: payload })
}
}
...TRIM...
But Nuxt won't allow me
[nuxt] [request error] nuxt instance unavailable
at useNuxtApp (/D:/XXXX/frontend/prms-fe/.nuxt/dist/server/server.mjs:472:13)
at Module.useRuntimeConfig (/D:/XXXX/frontend/prms-fe/.nuxt/dist/server/server.mjs:480:10)
at $id_Yl353ZXbaH (/D:/XXXX/frontend/prms-fe/.nuxt/dist/server/server.mjs:38358:90)
at async __instantiateModule__ (/D:/XXXX/frontend/prms-fe/.nuxt/dist/server/server.mjs:40864:3)
I have been looking for solution online, followed instruction from the official discussion to no avail.
EDIT
I don't want to use Nitro, since my backend is already written on Laravel. I need to access the host without re-typing it all over the place so I thought I could use .env and runtimeConfig.
you are trying to access Nuxt instance while it's not ready yet. To make it work, write your composable as a function :
import { $fetch } from 'ohmyfetch'
export const useApiFetch = (url, params) => {
const instance = $fetch.create({ baseURL: useRuntimeConfig().apiUrl })
return instance(url, params)
}

Cannot call AWS Cognito APIs from AWS lambda, but the same code runs fine in local node.js

This is my first question ever here:-)
I need to list users in our Cognito use pool. Seems this can be done only using the aws-sdk CognitoIdentityServiceProvider APIs. I got the below code to work perfectly from my local node.js. It lists all users as expected.
However, the same code behaves differently when put into an AWS lambda function. It still runs, but it never waits for the Cognito listUsers() call to return. It just simply completes, like the "await" is not waiting at all. None of the console.log() is invoked from the client.listUsers(params, function(err, data)..._ block.
I tested this inside Lambda directly as well as from AWS API gateway. The same null is return. The call itself is successful, just no data is returned.
See log at the end.
BTW, I did create a role and add a policy giving the role full access to the cognito user pool.
What did I miss? Appreciate your help!
Martin C.
-----------------code------------------------
async function getUserList() {
console.log("enter LAMDA function**********");
var aws = require('aws-sdk');
aws.config.update({accessKeyId: 'xxxxxxxx', secretAccessKey: 'xxxxxxxxxxx'});
var CognitoIdentityServiceProvider = aws.CognitoIdentityServiceProvider;
var client = new CognitoIdentityServiceProvider({ apiVersion: '2016-04-19', region: 'us-east-2' });
var params = {
UserPoolId: 'us-east-xxxxxxxx', /* required */
AttributesToGet: [
'given_name','family_name','phone_number','email','profile'
],
Filter: null,
Limit: 0,
PaginationToken: null
};
console.log("Right before call the listUser method");
let result = await client.listUsers(params, function(err, data) {
console.log("call back reached!");
if (err) {
console.log(err, err.stack); // an error occurred
const response = {
statusCode: 500,
body: JSON.stringify('An error occurred.'),
}
return response;
}
else {
console.log(data);
var count = data.Users.length;
// successful response
const response = {
statusCode: 200,
body: JSON.stringify("sucessful list users! User count="+count)
}
return response;
}
});
console.log("no waiting here. async!!!")
}
getUserList();
***************Lambda log*****************
**************Log when called from node.js****************
getUserList is your lambda function? I don't know why you call it by your self getUserList().
I see, you are using lambda runtime is nodejs version > 8, you use await keyword with a callback function(fail) => you not wait anything.
When a function call by Lambda, the function (async function) will finish when get a return or run to end of function (without return), in your case the function finish when console.log("no waiting here. async!!!") has been executed. In local environment, the funciton finishs when callstack has been clear (do not have any callback function in callstack).
Right way, you have use promise version of aws-sdk then use await syntax to get a result. Relate to How to use Async and Await with AWS SDK Javascript
async function getUserList() {
console.log("enter LAMDA function**********");
var aws = require('aws-sdk');
aws.config.update({ accessKeyId: 'xxxxxxxx', secretAccessKey: 'xxxxxxxxxxx' });
var CognitoIdentityServiceProvider = aws.CognitoIdentityServiceProvider;
var client = new CognitoIdentityServiceProvider({ apiVersion: '2016-04-19', region: 'us-east-2' });
var params = {
UserPoolId: 'us-east-xxxxxxxx', /* required */
AttributesToGet: [
'given_name', 'family_name', 'phone_number', 'email', 'profile'
],
Filter: null,
Limit: 0,
PaginationToken: null
};
console.log("Right before call the listUser method");
try {
let result = await client.listUsers(params).promise(); // use Promise style
console.log(data);
var count = data.Users.length;
// successful response
const response = {
statusCode: 200,
body: JSON.stringify("sucessful list users! User count=" + count)
}
return response; // return to finish function
} catch (err) {
console.log(err, err.stack); // an error occurred
const response = {
statusCode: 500,
body: JSON.stringify('An error occurred.'),
}
return response;
}
}
getUserList(); // remove this line when deploy funtion to Lambda.

jest test finishes before the mocked firebase function is called and hence fails

I am trying to use jest to test calls to firebase. Firebase is an online database service offered by Google. I am mocking the firebase module like below
'use strict';
const firebase = jest.genMockFromModule('firebase');
const ref = jest.fn(() => {
return {
child: jest.fn(() => {
return ref
}),
update: jest.fn(() => {
console.log('Called update')
return Promise.resolve()
})
}
})
firebase.initializeApp = jest.fn()
firebase.database = jest.fn(() => {
return {
ref: ref
}
})
module.exports = firebase
I am only mocking the functions that I need to test at the moment. Below is my test case.
it('+++ actionCreator addAlarm', () => {
const store = mockStore(initialState)
store.dispatch(ActionCreators.addAlarm(alarm));
// Make sure that the scheduleNotifications API is called
expect(scheduleNotifications).toHaveBeenCalled();
expect(firebase.initializeApp).toHaveBeenCalled();
expect(firebase.database().ref().update).toHaveBeenCalled();
});
The last line in the test case is trying to make sure that I am calling the firebase update function which is mocked.
Below is the console output
abcs-MBP-2:GalarmApp abc$ npm test
> GalarmApp#1.0.53 test /Users/abc/Projects/GalarmApp
> jest
FAIL __tests__/actionsSpecs.js
● >>>A C T I O N --- Test galarm actions: › +++ actionCreator addAlarm
expect(jest.fn()).toHaveBeenCalled()
Expected mock function to have been called.
at Object.<anonymous> (__tests__/actionsSpecs.js:58:52)
at tryCallTwo (node_modules/promise/lib/core.js:45:5)
at doResolve (node_modules/promise/lib/core.js:200:13)
at new Promise (node_modules/promise/lib/core.js:66:3)
at Promise.resolve.then.el (node_modules/p-map/index.js:42:16)
at tryCallOne (node_modules/promise/lib/core.js:37:12)
at node_modules/promise/lib/core.js:123:15
>>>A C T I O N --- Test galarm actions:
✕ +++ actionCreator addAlarm (8ms)
✓ +++ actionCreator setConnectionStatus (4ms)
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 passed, 2 total
Snapshots: 1 passed, 1 total
Time: 1.989s, estimated 2s
Ran all test suites.
console.warn node_modules/rn-host-detect/index.js:45
[SECURITY] node-uuid: crypto not usable, falling back to insecure Math.random()
console.log __mocks__/firebase.js:11
Called update
The test case is failing on the line where I check that the update function is called. If you look down in the console output, you will see that Called update console is present which means that the update function is called but it is called after the test case has failed.
This is the addAlarm action which is a thunk action
const addAlarm = (alarm) => (dispatch, getState) => {
if(alarm.status) {
NotificationManager.scheduleNotifications(alarm);
}
const alarmObjForFirebase = this.createAlarmObjForFirebase(alarm)
firebaseRef.update(alarmObjForFirebase)
}
The call to firebase update function is not happening asynchronously as far as I understand.
Please let me know if you have pointers on how I may be able to fix this problem.
I have found the problem in my code and posting as a solution for others benefit. The problem is the way the update mock was defined. The way it was defined, I was getting a new instance of the update mock function every time I will make a call to firebase.database().ref().update. Since this is a new instance of the mock function, it wouldn't contain any data aboutupdate` function being called in the past.
I needed to change the code as follows
const update = jest.fn(() => {
return Promise.resolve()
})
const ref = jest.fn(() => {
return {
update: update
}
})
This way, I am not creating new instances of the update mock function and I was able to assert that it has been called during the testcase.

Firebase Storage & Cloud Functions - ECONNRESET

I developed a Firebase Cloud function that processes several manipulations on uploaded images.
My code is based on this documentation article and this Cloud Function example. Hence, it is using Google Cloud Storage package.
It is working fine almost all the time, but sometimes I am getting this error when uploading to or deleting from Storage :
Error: read ECONNRESET
at exports._errnoException (util.js:1026:11)
at TLSWrap.onread (net.js:569:26)
I am using the default bucket of my application, referenced by event.data.bucket.
Let me know if you need additional information or code snippets, even if my code is really close to the Function example I linked before.
I found this GitHub issue, but I checked that I am returning a promise everytime. For example, here is the deletion part that triggers the error :
index.js
exports.exampleFunction = functions.storage.object().onChange(event => {
return f_thumbnails.exampleFunction(event);
});
example_function.js
module.exports = exports = function (_admin, _config) {
admin = _admin;
config = _config;
return {
"exampleFunction": function (event) {
return exampleFunction(event);
}
};
};
const exampleFunction = function (event) {
const gcsSourceFilePath = event.data.name;
const gcsSourceFilePathSplit = gcsSourceFilePath.split('/');
const gcsBaseFolder = gcsSourceFilePathSplit.length > 0 ? gcsSourceFilePathSplit[0] : '';
const gcsSourceFileName = gcsSourceFilePathSplit.pop();
const gceSourceFileDir = gcsSourceFilePathSplit.join('/') + (gcsSourceFilePathSplit.length > 0 ? '/' : '');
// Not an image
if (!event.data.contentType.startsWith('image/')) {
console.log('Not an image !');
return;
}
// Thumbnail
if (gcsSourceFileName.startsWith(config.IMAGES_THUMBNAIL_PREFIX)) {
console.log('Thumbnail !');
return;
}
const bucket = gcs.bucket(event.data.bucket);
const gcsThumbnailFilePath = gceSourceFileDir + config.IMAGES_THUMBNAIL_PREFIX + gcsSourceFileName;
// File deletion
if (event.data.resourceState === 'not_exists') {
console.log('Thumbnail deletion : ' + gcsThumbnailFilePath);
return bucket.file(gcsThumbnailFilePath).delete().then(() => {
console.log('Deleted thumbnail ' + gcsThumbnailFilePath);
});
}
...
This seems to be related to the google-cloud-node library's handling of sockets, and the default socket timeout in the Cloud Functions environment.
One solution verified by a user is to modify the way the library invokes requests, to not keep the socket open forever by specifying forever: false, eg.
var request = require('request').defaults({
timeout: 60000,
gzip: true,
forever: false,
pool: {
maxSockets: Infinity
}
});
This is hardcoded in packages/common/src/utils.js, so you'll need to vendor a copy of the modified library into your project rather than include it as an NPM dependency. See the related public issue for more details on the issue and a link to a fork with the patch applied.

Running tests with localStorage

i have followed this tutorial from Codelab and yeoman. When implemented right you are using local storage to store the TodoList. I have problems with setting up with my tests, to test if this works. This is what i've got so far:
'use strict';
describe('Controller: MainCtrl', function () {
// load the controller's module
beforeEach(module('yeoTodoApp'), module('LocalStorageModule'));
var MainCtrl,
scope;
// Initialize the controller and a mock scope
beforeEach(inject(function ($controller, $rootScope, $httpBackend) {
scope = $rootScope.$new();
MainCtrl = $controller('MainCtrl', {
$scope: scope
});
}));
it('should add items to the list', function () {
var beforeLength = scope.todos.length;
scope.todo = 'Test 1';
scope.addTodo();
var afterLength = scope.todos.length;
expect(afterLength-beforeLength).toBe(1);
});
it('should add items to the list then remove', function () {
var beforeLength = scope.todos.length;
scope.todo = 'Test 1';
scope.addTodo();
scope.removeTodo(0);
var afterLength = scope.todos.length;
expect(afterLength-beforeLength).toBe(0);
});
});
The error i get is
line 12 col 68 '$httpBackend' is defined but never used.
});
How would i write my unit tests to sit the local storage?
I think at the moment the idea is kind of mocking your local storage:
Write unit tests
For an extra challenge, revisit unit testing in Step 8 and consider
how you might update your tests now that the code is using local
storage.
Tip: It's not a straight forward answer and involves knowing about
mock services. Check out Unit Testing Best Practices in AngularJS,
specifically the Mocking Services and Modules in AngularJS section.
Things may have changed since this question was asked. Anyhow, here is my solution:
'use strict';
describe('Controller: MainCtrl', function () {
// load the controller's module
beforeEach(module('mytodoyoappApp'));
var MainCtrl,
scope,
localStorage, store;
// Initialize the controller and a mock scope
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new();
MainCtrl = $controller('MainCtrl', {
$scope:scope
// place here mocked dependencies
});
/*mock the localStorageService*/
store={};
localStorage = {
set: function(key, value) {
store[key] = value;
},
get: function(key) {
return store[key];
}
};
}));
it('should check the list length', function () {
expect(MainCtrl.todos.length).toBe(0);
});
it('should add items to the list', function () {
MainCtrl.todoadded = 'Test 1';
MainCtrl.addTodo();
expect(MainCtrl.todos.length).toBe(1);
});
it('should add then remove an item from the list', function () {
MainCtrl.todoadded = 'Test 2';
MainCtrl.addTodo();
MainCtrl.removeTodo(0);
expect(MainCtrl.todos.length).toBe(0);
});
it('should check that the localstorage is undefined before being set', function() {
var a=localStorage.get('todos');
expect(a).toBeUndefined();
});
it('should set and get the localstorage', function() {
localStorage.set('todos', ['Test 3']);
var a=localStorage.get('todos');
expect(a).toEqual(['Test 3']);
localStorage.set('todos', ['Test 4']);
var b=localStorage.get('todos');
expect(b).toEqual(['Test 4']);
});
});
your setup is correct now (after you removed $httpBackend from the arguments list)
Controller: MainCtrl should add items to the list then remove FAILED
this error is a simple test error, which means that your code somewhere doesnt work as expected (your second test fails)
i for myself would check todos length, and not the result of a mathematical operation.
i would write your tests the test like this:
it('should add items to the list then remove', function () {
scope.todo = 'Test 1';
expect(scope.todos.length).toBe(0);
scope.addTodo();
expect(scope.todos.length).toBe(1);
scope.removeTodo(0);
expect(scope.todos.length).toBe(0);
});
you use jasmine as a test-tool. jasmine logs on errors exactly which expectation fails, so you should get something like
expect '1' to be '0'
go from there!

Resources