I created an interceptor with provider and bound it to "rpc"
export class RPCProvider implements Provider<Interceptor> {
constructor(
#inject(CertifierBindings.CERTIFIER)
public certifierModule: CertifierModule
) { }
value() {
return this.intercept.bind(this);
}
async intercept<T>(
invocationCtx: InvocationContext,
next: () => ValueOrPromise<T>,
) {
// i want to pass some parameters in here
// ...
return await next();
}
}
application.ts
this.bind('rpc').toProvider(RPCProvider);
I can use it like this:
#intercept('rpc')
#authenticate('basic', {
scope: []
})
#post('/test/v1/anything')
async test(): Promise<any> {
return await this.dbMain.col("Maintainer").aggregateAndToArray([]);
}
But how can i pass parameters every time I use it? Something like this:
#intercept(rpc({
a:1, // <=
b:2
}))
#authenticate('basic', {
scope: []
})
#post('/test/v1/anything')
async test(): Promise<any> {
return await this.dbMain.col("Maintainer").aggregateAndToArray([]);
}
export const MyInterceptor = function (
...args: any[] // <= pass custom parameters
): Interceptor {
return async function (invocationCtx, next) {
// get binding with `invocationCtx`
const bindingObject = await invocationCtx.get<any>('binding-key');
}
}
Related
Context
I am trying to write a jest test for an authentication middleware for a resolver function. I am attempting to mock an implementation so that the next function is called so that the test passes.
Error
The error I receive is "next is not a function". I can verify that the mocked function is called through expect(isAuth).toHaveBeenCalledTimes(1);, but there is clearly an issue with my mocked implementation. Any help is much appreciated.
Code
//isAuth Middleware
import { MiddlewareFn } from "type-graphql";
import { Context } from "../utils/interfaces/context";
export const isAuth: MiddlewareFn<Context> = ({ context }, next) => {
const loggedInUserId = context.req.session.id;
if (!loggedInUserId) {
throw new Error("Not authenticated!");
}
return next();
};
//transaction.test.ts
jest.mock("../middleware/isAuth", () => {
return {
isAuth: jest.fn((_, next) => next()), //also tried (next) => next() and (next)=>Promise.resolve(next())
};
});
test("should create a txn successfully", async () => {
//ARRANGE
const user = await createUser(orm);
const txn = createTxnOptions();
const txnToBeCreated = { ...txn, userId: user.id };
//ACT
const response = await testClientMutate(
TXN_QUERIES_AND_MUTATIONS.CREATE_TXN,
{
variables: txnToBeCreated,
}
);
//expect(isAuth).toHaveBeenCalledTimes(1); passes so it's getting called
console.log(response);
const newlyCreatedTxn: Transaction = (response.data as any)
?.createTransaction;
//ASSERT
const dbTxn = await em.findOne(Transaction, {
id: newlyCreatedTxn.id,
});
expect(newlyCreatedTxn.id).toBe(dbTxn?.id);
});
//transaction.resolver.ts
import { Transaction } from "../entities/Transaction";
import {
Arg,
Ctx,
Mutation,
Query,
Resolver,
UseMiddleware,
} from "type-graphql";
import { Context } from "../utils/interfaces/context";
import { isAuth } from "../middleware/isAuth";
#Mutation(() => Transaction)
#UseMiddleware(isAuth)
async createTransaction(
#Arg("title") title: string,
#Arg("userId") userId: string,
#Ctx() { em }: Context
): Promise<Transaction> {
const transaction = em.create(Transaction, {
title,
user: userId,
});
await em.persistAndFlush(transaction);
return transaction;
}
Replace
jest.mock("../middleware/isAuth", () => {
return {
isAuth: jest.fn((_, next) => next()), //also tried (next) => next() and (next)=>Promise.resolve(next())
};
});
With
jest.mock("../middleware/isAuth", () => {
return {
isAuth: (_, next) => next()
};
});
I'm hoping to get some insight into an issue I'm having.
I have a mixin that produces a guard. The resulting guard uses a service that is injected. Here's the code for the mixin:
import {
CanActivate,
ExecutionContext,
Injectable,
mixin,
} from '#nestjs/common';
import { GqlExecutionContext } from '#nestjs/graphql';
import { AccountService } from 'src/modules/account/account.service';
export const ForAccountGuard = (
paramName: string,
{ required = false }: { required?: boolean } = {}
) => {
#Injectable()
class _ForAccountGuard implements CanActivate {
constructor(private readonly accountService: AccountService) {}
async canActivate(context: ExecutionContext) {
const ctx = GqlExecutionContext.create(context);
const accountId = ctx.getArgs()[paramName];
const currentUser = ctx.getContext().user;
if (required && !accountId) {
return false;
}
if (accountId) {
const account = await this.accountService.findUserAccount(
accountId,
currentUser.id
);
return !!account;
}
return true;
}
}
return mixin(_ForAccountGuard);
};
In my tests for a resolver that uses this mixin as a guard I'm doing the following:
#Query(() => [PropertyEntity])
#UseGuards(ForAccountGuard('accountId'))
async allProperties(#Args() { accountId }: AllPropertiesArgs) {
// <implementation removed>
}
So, the issue I'm running into is that I get the following error when running tests:
Cannot find module 'src/modules/account/account.service' from 'modules/common/guards/for-account.guard.ts'
Require stack:
modules/common/guards/for-account.guard.ts
modules/property/property.resolver.spec.ts
It looks like the injected AccountService isn't being resolved.
I'm not exactly sure how to tell Nest's testing module to override a guard that is a mixin. I've been trying it like this, but it doesn't seem to be working:
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
PropertyResolver,
...
],
})
.overrideGuard(ForAccountGuard)
.useValue(createMock<typeof ForAccountGuard>())
.compile();
);
});
So, how am I supposed to mock out a guard that is a mixin?
Okay, after tinkering a bit, I figured out a solution.
Abandoning the overrideGuard method and just going for a straight-up jest.mock of the entire mixin seems to do the trick.
So, I created a mock:
import { CanActivate, Injectable, mixin } from '#nestjs/common';
export const mockForAccountGuard = () => {
#Injectable()
class _ForAccountGuardMock implements CanActivate {
canActivate() {
return true;
}
}
return mixin(_ForAccountGuardMock);
};
And then I used jest.mock to mock it out:
// in my test file
import { mockForAccountGuard } from '../common/guards/__mocks__/for-account.guard';
import { ForAccountGuard } from '../common/guards/for-account.guard';
jest.mock('../common/guards/for-account.guard', () => ({
ForAccountGuard: mockForAccountGuard,
}));
...
describe('PropertyResolver', () => {
...
beforeEach(() => {
...
const module: TestingModule = await Test.createTestingModule({
...
}).compile() // note, not using overrideGuards here
});
})
And that seems to do the trick!
I'm using NodeJS and a MongoDB. I have this simple function for returning a generic property of a document ...
import mongoose, { Document, Schema } from "mongoose";
export interface IMyObject extends Document {
...
}
...
export async function getProperty(
req: CustomRequest<MyDto>,
res: Response,
next: NextFunction
): Promise<void> {
const {
params: { propertyName, code },
} = req;
try {
const my_obj = await MyObject.findOne({ code });
const propertyValue = my_obj ? my_obj.get(propertyName) : null;
if (propertyValue) {
res.status(200).json(propertyValue);
...
I'm struggling to figure out how to test this function. In particular, how do I mock an instance of my object that's compatible with the "get" method? I tried this
it("Should return the proper result", async () => {
const myObject = {
name: "jon",
};
MyObject.findOne = jest.fn().mockResolvedValue(myObject.name);
const resp = await superTestApp.get(
"/getProperty/name/7777"
);
expect(resp.status).toBe(StatusCodes.OK);
expect(resp.body).toEqual("happy");
but this fails with
TypeError: my_object.get is not a function
You would need to spy your object and its methods. Something like:
import MyObject from '..';
const mockedData = {
get: (v) => v
};
let objectSpy;
// spy the method and set the mocked data before all tests execution
beforeAll(() => {
objectSpy = jest.spyOn(MyObject, 'findOne');
objectSpy.mockReturnValue(mockedData);
});
// clear the mock the method after all tests execution
afterAll(() => {
objectSpy.mockClear();
});
// call your method, should be returning same content as `mockedData` const
test('init', () => {
const response = MyObject.findOne();
expect(response.get('whatever')).toEqual(mockedData.get('whatever'));
});
Ok Here is my code:
routes.test.js
import cisProvider from "./cognito-provider";
test ('User' , () => {
expect.assertions(1);
let data = await xcisProvider.forgoPassword({
ClientId: '2fpfiodf5ppsqg6tnndfnkl5r',
UserName: 'naman.jain#xe.com'
}
);
expect(data.code_delivery_details.DeliveryMedium).toEqual("EMAIL");
});
And here is what function I am trying to access
cognito-provider.js
class CognitoProvider {
constructor(config) {}
forgotPassword = params => {
const { Username: username, ClientId: clientId } = params;
return this.getHashedClientSecret(username, clientId)
.then(clientSecretHash => {
params = Object.assign(params, {
SecretHash: clientSecretHash
});
return this.provider.forgotPassword(params).promise();
});
};
}
export default CognitoProvider;
I recieve the following error when perform the test run
SyntaxError: routes.test.js: Can not use keyword 'await' outside an async function (34:15)
The line it refers to is :
let data = await xcisProvider.forgoPassword({ ...
I want to retrieve data from a model provider, but all I am getting got is 'undefined' in my controller.
Here is the code:
Controller:
pdmAtWeb.controller('SearchCtrl', function($scope, ItemModel){
$scope.updateTableFromSearch = function(){
$scope.myData = ItemModel.findAllItems();
console.log($scope.myData);
};});
Provider
pdmAtWeb.provider('ItemModel', function () {
this.defaultEndpoint = '/item';
this.defaultServiceUrl = 'http://localhost:8080/webservice';
this.setDefaultEndpoint = function (newEndpoint) {
this.defaultEndpoint = newEndpoint;
};
this.setDefaultServiceUrl = function (newServiceUrl) {
this.defaultServiceUrl = newServiceUrl;
}
this.$get = function ($http) {
var endpoint = this.endpoint;
var serviceUrl = this.serviceUrl;
var refreshConnection = function () {
// reconnect
}
return{
findAllItems: function () {
$http({method: 'GET', url: serviceUrl + endpoint}).
success(function (data, status, headers, config) {
console.log(data);
return data;
}).
error(function (data, status, headers, config) {
});
}
}
}});
The provider "ItemModel" receives the correct data from the web service. Perhaps this is an async problem, but I'm not sure.
UPDATE
After adding a deferred/promise implementation it works as expected. Here is the final code:
Controller:
pdmAtWeb.controller('SearchCtrl', function($scope, ItemModel){
$scope.updateTableFromSearch = function(){
ItemModel.findAllItems().then(function(data){
console.log(data);
$scope.myData = data;
});
};
});
Provider
pdmAtWeb.provider('ItemModel', function () {
this.defaultEndpoint = '/item';
this.defaultServiceUrl = 'http://localhost:8080/webservice';
this.setDefaultEndpoint = function (newEndpoint) {
this.defaultEndpoint = newEndpoint;
};
this.setDefaultServiceUrl = function (newServiceUrl) {
this.defaultServiceUrl = newServiceUrl;
}
this.$get = function ($http, $q) {
var endpoint = this.defaultEndpoint;
var serviceUrl = this.defaultServiceUrl;
var refreshConnection = function () {
// reconnect
}
return{
findAllItems: function () {
var deferred = $q.defer();
$http({method: 'GET', url: serviceUrl + endpoint}).
success(function (data, status, headers, config) {
deferred.resolve(data);
}).
error(function (data, status, headers, config) {
deferred.reject();
});
return deferred.promise;
}
}
}
});
You dont need a deferred to accomplish this. Your $http already returns a promise. In your first example, the reason you are getting an undefined is because you are not returning anything. Check your findAllItems . It is not returning anything.
If you do return $http.get(.....) everything should work without using deferred explicitly.
Here is the corrected version :
pdmAtWeb.provider('ItemModel', function () {
this.defaultEndpoint = '/item';
this.defaultServiceUrl = 'http://localhost:8080/webservice';
this.setDefaultEndpoint = function (newEndpoint) {
this.defaultEndpoint = newEndpoint;
};
this.setDefaultServiceUrl = function (newServiceUrl) {
this.defaultServiceUrl = newServiceUrl;
}
this.$get = function ($http) {
var endpoint = this.endpoint;
var serviceUrl = this.serviceUrl;
var refreshConnection = function () {
// reconnect
}
return{
findAllItems: function () {
//NOTE addition of return below.
return $http({method: 'GET', url: serviceUrl + endpoint}).
success(function (data, status, headers, config) {
console.log(data);
//NOTE: YOU SHOULD ALSO return data here for it to work.
return data;
}).
error(function (data, status, headers, config) {
});
}
}
}});