How to set values in Vuex store in Nuxt.js app - javascript

I have never used Vuex with Nuxt.js, so I have met the problem. Here is my store/index.js file:
export const state = () => ({
wrongCredentials: false,
logged: false,
uxd: null
})
export const mutations = {
setWrongCredentials(state, value) {
state.wrongCredentials = value
},
setLogged(state, value) {
state.logged = value
},
setUxd(state, value) {
state.uxd = value
},
}
As you can see there is state and mutations. In my other file, where I check user's JWT token and, depending on resultM I want set values in store:
import jwt from 'jsonwebtoken'
import cert from '../jwt/public'
export default {
verify (token) {
jwt.verify(token, cert, async (err, decoded) => {
if (err) {
this.$store.state.wrongCredentials = true
this.$store.state.logged = false
this.$store.state.uxd = null
} else {
this.$store.state.wrongCredentials = false
this.$store.state.logged = true
this.$store.state.uxd = decoded.uxd
}
})
}
}
The code you see doesn't work correctly, it just doesn't set values, so, I have created mutations and did something like this:
await this.$store.dispatch('setWrongCredentials', true)
Also doesn't work. The problem is, I don't know how to work with Vuex store not in .vue files, so, how can I set values in store?

Unfortunately, I have not found solution of this problem like I wanted in .js file, so, here is another solution.
First of all, you need to set no only state and mutations, but also actions:
export const actions = {
fetchWrongCredentials(ctx, value) {
ctx.commit('setWrongCredentials', value)
},
fetchLogged(ctx, value) {
ctx.commit('setLogged', value)
},
fetchUxd(ctx, value) {
ctx.commit('setUxd', value)
}
}
And in your Vue component you need to set values in state using actions, just like that:
methods: {
...mapActions(['fetchWrongCredentials', 'fetchLogged', 'fetchUxd']),
async login() {
await login({
password: this.userPassword,
login: this.userLogin,
twoFactor: this.twoFactor
}).then(result => {
const jwtRes = jwt.verify(result.token)
this.fetchLogged(true)
this.fetchWrongCredentials(false)
this.fetchUxd(jwtRes.token)
}).catch(() => {
this.fetchLogged(false)
this.fetchWrongCredentials(true)
this.fetchUxd(null)
this.error = true
setTimeout(() => {
this.error = false
}, 1000)
})
}
}
In my case, I had to modify that .js file:
import jwt from 'jsonwebtoken'
import cert from '../jwt/public'
export default {
verify (token) {
return jwt.verify(token, cert, (err, decoded) => {
if (err) {
return false
} else {
return { token: decoded.uxd }
}
})
}
}

Related

How to use encrypt-storage with zustand in react?

I'm using zustand with persist plugin to store the state of my application. I want to use localstorage but the cache has to be encrypted.
For encryption, I'm using encrypt-storage. For encryption keys, I want to make an API call to the backend and initialise the encrypt storage.
The problem is while the API call is being made, the storage is still undefined. How to properly initialise zustand with encrypt-storage ?
Here is what I have tried :
import { EncryptStorage } from "encrypt-storage";
import { create } from "zustand";
import { devtools, persist, } from "zustand/middleware";
import { createJSONStorage } from "zustand/middleware"
const fake_api = (ms: number) => new Promise(resolve => {
setTimeout(resolve, ms)
})
export function KeyResolver(callback: () => void) {
const fn = async () => {
//
await fake_api(2000);
console.log("encryption key retrieved")
encryptStorage.EKEY = 'secret-key-value';
encryptStorage.storage = new EncryptStorage(encryptStorage.EKEY, {
stateManagementUse: true,
});
callback();
};
if (!encryptStorage.EKEY) {
fn();
}
}
interface IEncryptStorage {
storage: undefined | EncryptStorage,
EKEY: null | string,
}
export const encryptStorage: IEncryptStorage = {
storage: undefined,
EKEY: null,
}
const useOptimiserStore = create<IOptimiserStore>()(
devtools(
persist(
(set) => ({
...initialOtimiserStoreState,
_hasHydrated: false,
setHyderated: (val) => set({ _hasHydrated: val })
}),
{
name: "optimiser-storage",
// #ts-expect-error
storage: createJSONStorage(() => encryptStorage.storage),
onRehydrateStorage: () => {
KeyResolver(() => {
useOptimiserStore.getState().setHyderated(true)
});
}
}
),
{
name: "optimiser-storage",
}
)
);
// And i'm using it inside my component like this:
const Component = () => {
const hasHyderated = useOptimiserStore(state => state._hasHydrated);
if (!hasHyderated) {
return <>retreiving encryption keys </>
}
return <div> ... </div>
}
But I get the following error:
Uncaught TypeError: can't access property "setItem", storage is undefined
I managed to make it work by implementing a custom storage engine.
https://docs.pmnd.rs/zustand/integrations/persisting-store-data#how-can-i-use-a-custom-storage-engine

