100 requests sent in 500ms unnecessarily using Cypress. Why? - testing

I am intercepting a request to http://localhost:18545 in Cypress.
The cy.intercept() is within a beforeEach. After the test passes, the requests skyrocket to 100 requests in 500ms. Why?
This is the code that can be condensed but still working on a few things:
import { startServer } from '../../cypress/support/e2e'
describe('visit and interact with home page', () => {
const serverUrl = `http://testurl.invalidtld`
const ethProvider = `http://localhost:18545`
Cypress.on('uncaught:exception', (err, runnable) => {
return false
})
beforeEach(() => {
// deploy unirep and unirep social contract
const context = startServer()
Object.assign(context)
cy.intercept('GET', `${serverUrl}/api/post?*`, {
body: {
posts: 'string',
},
}).as('getApiContent')
cy.intercept('GET', `${serverUrl}/api/config`, {
body: {
unirepAddress: '0x41afd703a36b19D9BB94E3083baA5E4F70f5adD6',
unirepSocialAddress:
'0x903Ae15BfbddFAD6cd44B4cC1CF01EEBa0742456',
},
}).as('getApiConfig')
cy.intercept(`${ethProvider}*`, (req) => {
// conditonal logic to return different responses based on the request url
// console.log(req.body)
const { method, params, id } = req.body
if (method === 'eth_chainId') {
req.reply({
body: {
id: 1,
jsonrpc: '2.0',
result: '0x111111',
},
})
} else if (
method === 'eth_call' &&
params[0]?.data === '0x79502c55'
) {
req.reply({
body: {
id,
jsonrpc: '2.0',
result:
'0x' +
Array(10 * 64)
.fill(0)
.map((_, i) => (i % 64 === 63 ? 1 : 0))
.join(''),
},
})
} else if (method === 'eth_call') {
// other uint256 retrievals
req.reply({
body: {
id,
jsonrpc: '2.0',
result: '0x0000000000000000000000000000000000000000000000000000000000000001',
},
})
} else {
req.reply({
body: { test: 'test' },
})
}
}).as('ethProvider')
cy.intercept('GET', `${serverUrl}/api/genInvitationCode/*`, {
fixture: 'genInvitationCode.json',
}).as('genInvitationCode')
cy.intercept('GET', `${serverUrl}/api/signup*`, {
fixture: 'signup.json',
}).as('signup')
})
it('navigate to the signup page and signup a user', () => {
cy.visit('/')
cy.findByText('Join').click()
cy.findByRole('textbox').type('testprivatekey')
cy.findByText('Let me in').click()
})
})
I can add more context to the startServer logic within the e2e directory if requested.

Related

Why updating object in array is not working in VUE?

