I have below file in nestjs. test cases for this code is getting passed. but coverage issue is coming so pipeline getting failed.
import { Injectable, CanActivate, ExecutionContext, Logger } from '#nestjs/common';
import { Reflector } from '#nestjs/core';
import { ROLES_KEY } from './roles.decorator';
import { Role } from './role.enum';
#Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.getAllAndOverride<string[]>(ROLES_KEY, [
context.getHandler(),
context.getClass(),
]);
const { user } = context.switchToHttp().getRequest();
if (!user.role) {
Logger.error('User does not have a role set');
return false;
}
if (user.role === Role.Admin) {
return true;
}
if (!Array.isArray(requiredRoles) || !requiredRoles.length) {
// No #Roles() decorator set, deny access as not admin
return false;
}
if (requiredRoles.includes(Role.All)) {
return true;
}
return requiredRoles.includes(user.role);
}
}
I am writing below test case file for it.
import { createMock } from '#golevelup/ts-jest';
import { ExecutionContext } from "#nestjs/common";
import { Reflector } from "#nestjs/core";
import { RolesGuard } from "./roles.guard";
describe('RolesGuard', () => {
let guard: RolesGuard;
let reflector: Reflector;
beforeEach(() => {
reflector = new Reflector();
guard = new RolesGuard(reflector);
});
it('should be defined', () => {
expect(guard).toBeDefined();
});
it('should return false if user does not exist', () => {
reflector.getAllAndOverride = jest.fn().mockReturnValue(true);
const context = createMock<ExecutionContext>();
const canActivate = guard.canActivate(context);
expect(canActivate).toBe(false);
})
})
but coverage is not showing for below line
if (user.role === Role.Admin) {
return true;
}
if (!Array.isArray(requiredRoles) || !requiredRoles.length) {
// No #Roles() decorator set, deny access as not admin
return false;
}
if (requiredRoles.includes(Role.All)) {
return true;
}
return requiredRoles.includes(user.role);
could you please help me on the same? I am not able to understand how to handle coverage of these lines. I am invoking method but still it is showing.
I also asked similar question test case for roles guard file in nestjs
Related
I'm testing my Login component which implements Microsoft authentication for login. I'm returning empty fragment from Login.js. How can I test this using React Testing library and jest?
import { useEffect } from 'react';
import { msalInstance } from './authConfig';
export const Login = () => {
useEffect(() => {
msalInstance
.handleRedirectPromise()
.then((tokenResponse) => {
if (!tokenResponse) {
const accounts = msalInstance.getAllAccounts();
if (accounts.length === 0) {
// No user signed in
msalInstance.loginRedirect();
}
}
})
.catch((err) => {
console.error(err);
});
}, []);
return <></>;
};
I'm trying to use repository pattern with TypeScript
Now I have base.repository that implements all of the functions that I need, I made it a generic type, and I wanna pass the model while injecting it in constructor, but for some reason, while passing the value, I have undefined state of the particular model, what am I doing wrong?
In the console.log() it shows me that the model is undefined while in file register.service.ts it shows me also undefined, but I passed it as generic.
register.service.ts
import { BaseRepository } from "../repositories/base.repository";
import { Creator } from '../data/models/Creator'
import { RegisterDto } from "../types/dtos/register.dto";
import { Injectable } from '#nestjs/common'
import { RegisterMapper } from '../mappers/register.mapper'
import { errors } from '../errors'
import { mailer } from '../utils/nodemailer'
#Injectable()
export class RegisterService {
constructor(
private readonly repository: BaseRepository<Creator>,
private readonly mapper: RegisterMapper
) { }
async createAccount (doc: RegisterDto) {
const emailExist = await this.existByEmail(doc.email)
if (emailExist) {
return errors.EMAIL_EXIST()
}
const created = await this.repository.create(this.mapper.toDomain(doc))
await mailer(doc.email)
return created
}
private async existByEmail(email: string): Promise<boolean> {
console.log(email)
console.log(this.repository)
const response = await this.repository.get({ email })
return !!response.email;
}
}
base.repository.ts
import { ModelType } from '#typegoose/typegoose/lib/types'
import { DuplicateKeyError } from '../errors/DuplicateKeyError'
import { DocumentNotFoundError } from '../errors/DocumentNotFoundError'
import { Model } from 'mongoose'
import { Inject, Injectable, Optional } from '#nestjs/common'
#Injectable()
export class BaseRepository<T = any> {
constructor(
#Optional() #Inject('MODEL') private Model: any
) { }
async create (object): Promise<T> {
const Model = this.Model
console.log(Model)
const uniqueKey = Model.getUniqueKey ? Model.getUniqueKey() : null
if (uniqueKey && object[uniqueKey]) {
const criteria = {
[uniqueKey]: object[uniqueKey]
}
const existing = await Model.findOne(criteria)
if (existing) {
throw new DuplicateKeyError(Model, criteria)
}
}
const model = new Model(object)
return model.save()
}
async update (criteria, object, options = {}) {
const Model = this.Model
const uniqueKey = Model.getUniqueKey ? Model.getUniqueKey() : '_id'
const data = { ...object }
delete data[uniqueKey]
delete data.createdAt
return this.updateRaw(criteria, { $set: { ...data } }, options)
}
async updateRaw (criteria, data, options = {}) {
const query = this._getDbQuery(criteria, options)
const result = await this.Model.findOneAndUpdate(query, data, { new: true, ...options })
if (!result) {
throw new DocumentNotFoundError(this.Model, query)
}
return result
}
async save (modelInstance) {
return modelInstance.save()
}
async get (criteria, options: any = {}): Promise<T | undefined> {
console.log(Model)
const promise = await this.Model.findOne(this._getDbQuery(criteria, options)).exec()
if (options.select) {
promise.select(options.select)
}
return promise
}
async find (criteria, options): Promise<ReturnType<ModelType<T>['find']>> {
return this.Model.find(this._getDbQuery(criteria, options))
}
async resolve (criteria): Promise<T> {
return this.Model.resolve(this._getDbQuery(criteria))
}
async count (query) {
return this.Model.countDocuments(this._getDbQuery(query))
}
async delete (criteria) {
return this.Model.remove(this._getDbQuery(criteria))
}
_getDbQuery (criteria, options: any = {}) {
if ('getDbQuery' in criteria) {
const dbQuery = criteria.getDbQuery(options)
return 'find' in dbQuery
? dbQuery.find
: dbQuery
} else {
return criteria
}
}
}
What should I do to get the actual model in this repository?
I have added inject tokens in each service before repository Injection
Now the code looks as follows
import { BaseRepository } from "../repositories/base.repository";
import { Creator } from '../data/models/Creator'
import { RegisterDto } from "../types/dtos/register.dto";
import { Injectable } from '#nestjs/common'
import { RegisterMapper } from '../mappers/register.mapper'
import { errors } from '../errors'
import { mailer } from '../utils/nodemailer'
import { CREATOR } from '../utils'
#Injectable()
export class RegisterService {
registerMapper = new RegisterMapper()
constructor(
#Inject(CREATOR) private readonly repository: BaseRepository<Creator>,
) { }
async createAccount (doc: RegisterDto) {
const emailExist = await this.existByEmail(doc.email)
if (emailExist) {
return errors.EMAIL_EXIST()
}
const created = await this.repository.create(this.mapper.toDomain(doc))
await mailer(doc.email)
return created
}
private async existByEmail(email: string): Promise<boolean> {
console.log(email)
console.log(this.repository)
const response = await this.repository.get({ email })
return !!response.email;
}
}
inject-tokens.ts
export const CREATOR = 'CREATOR'
I had asked this question before.
I looked carefully at the exchange section, which I advised, and I think there is no problem with the exchange section.
At least in my opinion there is no problem and I hardly know the cause of the problem.
And I was so frustrated that I put everything in the code.
If anyone can provide us with a clue to this problem, please reply to me.
interactions.js
import Web3 from 'web3'
import {
web3Loaded,
web3AccountLoaded,
tokenLoaded,
exchangeLoaded,
cancelledOrdersLoaded
} from './actions'
import Token from '../abis/Token.json'
import Exchange from '../abis/Exchange.json'
export const loadWeb3 = (dispatch) => {
const web3 = new Web3(Web3.givenProvider || 'http://localhost:7545')
dispatch(web3Loaded(web3))
return web3
}
export const loadAccount = async (web3, dispatch) => {
const accounts = await web3.eth.getAccounts()
const account = accounts[0]
dispatch(web3AccountLoaded(account))
return account
}
export const loadToken = async (web3, networkId, dispatch) => {
try {
const token = new web3.eth.Contract(Token.abi, Token.networks[networkId].address)
dispatch(tokenLoaded(token))
return token
} catch (error) {
console.log('Contract not deployed to the current network. Please select another network with Metamask.')
return null
}
}
export const loadExchange = async (web3, networkId, dispatch) => {
try {
const exchange = new web3.eth.Contract(Exchange.abi, Exchange.networks[networkId].address)
dispatch(exchangeLoaded(exchange))
return exchange
} catch (error) {
console.log('Contract not deployed to the current network. Please select another network with Metamask.')
return null
}
}
export const loadAllOrders = async (exchange, dispatch) => {
// if (exchange) { // Make sure exchange has been defined
// const exchange = new web3.eth.Contract(Exchange.abi, Exchange.networks[networkId].address)
const cancelStream = await exchange.getPastEvents('Cancel', { fromBlock: 0, toBlock: 'latest' })
// // await loadAllOrders(this.props.exchange, dispatch)
console.log(cancelStream)
}
App.js
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import Navbar from './Navbar'
import Web3 from 'web3';
import Content from './Content'
import { connect } from 'react-redux'
// import Token from '../abis/Token.json'
import {
loadWeb3,
loadAccount,
loadToken,
loadExchange
} from '../store/interactions'
import { contractsLoadedSelector } from '../store/selectors'
class App extends Component {
componentWillMount() {
this.loadBlockchainData(this.props.dispatch)
}
async loadBlockchainData(dispatch) {
const web3 = loadWeb3(dispatch)
const network = await web3.eth.net.getNetworkType()
const networkId = await web3.eth.net.getId()
const accounts = await loadAccount(web3, dispatch)
const token = await loadToken(web3, networkId, dispatch)
if(!token) {
window.alert('Token smart contract not detected on the current network. Please select another network with Metamask.')
return
}
const exchange = await loadExchange(web3, networkId, dispatch)
if(!exchange) {
window.alert('Exchange smart contract not detected on the current network. Please select another network with Metamask.')
return
}
}
render() {
return (
<div>
<Navbar />
{ this.props.contractsLoaded ? <Content /> : <div className="content"></div> }
</div>
);
}
}
function mapStateToProps(state) {
return {
contractsLoaded: contractsLoadedSelector(state)
}
}
export default connect(mapStateToProps)(App);
reducers.js
import { combineReducers } from 'redux';
function web3(state={}, action) {
switch (action.type) {
case 'WEB3_LOADED':
return { ...state, connection: action.connection }
case 'WEB3_ACCOUNT_LOADED':
return { ...state, account: action.account }
default:
return state
}
}
function token(state = {}, action) {
switch (action.type) {
case 'TOKEN_LOADED':
return { ...state, loaded: true, contract: action.contract }
default:
return state
}
}
function exchange(state = {}, action) {
switch (action.type) {
case 'EXCHANGE_LOADED':
return { ...state, loaded: true, contract: action.contract }
case 'CANCELLED_ORDERS_LOADED':
return { ...state, cancelledOrders: { loaded: true, data: action.cancelledOrders } }
// case 'FILLED_ORDERS_LOADED':
// return { ...state, filledOrders: { loaded: true, data: action.filledOrders } }
// case 'ALL_ORDERS_LOADED':
// return { ...state, allOrders: { loaded: true, data: action.allOrders } }
default:
return state
}
}
const rootReducer = combineReducers({
web3,
token,
exchange
})
export default rootReducer
Content.js
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { exchangeSelector } from '../store/selectors'
import { loadAllOrders } from '../store/interactions'
class Content extends Component {
componentWillMount() {
this.loadBlockchainData(this.props.dispatch)
}
// async loadBlockchainData(exchange, dispatch) {
async loadBlockchainData(dispatch) {
await loadAllOrders(this.props.exchange, dispatch)
// this.loadBlockchainData(this.props.exchange)
// await loadAllOrders(exchange, dispatch)
}
function mapStateToProps(state) {
return {
exchange: state.exchangeSelector
}
}
export default connect(mapStateToProps)(Content)
selectors.js
import { get } from 'lodash'
import { createSelector } from 'reselect'
const account = state => get(state, 'web3.account')
export const accountSelector = createSelector(account, a => a)
const tokenLoaded = state => get(state, 'token.loaded', false)
export const tokenLoadedSelector = createSelector(tokenLoaded, tl => tl)
const exchangeLoaded = state => get(state, 'exchange.loaded', false)
export const exchangeLoadedSelector = createSelector(exchangeLoaded, el => el)
const exchange = state => get(state, 'exchange.contract')
export const exchangeSelector = createSelector(exchange, e => e)
export const contractsLoadedSelector = createSelector(
tokenLoaded,
exchangeLoaded,
(tl, el) => (tl && el)
)
Check exchange to make sure not undefined
export const loadAllOrders = async (exchange, dispatch) => {
const cancelStream = exchange ?
await exchange.getPastEvents('Cancel', { fromBlock: 0, toBlock: 'latest' })
: null // Check if exchange defined then call getPastEvents
console.log(cancelStream)
}
I am using mobx-react-lite with hooks.
I have two store.
AuthStore
SomeOtherStore
This is my dummy AuthStore
import { observable, decorate, action } from 'mobx';
import { createContext } from 'react';
import { ROLE_LOGISTICS_MANAGER } from '../settings/constants';
import AuthService from '../services/AuthService';
class AuthStore {
constructor() {
this.authService = new AuthService();
}
currentMode = ROLE_LOGISTICS_MANAGER;
authenticating = true;
isLoggedIn = false;
userId = null;
loginLoading = false;
login = async params => {
this.loginLoading = true;
try {
const data = await this.authService.loginAsync(params);
this.loginLoading = false;
this.isLoggedIn = true;
} catch (e) {
console.error(e);
this.loginError = e;
} finally {
this.loginLoading = false;
}
};
}
decorate(AuthStore, {
currentMode: observable,
loginLoading: observable,
isLoggedIn: observable,
authenticating: observable,
userId: observable,
fetchUser: action,
login: action
});
export const AuthStoreContext = createContext(new AuthStore());
Now Lets say I want to change isLoggedIn from another store,
How can I do that? I tried to find ways in docs, couldn't find a solid solution.
I am using hooks with mobx-react-lite
So normally I use mobx like
const authStore = useContext(AuthStoreContext);
It's a common pattern to have stores as properties on a RootStore, each having references back to the root. So you could have a structure like:
class RootStore {
constructor (auth, ui) {
this.auth = new AuthStore(this)
this.ui = new UiStore(this)
}
}
class AuthStore {
constructor (rootStore) {
this.rootStore = rootStore
}
logout() {
this.isLoggedIn = false
}
}
decorate(AuthStore, {
logout: action
})
Then, when you need to call a function on another store, you can use the reference to the root as a pathway. The pattern's described in more detail here. A possible example of use with useContext might be:
const { someStore } = useContext(rootStoreContext)
someStore.rootStore.auth.logout()
Inside of the package.json file I have:
history: "^4.7.2",
react: "^16.4.1",
react-dom: "^16.4.1",
react-redux: "^5.0.7",
react-redux-form: "^1.16.9",
react-router-dom: "^4.3.1",
react-router-redux: "^4.0.8",
react-scripts: "1.1.4",
redux: "^3.5.2",
redux-form: "^7.3.0",
redux-logger: "^2.6.1"
The error I'm getting is
Cannot read property '0' of undefined;
resolveModel
node_modules/react-redux-form/lib/utils/resolve-model.js:32
function resolveModel(model, parentModel) {
if (parentModel) {
console.log('parentModel :', parentModel);
if (model[0] === '.' || model[0] === '[') { <-- It points to this line over here.
return '' + parentModel + model;
}
if (typeof model === 'function') {
return function (state) {
return model(state, parentModel);
};
}
}
return model;
}
function wrapWithModelResolver(WrappedComponent) {
var ResolvedModelWrapper = function (_PureComponent) {
_inherits(ResolvedModelWrapper, _PureComponent);
function ResolvedModelWrapper() {
_classCallCheck(this, ResolvedModelWrapper);
return _possibleConstructorReturn(this, (ResolvedModelWrapper.__proto__ || Object.getPrototypeOf(ResolvedModelWrapper)).apply(this, arguments));
}
_createClass(ResolvedModelWrapper, [{
key: 'render',
value: function render() {
var _context = this.context,
parentModel = _context.model,
localStore = _context.localStore;
var resolvedModel = resolveModel(this.props.model, parentModel); <------------------------ resolveModel gets called here.
return _react2.default.createElement(WrappedComponent, _extends({}, this.props, {
model: resolvedModel,
store: localStore || undefined
}));
}
}]);
return ResolvedModelWrapper;
}(_react.PureComponent);
ResolvedModelWrapper.displayName = 'Modeled(' + WrappedComponent.displayName + ')';
process.env.NODE_ENV !== "production" ? ResolvedModelWrapper.propTypes = {
model: _propTypes2.default.any
} : void 0;
ResolvedModelWrapper.contextTypes = {
model: _propTypes2.default.any,
localStore: _propTypes2.default.shape({
subscribe: _propTypes2.default.func,
dispatch: _propTypes2.default.func,
getState: _propTypes2.default.func
})
};
return ResolvedModelWrapper;
}
Inside of src/reducers/index.js
import { combineReducers } from 'redux';
import { createForms, formReducer } from 'react-redux-form';
import { routerReducer } from 'react-router-redux';
const SoundCloudState = {
input: ''
}
const reducer = combineReducers({
...createForms({
SoundCloud: SoundCloudState
}),
routing: routerReducer,
form: formReducer
});
export default reducer;
Inside of SoundCloudExp.js. In form component:
import React, { Component } from 'react';
import {querySC} from './actions/index';
import { connect } from 'react-redux';
import { Form, Control, actions, Errors } from 'react-redux-form';
class SoundCloudExp extends Component {
handleSubmit(query){
const {querySC, dispatch} = this.props;
let SCPromise = fetch('/', {
method: 'post',
body: query
})
.then((res) => res.json())
.then((res) => {
querySC(res);
});
dispatch(actions.submit('SoundCloud', SCPromise));
}
render(){
return (
<Form id="SC-search" model="SoundCloud" onSubmit={(query) => this.handleSubmit(query)}>
<div className='search-bar'>
<Control.text model=".input"
className='search'
placeholder='Search'/>
<Errors model=".input"
messages={{NoSearchResults: 'This query returned no results.'}}/>
</div>
<Control.button className='search-btn'>
Search
</Control.button>
</Form>
)
}
}
export default connect(null, {querySC})(SoundCloudExp);
What is peculiar is that when I include console.log statements inside of the resolveModel.js file, I get three different variations of the expected response. Why is that?
So basically I had to set a proper trigger for the NoSearchResults error, so I set that up in a sibling folder called services in a file called checkQueryValidity.js:
export default function checkQueryValidty(val){
return async (dispatch) => {
dispatch(actions.setPending('SoundCloud.input', true));
try {
let response = await querySC(val);
dispatch(actions.setValidity('SoundCloud.input', {
queries: response.queries
}));
}
catch(error){
dispatch(actions.setErrors('SoundCloud.input', {
NoSearchResults: error.message
}));
}
dispatch(actions.setPending('SoundCloud.input', false));
}
}
And in the file SoundCloudExp.jsx, I replaced SCPromise w/ checkQueryValidity(res) and replaced querySC(res); with dispatch(actions.submit('SoundCloud', checkQueryValidty(res)));