test case for roles guard file in nestjs

I am not sure I how to write unit test case file for guard in nestjs. I have below Role.guard.ts file. I have to create Role.guard.spec.ts file. can somebody help please?
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 wrote below code but coverage issue is coming.
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);
})
})
below lines are not getting covered.
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);
Edit 1:-
Below test cases are covering my some part of code.
it('should return true if user exist', () => {
reflector.getAllAndOverride = jest.fn().mockReturnValue(true);
const context = createMock<ExecutionContext>({
switchToHttp: () => ({
getRequest: () => ({
user: {
role:'admin'
}
}),
}),
});
const canActivate = guard.canActivate(context);
expect(canActivate).toBe(true);
})
it('should return false if user does not exist', () => {
reflector.getAllAndOverride = jest.fn().mockReturnValue(true);
const context = createMock<ExecutionContext>({
switchToHttp: () => ({
getRequest: () => ({
user: {
role:'user'
}
}),
}),
});
const canActivate = guard.canActivate(context);
expect(canActivate).toBe(false);
})
But below code still not getting covered.
if (requiredRoles.includes(Role.All)) {
return true;
}
return requiredRoles.includes(user.role);
}
Can sombody help me on the same?
Looks like you need to be able to craft the payload appropriately such that your code is hit. There are a variety of ways to do this but, in a "Nest"-y way, we can try something like what the docs tell us. Notice in that link that the provided testing utilities make this a lot easier to mock.
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(async () => {
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);
})
it('should return false if user does exist', () => {
reflector.getAllAndOverride = jest.fn().mockReturnValue(true);
// Mock the "class" type of this so we can get what we want.
// We want to tell it to return an object where role is defined.
const context = createMock<ExecutionContext>({
switchToHttp: () => ({
getRequest: () => ({
user: { role: { /* enter your data here */ }
}),
}),
});
const canActivate = guard.canActivate(context);
expect(canActivate).toBe(false);
})
})
From here your context is successfully hydrated with whatever you want and the second test should start showing up as covering your other code branches. You can now edit the role attribute to look however you want. Notice also in the above beforeEach call you should be able to switch to using testing modules instead. This is not exhaustive though, you'll likely need to add additional test cases to cover your other branches. If you follow as I've done here, that should be relatively trivial.
Does what I've done here make sense? By crafting a custom payload to the object, the roles attribute is present, allowing the test to evaluate other cases. I got my override from the createMock function directly from the docs for golevelup.

How do I access localStorage or mock localStorage for Jest + vue-test-utils tests?

