So I just started using jest and I am trying to figure out a difficult problem.
I am working on a Strapi plugin and I am trying to test this function
module.exports = {
async index(ctx) {
let verification = {}
// Checks if there is a captcha provider
console.log('strapi' + strapi)
if (!(strapi.config.get('plugin.ezforms.captchaProvider.name') === 'none') && (strapi.config.get('plugin.ezforms.captchaProvider.name'))) {
verification = await strapi.plugin('ezforms').service(strapi.config.get('plugin.ezforms.captchaProvider.name')).validate(ctx.request.body.token)
//throws error if invalid
if (!verification.valid) {
...
The issue is that the strapi object is injected when running strapi and I want to know how I can inject a fake variable into this function.
I've already implemented a fake strapi object in my test and it looks something like this
test('should return captcha error', async function () {
strapi.plugin().service().validate = jest.fn(function () {
return {
error: {
valid: false,
message: 'Unable to verify captcha',
code: 500
}
}
})
let result = await submitController.index(ctx)
expect(result).toEqual(500)
My issue right now is that when running index() it doesn't have a reference to the Strapi object
describe('Submit Controller', function () {
let strapi
beforeAll(async function () {
strapi = {
...
plugin: function () {
return {
service: function () {
return {
validate: function () {
return {
error: {
valid: false,
message: 'Unable to verify captcha',
code: 500
}
}
}
}
}
}
}
}
})```
I reference in the test file but the `index()` function doesn't have access
[![screenshot][1]][1]
How can I inject my fake Strapi object into the index() function
[1]: https://i.stack.imgur.com/rCc7N.png
Right now it seems that strapi is a free variable for index() method. Due to lexical scope index() method has an access to free variables which were in a scope when the method is defined.
I do not know a structure of your project but as a solution I would recommend to explicitly pass a strapi object as a parameter in index method
module.exports = {
async index(ctx, strapi) {
let verification = {}
// Checks if there is a captcha provider
console.log('strapi' + strapi)
if (!(strapi.config.get('plugin.ezforms.captchaProvider.name') === 'none') && (strapi.config.get('plugin.ezforms.captchaProvider.name'))) {
verification = await strapi.plugin('ezforms').service(strapi.config.get('plugin.ezforms.captchaProvider.name')).validate(ctx.request.body.token)
//throws error if invalid
if (!verification.valid) {
...
Turns out you can pass the strapi object in the root of the module export.
module.exports = ({ strapi }) => ({
async index(ctx) {
...
Then in your test you can do
let result = await submitController({strapi}).index(ctx)
Related
I have a Vue.js application and on the /callback route I am trying to have it do a few things. So far I am not having any luck with it because I am seeing things run async. I understand that it is normally how Vue/Javascript works however I am trying to force it to not be async.
The main issue I am having is sometimes the this.$store... are running before the items are set. This is an issue because of how things run on other Vuex actions. Mainly the getCompany one requires the loadToken one to complete as it is pulling the values from the local storage which is being set above.
I don't want to change this and how it works because of how the Vue router is set up to pull the token from local storage on each page reload. This token is used to connect to the backend so it needs to be pulled from local storage each reload as I don't want a user to log in just because they reload the page.
Code:
created() {
setTimeout(() => {
localStorage.setItem('token', this.$auth.token)
localStorage.setItem('user_data', JSON.stringify(this.$auth.user))
// Load company data
this.$store.dispatch('loadToken')
this.$store.dispatch('getCompany')
if(this.$auth == null || this.$auth.id_token['https://hello.io/account_signup_type/is_new']) {
this.$router.push('/setup')
} else {
// Load user data from Auth0
// Go to chat page
this.$router.push('/chat')
}
}, 500)
}
edit main.js code
import { Auth0Plugin } from '#/auth/auth0-plugin';
// Install the authentication plugin
Vue.use(Auth0Plugin, {
domain,
clientId,
audience,
onRedirectCallback: (appState) => {
router.push(
appState && appState.targetUrl
? appState.targetUrl
: window.location.pathname,
);
},
});
auth0-plugin
/**
* External Modules
*/
import Vue from 'vue';
import createAuth0Client from '#auth0/auth0-spa-js';
/**
* Vue.js Instance Definition
*/
let instance;
export const getInstance = () => instance;
/**
* Vue.js Instance Initialization
*/
export const useAuth0 = ({
onRedirectCallback = () =>
window.history.replaceState({}, document.title, window.location.pathname),
redirectUri = `${window.location.origin}/callback`,
...pluginOptions
}) => {
if (instance) return instance;
instance = new Vue({
data() {
return {
auth0Client: null,
isLoading: true,
isAuthenticated: false,
user: {},
error: null,
token: null,
id_token: null
};
},
methods: {
async handleRedirectCallback() {
this.isLoading = true;
try {
await this.auth0Client.handleRedirectCallback();
this.user = await this.auth0Client.getUser();
this.isAuthenticated = true;
} catch (error) {
this.error = error;
} finally {
this.isLoading = false;
}
},
loginWithRedirect(options) {
return this.auth0Client.loginWithRedirect(options);
},
logout(options) {
return this.auth0Client.logout(options);
},
getTokenSilently(o) {
return this.auth0Client.getTokenSilently(o);
},
getIdTokenClaims(o) {
return this.auth0Client.getIdTokenClaims(o);
}
},
async created() {
this.auth0Client = await createAuth0Client({
...pluginOptions,
// responseType: 'id_token',
domain: pluginOptions.domain,
client_id: pluginOptions.clientId,
audience: pluginOptions.audience,
redirect_uri: redirectUri,
});
try {
if (
window.location.search.includes('code=') &&
window.location.search.includes('state=')
) {
const { appState } = await this.auth0Client.handleRedirectCallback();
onRedirectCallback(appState);
}
} catch (error) {
this.error = error;
} finally {
this.isAuthenticated = await this.auth0Client.isAuthenticated();
this.user = await this.auth0Client.getUser();
this.$auth.getTokenSilently().then(token => this.token = token)
this.$auth.getIdTokenClaims().then(id_token => this.id_token = id_token)
this.isLoading = false;
}
},
});
return instance;
};
/**
* Vue.js Plugin Definition
*/
export const Auth0Plugin = {
install(Vue, options) {
Vue.prototype.$auth = useAuth0(options);
},
};
edit - updated router.beforeEach
router.beforeEach(async (to, from, next) => {
const auth = getInstance()
if(to.path == '/callback' && auth != null) {
console.log('Callback')
console.log(`Token: ${auth.token}`)
console.log(`User: ${JSON.stringify(auth.user)}`)
localStorage.setItem('token', auth.token)
localStorage.setItem('user_data', JSON.stringify(auth.user))
await store.dispatch('loadToken')
await store.dispatch('getCompany')
return next()
}
if(to.path != '/login' && to.path != '/setup') {
await store.dispatch('loadToken')
await store.dispatch('getCompany')
.then(() => {
return next()
})
} else {
return next()
}
})
edit - adding guide that I followed from Auth0 to get the code I have now - mostly
https://auth0.com/blog/complete-guide-to-vue-user-authentication/
The problem is that there is race condition because dispatch calls return promises that weren't chained before accessing the result of their work.
A good practice is to chain every promise, unless proven other wise.
The code that created contains actually belongs to the router in general because authentication logic is application-wide.
It's unnecessary to access global dependencies on this component instance. This is done for historical reasons because Vue originally was used in non-modular environment. In order to use outside components, global dependencies such as store need to be explicitly imported. In case this cannot be done, this needs to be fixed.
In this case auth instance is available through getInstance. In case the authentication shouldn't be done on each navigation, this needs to be done on condition, e.g.:
import { getInstance } from '.../auth';
import store from '.../store';
...
router.beforeEach(async (to, from, next) => {
const auth = getInstance();
if (...) {
...
await store.dispatch('loadToken')
await store.dispatch('getCompany')
...
next('/setup')
...
} else {
next()
}
})
getInstance doesn't serve a good purpose because it just exposes a variable. Instead, instance could be exported and imported directly, the behaviour would be the same due to how ES modules work.
Also global store already holds application logic and commonly used to handle authentication, including local storage operations.
Trying to use some installed npm packages like fs-extra into below js files given by Truffle.But it says "can't find module "fs-extra".
1) Tried importing local js files using require() method but that fails too.
2) Tried running separate js files using node and it works just fine.
3) Issue comes when I try to use require("fs-extra") inside a function declared in APP object.
App = {
web3Provider: null,
contracts: {},
init: async function () {
return await App.initWeb3();
},
initWeb3: async function () {
// Modern dapp browsers...
if (window.ethereum) {
App.web3Provider = window.ethereum;
try {
// Request account access
await window.ethereum.enable();
} catch (error) {
// User denied account access...
console.error("User denied account access")
}
}
// Legacy dapp browsers...
else if (window.web3) {
App.web3Provider = window.web3.currentProvider;
}
// If no injected web3 instance is detected, fall back to Ganache
else {
App.web3Provider = new Web3.providers.HttpProvider('http://0.0.0.0:9283');
}
web3 = new Web3(App.web3Provider);
return App.initContract();
},
initContract: function () {
$.getJSON('UserCreation.json', function (data) { //<VK>Satish to add his contract file here
// Get the necessary contract artifact file and instantiate it with truffle-contract
var CMArtifact = data;
App.contracts.UserCreation = TruffleContract(CMArtifact);
App.contracts.UserCreation.setProvider(App.web3Provider);
});
return App.bindEvents();
},
createUser: function (event) {
event.preventDefault();
var username = $("#sign-up-username").val();
var title = $("#sign-up-title").val();
var intro = $("#sign-up-intro").val();
const utility=require('fs-extra'); // Failing to find module
}
}
$(function () {
console.log("initiaing farmer")
$(window).load(function () {
App.init();
});
});
Expected: Should be able to call methods from fs-extra package
Actual : can't find module "fs-extra"
npm ls fs-extra to check if you've installed it correctly. Then try npm install fs-extra.
require('fs-extra') will only work in server side javascript (nodejs) .
If your code runs on a browser require will not work
I'm replicating this Google authored tutorial and I have run into a problem and error that I can't figure out how to resolve.
On the Google Cloud Function import json to bigquery, I get an error " TypeError: job.promise is not a function "
Which is located towards the bottom of the function, the code in question is:
.then(([job]) => job.promise())
The error led me to this discussion about the API used, but I don't understand how to resolve the error.
I tried .then(([ job ]) => waitJobFinish(job)) and removing the line resolves the error but doesn't insert anything.
Tertiary question: I also can't find documentation on how to trigger a test of the function so that I can read my console.logs in the google cloud function console, which would help to figure this out . I can test the json POST part of this function, but I can't find what json to trigger a test of a new file write to cloud storage - the test says must include a bucket but I don't know what json to format (the json I use to test the post -> store to cloud storage doesn't work)
Here is the full function which I've pulled into it's own function:
(function () {
'use strict';
// Get a reference to the Cloud Storage component
const storage = require('#google-cloud/storage')();
// Get a reference to the BigQuery component
const bigquery = require('#google-cloud/bigquery')();
function getTable () {
const dataset = bigquery.dataset("iterableToBigquery");
return dataset.get({ autoCreate: true })
.then(([dataset]) => dataset.table("iterableToBigquery").get({ autoCreate: true }));
}
//set trigger for new files to google storage bucket
exports.iterableToBigquery = (event) => {
const file = event.data;
if (file.resourceState === 'not_exists') {
// This was a deletion event, we don't want to process this
return;
}
return Promise.resolve()
.then(() => {
if (!file.bucket) {
throw new Error('Bucket not provided. Make sure you have a "bucket" property in your request');
} else if (!file.name) {
throw new Error('Filename not provided. Make sure you have a "name" property in your request');
}
return getTable();
})
.then(([table]) => {
const fileObj = storage.bucket(file.bucket).file(file.name);
console.log(`Starting job for ${file.name}`);
const metadata = {
autodetect: true,
sourceFormat: 'NEWLINE_DELIMITED_JSON'
};
return table.import(fileObj, metadata);
})
.then(([job]) => job.promise())
//.then(([ job ]) => waitJobFinish(job))
.then(() => console.log(`Job complete for ${file.name}`))
.catch((err) => {
console.log(`Job failed for ${file.name}`);
return Promise.reject(err);
});
};
}());
So I couldn't figure out how to fix google's example, but I was able to get this load from js to work with the following code in google cloud function:
'use strict';
/*jshint esversion: 6 */
// Get a reference to the Cloud Storage component
const storage = require('#google-cloud/storage')();
// Get a reference to the BigQuery component
const bigquery = require('#google-cloud/bigquery')();
exports.iterableToBigquery = (event) => {
const file = event.data;
if (file.resourceState === 'not_exists') {
// This was a deletion event, we don't want to process this
return;
}
const importmetadata = {
autodetect: false,
sourceFormat: 'NEWLINE_DELIMITED_JSON'
};
let job;
// Loads data from a Google Cloud Storage file into the table
bigquery
.dataset("analytics")
.table("iterable")
.import(storage.bucket(file.bucket).file(file.name),importmetadata)
.then(results => {
job = results[0];
console.log(`Job ${job.id} started.`);
// Wait for the job to finish
return job;
})
.then(metadata => {
// Check the job's status for errors
const errors = metadata.status.errors;
if (errors && errors.length > 0) {
throw errors;
}
})
.then(() => {
console.log(`Job ${job.id} completed.`);
})
.catch(err => {
console.error('ERROR:', err);
});
};
I would like to do some unit testing (mocha/chai) for my meteor application. I'm using validated methods (which shouldn't matter for this).
In my method I'm checking if the user has admin permission to perform a collection update.
How do I set a 'dummy' in my unit test, as right now the test will always fail with a 403 error throw.
Unit test
describe('method', () => {
it('should update document', (done) => {
articleUpdate.call({ _id, value })
}
})
Method
const articleUpdate = new ValidatedMethod({
name: 'article.update',
validate: null,
run ({ _id, value }) {
const loggedInUser = Meteor.user()
const isAdmin = Roles.userIsInRole(loggedInUser, ['group'], 'admin')
if (!isAdmin) { throw new Meteor.Error(403, 'Access denied') }
Articles.update(_id, {
$set: { content: value }
})
}
})
In test mode you can use _execute to execute a validated method to pass a context, see here. However, the easiest thing here seems to stub Roles.userIsInRole like so:
import { sandbox } from 'sinon';
const sb = sandbox.create();
describe('method', () => {
it('should update document', () => {
sb.stub(Roles, 'userIsInRole').callsFake(() => true);
articleUpdate.call({ _id, value })
}
})
So i have this little function
module.exports = {
setupNewUser(info, callback) {
var user = {
name: info.name,
nameLowercase: info.name.toLowerCase()
}
try {
Database.save(user, callback)
}
catch(err) {
callback(err)
}
}
}
and im using sinon to test this method
const setupNewUser = require('./index').setupNewUser
const sinon = require('sinon')
const assert = require('assert')
const Database = {
save(info, cb) {
if (info === undefined) {
return cb('nope')
} else {
return cb()
}
}
}
describe('#save()', function () {
it('should call save once', function() {
var save = sinon.spy(Database, 'save')
setupNewUser({ name: 'test' }, function() { })
save.restore()
sinon.assert.calledOnce(save)
})
})
When i ran the test it fail any knows why ?
Error message
AssertError: expected save to be called once but was called 0 times
I believe the reason this is happening is because you're not actually stubbing out the method you think you are. In your test code, your intention was to create a fake Database object so that your actual source code will call this object's method. What you need to stub out is that actual Database object that your source code uses.
Normally in your source code, you'd probably import the Database object. You'll need to import the same Database object and stub it out in your test code as well.