I'm trying to make an update and I managed to do it in the firebase, but in the store is not updating. Here is my code
editCar() {
let result = this.balmUI.validate(this.formData);
let { valid, message } = result;
this.message = message;
console.log(`Vrei sa editezi masina: ${this.formData.vehicle}`);
console.log(utils.url);
if (valid) {
let data = {
vehicle: this.formData.vehicle,
color: this.formData.color,
fuel: this.formData.fuel,
status: this.formData.status,
price: this.formData.price,
};
let requestParameters = { ...utils.globalRequestParameters };
let token = window.localStorage.getItem("token");
requestParameters.headers.Authorization = "Bearer " + token;
requestParameters.method = "PUT";
requestParameters.body = JSON.stringify(data);
fetch(utils.url + "cars/" + this.formData.id, requestParameters)
.then((res) => res.json())
.then((res) => {
console.log(res.message);
if (
res.message === "Decoding error!" ||
res.message === "Your token expired!"
) {
console.log("nu ai voie!");
} else {
data.id = res.id;
this.$store.dispatch("editCar", data);
this.$router.push("/");
}
});
}
This is the index from store, which contais my mutation and action. Anything else is working properly
import { createStore } from 'vuex'
export default createStore({
state: {
cars: [],
isAuthentif: false
},
getters: {
cars: state => {
return state.cars
}
},
mutations: {
SET_AUTH: (state, status) => {
state.isAuthentif = status
},
SET_CARS: (state, cars) => {
state.cars = cars
},
ADD_CAR: (state, car) => {
state.cars.push(car)
},
DELETE_CAR: (state, id) => {
var index = state.cars.findIndex(car => car.id == id)
state.cars.splice(index, 1);
},
EDIT_CAR: (state, car) => {
state.cars.forEach(c => {
if(c.id === car.id) {
c = car;
}
})
}
},
actions: {
login: ({ commit }, payload) => {
commit('SET_AUTH', payload)
},
fetchCars: ({ commit }, payload) => {
commit('SET_CARS', payload)
},
addCar: ({ commit }, payload) => {
commit('ADD_CAR', payload)
},
deleteCar: ({ commit }, payload) => {
commit('DELETE_CAR', payload)
},
editCar: ({ commit }, payload) => {
commit('EDIT_CAR', payload)
}
},
modules: {
}
})
EDIT_CAR is the problem, I think.
What is wrong? Why it is not updating on the screen.
I've also tried to use this https://vuex.vuejs.org/guide/mutations.html#object-style-commit
like this c = {...c, car} but is not working
Your problem is not in the mutation. The problem is in your editCar() in here this.$store.dispatch("editCar", data);
You put data, and with vehicle, color, fuel, status and price and then in your mutation you verify ID. You didn't pass any id. You cand make a new object if you don't want you id, like this:
editCar() {
let result = this.balmUI.validate(this.formData);
let { valid, message } = result;
this.message = message;
console.log(`Vrei sa editezi masina: ${this.formData.vehicle}`);
console.log(utils.url);
if (valid) {
let data = {
vehicle: this.formData.vehicle,
color: this.formData.color,
fuel: this.formData.fuel,
status: this.formData.status,
price: this.formData.price,
};
let requestParameters = { ...utils.globalRequestParameters };
let token = window.localStorage.getItem("token");
requestParameters.headers.Authorization = "Bearer " + token;
requestParameters.method = "PUT";
requestParameters.body = JSON.stringify(data);
fetch(utils.url + "cars/" + this.formData.id, requestParameters)
.then((res) => res.json())
.then((res) => {
console.log(res.message);
if (
res.message === "Decoding error!" ||
res.message === "Your token expired!"
) {
console.log("nu ai voie!");
} else {
let newData = {
id: this.formData.id,
vehicle: data.vehicle,
color: data.color,
fuel: data.fuel,
status: data.status,
price: data.price,
};
this.$store.dispatch("editCar", newData);
this.$router.push("/");
}
});
}
},
Also in your mutation cand make something like this:
EDIT_CAR: (state, car) => {
Object.assign(state.cars[state.cars.findIndex(c => c.id === car.id)], car);
}
Can you try to change your EDIT_CAR mutation to:
const index = state.cars.findIndex(x => x.id === car.id)
state.cars.splice(index, 1, car)
If you haven't done already, place a console.log(car) at the beginning of the mutation so you make sure it gets called and the car payload is what you expect.

How to encode and parse / decode a nested query string Javascript

I'm sending form data from React Hook Form to Netlify via their submission-created function. I don't have any problem with encoding individual form field values, but now I'm trying to encode an array of objects.
Here is an example of my form data:
{
_id: "12345-67890-asdf-qwer",
language: "Spanish",
formId: "add-registration-form",
got-ya: "",
classType: "Private lessons",
size: "1",
days: [
{
day: "Monday",
start: 08:00",
end: "09:30"
},
{
day: "Wednesday",
start: "08:00",
end: "09:30"
}
]
}
The only problem I have is with the "days" array. I've tried various ways to encode this and this is the function I've currently been working with (which isn't ideal):
const encode = (data) => {
return Object.keys(data).map(key => {
let val = data[key]
if (val !== null && typeof val === 'object') val = encode(val)
return `${key}=${encodeURIComponent(`${val}`.replace(/\s/g, '_'))}`
}).join('&')
}
I tried using a library like qs to stringify the data, but I can't figure out how to make that work.
And here is the function posting the data to Netlify:
// Handles the post process to Netlify so I can access their serverless functions
const handlePost = (formData, event) => {
event.preventDefault()
fetch(`/`, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: encode({ "form-name": 'add-registration-form', ...formData }),
})
.then((response) => {
if(response.status === 200) {
navigate("../../")
} else {
alert("ERROR!")
}
console.log(response)
})
.catch((error) => {
setFormStatus("error")
console.log(error)
})
}
Finally, here is a sample of my submission-created file to receive and parse the encoded data:
const sanityClient = require("#sanity/client")
const client = sanityClient({
projectId: process.env.GATSBY_SANITY_PROJECT_ID,
dataset: process.env.GATSBY_SANITY_DATASET,
token: process.env.SANITY_FORM_SUBMIT_TOKEN,
useCDN: false,
})
const { nanoid } = require('nanoid');
exports.handler = async function (event, context, callback) {
// Pulling out the payload from the body
const { payload } = JSON.parse(event.body)
// Checking which form has been submitted
const isAddRegistrationForm = payload.data.formId === "add-registration-form"
// Build the document JSON and submit it to SANITY
if (isAddRegistrationForm) {
// How do I decode the "days" data from payload?
let schedule = payload.data.days.map(d => (
{
_key: nanoid(),
_type: "classDayTime",
day: d.day,
time: {
_type: "timeRange",
start: d.start,
end: d.end
}
}
))
const addRegistrationForm = {
_type: "addRegistrationForm",
_studentId: payload.data._id,
classType: payload.data.classType,
schedule: schedule,
language: payload.data.language,
classSize: payload.data.size,
}
const result = await client.create(addRegistrationForm).catch((err) => console.log(err))
}
callback(null, {
statusCode: 200,
})
}
So, how do I properly encode my form data with a nested array of objects before sending it to Netlify? And then in the Netlify function how do I parse / decode that data to be able to submit it to Sanity?
So, the qs library proved to be my savior after all. I just wasn't implementing it correctly before. So, with the same form data structure, just make sure to import qs to your form component file:
import qs from 'qs'
and then make your encode function nice and succinct with:
// Transforms the form data from the React Hook Form output to a format Netlify can read
const encode = (data) => {
return qs.stringify(data)
}
Next, use this encode function in your handle submit function for the form:
// Handles the post process to Netlify so we can access their serverless functions
const handlePost = (formData, event) => {
event.preventDefault()
fetch(`/`, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: encode({ "form-name": 'add-registration-form', ...formData }),
})
.then((response) => {
reset()
if(response.status === 200) {
alert("SUCCESS!")
} else {
alert("ERROR!")
}
console.log(response)
})
.catch((error) => {
console.log(error)
})
}
Finally, this is what your Netlify submission-created.js file should look like more or less:
const sanityClient = require("#sanity/client")
const client = sanityClient({
projectId: process.env.GATSBY_SANITY_PROJECT_ID,
dataset: process.env.GATSBY_SANITY_DATASET,
token: process.env.SANITY_FORM_SUBMIT_TOKEN,
useCDN: false,
})
const qs = require('qs')
const { nanoid } = require('nanoid');
exports.handler = async function (event, context, callback) {
// Pulling out the payload from the body
const { payload } = JSON.parse(event.body)
// Checking which form has been submitted
const isAddRegistrationForm = payload.data.formId === "add-registration-form"
// Build the document JSON and submit it to SANITY
if (isAddRegistrationForm) {
const parsedData = qs.parse(payload.data)
let schedule = parsedData.days
.map(d => (
{
_key: nanoid(),
_type: "classDayTime",
day: d.day,
time: {
_type: "timeRange",
start: d.start,
end: d.end
}
}
))
const addRegistrationForm = {
_type: "addRegistrationForm",
submitDate: new Date().toISOString(),
_studentId: parsedData._id,
classType: parsedData.classType,
schedule: schedule,
language: parsedData.language,
classSize: parsedData.size,
}
const result = await client.create(addRegistrationForm).catch((err) => console.log(err))
}
callback(null, {
statusCode: 200,
})
}

