Jest test throws "Cannot read property 'prototype' of undefined" when tested action returns a call to fetch - reactjs

I'm trying to debug a Jest test in a React/Redux app; I'm running the following test (I haven't implemented faker yet).
it("Should create a SUBMIT_CONTACT_ME_FORM_SUCCESS action when contact me form submission completes successfully.", () => {
const contactMe = {
id: 1,
name: "John Shepard",
email: "shepard#n7.gov",
comments: "I am not the very model of a scientist salarian."
};
fetchMock.postOnce(contactMeURL, {
headers: { "content-type": "application/json" },
body: contactMe
});
const expectedActions = [
{
type: navbarActions.SUBMIT_CONTACT_ME_FORM_REQUEST,
contactMeForm
},
{
type: navbarActions.SUBMIT_CONTACT_ME_FORM_SUCCESS,
contactMe,
receivedAt: 1
}
;
const store = mockStore({ contactMe: null });
return store.dispatch(navbarActions.submitContactMeForm(contactMeForm, 1))
.then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
});
However, my logs point to the beginning of the fetch in the following statement within the tested action.
return fetch(`${apiRoot}/contact`, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(contactMeForm)
})
.then(response => response.json())
.then(json => {
if (json.err) {
dispatch(submitContactMeFormFailure(json.err, forcedTime))
} else {
dispatch(submitContactMeFormSuccess(json, forcedTime))
}
});
It seems to be a timing/lifecycle issue, but I can't isolate or correct it. I'm using the following dev dependencies.
"devDependencies": {
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.6",
"fetch-mock": "^9.11.0",
"jasmine": "^3.9.0",
"jasmine-enzyme": "^7.1.2",
"node-fetch": "^3.0.0",
"redux-mock-store": "^1.5.4"
}

This might not be the answer but try running your test with async
it("Should ... successfully.", async() => {
const contactMe = {
id: 1,
name: "John Shepard",
email: "shepard#n7.gov",
comments: "I am not the very model of a scientist salarian."
};
fetchMock.postOnce(contactMeURL, {
headers: { "content-type": "application/json" },
body: contactMe
});
const expectedActions = [
{
type: navbarActions.SUBMIT_CONTACT_ME_FORM_REQUEST,
contactMeForm
},
{
type: navbarActions.SUBMIT_CONTACT_ME_FORM_SUCCESS,
contactMe,
receivedAt: 1
};
const store = mockStore({ contactMe: null });
await store.dispatch(navbarActions.submitContactMeForm(contactMeForm, 1));
expect(store.getActions()).toEqual(expectedActions);
});

Related

UNHANDLED_EXCEPTION: DiscordAPIError[50035]: Invalid Form Body 3.type[ENUM_TYPE_COERCE]: Value "6" is not a valid enum value

I'm developping a Discord BOT on repplit and when i launch it with CTR+Enter (or with the run button), i'm getting the following error :
UNHANDLED_EXCEPTION: DiscordAPIError[50035]: Invalid Form Body
3.type[ENUM_TYPE_COERCE]: Value "6" is not a valid enum value.
Promise {
<rejected> DiscordAPIError[50035]: Invalid Form Body
3.type[ENUM_TYPE_COERCE]: Value "6" is not a valid enum value.
at SequentialHandler.runRequest (/home/runner/AIUC/node_modules/discord.js/node_modules/#discordjs/rest/dist/index.js:667:15)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async SequentialHandler.queueRequest (/home/runner/AIUC/node_modules/discord.js/node_modules/#discordjs/rest/dist/index.js:464:14)
at async REST.request (/home/runner/AIUC/node_modules/discord.js/node_modules/#discordjs/rest/dist/index.js:910:22) {
requestBody: { files: undefined, json: [Array] },
rawError: { code: 50035, errors: [Object], message: 'Invalid Form Body' },
code: 50035,
status: 400,
method: 'PUT',
url: 'https://discord.com/api/v10/applications/1069961347166646272/guilds/1069355488258560122/commands'
}
}
I don't know where my error could come from. You can see bellow my package.json
{
"name": "nodejs",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"#discordjs/opus": "^0.8.0",
"#discordjs/rest": "^1.5.1-dev.1676247033-0e4224b.0",
"#discordjs/voice": "^0.14.0",
"#distube/soundcloud": "^1.3.0",
"#distube/spotify": "^1.5.1",
"#distube/yt-dlp": "^1.1.3",
"chalk": "4.1.2",
"dayjs": "1.10.7",
"discord.js": "^14.7.1",
"distube": "^4.0.4",
"express": "^4.18.2",
"ffmpeg-static": "4.4.0",
"glob": "7.2.0",
"libsodium-wrappers": "^0.7.10",
"lyrics-finder": "^21.7.0",
"moment": "^2.29.4",
"moment-duration-format": "^2.3.2",
"mongoose": "^6.9.1",
"ms": "2.1.3",
"node-fetch": "^3.2.6",
"opusscript": "^0.0.8",
"pm2": "^5.2.2",
"systeminformation": "^5.17.9",
"ytdl-core": "^4.11.2"
},
"description": ""
}
This error appear when i'm update my Discord BOT from DiscordJS v13 to DiscordJS v14. I've tried to add a register.js file to initialize my REST API but the problem still...
EDIT
index.js
const { Client, Collection, GatewayIntentBits } = require('discord.js');
const { DisTube } = require('distube');
const { SpotifyPlugin } = require('#distube/spotify');
const { SoundCloudPlugin } = require('#distube/soundcloud');
const { YtDlpPlugin } = require('#distube/yt-dlp');
const client = new Client(
{
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMembers,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.GuildVoiceStates,
GatewayIntentBits.MessageContent
]
}
);
const mongoose = require('mongoose');
const Logger = require('./utils/Logger');
['commands', 'buttons', 'selects'].forEach(x => client[x] = new Collection());
['CommandUtil', 'EventUtil', 'ButtonUtil', 'SelectUtil'].forEach(handler => { require(`./utils/handlers/${handler}`)(client) });
require('./utils/Functions')(client);
mongoose.set('strictQuery', true);
process.on('exit', code => { Logger.client(`Process shutdown with the following code: ${code}`) });
process.on('uncaughtException', (err, origin) => {
Logger.error(`UNCAUGHT_EXCEPTION: ${err}`);
console.error(`Origin: ${origin}`);
});
process.on('unhandledRejection', (reason, promise) => {
Logger.warn(`UNHANDLED_EXCEPTION: ${reason}`);
console.log(promise);
});
process.on('warning', (...args) => Logger.warn(...args));
mongoose.connect(process.env.DATABASE_URI, {
autoIndex: false,
maxPoolSize: 10,
serverSelectionTimeoutMS: 5000,
socketTimeoutMS: 45000,
family: 4
}).then(() => { Logger.client('Connected to the database!') })
.catch(err => { Logger.error(err) });
client.distube = new DisTube(client, {
leaveOnStop: false,
emitNewSongOnly: true,
emitAddSongWhenCreatingQueue: false,
emitAddListWhenCreatingQueue: false,
plugins: [
new SpotifyPlugin({
emitEventsAfterFetching: true
}),
new SoundCloudPlugin(),
new YtDlpPlugin()
]
});
client.login(process.env.DISCORD_TOKEN);
resident-evil.js
const { EmbedBuilder, ApplicationCommandOptionType } = require('discord.js');
const { ResidentEvilRPGCharacter } = require('../../models');
module.exports = {
name: 'resident-evil',
category: 'context',
permissions: ['SEND_MESSAGES'],
ownerOnly: false,
usage: 'Use context menu!',
examples: ['Use context menu!'],
type: ApplicationCommandOptionType.User,
async runInteraction(client, interaction) {
const member = await interaction.guild.members.fetch(interaction.targetId);
const residentEvilData = await ResidentEvilRPGCharacter.findOne({ id: interaction.guild.id, member: member.id });
if (!residentEvilData) return interaction.reply({ content: 'You have no character in Resident Evil RPG' })
const embed = new EmbedBuilder()
.setAuthor({ name: `${member.user.tag} (${member.id})` })
.setColor('#0047AB')
.setImage(`${residentEvilData['picture']}`)
.setThumbnail(member.user.displayAvatarURL())
.setDescription(`**+ Lastname**\n${residentEvilData['lastname']}\n\n**+ Firstname**\n${residentEvilData['firstname']}\n\n**+ Surname**\n${residentEvilData['surname']}\n\n**+ Age**\n${residentEvilData['age']}\n\n**+ Sexe**\n${residentEvilData['sexe']}\n\n**+ Family**\n${residentEvilData['family']}\n\n**+ Nationality**\n${residentEvilData['nationality']}\n\n**+ Sexual Orientation**\n${residentEvilData['sexualorientation']}\n\n**+ Political Party**\n${residentEvilData['politicalparty']}\n\n**+ Religion**\n${residentEvilData['religion']}\n\n**+ History**\n${residentEvilData['history']}\n\n${residentEvilData['other']}`);
interaction.reply({ embeds: [embed] });
}
}
I finally find where this error come from, and the error come from my context command resident-evil.js
Instead of type: ApplicationCommandOptionType.User, we need to replace this value by 2! Why ??? I don't know, because the DiscordJS Documentation tell us to use ApplicationCommandOptionType.User! My only conclusion of that is with Discord JS v14 context menu are split into 2 different type :
UserContextMenu
MessageContextMenu
So maybe DiscordJS considered these context menu command as SubCommandGroup. So the correct file is the following files:
resident-evil.js
const { EmbedBuilder, ApplicationCommandOptionType } = require('discord.js');
const { ResidentEvilRPGCharacter } = require('../../models');
module.exports = {
name: 'resident-evil',
category: 'context',
permissions: ['SEND_MESSAGES'],
ownerOnly: false,
usage: 'Use context menu!',
examples: ['Use context menu!'],
type: 2,
async runInteraction(client, interaction) {
const member = await interaction.guild.members.fetch(interaction.targetId);
const residentEvilData = await ResidentEvilRPGCharacter.findOne({ id: interaction.guild.id, member: member.id });
if (!residentEvilData) return interaction.reply({ content: 'You have no character in Resident Evil RPG' })
const embed = new EmbedBuilder()
.setAuthor({ name: `${member.user.tag} (${member.id})` })
.setColor('#0047AB')
.setImage(`${residentEvilData['picture']}`)
.setThumbnail(member.user.displayAvatarURL())
.setDescription(`**+ Lastname**\n${residentEvilData['lastname']}\n\n**+ Firstname**\n${residentEvilData['firstname']}\n\n**+ Surname**\n${residentEvilData['surname']}\n\n**+ Age**\n${residentEvilData['age']}\n\n**+ Sexe**\n${residentEvilData['sexe']}\n\n**+ Family**\n${residentEvilData['family']}\n\n**+ Nationality**\n${residentEvilData['nationality']}\n\n**+ Sexual Orientation**\n${residentEvilData['sexualorientation']}\n\n**+ Political Party**\n${residentEvilData['politicalparty']}\n\n**+ Religion**\n${residentEvilData['religion']}\n\n**+ History**\n${residentEvilData['history']}\n\n${residentEvilData['other']}`);
interaction.reply({ embeds: [embed] });
}
}
There were some changes to enums in Discord.js. It looks like the number 6 was a string and not a number, you should remove the quotations around it. (of course, this is just an assumption since you didn't share any of your code.)
For more info, you can check the Discord JS Guide!

How to format API response

I'm writing a react app that fetches data from a DynamoDB table using a Lambda function. The function successfully retrieves an array of objects as shown below. I've tested this in the AWS console and in Postman.
[
{
"Obj1Key1": {
"S": "Obj1Value1"
},
"Obj1Key2": {
"S": "Obj1Value2"
},
"Obj1Key3": {
"S": "Obj1Value3"
},
"Obj1Key4": {
"S": "Obj1Value4"
}
},
{
"Obj2Key1": {
"S": "Obj2Value1"
},
"Obj2Key2": {
"S": "Obj2Value2"
},
"Obj2Key3": {
"S": "Obj2Value3"
},
"Obj2Key4": {
"S": "Obj2Value4"
}
},
{
"Obj3Key1": {
"S": "Obj3Value1"
},
"Obj3Key2": {
"S": "Obj3Value2"
},
"Obj3Key3": {
"S": "Obj3Value3"
},
"Obj3Key4": {
"S": "Obj3Value4"
}
}
]
..and below is the function in the React app that gets the data:
...
...
const [ response , setResponse ] = useState();
...
async function fetchData() {
try {
const body = JSON.stringify({
etc....
});
await fetch("https://url..etc", {
method: "POST",
headers: { "Content-Type": "application/json"},
body: body
}).then(res => setResponse(res))
} catch (error) {
console.log(error);
}
};
However When I get the raw response from the React app, and I console log it, my console shows the following
How can I get the array of objects from this response? I tried different combinations of parsing and stringifying but was not able to get the array as an array. Adding .then(res => res.text()). before .then(res => setResponse(res)) gets me the response I'm looking for but as a string. I need to get it as an array.
Thank you

POST https://google-translate1.p.rapidapi.com/language/translate/v2 502 (Bad Gateway) When fetching in React

An example of the rapidapi google translate API code for the JavaScript fetch method is:
fetch("https://google-translate1.p.rapidapi.com/language/translate/v2", {
"method": "POST",
"headers": {
"x-rapidapi-host": "google-translate1.p.rapidapi.com",
"x-rapidapi-key": "MY-API-KEY",
"accept-encoding": "application/gzip",
"content-type": "application/x-www-form-urlencoded"
},
"body": {
"source": "en",
"q": "Hello, world!",
"target": "es"
}
})
.then(response => {
console.log(response);
})
.catch(err => {
console.log(err);
})
Now I copied it, and used it for the React component as follows:
fetch("https://google-translate1.p.rapidapi.com/language/translate/v2", {
method: "POST",
body: {
"source": "en",
"q": "Hello, world!",
"target": "es"
},
headers: {
"x-rapidapi-host": "google-translate1.p.rapidapi.com",
"x-rapidapi-key": "MY-API-KEY",
"accept-encoding": "application/gzip",
"content-type": "application/x-www-form-urlencoded"
}
})
.then(response => {
console.log(response);
})
.catch(err => {
console.log(err);
})
After running the program, I get the error "POST https://google-translate1.p.rapidapi.com/language/translate/v2 502 (Bad Gateway)" on the console!
How can I have a healthy connection to the server?
this code solved the same problem for me
<script>
const key = "your key"
const qs = obj => {
return new URLSearchParams(obj).toString();
}
const word = "Hello, world";
const data = qs({
q: word,
source: "en",
target: "es",
})
const options = {
method: "POST",
url: "https://google-translate1.p.rapidapi.com/language/translate/v2",
headers: {
"content-type": "application/x-www-form-urlencoded",
"x-rapidapi-key": key,
"x-rapidapi-host": "google-translate1.p.rapidapi.com",
},
data: data,
};
axios.request(options).then(function (response) {
console.log(response.data);
})
.catch(function (error) {
console.error(99, error);
});
</script>

Test an Action with multiple dispatches (Loading, error and success)

How could I create a test for an Action with multiple dispatches(Loading, error & success), let see one of my actions:
import axios from 'axios';
import { CompaniesActionTypes } from './types';
export const getCompanies = () => async (dispatch: any) => {
dispatch({
type: CompaniesActionTypes.LOADING
})
try {
const response = await axios.get('app/admin/companies');
dispatch({
type: CompaniesActionTypes.GET_COMPANIES,
payload: response.data
})
} catch (error) {
console.log(error.message);
dispatch({
type: CompaniesActionTypes.ERROR,
payload: 'There was an error while requesting list of companies, please try again later.'
})
}
}
To have more information, below is my reducer for this scenario:
import { CompaniesActionTypes, CompaniesState } from './types';
import { Reducer } from 'redux';
const INITIAL_STATE: CompaniesState = {
data: [],
loading: false,
error: ''
}
export const reducer: Reducer<CompaniesState> = (state = INITIAL_STATE, action) => {
switch (action.type) {
case CompaniesActionTypes.GET_COMPANIES:
return {...state, data: action.payload, loading: false, error: ''}
case CompaniesActionTypes.LOADING:
return {...state, loading: true};
case CompaniesActionTypes.ERROR:
return {...state, error: action.payload, loading: false};
default:
return state
}
}
Note: As you can see I'm using typescript, should not be a problem.
so what I'm trying is:
// Actions
describe('creating actions', () => {
it('should create an action to get companies', () => {
const expectedAction = {
type: CompaniesActionTypes.GET_COMPANIES,
payload: Promise.resolve()
}
expect(actions.getCompanies()).toEqual(expectedAction)
})
})
For the first action test Im getting this error:
Expected: {"payload": {}, "type": "##companies/GET_COMPANIES"}
Received: [Function anonymous]
const middlewares = [thunk]
const mockStore = configureMockStore(middlewares)
describe('actions', () => {
afterEach(() => {
fetchMock.restore()
})
it('creates GET_COMPANIES when fetching companies', () => {
fetchMock.getOnce('app/admin/client/companies', {
body: mock,
headers: { 'content-type': 'application/json' }
})
const expectedActions = [{ type: CompaniesActionTypes.GET_COMPANIES, payload: mock }]
const store = mockStore({})
return store.dispatch(actions.getCompanies()).then(() => {
expect(store.getActions()).toEqual(expectedActions)
})
})
})
For this example I'm having problems with the dispatches:
- Expected
+ Received
Array [
Object {
- "payload": Array [
- Object {
- "id": 1,
- "name": "Company Test 1",
+ "type": "##companies/LOADING_COMPANIES",
},
Object {
- "id": 2,
- "name": "Company Test 2",
- },
- Object {
- "id": 3,
- "name": "Company Test 3",
- },
- ],
- "type": "##companies/GET_COMPANIES",
+ "payload": "There was an error while requesting list of companies, please try again later.",
+ "type": "##companies/ERROR_COMPANIES",
},
]
whats going on with:
"type": "##companies/LOADING_COMPANIES",
"type": "##companies/GET_COMPANIES",
"type": "##companies/ERROR_COMPANIES",
Any idea how to manage this scenario for testing? I guess it's because timing but I have no idea how to implement all the steps

Mongoose Update array in a document does not work as expected

I'm scratching my head since a couple day on how to update the content of an array with Mongoose.
Here is my schema to begin with:
const playedGameSchema = new Schema ({
created: Date,
updated: Date,
game: {
type: Schema.Types.ObjectId,
ref: 'game'
},
creator: {
id: {
type: Schema.Types.ObjectId,
ref: 'user'
},
score: Number
},
partners: [{
id: {
type: Schema.Types.ObjectId,
ref: 'user'
},
score: Number
}]
});
module.exports = mongoose.model('PlayedGame', playedGameSchema);
Basically, what I want to achieve is to, at the same time:
- Update the creator.score (successful with dot notation).
- Update the score key for each partner (unsuccessful).
Here is the result of a document created:
{
"creator": {
"id": "5b8544fa11235d9f02a9b4f1",
"score": 0
},
"_id": "5bb6375f5f68cc5c52bc93ae",
"game": "5b45080bb1806be939bfde03",
"partners": [
{
"_id": "5bb637605f68cc5cafbc93b0",
"id": "5b85497111235d677ba9b4f2",
"score": 0
},
{
"_id": "5bb637605f68ccc70ebc93af",
"id": "5b85497111235d677ba9b4f2",
"score": 0
}
],
"created": "2018-10-04T15:53:03.386Z",
"updated": "2018-10-04T15:53:03.386Z",
"__v": 0
}
As I said, I was able to change the score of the score creator by passing something like { "creator.score": 500 } as a second parameter, then I switch to trying to update the array.
Here is my lambda function to update the score for each partner:
export const update: Handler = (event: APIGatewayEvent, context: Context, cb: Callback) => {
context.callbackWaitsForEmptyEventLoop = false;
const body = JSON.parse(event.body);
let partnersScore: object = {};
if(body.update.partners) {
body.update.partners.forEach((score, index) => {
const key = `partners.${index}.$.score`;
partnersScore = Object.assign(partnersScore, { [key]: score});
console.log(partnersScore);
});
}
connectToDatabase().then(() => {
console.log('connected', partnersScore)
PlayedGame.findByIdAndUpdate(body.id, { $set: { partners: partnersScore } },{ new: true})
.then(game => cb(null, {
statusCode: 200,
headers: defaultResponseHeader,
body: JSON.stringify(game)
}))
.catch(err => {
cb(null, {
statusCode: err.statusCode || 500,
headers: { 'Content-Type': 'text/plain' },
body: err
})});
});
}
Which passes a nice { 'partners.0.$.score': 500, 'partners.1.$.score': 1000 } to the $set.
Unfortunately, the result to my request is a partners array that contains only one empty object.
{
"creator": {
"id": "5b8544fa11235d9f02a9b4f1",
"score": 0
},
"_id": "5bb6375f5f68cc5c52bc93ae",
"game": "5b45080bb1806be939bfde03",
"partners": [
{
"_id": "5bb63775f6d99b7b76443741"
}
],
"created": "2018-10-04T15:53:03.386Z",
"updated": "2018-10-04T15:53:03.386Z",
"__v": 0
}
Can anyone guide me into updating the creator score and all partners score at the same time?
My thoughs about findOneAndUpdate method on a model is that it's better because it doesn't require the data to be changed outside of the BDD, but wanting to update array keys and another key seems very difficult.
Instead, I relied on a set/save logic, like this:
PlayedGame.findById(body.id)
.then(game => {
game.set('creator.score', update.creatorScore);
update.partners.forEach((score, index) => game.set(`partners.${index}.score`, score));
game.save()
.then(result => {
cb(null, {
statusCode: 200,
headers: defaultResponseHeader,
body: JSON.stringify(result)
})
})
.catch(err => {
cb(null, {
statusCode: err.statusCode || 500,
headers: { 'Content-Type': 'text/plain' },
body: JSON.stringify({ 'Update failed: ': err })
})});
})

Resources