I am trying to test an axios request, and I need to use an auth token in order to access the endpoint, however my test fails because I am getting "Bearer null" and inputting this into my headers.Authorization. Here is my actual code below
File I'm testing:
this.$axios.get(url, { headers: { Authorization: `Bearer ${localStorage.getItem("access-token")}` } })
.then((response) => {
this.loading = true;
// Get latest barcode created and default it to our "from" input
this.barcodeFrom = response.data.data[response.data.data.length - 1]['i_end_uid'] + 1;
this.barcodeTo = this.barcodeFrom + 1;
this.barcodeRanges = response.data.data;
// Here we add to the data array to make printed barcodes more obvious for the user
this.barcodeRanges.map(item => item['range'] = `${item['i_start_uid']} - ${item['i_end_uid']}`);
// Make newest barcodes appear at the top
this.barcodeRanges.sort((a, b) => new Date(b['created_at']) - new Date(a['created_at']));
})
.catch((error) => {
console.log('Barcode retrieval error:', error);
this.barcodeFrom === 0 ? null : this.snackbarError = true;
})
.finally(() => {
// Edge case when there's no barcode records
this.barcodeFrom === 0 ? this.barcodeTo = 1 : null;
this.loading = false
});
console.log('bcr', this.barcodeRanges);
Test file:
import Vuetify from "vuetify";
import Vuex from "vuex";
import { createLocalVue, shallowMount } from "#vue/test-utils";
import VueMobileDetection from "vue-mobile-detection";
import axios from 'axios';
import index from "#/pages/barcode_logs/index";
describe('/pages/barcode_logs/index.vue', () => {
// Initialize our 3rd party stuff
const localVue = createLocalVue();
localVue.use(Vuetify);
localVue.use(Vuex);
localVue.use(axios);
localVue.use(VueMobileDetection);
// Initialize store
let store;
// Create store
store = new Vuex.Store({
modules: {
core: {
state: {
labgroup:{
current: {
id: 1
}
}
}
}
}
});
// Set-up wrapper options
const wrapperOptions = {
localVue,
store,
mocks: {
$axios: {
get: jest.fn(() => Promise.resolve({ data: {} }))
}
}
};
// Prep spies for our component methods we want to validate
const spycreateBarcodes = jest.spyOn(index.methods, 'createBarcodes');
const createdHook = jest.spyOn(index, 'created');
// Mount the component we're testing
const wrapper = shallowMount(index, wrapperOptions);
test('if barcode logs were retrieved', () => {
expect(createdHook).toHaveBeenCalled();
expect(wrapper.vm.barcodeRanges).toHaveLength(11);
});
});
How do I mock or get the actual auth token in to work in my test?
const setItem = jest.spyOn(Storage.prototype, 'setItem')
const getItem = jest.spyOn(Storage.prototype, 'getItem')
expect(setItem).toHaveBeenCalled()
expect(getItem).toHaveBeenCalled()
You can try to mock localStorage before creating instance of a wrapper like this:
global.localStorage = {
state: {
'access-token': 'superHashedString'
},
setItem (key, item) {
this.state[key] = item
},
getItem (key) {
return this.state[key]
}
}
You can also spy on localStorage functions to check what arguments they were called with:
jest.spyOn(global.localStorage, 'setItem')
jest.spyOn(global.localStorage, 'getItem')
OR
You can delete localVue.use(axios) to let your $axios mock work correctly.
This
mocks: {
$axios: {
get: jest.fn(() => Promise.resolve({ data: {} }))
}
}
is not working because of that
localVue.use(axios)

How do I use redux to do multiple actions in the saga and track the overall success/fail in the component?