Can you share your dataProvider with client-side sorting and filtering?

I have a basic REST API I'm using (no queries, no pagination) and I have it integrated very nicely into the React Admin, with edit, show, and create actions. All very nice. However, my API does not have any filtering or sorting so the power of the Datagrid is lost because I can't search or sort the columns.
I know I need to implement client-side functions to filter and sort the API myself within a custom dataProvider. Instead of building this all from scratch, can anyone share their custom dataProvider with me that already has local sort, filter, etc. that I can adapt instead of building from scratch?
Alternatively, I implemented Tubular for React last night and I loved its ease-of-use but it lacks good integration with React Admin. Has anyone implemented Tubular within React Admin and how did you approach it?
Thank you in advance for any assistance!!
Here is dataProvider I have written for our project. All filtering, sorting, pagination were done on the client side.
import { fetchUtils, DataProvider } from 'ra-core';
const dataProvider = (apiUrl : string, httpClient = fetchUtils.fetchJson) : DataProvider => ({
getList: async (resource, params) => {
const { json } = await httpClient(`${ apiUrl }/${ resource }`);
const feedFilter = params.filter["feed"];
const stateFilter = params.filter["state"];
const result = json
.filter(e => {
if (!feedFilter) {
return true;
}
return e.feed.includes(feedFilter.toUpperCase());
})
.filter(e => {
if (!stateFilter) {
return true;
}
return e.state === stateFilter;
});
const { field, order } = params.sort;
result.sort(dynamicSort(field, order));
const { page, perPage } = params.pagination;
const showedResult = result.slice((page - 1) * perPage, page * perPage);
return {
data: showedResult.map((resource : { feed : string; }) => ({ ...resource, id: resource.feed })),
total: result.length,
};
},
getMany: async (resource) => {
const url = `${ apiUrl }/${ resource }`;
const { json } = await httpClient(url);
return ({
data: json.map((resource : { feed : string; }) => ({ ...resource, id: resource.feed })),
});
},
getManyReference: async (resource) => {
const url = `${ apiUrl }/${ resource }`;
const { headers, json } = await httpClient(url);
return ({
data: json.map((resource : { feed : string; }) => ({ ...resource, id: resource.feed })),
total: parseInt(headers.get('X-Total-Count') || "", 10),
});
},
getOne: (resource, params) =>
httpClient(`${ apiUrl }/${ resource }/${ params.id }`).then(({ json }) => ({
data: json,
})),
update: (resource, params) =>
httpClient(`${ apiUrl }/${ resource }/${ params.id }`, {
method: 'PUT',
body: JSON.stringify(params.data),
}).then(({ json }) => ({ data: json })),
updateMany: async (resource, params) => {
const { json } = await httpClient(`${ apiUrl }/${ resource }`, {
method: 'PUT',
body: JSON.stringify(params.data),
});
return ({ data: json });
},
create: (resource, params) =>
httpClient(`${ apiUrl }/${ resource }`, {
method: 'POST',
body: JSON.stringify(params.data),
}).then(({ json }) => ({
data: { ...params.data, id: json.id },
})),
delete: (resource, params) =>
httpClient(`${ apiUrl }/${ resource }/${ params.id }`, {
method: 'DELETE',
}).then(({ json }) => ({ data: json })),
deleteMany: async (resource, params) => {
const { json } = await httpClient(`${ apiUrl }/${ resource }`, {
method: 'DELETE',
body: JSON.stringify(params.ids),
});
return ({ data: json });
},
});
function dynamicSort(property : string, order : string){
let sortOrder = 1;
if (order === "DESC") {
sortOrder = -1;
}
return function (a : any, b : any){
let aProp = a[property];
let bProp = b[property];
if (!a.hasOwnProperty(property)) {
aProp = ''
}
if (!b.hasOwnProperty(property)) {
bProp = ''
}
const result = (aProp < bProp) ? -1 : (aProp > bProp) ? 1 : 0;
return result * sortOrder;
}
}
const cacheDataProviderProxy = (dataProvider : any, duration = 5 * 60 * 1000) =>
new Proxy(dataProvider, {
get: (dataProvider, name) => (resource : string, params : any) => {
if (name === 'getOne' || name === 'getMany' || name === 'getList') {
return dataProvider.name(resource, params).then((response : { validUntil : Date; }) => {
const validUntil = new Date();
validUntil.setTime(validUntil.getTime() + duration);
response.validUntil = validUntil;
return response;
});
}
return dataProvider.name(resource, params);
},
});
export default cacheDataProviderProxy(dataProvider);

