Related
Where am i going wrong here?
Using mocha, chai, sinon and proxyquire for an express server and sequelize ORM linked with a postgres database
I am trying to test a login controller route from my express server
Before I show the file which I want to run my test on here is what "../services/authService.js" file looks like
../services/authService
const UserService = require("./userService");
module.exports = class AuthService extends UserService {
};
// so UserService will have the method findByEmail
// UserService class looks like this and it is coming from another file require("./userService.js) as stated above
/*
class UserService {
async findByEmail(email) {
try {
const user = await User.findOne({ where: { email: email }});
if (user) {
return user;
}
throw new Error("User not found");
} catch (err) {
err.code = 404;
throw err
}
}
}
*/
And here is the auth-controller.js file which I want to run the test on
auth-controller.js
const bcrypt = require('bcryptjs');
const AuthService = require("../services/authService"); // is a class which extends from another calls see the code above
const authService = new AuthService();
const jwtGenerator = require('../utils/jwtGenerator');
const createError = require("http-errors");
exports.loginRoute = async (req, res, next) => {
try {
req.body.password = String(req.body.password);
// db query trying to force a sinon.stub to resolve a fake value. But code wont pass here hence 500 error
const userQuery = await authService.findByEmail(req.body.email);
const compare = await bcrypt.compare(req.body.password, userQuery.password);
if (!compare) throw createError(401, 'Incorrect password.');
const user = {
id: userQuery.id, role: userQuery.is_admin ? "Administrator" : "User", email: userQuery.email, Authorized: true
}
const token = jwtGenerator(user);
return res
.cookie("access_token", token, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
}).status(200).json({ message: "Logged in successfully 😊 👌", user, token });
} catch (error) {
next(error);
}
}
This code works in production but I cannot seem to test it. I used proxyquire to require the modules that the function uses. I have a big problem in making proxyquire work when it comes to my class AuthService here is my test file. As proxyquire is not working with classes some how. proxyquire is not using make AuthServiceMock at all cant figure out why.
First of these are my helper variables which I will use in the test file
../test-utils/user-helper
const createAccessToken = (payload) => jwt.sign(payload, TOKEN, {expiresIn: "1h"});
let loginDetail = {
email: "admin#test.com",
password: "123456"
};
let loginAdminUser = {
id: 1,
email: "admin#test.com",
password: "123456",
is_admin: true
}
const loginUser = {
id: 1,
email: "admin#test.com",
password: "123456",
is_admin: true
}
const adminUser = {
id: 1,
email: 'admin#test.com',
password: '123456',
is_admin: true,
first_name: 'john',
last_name: 'doe',
created_at: "2020-06-26T09:31:36.630Z",
updated_at: "2020-06-26T09:31:49.627Z"
}
module.exports = {
createAccessToken,
loginDetail,
loginAdminUser,
loginUser,
adminUser
}
And here is the test file I placed comments espcially around proxyquire when I am trying to use it as this is giving me some issues when it comes to using it with classes. And as well it is not calling mocked/stubbed npm modules for some reason
auth-controller.spec.js
"use strict";
const _ = require("lodash");
const path = require("path");
const proxyquire = require("proxyquire").noCallThru().noPreserveCache();
const chai = require("chai");
const { expect } = chai;
const sinon = require("sinon");
const sinonChai = require("sinon-chai");
chai.use(sinonChai);
// const AuthServiceOriginalClass = require("../../services/authService"); If i use this directly in proxyquire it calls the original class
const { createAccessToken, loginDetail, loginAdminUser, loginUser, adminUser } = require("../test-utils/user-helper");
const controllerPath = path.resolve('./controllers/authController.js');
describe("login route", () => {
let proxy, authService, bcryptStub, fakeCallback, fakeReq, fakeRes, fakeNext, resolveFn, token;
let result, bcryptStubbing, response;
class UserServiceMock {
async findByEmail(email) {
try {
if (email) {
return loginAdminUser;
}
} catch (error) {
throw error;
}
}
}
class AuthServiceMock extends UserServiceMock {};
bcryptStub = {
compare: function() { return true }
};
let tokeen = (kk) => {
return createAccessToken(kk);
}
// token = sinon.mock(createAccessToken(loginAdminUser)); // ?? which 1 to use?
token = sinon.spy(createAccessToken); // ?? which 1 to use?
// token = sinon.stub(createAccessToken) ?? which 1 to use?
proxy = proxyquire(controllerPath, {
"../services/authService.js": AuthServiceMock, // seems like this is not called at all
// "../services/authService.js": AuthServiceOriginalClass, // commented out if use this instead it calls the original class instant
"bcryptjs": bcryptStub,
"../utils/jwtGenerator": token,
// "#noCallThru": true // keep on or off?
});
before("Stub my methods", () => {
authService = new AuthServiceMock();
// If I call the entire loginRoute I want this stub authTry to be called inside of it and resolve that object value
authTry = sinon.stub(authService, "findByEmail").withArgs(loginDetail.email).resolves(loginAdminUser);
sinon.stub(bcryptStub, "compare").resolves(true); // force it to return true as that seems to be like the code of authController.js
// sinon.stub(token, "createAccessToken")
});
before("call the function loginRoute", async () => {
// fakeCallback = new Promise((res, rej) => {
// resolveFn = res
// });
fakeReq = {
body: {
email: loginDetail.email,
password: loginDetail.password
}
};
fakeRes = {
cookie: sinon.spy(),
status: sinon.spy(),
json: sinon.spy()
}
fakeNext = sinon.stub();
await proxy.loginRoute(fakeReq, fakeReq, fakeNext).then((_result) => {
result = _result;
});
console.log("result")
console.log(result) // undefined
console.log("result")
});
it("login route test if the stubs are called", async () => {
expect(authService.findByEmail).to.have.been.called // never called
// expect(bcryptStubbing).to.have.been.called // never called
// expect(response.status).to.deep.equal(200); // doesn't work
}).timeout(10000);
after(() => {
sinon.reset()
});
});
Where am i going wrong here in the test?
I'm learning JavaScript with Express.js Framework. I'm trying to create a simple project for a restaurant (just for learning purposes), and I'm trying to create the CRUD of ingredients. I created the repository for all Prisma (ORM) requests, the service with business logic, and a controller for request/response handling. I wish to know if there is a better way of instantiating the service and repository on my controller. I'm doing that for each method. It worked, but I'm repeating this code block a lot.
This is IngredientRepository:
const prisma = require("../../prisma/client");
class IngredientRepository {
async create({ name, price, image }) {
return await prisma.ingredient.create({
data: {
name,
price,
image,
},
});
}
async findByName({ name }) {
return await prisma.ingredient.findUnique({ where: { name } });
}
async findById({ id }) {
return await prisma.ingredient.findUnique({
where: { id },
});
}
async updateImage({ id, image }) {
return await prisma.ingredient.update({
where: { id },
data: {
image,
},
});
}
}
module.exports = IngredientRepository;
IngredientService:
const Error = require("../middlewares/Error");
const DiskStorage = require("../providers/DiskStorage");
class IngredientService {
constructor(ingredientRepository, userRepository) {
this.ingredientRepository = ingredientRepository;
this.userRepository = userRepository;
}
async create({ name, price, image, loggedUser }) {
const userIsAdmin = await this.userRepository.findById({ loggedUser });
if (userIsAdmin.admin) {
throw Error("", 401);
}
const ingredient = await this.ingredientRepository.findByName({
name: name.toLowerCase(),
});
if (ingredient) {
throw new Error("Ingredient already exists");
}
return await this.ingredientRepository.create({
name: name.toLowerCase(),
price,
image,
});
}
async updateImage({ id, image }) {
const diskStorage = new DiskStorage();
const ingredient = await this.ingredientRepository.findById({ id: id.id });
if (!ingredient) {
throw new Error("This ingredient doesn't exist", 401);
}
if (ingredient.image) {
await diskStorage.deleteFile(ingredient.image);
}
const filename = await diskStorage.saveFile(image);
ingredient.image = filename;
const updatedIngredient = await this.ingredientRepository.updateImage({
id: id.id,
image: ingredient.image,
});
return updatedIngredient;
}
}
module.exports = IngredientService;
IngredientController:
const IngredientRepository = require("../repositories/IngredientRepository");
const UserRepository = require("../repositories/UserRepository");
const IngredientService = require("../services/IngredientService");
class IngredientController {
async create(request, response) {
const ingredientRepository = new IngredientRepository();
const userRepository = new UserRepository();
const ingredientService = new IngredientService(
ingredientRepository,
userRepository
);
const loggedUser = request.user.id;
const { name, price, image } = request.body;
const ingredientCreated = await ingredientService.create({
loggedUser,
name,
price,
image,
});
return response.json(ingredientCreated);
}
async updateImage(request, response) {
const ingredientRepository = new IngredientRepository();
const userRepository = new UserRepository();
const ingredientService = new IngredientService(
ingredientRepository,
userRepository
);
const loggedUser = request.user.id;
const id = request.params;
const image = request.file.filename;
const ingredientWithImageUpdated = await ingredientService.updateImage({
loggedUser,
id,
image,
});
return response.json(ingredientWithImageUpdated);
}
}
module.exports = IngredientController;
It is possible to create your dependencies in constructor of IngredientController and then use that objects in methods.
Let me show an example:
class IngredientRepository {
findByName() {
return 'findByName: ' + Date.now();
}
}
and:
class IngredientController {
constructor (){
this.ingredientRepository = new IngredientRepository();
}
create(){
// you can use your object here
console.log(this.ingredientRepository.findByName())
}
update(){
// you can use your object here
console.log(this.ingredientRepository.findByName())
}
}
An example:
class IngredientRepository {
findByName() {
return 'findByName: ' + Date.now();
}
}
class IngredientController {
constructor (){
this.ingredientRepository = new IngredientRepository();
}
create(){
console.log(this.ingredientRepository.findByName())
}
update(){
console.log(this.ingredientRepository.findByName())
}
}
const ingredientController = new IngredientController();
console.log(ingredientController.create())
console.log(ingredientController.update())
It works just once for the below code
import {
graphql,
GraphQLSchema,
GraphQLObjectType,
GraphQLString,
buildSchema,
} from "https://cdn.pika.dev/graphql/^15.0.0";
import { serve } from "https://deno.land/std#0.50.0/http/server.ts";
var schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: "RootQueryType",
fields: {
hello: {
type: GraphQLString,
resolve() {
return "world";
},
},
},
}),
});
var query = "{ hello }";
graphql(schema, query).then((result) => {
console.log(result);
});
How to keep it listening, just like express
Something like this
var express = require('express');
var graphqlHTTP = require('express-graphql');
var { buildSchema } = require('graphql');
// Construct a schema, using GraphQL schema language
var schema = buildSchema(`
type Query {
hello: String
}
`);
// The root provides a resolver function for each API endpoint
var root = {
hello: () => {
return 'Hello world!';
},
};
var app = express();
app.use('/graphql', graphqlHTTP({
schema: schema,
rootValue: root,
graphiql: true,
}));
app.listen(4000);
console.log('Running a GraphQL API server at http://localhost:4000/graphql');
import {
graphql,
buildSchema,
} from "https://cdn.pika.dev/graphql/^15.0.0";
import {Application, Router} from "https://deno.land/x/oak/mod.ts";
var schema = buildSchema(`
type Query {
hello: String
}
`);
var resolver = {hello: () => 'Hello world!'}
const executeSchema = async (query:any) => {
const result = await graphql(schema, query, resolver);
return result;
}
var router = new Router();
router.post("/graph", async ({request, response}) => {
if(request.hasBody) {
const body = await request.body();
const result = await executeSchema(body.value);
response.body = result;
} else {
response.body = "Query Unknown";
}
})
let app = new Application();
app.use(router.routes());
app.use(router.allowedMethods());
console.log("Server running");
app.listen({port: 5000})
You can now use https://deno.land/x/deno_graphql to achieve this goal.
It provides everything needed out-of-the-box and works with multiple Deno frameworks (oak, abc, attain, etc).
This is how you code looks like (with oak for example):
import { Application, Context, Router } from "https://deno.land/x/oak/mod.ts";
import {
gql,
graphqlHttp,
makeExecutableSchema,
} from "https://deno.land/x/deno_graphql/oak.ts";
const typeDefs = gql`
type Query {
hello: String
}
`;
const resolvers = {
Query: {
hello: () => "Hello world!",
},
};
const context = (context: Context) => ({
request: context.request,
});
const schema = makeExecutableSchema({ typeDefs, resolvers });
const app = new Application();
const router = new Router();
router.post("/graphql", graphqlHttp({ schema, context }));
app.use(router.routes());
await app.listen({ port: 4000 });
PS : i'm the author of the package, so you can ask me anything.
Hope this helps!
Here is an example using oak working with your GraphQL code.
First let's say you have a repository graphRepository.ts with your graph schema:
import {
graphql,
GraphQLSchema,
GraphQLObjectType,
GraphQLString
} from "https://cdn.pika.dev/graphql/^15.0.0";
var schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: "RootQueryType",
fields: {
hello: {
type: GraphQLString,
resolve() {
return "world";
},
},
},
}),
});
export async function querySchema(query: any) {
return await graphql(schema, query)
.then(async (result) => {
return result;
});
}
Now start your app.ts listener with the routes, and use the following URL to call the endpoint:
http://localhost:8000/graph/query/hello
import { Application, Router } from "https://deno.land/x/oak/mod.ts";
import { querySchema } from "./graphRepository.ts";
const router = new Router();
router
.get("/graph/query/:value", async (context) => {
const queryValue: any = context.params.value;
const query = `{ ${queryValue}}`
const result = await querySchema(query);
console.log(result)
context.response.body = result;
})
const app = new Application();
app.use(router.routes());
app.use(router.allowedMethods());
await app.listen({ port: 8000 });
here is a code example using oak and middleware.
You also can enjoy the playground GUI like an apollo one.
import { Application } from "https://deno.land/x/oak/mod.ts";
import { applyGraphQL, gql } from "https://deno.land/x/oak_graphql/mod.ts";
const app = new Application();
app.use(async (ctx, next) => {
await next();
const rt = ctx.response.headers.get("X-Response-Time");
console.log(`${ctx.request.method} ${ctx.request.url} - ${rt}`);
});
app.use(async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
ctx.response.headers.set("X-Response-Time", `${ms}ms`);
});
const types = gql`
type User {
firstName: String
lastName: String
}
input UserInput {
firstName: String
lastName: String
}
type ResolveType {
done: Boolean
}
type Query {
getUser(id: String): User
}
type Mutation {
setUser(input: UserInput!): ResolveType!
}
`;
const resolvers = {
Query: {
getUser: (parent: any, {id}: any, context: any, info: any) => {
console.log("id", id, context);
return {
firstName: "wooseok",
lastName: "lee",
};
},
},
Mutation: {
setUser: (parent: any, {firstName, lastName}: any, context: any, info: any) => {
console.log("input:", firstName, lastName);
return {
done: true,
};
},
},
};
const GraphQLService = applyGraphQL({
typeDefs: types,
resolvers: resolvers
})
app.use(GraphQLService.routes(), GraphQLService.allowedMethods());
console.log("Server start at http://localhost:8080");
await app.listen({ port: 8080 });
I have created gql for making GraphQL servers that aren't tied to a web framework. All of the responses above show Oak integration but you don't really have to use it to have a GraphQL server. You can go with std/http instead:
import { serve } from 'https://deno.land/std#0.90.0/http/server.ts'
import { GraphQLHTTP } from 'https://deno.land/x/gql/mod.ts'
import { makeExecutableSchema } from 'https://deno.land/x/graphql_tools/mod.ts'
import { gql } from 'https://deno.land/x/graphql_tag/mod.ts'
const typeDefs = gql`
type Query {
hello: String
}
`
const resolvers = {
Query: {
hello: () => `Hello World!`
}
}
const schema = makeExecutableSchema({ resolvers, typeDefs })
const s = serve({ port: 3000 })
for await (const req of s) {
req.url.startsWith('/graphql')
? await GraphQLHTTP({
schema,
graphiql: true
})(req)
: req.respond({
status: 404
})
}
I've been following all the results I found on Google on how to make an AuthDirective but I can't make it work.
I'll leave all the files to see if I'm making an error on the implementation but I guess not because I tested even without having any conditions inside the field.resolve.
One particular thing I found is that field.resolve, comes with 4 args, the first of them is always undefined, not sure if that could be it but just as an additional tip.
So here's the code:
This is a part of my typedefs
directive #isAuthenticated on FIELD_DEFINITION
scalar Date
type User {
id: ID,
name: String
email: String
password: String
createdAt: Date
}
type Token {
token: String
}
# IF I REMOVE #isAuthenticated this works!
type Query {
user(name: String): User! #isAuthenticated
users: [User!]! #isAuthenticated
}
type Mutation {
createUser(name: String, email: String, password: String): Boolean!
login(email: String!, password: String!): Token!
}
Here is my schema:
import path from 'path';
import { makeExecutableSchema } from 'graphql-tools';
import { fileLoader, mergeResolvers, mergeTypes } from 'merge-graphql-schemas';
import schemaDirectives from './Directives';
const typedefsArray = fileLoader(path.join(__dirname, './**/*.graphql'));
const resolversArray = fileLoader(path.join(__dirname, './**/*.resolvers.ts'));
const typeDefs = mergeTypes(typedefsArray);
const resolvers = mergeResolvers(resolversArray);
const schema = makeExecutableSchema({ typeDefs, resolvers, schemaDirectives });
export default schema;
Here is my AuthDirective
import { defaultFieldResolver } from 'graphql';
import { AuthenticationError, SchemaDirectiveVisitor } from 'apollo-server-express';
class AuthDirective extends SchemaDirectiveVisitor {
visitFieldDefinition(field: any) {
const { resolver = defaultFieldResolver } = field;
field.resolve = async function (...args: any) {
const [_, __, context] = args;
const { req } = context;
const accessToken = req.headers.authorization
? req.headers.authorization.replace('Bearer ', '')
: null;
if (accessToken) {
// here I was doing jwt.verify(accessToken) but I removed it for now to simplify
const result = await resolver.apply(this, args);
return result; // <--- THIS IS ALWAYS UNDEFINED
} else {
throw new AuthenticationError('Unauthorized field');
}
};
}
}
export default AuthDirective;
which lives under ./Directives and is being exported from an index as:
import AuthDirective from './auth';
export default {
isAuthenticated: AuthDirective
}
Finally my user resolver. Like I said before, if I remove the directive from the user graphql query its working as it should:
import jwt from 'jsonwebtoken';
import { AuthenticationError, UserInputError } from 'apollo-server-express';
import { IUser } from './IUser';
import UserModel from './user.schema';
import { Config } from '../../config';
const { APP_SECRET } = Config;
const createToken = async (user: IUser, secret: string, expiresIn: string) => {
const { id, email, name } = user;
return jwt.sign({ id, email, name }, secret, { expiresIn });
};
const userResolvers = {
Query: {
user: (_: any, { name }: { name: string }) => UserModel.findOne({ name }).exec(),
users: () => UserModel.find({}).exec()
},
Mutation: {
createUser: async (_: any, args: IUser) => {
await new UserModel(args).save();
return true;
},
login: async (_: any, { email, password }: IUser) => {
const user = await UserModel.findOne({ email }).exec();
if (!user) {
throw new UserInputError('No user found with this login credentials.');
}
const isValid = await user.validatePassword(password);
if (!isValid) {
throw new AuthenticationError('Invalid password.');
}
return { token: createToken(user, APP_SECRET, '30m') };
}
}
};
export default userResolvers;
and my server:
import morgan from 'morgan';
import express from 'express';
import bodyParser from 'body-parser';
import compression from 'compression';
import depthLimit from 'graphql-depth-limit';
import { ApolloServer } from 'apollo-server-express';
import { Config, DBConfig } from './config';
import schema from './domain';
const { PORT } = Config;
const app = express();
const apolloServer = new ApolloServer({
schema,
validationRules: [depthLimit(7)],
context: ({ req, res }: any) => ({ req, res })
});
app.use(express.json());
app.use(compression());
app.use(morgan('combined'));
app.use(bodyParser.urlencoded({ extended: true }));
apolloServer.applyMiddleware({ app });
DBConfig.init();
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
There is no resolver property on field, so this
const { resolver = defaultFieldResolver } = field;
will always result in resolver having the value of defaultFieldResolver. If you're using the directive on some field that had a custom resolver, that custom resolver is never being called -- only the default behavior is used, which could indeed return undefined.
You need to make sure you're using the correct property:
const { resolve = defaultFieldResolver } = field;
or you can rename the resulting variable when you destructure field:
const { resolve: resolver = defaultFieldResolver } = field;
Im trying to extend a Mongoose Model using ES6 syntax. While I can call successfully find({}) to retrieve data from mongo database, I am not able to call save() to save data. Both are executed inside the Model.
The error returned is Error: TypeError: this.save is not a function
const mongoose = require('mongoose')
const {Schema, Model} = mongoose
const PersonSchema = new Schema(
{
name: { type: String, required: true, maxlength: 1000 }
},
{ timestamps: { createdAt: 'created_at', updatedAt: 'update_at' } }
)
class PersonClass extends Model {
static getAll() {
return this.find({})
}
static insert(name) {
this.name = 'testName'
return this.save()
}
}
PersonSchema.loadClass(PersonClass);
let Person = mongoose.model('Persons', PersonSchema); // is this even necessary?
(async () => {
try {
let result = await Person.getAll() // Works!
console.log(result)
let result2 = await Person.insert() // FAILS
console.log(result2)
} catch (err) {
throw new Error(err)
}
})()
Im using:
Nodejs 7.10
mongoose 5.3.15
This is normal. You're trying to access a non static method from a static method.
You need to do something like this:
static insert(name) {
const instance = new this();
instance.name = 'testName'
return instance.save()
}
Some working example:
class Model {
save(){
console.log("saving...");
return this;
}
}
class SomeModel extends Model {
static insert(name){
const instance = new this();
instance.name = name;
return instance.save();
}
}
const res = SomeModel.insert("some name");
console.log(res.name);
Here is an example of what works and what doesn't work.
class SomeParentClass {
static saveStatic(){
console.log("static saving...");
}
save(){
console.log("saving...");
}
}
class SomeClass extends SomeParentClass {
static funcStatic(){
this.saveStatic();
}
func(){
this.save();
}
static funcStaticFail(){
this.save();
}
}
//works
SomeClass.funcStatic();
//works
const sc = new SomeClass();
sc.func();
//fails.. this is what you're trying to do.
SomeClass.funcStaticFail();