I have the following validation code at a Service Class:
order.items.map(async (item) => {
const itemResult = await this.itemRepository.getById(item.id)
if(itemResult == undefined && itemResult == null){
throw new NotFoundError('Item not found in database')
}
})
And I created the following test at Jest with an Order that doesn't have an Item on the Database:
it('Should validate a Order', async () => {
const item: Item = {
id: '2',
name: 'Item do not exist',
price: '20.0'
}
const order = {
id: uuidv4(),
amount: '100.0',
items: [item],
created_at: new Date()
} as Order
await expect(async () => {
orderService.createOrder(order)
}).toThrowError()
})
But when I run the Jest the test fails and the terminal is shown:
RUNS src/core/service/OrderService.spec.ts
/home/user/repo/projetc/src/core/service/OrderService.ts:1162
throw new NotFoundError_1.NotFoundError('Item not found in database');
^
NotFoundError: Item not found in database
at OrderService.<anonymous> (/home/user/repo/projetc/src/core/service/OrderService.ts:1162:19)
at Generator.next (<anonymous>)
at fulfilled (/home/user/repo/projetc/src/core/service/OrderService.ts:1058:24)
[Update]
After the hoangdv comment I changed the way that I'm validating for this:
const itemResult = await Promise.all(order.items.map(async (item) => {
return await this.itemRepository.getById(item.id)
}))
itemResult.forEach((item) => {
if(!item){
throw new NotFoundError('Item not found in database')
}
})
And changed the test to add rejects Jest property:
await expect(async () => {
await orderService.createOrder(order)
}).rejects.toThrow(NotFoundError)
Related
Hello there,
im trying to upload files (.csv, .xlsx of 5000 row and 6 col) to firebase with a cloud functions that i call from the frontend, the cloud function start and after a few seconds of run its make an error "code":"4" "details":"Deadline exceeded"
(Blaze plan)
the upload is very slow
getting an Deadline exceeded error with code 4
its crashing the server after i get the error
explication of the code below:
i do a for of loop that iterate over an array of array that looks like this
[['Dufour', 'Annie', 'annie.duf#gmail.com', 33683333005, 'f'], ['john','Doe', 'john#gmail.com', 33223424, 'm']] then i format each row in order to
structure it as a firestore doc with many fields and try to add it to firestore
then its crash
i cant figure out when its crashing its really random
and when im trying a smaller file (1000 row) its also crashing after some seconds of execution
export const createNewLeads = functions.region("europe-west3").https.onCall(async (data, context) => {
if (!context.auth) {
throw new functions.https.HttpsError('failed-precondition', 'The function must be called' + 'while authenticated.');
}
const { category, subcategory, allLeads, downloadUrl, file_id, fileDetails } = data;
for (const lead of allLeads) {
const leads_infos = format_lead_infos(lead , fileDetails);
const leads_meta = format_lead_metadata(downloadUrl, file_id, fileDetails);
const db_lead_ref = admin.firestore().collection('leads');
const new_lead_doc_id = db_lead_ref.doc().id;
db_lead_ref
.doc(new_lead_doc_id).set({
lead_id: new_lead_doc_id,
category: category,
subcategory: subcategory,
createdOn: Date(),
uploaded_by: context.auth.uid,
purchased_by: null,
metadata: leads_meta,
infos: leads_infos
}).then(() => {
functions.logger.info('createLeads SUCCESS', { docId: new_lead_doc_id });
return {
user_id: new_lead_doc_id,
created: true,
};
}).catch((error: any) => {
functions.logger.info('createLeads ERROR', { error });
return {
error: true,
err_mess: error,
};
});
}
});
As far as I can see you are not handling the asynchronous nature of writing to Firestore correctly, as you're not awaiting the call to set(). You're also returning a result on the first successful document write in the loop, which seems suspicious.
This should be closer to what you need:
try {
for (const lead of allLeads) {
const leads_infos = format_lead_infos(lead , fileDetails);
const leads_meta = format_lead_metadata(downloadUrl, file_id, fileDetails);
const db_lead_ref = admin.firestore().collection('leads');
const new_lead_doc_id = db_lead_ref.doc().id;
await db_lead_ref.doc(new_lead_doc_id).set({
lead_id: new_lead_doc_id,
category: category,
subcategory: subcategory,
createdOn: Date(),
uploaded_by: context.auth.uid,
purchased_by: null,
metadata: leads_meta,
infos: leads_infos
});
}
}
catch (error: any) => {
functions.logger.info('createLeads ERROR', { error });
return {
error: true,
err_mess: error,
};
}
functions.logger.info('createLeads SUCCESS', { docId: new_lead_doc_id
return {
user_id: new_lead_doc_id,
created: true,
};
In this code we await each document write operation and then return an error if any of them fails, and success only once all of them succeed.
Hello Frank thanks for helping us i copy the code you give to us bu after a few seconds of running i got this error
error of the code from the firebase console log
export const createNewLeads = functions.region("europe-west3").https.onCall(async (data, context) => {
if (!context.auth) {
throw new functions.https.HttpsError('failed-precondition', 'The function must be called' + 'while authentificated.');
}
const { category, subcategory, allLeads, downloadUrl, file_id, fileDetails } = data;
const db_lead_ref = admin.firestore().collection('leads');
const new_lead_doc_id = db_lead_ref.doc().id;
try {
for (const lead of allLeads) {
const leads_infos = format_lead_infos(lead , fileDetails);
const leads_meta = format_lead_metadata(downloadUrl, file_id, fileDetails);
await db_lead_ref.doc(new_lead_doc_id).set({
lead_id: new_lead_doc_id,
category: category,
subcategory: subcategory,
createdOn: Date(),
uploaded_by: context.auth.uid,
purchased_by: null,
metadata: leads_meta,
infos: leads_infos
});
}
} catch (error:any) {
functions.logger.info('createLeads ERROR', { error });
return {
error: true,
err_mess: error,
}
}
functions.logger.info('createLeads SUCCESS', { docId: new_lead_doc_id })
return {
user_id: new_lead_doc_id,
created: true,
}
});
So i tried following the https://discordjs.guide/additional-info/rest-api.html guide before making my own. But I can't get either to work.
Firstly with /cat it crashes and the console returns with:
SyntaxError: Unexpected end of JSON input
at JSON.parse (<anonymous>)
at getJSONResponse (BOTLOCATION\index.js:77:14)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at async Client.<anonymous> (BOTLOCATION\index.js:90:20)
And /urban works but no matter what term I enter it returns with NULL.
Here is the code, its nearly identical from the guides apart from the added SlashCommandBuilder and REST.
const { request } = require('undici');
const clientId = 'CLIENTID_HERE';
const guildId = 'GUILDID_HERE';
const { SlashCommandBuilder } = require('#discordjs/builders');
const { REST } = require('#discordjs/rest');
const { Routes } = require('discord-api-types/v9');
const commands = [
new SlashCommandBuilder().setName('cat').setDescription('Cat thing idk'),
new SlashCommandBuilder().setName('urban').setDescription('Urban Dictionary Thing'),
]
.map(command => command.toJSON());
const rest = new REST({ version: '9' }).setToken("TOKEN_HERE");
rest.put(Routes.applicationGuildCommands(clientId, guildId), { body: commands })
//rest.put(Routes.applicationGuildCommands(clientId), { body: commands })
.then(() => console.log('Successfully registered application commands.'))
.catch(console.error);
const trim = (str, max) => (str.length > max ? `${str.slice(0, max - 3)}...` : str);
async function getJSONResponse(body) {
let fullBody = '';
for await (const data of body) {
fullBody += data.toString();
}
return JSON.parse(fullBody);
}
client.on('interactionCreate', async interaction => {
if (!interaction.isCommand()) return;
const { commandName } = interaction;
await interaction.deferReply();
if (commandName === 'cat') {
const catResult = await request('https://aws.random.cat/meow');
const { file } = await getJSONResponse(catResult.body);
interaction.reply({ files: [{ attachment: file, name: 'cat.png' }] });
} else if (commandName === 'urban') {
const term = interaction.options.getString('term');
const query = new URLSearchParams({ term });
const dictResult = await request(`https://api.urbandictionary.com/v0/define?${query}`);
const { list } = await getJSONResponse(dictResult.body);
if (!list.length) {
return interaction.editReply(`No results found for **${term}**.`);
}
const [answer] = list;
const embed = new MessageEmbed()
.setColor('#EFFF00')
.setTitle(answer.word)
.setURL(answer.permalink)
.addFields(
{ name: 'Definition', value: trim(answer.definition, 1024) },
{ name: 'Example', value: trim(answer.example, 1024) },
{
name: 'Rating',
value: `${answer.thumbs_up} thumbs up. ${answer.thumbs_down} thumbs down.`,
},
);
interaction.editReply({ embeds: [embed] });
}
});
So for the cat command since there is a deferReply first we need to use editReply since deferReply counts as the first/initial reply.
await interaction.deferReply();
const catResult = await request('https://aws.random.cat/meow').catch((err) => { console.log(err); });;
const { file } = await getJSONResponse(catResult.body).catch((err) => { console.log(err); });
return await interaction.editReply({ files: [{ attachment: file, name: 'cat.png' }] });
I also added a .catch to the end of each await, this was just for testing however I recommend it.
Now with the urban command, the reason it is using null is since you don't have the string option's text. We can check for it by adding an if statement.
await interaction.deferReply();
const term = interaction.options.getString('term');
if (!term) return await interaction.editReply('Please provide a term.'); // We need to add this check to see if the user provided the term option or not.
const query = new URLSearchParams({ term });
const dictResult = await request(`https://api.urbandictionary.com/v0/define?${query}`);
const { list } = await getJSONResponse(dictResult.body);
if (!list.length) {
return interaction.editReply(`No results found for **${term}**.`);
}
const [answer] = list;
const embed = new MessageEmbed()
.setColor('#EFFF00')
.setTitle(answer.word)
.setURL(answer.permalink)
.addFields(
{ name: 'Definition', value: trim(answer.definition, 1024) },
{ name: 'Example', value: trim(answer.example, 1024) },
{
name: 'Rating',
value: `${answer.thumbs_up} thumbs up. ${answer.thumbs_down} thumbs down.`,
},
);
return await interaction.editReply({ embeds: [embed] });
IMPORTANT: When you are building your slash command you are not setting a string option. In the commands array, when creating the second slash command called urban we will add the support for the string option there. (An example using the string option, discord.js guide all command options)
This is how we can do this:
const commands = [
new SlashCommandBuilder().setName('cat')
.setDescription('Cat thing idk'),
new SlashCommandBuilder()
.setName('urban')
.setDescription('Urban Dictionary Thing')
.addStringOption((option) => option.setName('term').setDescription('term')) // We first add the string option then set the name to 'term' which is what the code calls for and then the description.
].map((command) => command.toJSON());
If you would like to make the term input required, add .setRequired(true) which will not allow the command to be ran without entering the term to search.
Once you do that you should be all good! Tested the code and it's working once that's fixed
I'm trying to test my resolvers but i'd like to test each field of the response, here's the code to call the response:
interface Options {
source: string;
variableValues?: Maybe<{ [key: string]: unknown | null }>;
}
let schema: GraphQLSchema;
const gCall = async ({
source,
variableValues,
}: Options): Promise<ExecutionResult> => {
if (!schema) {
schema = await createSchema();
}
return graphql({
schema,
source,
variableValues,
});
};
export default gCall;
And that's the code to test the resolver:
let connection: Connection;
const challengeMutation = `
mutation CreateChallenge($data: CreateChallengeInput!) {
createChallenge(data: $data) {
id
name
category
startDate
endDate
goal
description
}
}
`;
describe('Create Challenge', () => {
beforeAll(async () => {
connection = await databaseTestConnection();
await connection.createQueryBuilder().delete().from(Challenge).execute();
});
afterAll(async () => {
await connection.createQueryBuilder().delete().from(Challenge).execute();
await connection.close();
});
it('should create challenge', async () => {
const challenge = {
name: 'some awesome name',
category: 'distância',
startDate: new Date(2020, 7, 4).toISOString(),
endDate: new Date(2020, 7, 5).toISOString(),
goal: 5000,
description: 'some excelent challenge description',
};
const response = await gCall({
source: challengeMutation,
variableValues: {
data: challenge,
},
});
expect(response).toMatchObject({
data: {
createChallenge: {
name: challenge.name,
category: challenge.category,
startDate: challenge.startDate,
endDate: challenge.endDate,
goal: challenge.goal,
description: challenge.description,
},
},
});
});
});
What I'd like to do is test the fields separately, like this:
expect(response.data.createChallenge.name).toEqual(challenge.name);
But I'm getting the following error when I try to execute the above code:
Object is possibly 'null' or 'undefined'.
What can I do to solve this error and to make this test better?
Object is possibly 'null' or 'undefined'.
TypeScript warns you that the response data might not exist as the graphql "server" might return error instead. So you should use ! operator to assert it's not null.
You should also do that after checking it's not undefined with expect().
I'm doing unit testing with jest and was able to successfully run some of it but there's certain code that I don't know how to test.
I have Create Organization method that needs to check first if the organization is already exist.
async createOrganization(opt) {
try {
const organizationExist = await this.OrganizationRepository.getOne({name: opt.name})
if (organizationExist) {
throw new Error('Organization already exist')
}
} catch (error) {
throw error
}
let organizationObject = {}
organizationObject.name = opt.name
return this.OrganizationRepository.save(organizationObject)
}
and so far this is the unit test code that I was able to cover
describe('Create Organization', () => {
it('should call getOne function', () => {
const mockGetOne = jest.spyOn(OrganizationRepository.prototype, 'getOne')
organizationService.createOrganization(expectedOrganization)
expect(mockGetOne).toHaveBeenCalledWith({name: 'sample org'})
})
it('should return created organization', async () => {
const mockSave = jest.spyOn(OrganizationRepository.prototype, 'save')
mockSave.mockReturnValue(Promise.resolve(expectedOrganization))
const result = await organizationService.createOrganization({name: 'sample org'})
expect(mockSave).toHaveBeenCalledWith({name: 'sample org'})
expect(result).toBe(expectedOrganization)
})
})
now what I want to test is this part
const organizationExist = await this.OrganizationRepository.getOne({name: opt.name})
if (organizationExist) {
throw new Error('Organization already exist')
}
I want to throw an error if the organization is already exist using the name parameter.
Hope you guys can help me. Thanks
you could use toThrowError to test this scenario.
it("Should throw error", async () => {
const mockGetOne = jest.spyOn(OrganizationRepository.prototype, 'getOne')
await organizationService.createOrganization({ name: 'sample org' }); ;
expect(mockGetOne).toHaveBeenCalledWith({ name: 'sample org' });
// Test the exact error message
expect( organizationService.createOrganization({ name: 'sample org' }))
.resolves
.toThrowError(new Error("Organization already exist"));
});
Are you looking for toThrow()?
expect(() => someFunctionCall()).toThrow();
I'm trying to write a jasmine test for an #Effect that has chained rxjs catchError operators, but am struggling to test any observables beyond the first catchError.
here's the effect:
#Effect() submitEndsheets$ = this.actions$.pipe(
ofType<SubmitEndSheets>(SpreadActionTypes.SUBMIT_ENDSHEETS),
withLatestFrom(this.store.pipe(select(fromAppStore.fromOrder.getDocumentId))),
concatMap(([action, documentId]) =>
this.spreadService.submitEndSheets(documentId).pipe(
map((response: ActionProcessorDto) => new SubmitEndSheetsSuccess(response.data)),
catchError((error) => of(undo(action))),
catchError((error) => of(new MessageModal({
message: error.message,
title: 'Submission Error!'
})
))
)
)
);
and the corresponding test:
it('handles errors by sending an undo action', () => {
const action = {
type: SpreadActionTypes.SUBMIT_ENDSHEETS,
};
const source = cold('a', { a: action });
const error = new Error('Error occurred!');
const service = createServiceStub(error);
const store = createStoreState();
const effects = new Effects(service, new Actions(source), store);
const expected = cold('ab', {
a: undo(action),
b: new MessageModal({
message: 'Sorry, something went wrong with your request. Please try again or contact support.',
title: 'Update Error!'
}),
});
expect(effects.submitEndsheets$).toBeObservable(expected);
});
for reference, here are the createServiceStub that mocks the service and createStoreState that, you guessed it, creates a mock store.
function createServiceStub(response: any) {
const service = jasmine.createSpyObj('spreadService', [
'load',
'update',
'updateSpreadPosition',
'submitEndSheets'
]);
const isError = response instanceof Error;
const serviceResponse = isError ? throwError(response) : of(response);
service.load.and.returnValue(serviceResponse);
service.update.and.returnValue(serviceResponse);
service.updateSpreadPosition.and.returnValue(serviceResponse);
service.submitEndSheets.and.returnValue(serviceResponse);
return service;
}
function createStoreState() {
const store = jasmine.createSpyObj('store', ['pipe']);
store.pipe.and.returnValue(of({ documentId: 123 }));
return store;
}
here's the test output:
FAILED TESTS:
✖ handles errors by sending an undo action
HeadlessChrome 0.0.0 (Mac OS X 10.14.2)
Expected $.length = 1 to equal 2.
Expected $[1] = undefined to equal Object({ frame: 10, notification: Notification({ kind: 'N', value: MessageModal({ payload: Object({ message: 'Sorry, something went wrong with your request. Please try again or contact support.', title: 'Update Error!' }), type: 'MESSAGE_MODAL' }), error: undefined, hasValue: true }) }).
at compare node_modules/jasmine-marbles/bundles/jasmine-marbles.umd.js:389:1)
at UserContext.<anonymous> src/app/book/store/spread/spread.effects.spec.ts:197:46)
at ZoneDelegate../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke node_modules/zone.js/dist/zone.js:388:1)
Thanks in advance for any help!
Update:
catchError can send an array of actions back out of the effect like so:
#Effect() submitEndsheets$ = this.actions$.pipe(
ofType<SubmitEndSheets>(SpreadActionTypes.SUBMIT_ENDSHEETS),
withLatestFrom(this.store.pipe(select(fromAppStore.fromOrder.getDocumentId))),
concatMap(([action, documentId]) =>
this.spreadService.submitEndSheets(documentId).pipe(
map((response: ActionProcessorDto) => new SubmitEndSheetsSuccess(response.data)),
catchError(error => [
new PopSingleToast({
severity: ToastSeverity.error,
summary: 'Failure',
detail: `Some error occurred: \n Error: ${error}`
}),
undo(action)
])
)
)
);
The corresponding test looks like so:
it('handles errors by sending an undo action', () => {
const action = {
type: SpreadActionTypes.SUBMIT_ENDSHEETS
};
const source = cold('a', { a: action });
const error = new Error('Error occurred!');
const service = createServiceStub(error);
const store = createStoreState();
const effects = new Effects(service, new Actions(source), store);
const expectedAction = new PopSingleToast({
severity: ToastSeverity.error,
summary: 'Failure',
detail: `Some error occurred: \n Error: ${error}`
});
const expected = cold('(ab)', {
a: expectedAction,
b: undo(action)
});
expect(effects.submitEndsheets$).toBeObservable(expected);
});
Thanks for to all for the help!
Having two catchErrors in a row like this means the second one will never trigger because the first one will eat the error.
You would need to rethrow the error in the first catchError to get inside the second one:
catchError(error => throw new Error()),
catchError(error => console.log('now I trigger'))
So I am afraid your questions doesn't make really sense.