How to place a component's logic outside the file .vue itself

I'm building a webapp in Nuxt.js and it's growing quite a bit.
I have a page which does two things: one when i'm creating a task and one when managing that task.
This page has a lot of methods, divided for when i create the task and when i manage the task.
How can i split these modules in two files and then import then only when I need them?
These methods need also to access the component's state and other function Nuxt imports such as axios.
async create() {
if (this.isSondaggioReady()) {
try {
await this.getImagesPath()
const o = { ...this.sondaggio }
delete o.id
o.questions = o.questions.map((question) => {
delete question.garbageCollector
if (question.type !== 'checkbox' && question.type !== 'radio') {
delete question.answers
delete question.hasAltro
} else {
question.answers = question.answers.map((answer) => {
delete answer._id
delete answer.file
delete answer.error
if (answer.type !== 'image') delete answer.caption
return answer
})
}
if (question.hasAltro) {
question.answers.push({
type: 'altro',
value: ''
})
}
return question
})
console.log('TO SEND', JSON.stringify(o, null, 2))
this.$store.commit('temp/showBottomLoader', {
show: true,
message: 'Crezione del sondaggio in corso'
})
const { data } = await this.$axios.post('/sondaggi/admin/create', o)
this.sondaggio.id = data
const s = {
_id: data,
author: this.$auth.user.email.slice(0, -13),
title: this.sondaggio.title
}
this.$store.commit('temp/pushHome', { key: 'sondaggi', attr: 'data', data: [...this.$store.state.temp.home.sondaggi.data, s] })
this.$store.dispatch('temp/showToast', 'Sondaggio creato correttamente')
this.$router.replace('/')
} catch (e) {
console.log(e)
this.$store.dispatch('temp/showToast', this.$getErrorMessage(e))
} finally {
this.$store.commit('temp/showBottomLoader', {
show: false,
message: 'Crezione del sondaggio in corso'
})
}
}
},
Here there's an example of what a method does. It calls an async functions which relies on HTTP axios requests:
async getImagesPath() {
this.sondaggio.questions.forEach((question, i) => {
question.answers.forEach((answer, j) => {
if (answer.file instanceof File || answer.value.includes('data:image')) {
this.uploadingImages.push({
coords: [i, j],
percentage: 0,
file: answer.file || answer.value
})
}
})
})
const requests = []
this.uploadingImages.forEach((img) => {
const temp = new FormData()
temp.append('img', img.file)
const req = this.$axios.post('/sondaggi/admin/images/add/' + this.sondaggio.title.replace(/\s+/g, ''), temp, {
onUploadProgress: function (progressEvent) {
img.percentage = Math.round(((progressEvent.loaded * 100) / progressEvent.total) * 90 / 100)
},
onDownloadProgress: function (progressEvent) {
img.percentage = 90 + Math.round(((progressEvent.loaded * 100) / progressEvent.total) * 10 / 100)
},
headers: { 'Content-Type': 'multipart/form-data' }
})
.catch((err) => {
console.log(err)
img.percentage = 150
})
requests.push(req)
})
try {
const response = await Promise.all(requests)
response.forEach(({ data }, i) => {
this.sondaggio.questions[this.uploadingImages[i].coords[0]].answers[this.uploadingImages[i].coords[1]].value = data[0]
})
this.$set(this.sondaggio, 'hasImages', this.uploadingImages.length > 0)
this.uploadingImages = []
await Promise.resolve()
} catch (e) {
console.log('handling gloval err', e)
await Promise.reject(e)
}
},
As you can see axios requests modify the component's state