In my EmailCollection/index.js file, in an updateUsersEmail function, I have the following code:
this.props.checkEmail(email)
.then(() => {
this.props.redirect();
})
.catch((error) => {
});
checkEmail and updateUserEmail are an actions defined as
export const updateUserEmail = (userEmail) => {
return createGraphqlAction(
{
query: `
mutation setLoginEmail($userEmail: String!) {
setLoginEmail(email: $userEmail) {
login {
email
}
state
name
id
}
}
`,
variables: {
userEmail,
},
},
UPDATE_USER_EMAIL,
);
};
export const checkEmail = (userEmail) => {
return createGraphqlAction(
{
query: `
query checkEmail($userEmail: String!) {
checkEmail(email: $userEmail) {
isEmailTaken
}
}
`,
variables: {
userEmail,
},
},
CHECK_IF_EMAIL_IS_TAKEN, <- this is a constant for a string of the same format to prevent any spelling issues
);
};
Finally, in my checkEmail.js, I have:
import { takeEvery, put } from 'redux-saga/effects';
import { updateUserEmail, CHECK_IF_EMAIL_IS_TAKEN } from './../actions';
export default function* confirmEmailSaga() {
yield takeEvery(CHECK_IF_EMAIL_IS_TAKEN.SUCCESS, function* (result) {
const { isEmailTaken } = result.data.checkEmail;
const { email } = result.variables.userEmail;
if (isEmailTaken) {
// TODO: return some error that EmailCollection.js can consume
} else {
yield put(updateUserEmail(email)); // this isn't updating, why?
}
});
Ideally in that the isEmailTaken logic above, I'd want to return something that the component can consume which gives info about the nature of the error so I can adjust the UI accordingly.
I'd also like to understand why yield put(updateUserEmail(email)) isn't causing the user's status to update the in database.
Thanks!

Vuex: Testing actions with API calls

I have been following these testing guidelines to test my vuex store.
But when I touched upon the actions part, I felt there is a lot going on that I couldn't understand.
The first part goes like:
// actions.js
import shop from '../api/shop'
export const getAllProducts = ({ commit }) => {
commit('REQUEST_PRODUCTS')
shop.getProducts(products => {
commit('RECEIVE_PRODUCTS', products)
})
}
// actions.spec.js
// use require syntax for inline loaders.
// with inject-loader, this returns a module factory
// that allows us to inject mocked dependencies.
import { expect } from 'chai'
const actionsInjector = require('inject!./actions')
// create the module with our mocks
const actions = actionsInjector({
'../api/shop': {
getProducts (cb) {
setTimeout(() => {
cb([ /* mocked response */ ])
}, 100)
}
}
})
I infer that this is to mock the service inside the action.
The part which follows is:
// helper for testing action with expected mutations
const testAction = (action, payload, state, expectedMutations, done) => {
let count = 0
// mock commit
const commit = (type, payload) => {
const mutation = expectedMutations[count]
expect(mutation.type).to.equal(type)
if (payload) {
expect(mutation.payload).to.deep.equal(payload)
}
count++
if (count >= expectedMutations.length) {
done()
}
}
// call the action with mocked store and arguments
action({ commit, state }, payload)
// check if no mutations should have been dispatched
if (expectedMutations.length === 0) {
expect(count).to.equal(0)
done()
}
}
describe('actions', () => {
it('getAllProducts', done => {
testAction(actions.getAllProducts, null, {}, [
{ type: 'REQUEST_PRODUCTS' },
{ type: 'RECEIVE_PRODUCTS', payload: { /* mocked response */ } }
], done)
})
})
This is where it I find it difficult to follow.
My store looks like:
import * as NameSpace from '../NameSpace'
import { ParseService } from '../../Services/parse'
const state = {
[NameSpace.AUTH_STATE]: {
auth: {},
error: null
}
}
const getters = {
[NameSpace.AUTH_GETTER]: state => {
return state[NameSpace.AUTH_STATE]
}
}
const mutations = {
[NameSpace.AUTH_MUTATION]: (state, payload) => {
state[NameSpace.AUTH_STATE] = payload
}
}
const actions = {
[NameSpace.ASYNC_AUTH_ACTION]: ({ commit }, payload) => {
ParseService.login(payload.username, payload.password)
.then((user) => {
commit(NameSpace.AUTH_MUTATION, {auth: user, error: null})
})
.catch((error) => {
commit(NameSpace.AUTH_MUTATION, {auth: [], error: error})
})
}
}
export default {
state,
getters,
mutations,
actions
}
And This is how I am trying to test:
import * as NameSpace from 'src/store/NameSpace'
import AuthStore from 'src/store/modules/authorization'
const actionsInjector = require('inject!../../../../../src/store/modules/authorization')
// This file is present at: test/unit/specs/store/modules/authorization.spec.js
// src and test are siblings
describe('AuthStore Actions', () => {
const injectedAction = actionsInjector({
'../../Services/parse': {
login (username, password) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() > 0.5) {
resolve({})
} else {
reject({})
}
}, 300)
})
}
}
})
it('Gets the user profile if the username and password matches', () => {
const testAction = (action, payload, state, mutations, done) => {
const commit = (payload) => {
if (payload) {
expect(mutations.payload).to.deep.equal(payload)
}
}
action({ commit, state }, payload)
.then(result => {
expect(state).to.deep.equal({auth: result, error: null})
})
.catch(error => {
expect(state).to.deep.equal({auth: [], error: error})
})
}
testAction(injectedAction.login, null, {}, [])
})
})
If I try to do this, I get:
"Gets the user profile if the username and password matches"
undefined is not a constructor (evaluating 'action({ commit: commit, state: state }, payload)')
"testAction#webpack:///test/unit/specs/store/modules/authorization.spec.js:96:13 <- index.js:26198:14
webpack:///test/unit/specs/store/modules/authorization.spec.js:104:15 <- index.js:26204:16"
I need help understanding what am I supposed to do to test such actions.
I know it's been awhile but I came across this question because I was having a similar problem. If you were to console.log injectedActions right before you make the testAction call you'd see that the injectedAction object actually looks like:
Object{default: Object{FUNC_NAME: function FUNC_NAME(_ref) { ... }}}
So the main solution here would be changing the testAction call to:
testAction(injectedAction.default.login, null, {}, [], done)
because you are exporting your action as defaults in your store.
A few other issues that are unrelated to your particular error... You do not need to manipulate the testAction boilerplate code. It will work as expected so long as you pass in the proper parameters. Also, be sure to pass done to testAction or your test will timeout. Hope this helps somebody else who comes across this!

Categories