import ava, { TestInterface } from 'ava'; import { Mongoose } from 'mongoose'; import { ForbiddenError, UserInputError } from 'apollo-server-micro'; import { Factory } from '../../../database/factory'; import * as testUtils from '../../../test/utils'; import { User } from '../../../app/user'; import { Recipe } from '../../../app/recipe'; import { createContext } from '../../../test/utils/create-mock-context'; import { gqlCall } from '../../../test/utils/gqlCall'; import { defineUserAbility } from '../../../server/authorization/user-authorization'; import { isIngredientItem, isIngredientGroup } from '../utils'; // // Setup // const test = ava as TestInterface<{ db: Mongoose }>; test.before(async t => { await testUtils.setupDB(t); }); test.afterEach.always(async t => { await testUtils.cleanupDB(t); }); test.after.always(async t => { await testUtils.tearDownDB(t); }); // ** Constants const updateIngredientMutation = ` mutation UpdateIngredient($input: UpdateIngredientInput!) { updateIngredient(input: $input) { id node { name } } } `; // // Tests // test.serial('Should property update a single ingredient', async t => { const user = await Factory.create('user'); const recipe = await Factory.create( 'recipe', { ownerId: user.id }, { ingredientCount: 1 } ); const ingredient = recipe.ingredientList.nodes[0]; const context = createContext({ state: { user, abilities: defineUserAbility(user) }, }); const response = await gqlCall({ source: updateIngredientMutation, contextValue: context, variableValues: { input: { recipeId: recipe.id, ingredientId: ingredient.id, ingredient: { name: 'Rump Roast', quantity: 3, measurement: 'POUND', note: 'extra beefy', }, }, }, }); t.falsy(response.errors); // Properly returned the new ingredient t.is(response.data.updateIngredient.node.name, 'Rump Roast'); // db is updated to new ingredient const fetchedIngredient = await Recipe.findById(recipe.id).then(r => { return r.ingredientList.nodes.id(ingredient.id); }); if (!isIngredientItem(fetchedIngredient)) { t.fail(); } else { t.is(fetchedIngredient.node.name, 'Rump Roast'); } }); test.serial( 'Should property update a single ingredient within a group', async t => { const user = await Factory.create('user'); const recipe = await Factory.create( 'recipe', { ownerId: user.id }, { ingredientCount: 0, ingredientGroupCount: 1 } ); const ingredientGroup = recipe.ingredientList.nodes[0]; if (!isIngredientGroup(ingredientGroup)) { t.fail(); return; } const ingredient = ingredientGroup.nodes[0]; const context = createContext({ state: { user, abilities: defineUserAbility(user) }, }); const response = await gqlCall({ source: updateIngredientMutation, contextValue: context, variableValues: { input: { recipeId: recipe.id, ingredientId: ingredient.id, order: 1, ingredient: { name: 'Rump Roast', }, }, }, }); t.falsy(response.errors); // Properly returned the new ingredient t.is(response.data.updateIngredient.node.name, 'Rump Roast'); // db is updated to new ingredient const fetchedIngredient = await Recipe.findById(recipe.id).then(r => { const ingGroup = r.ingredientList.nodes[0]; if (!isIngredientGroup(ingGroup)) { t.fail(); return; } return ingGroup.nodes[1]; }); t.is(fetchedIngredient.id, ingredient.id); t.is(fetchedIngredient.node.name, 'Rump Roast'); } ); test.serial('Should property update a single ingredient order', async t => { const user = await Factory.create('user'); const recipe = await Factory.create( 'recipe', { ownerId: user.id }, { ingredientCount: 4 } ); const ingredient = recipe.ingredientList.nodes[0]; const context = createContext({ state: { user, abilities: defineUserAbility(user) }, }); const response = await gqlCall({ source: updateIngredientMutation, contextValue: context, variableValues: { input: { recipeId: recipe.id, order: 1, ingredientId: ingredient.id, ingredient: { name: 'Rump Roast', quantity: 1, measurement: 'POUND', note: 'extra beefy', }, }, }, }); // Properly returned the new ingredient t.falsy(response.errors); t.is(response.data.updateIngredient.node.name, 'Rump Roast'); // The ingredient is now located at index 1 (item #2) const fetchedIngredient = await Recipe.findById(recipe.id).then(r => { return r.ingredientList.nodes[1]; }); t.is(fetchedIngredient.id, ingredient.id); }); test.serial('Should move to the correct group when it is set', async t => { const user = await Factory.create('user'); const recipe = await Factory.create( 'recipe', { ownerId: user.id }, { ingredientCount: 0, ingredientGroupCount: 2 } ); const context = createContext({ state: { user, abilities: defineUserAbility(user) }, }); const ingGroup1 = recipe.ingredientList.nodes[0]; const ingGroup2 = recipe.ingredientList.nodes[1]; if (!isIngredientGroup(ingGroup1)) { t.fail(); return; } const ing = ingGroup1.nodes[0]; const response = await gqlCall({ source: updateIngredientMutation, contextValue: context, variableValues: { input: { recipeId: recipe.id, ingredientId: ing.id, groupId: ingGroup2.id, order: 0, }, }, }); t.falsy(response.errors); // The ingredient is now located in group2 at index 0 const fetchedIngredienGroup = await Recipe.findById(recipe.id).then(r => { const ingGroup = r.ingredientList.nodes[1]; if (!isIngredientGroup(ingGroup)) { t.fail(); return; } return ingGroup; }); t.is(fetchedIngredienGroup.nodes[0].id, ing.id); }); test.serial('Should return an error if the group doesnt exist', async t => { const user = await Factory.create('user'); const recipe = await Factory.create('recipe', { ownerId: user.id, }); const ingredient = recipe.ingredientList.nodes[0]; const context = createContext({ state: { user, abilities: defineUserAbility(user) }, }); const response = await gqlCall({ source: updateIngredientMutation, contextValue: context, variableValues: { input: { recipeId: recipe.id, ingredientId: ingredient.id, groupId: 'incorrect-id', ingredient: { name: 'Rump Roast', quantity: 3, measurement: 'POUND', note: 'extra beefy', }, }, }, }); // Should retrun forbidden error t.true(response.errors.length > 0); t.true(response.errors[0].originalError instanceof UserInputError); }); test.serial( 'Should return an error if you dont have permission to edit the recipe', async t => { const user = await Factory.create('user'); const recipe = await Factory.create('recipe'); const ingredient = recipe.ingredientList.nodes[0]; const context = createContext({ state: { user, abilities: defineUserAbility(user) }, }); const response = await gqlCall({ source: updateIngredientMutation, contextValue: context, variableValues: { input: { recipeId: recipe.id, ingredientId: ingredient.id, ingredient: { name: 'Rump Roast', quantity: 3, measurement: 'POUND', note: 'extra beefy', }, }, }, }); // Should retrun forbidden error t.truthy(response.errors.length > 0); t.true(response.errors[0].originalError instanceof ForbiddenError); } );