Two custom methods/endpoints using loopBack, one works, the other gives a 401

I created two custom endpoints with Loopback.
Account.deleteAllHearingTests = function (req, callback) {
console.log('here comes the req to delete all hearing tests', req);
Account.findById(req.accessToken.userId)
.then(account => {
if (!account) {
throw new Error('cannot find user');
}
return app.models.HearingTest.updateAll({ accountId: account.id }, { isDeleted: new Date() });
})
.then(() => {
callback(null);
})
.catch(error => {
callback(error);
})
}
Account.remoteMethod(
'deleteAllHearingTests', {
http: {
path: '/clearHearingTests',
verb: 'post'
},
accepts: [
{ arg: 'req', type: 'object', http: { source: 'req' } }
],
returns: {}
}
);
the second one looks like this.
Account.deleteSingleHearingTest = function (req, callback) {
// console.log('accounts.js: deleteSingleHearingTest: are we being reached????', req)
Account.findById(req.accessToken.userId)
.then(account => {
if (!account) {
throw new Error('Cannot find user');
}
console.log('account.js: deleteSingleHearingTest: req.body.hearingTestId N: ', req.body.hearingTestId);
return app.models.HearingTest.updateAll({ accountId: account.id, id: req.body.hearingTestId }, { isDeleted: new Date() });
})
.then(() => {
callback(null);
})
.catch(error => {
callback(error);
});
}
Account.remoteMethod(
'deleteSingleHearingTest', {
http: {
path: '/deleteSingleHearingTest',
verb: 'post'
},
accepts: [
{ arg: 'req', type: 'object', description: 'removes a single hearing test', http: { source: 'req' } }
],
description: 'this is the end point for a single delete',
returns: {}
}
);
};
The first custom method returns a 401 status response when I make the initial fetch. The second returns a 200.
Inside my actions file the first method is called with something that looks like this:
export function deleteAllHearingTests() {
return (dispatch, getState) => {
let state = getState();
if (!state.user || !state.user.accessToken || !state.user.accessToken.id || !state.user.accessToken.userId) {
console.debug('deleteAllHearingTests', state.user);
// TODO: ERROR
return;
}
fetch(SERVERCONFIG.BASEURL + '/api/Accounts/clearHearingTests?access_token=' + state.user.accessToken.id, {
method: 'POST',
headers: SERVERCONFIG.HEADERS
})
.then(response => {
console.log('here is your response', response);
if (response.status !== 200) {
throw new Error('Something is wrong');
}
return response.json()
})
the second method is called with
export const deleteSingleHearingTest = (hearingTestNumber) => {
return (dispatch, getState) => {
let state = getState();
if (!state.user || !state.user.accessToken || !state.user.accessToken.id || !state.user.accessToken.userId) {
console.debug('writeTestResult', state.user);
// TODO: ERROR
return;
}
console.log('single delete ', SERVERCONFIG.BASEURL + '/api/Accounts/deleteSingleHearingTest?access_token=' + state.user.accessToken.id)
fetch(SERVERCONFIG.BASEURL + '/api/Accounts/deleteSingleHearingTest?access_token=' + state.user.accessToken.id, {
method: 'POST',
headers: SERVERCONFIG.HEADERS,
body: JSON.stringify({ "hearingTestId": hearingTestNumber })
})
.then(response => {
console.log('getting response from initial fetch inside deleteSingleReqport', response);
They are nearly identical, however, one works..the other fails. What are some possible causes for the 401?
Did you try to call those methods with external tool like a postman, so you would exactly know if you don't miss access_token or something else? Also, when you compare code from one function and another, you can see that you are colling the updateAll with different arguments. It's hard to say without original code, but maybe the issue is there? Compare below:
return app.models.HearingTest.updateAll(
{ accountId: account.id },
{ isDeleted: new Date() });
return app.models.HearingTest.updateAll(
{ accountId: account.id, id: req.body.hearingTestId },
{ isDeleted: new Date() });
Additionally, in fetch method they are also diffferences, you are missing in one case the below:
body: JSON.stringify({ "hearingTestId": hearingTestNumber })
What you could also do to debug and to provide more data is to run server in debug mode by calling:
export DEBUG=*; npm start