cpen321-recipe-roulette / server / test / testRecipes.test.js
testRecipes.test.js
Raw
jest.mock('node-fetch');

var fetch = require('node-fetch');
var dbFunctions = require("../db/db").Functions;
var app = require("../src/app");
var request = require("supertest");

const baseMockedRequestBody = { 
    userId: "test@ubc.ca",
    recipeId: "795512",
    recipeImage: "https://someimgurl.jpg"
}

let baseMockedDbFindRecordGetResponse = {
    ingredients: [
        {
            name: "Salmon",
            date: [new Date(2023, 12, 9), new Date(2023, 12, 11)]
        },
        {
            name: "Chicken",
            date: [new Date(2023, 12, 20), new Date(2023, 12, 25)]
        },
        {
            name: "Soy Sauce",
            date: [new Date(2024, 2, 18)]
        },
        {
            name: "Heavy Cream",
            date: [new Date(2024, 1, 15)]
        },
        {
            name: "Onion",
            date: [new Date(2023, 12, 15), new Date(2023, 12, 20)]
        },
        {
            name: "BBQ Sauce",
            date: [new Date(2024, 5, 20)]
        }
    ]
}

const mockedRecipes = [
    {
        recipeName: "Creamy Salmon Pasta",
        userId: "test@ubc.ca",
        recipeId: "577324",
        recipeSummary: "Tis a pasta recipe",
        recipeImage: "https://visitors-centre.jrc.ec.europa.eu/sites/default/files/thumbnail/kmffq_additional-illo4video_2019_5_fishplate%5B1%5D.jpg",
        numTimes: 2,
        likes: 5
    },
    {
        recipeName: "Sticky Baked Chicken",
        userId: "test2@gmail.com",
        recipeId: "623324",
        recipeSummary: "Tis a chicken recipe",
        recipeImage: "https://visitors-centre.jrc.ec.europa.eu/sites/default/files/thumbnail/kmffq_additional-illo4video_2019_5_fishplate%5B1%5D.jpg",
        numTimes: 3,
        likes: 2
    },
]

const mockedInstructions = "Placeholder instructions"

// Interface GET /recipes
describe("Get all available recipes", () => {
    // Input: Missing user email
    // Expected status code: 400
    // Expected behavior: Missing user email detected and error returned
    // Expected output: "Missing user email"
    test("Missing user email", async () => {
        let expectedResponse = "Missing user email";
        const res = await request(app).get("/recipes").set({email: ""});
        expect(res.status).toStrictEqual(400);
        expect(res.text).toEqual(expectedResponse);
        expect(res.body).toEqual({});
    });

    // Input: User with no ingredients saved
    // Expected status code: 200
    // Expected behavior: No recipes fetched
    // Expected output: Empty list of recipes
    test("No ingredients saved", async () => {
        let expectedResponse = [];
        jest.spyOn(dbFunctions, "dbFindRecord").mockReturnValue(null);
        const res = await request(app).get("/recipes").set({email: "test@ubc.ca"});
        expect(res.status).toStrictEqual(200);
        expect(res.body).toEqual(expectedResponse);
    });

    // Input: External API returns error
    // Expected status code: 500
    // Expected behavior: External API error detected and error returned
    // Expected output: "Cannot fetch recipes at this point"
    test("External API returns error", async () => {
        let expectedResponse = "Cannot fetch recipes at this point";
        jest.spyOn(dbFunctions, "dbFindRecord").mockReturnValue(baseMockedDbFindRecordGetResponse);
        fetch.mockReturnValue(Promise.resolve({ status: 401, json: () => Promise.resolve()}));
        const res = await request(app).get("/recipes").set({email: "test@ubc.ca"});
        expect(res.status).toStrictEqual(500);
        expect(res.text).toEqual(expectedResponse);
        expect(res.body).toEqual({});
    });

    // Input: Valid user with ingredients saved
    // Expected status code: 200
    // Expected behavior: Recipes fetched and returned
    // Expected output: Empty list of recipes
    test("Valid email and Ingredients saved", async () => {
        let expectedResponse = mockedRecipes;
        jest.spyOn(dbFunctions, "dbFindRecord").mockReturnValue(baseMockedDbFindRecordGetResponse);
        fetch.mockReturnValue(Promise.resolve({ status: 201, json: () => Promise.resolve(mockedRecipes)}));
        const res = await request(app).get("/recipes").set({email: "test@ubc.ca"});
        expect(res.status).toStrictEqual(200);
        expect(res.body).toEqual(expectedResponse);
    });
})

const baseMockedDbFindRecordPostResponse = {
    userId: baseMockedRequestBody.userId,
    recipeId: baseMockedRequestBody.recipeId,
    recipeName: 'Whole 30 Slow Cooker',
    recipeImage: baseMockedRequestBody.recipeImage,
    numTimes: 1,
    likes: 0,
    recipeSummary: `Whole 30 Slow Cooker is a <b>gluten free, dairy free, and whole 30</b> hor d'oeuvre. For <b>$17.22 per serving</b>, this recipe <b>covers 67%</b> of your daily requirements of vitamins and minerals. This recipe makes 65 servings with <b>3760 calories</b>, <b>365g of protein</b>, and <b>237g of fat</b> each. If you have conquer your fear of cooking a chicken the easy way! from one lovely life, pulled pork taco salad from anya's eats, easy slow cooker taco meat from rubies and radishes, and a few other ingredients on hand, you can make it. 24 people have tried and liked this recipe. From preparation to the plate, 
  this recipe takes about <b>45 minutes</b>. It is brought to you by Pink When. All things considered, we decided this recipe <b>deserves a spoonacular score of 3%</b>. This score is very bad (but still fixable). If you like this recipe, you might also like recipes such as <a href="https://spoonacular.com/recipes/slow-cooker-easy-slow-cooker-pot-roasted-steak-174176">Slow-Cooker Easy Slow-Cooker Pot-Roasted Steak</a>, <a href="https://spoonacular.com/recipes/red-eye-bbq-ribs-slow-cooker-75-days-of-summer-slow-cooker-s-504481">Red Eye BBQ Ribs Slow Cooker – 75 Days of Summer Slow Cooker s</a>, and <a href="https://spoonacular.com/recipes/slow-cooker-beef-gyros-75-days-of-summer-slow-cooker-s-504655">Slow Cooker Beef Gyros – 75 Days of Summer Slow Cooker s</a>.`
}

// Interface POST /recipes
describe("Save chosen recipe and return its details", () => {
    // Input: Missing userId and recipeId
    // Expected status code: 400
    // Expected behavior: Missing parameters detected and error returned
    // Expected output: "Request body parameters must not be empty"
    test("Missing body parameters", async () => {
        let expectedResponse = "Request body parameters must not be empty";
        const res = await request(app).post("/recipes").send({
            userId: "",
            recipeId: null
        });
        expect(res.status).toStrictEqual(400);
        expect(res.text).toEqual(expectedResponse);
        expect(res.body).toEqual({});
    });

    // Input: External API returns error on first call
    // Expected status code: 500
    // Expected behavior: External API error detected and error returned
    // Expected output: "Cannot save chosen recipe at this point"
    test("External API fails on first call", async () => {
        let expectedResponse = "Cannot save chosen recipe at this point";
        jest.spyOn(dbFunctions, "dbFindRecord").mockReturnValue(null);
        fetch.mockReturnValue(Promise.resolve({ status: 408, json: () => Promise.resolve({}) }));
        const res = await request(app).post("/recipes").send(baseMockedRequestBody);
        expect(res.status).toStrictEqual(500);
        expect(res.text).toEqual(expectedResponse);
        expect(res.body).toEqual({});
    });

    // Input: External API returns error on second call
    // Expected status code: 500
    // Expected behavior: External API error detected and error returned
    // Expected output: "Cannot fetch instructions at this point"
    test("External API fails on second call", async () => {
        let expectedResponse = "Cannot fetch instructions at this point";
        jest.spyOn(dbFunctions, "dbFindRecord").mockReturnValue(null);
        jest.spyOn(dbFunctions, "dbSaveRecord").mockReturnValue(null);
        fetch.mockReturnValueOnce(Promise.resolve({ status: 201, json: () => Promise.resolve({
            summary: baseMockedDbFindRecordPostResponse.recipeSummary,
            title: baseMockedDbFindRecordPostResponse.recipeName
        })}));
        fetch.mockReturnValueOnce(Promise.resolve({ status: 405, json: () => Promise.resolve({})}));
        const res = await request(app).post("/recipes").send(baseMockedRequestBody);
        expect(res.status).toStrictEqual(500);
        expect(res.text).toEqual(expectedResponse);
        expect(res.body).toEqual({});
    });

    // Input: Valid parameters and recipe already exists in database 
    // Expected status code: 200
    // Expected behavior: Recipe is fetched from db and numTimes incremented
    // Expected output: The saved recipe
    test("Recipe exists in db", async () => {
        let expectedResponse = Object.assign({}, baseMockedDbFindRecordPostResponse);
        expectedResponse.instructions = mockedInstructions;
        expectedResponse.numTimes++;
        jest.spyOn(dbFunctions, "dbFindRecord").mockReturnValue(Object.assign({}, baseMockedDbFindRecordPostResponse));
        jest.spyOn(dbFunctions, "dbSaveRecord").mockReturnValue(null);
        fetch.mockReturnValue(Promise.resolve({ status: 201, json: () => Promise.resolve(mockedInstructions)}));
        const res = await request(app).post("/recipes").send(baseMockedRequestBody);
        expect(res.status).toStrictEqual(200);
        expect(res.body).toEqual(expectedResponse);
    });

    // Input: Valid parameters and recipe does not exist in database 
    // Expected status code: 200
    // Expected behavior: Recipe is created and added to db
    // Expected output: The saved recipe
    test("Recipe does not exist in db", async () => {
        let expectedResponse = Object.assign({}, baseMockedDbFindRecordPostResponse);
        expectedResponse.instructions = mockedInstructions;
        jest.spyOn(dbFunctions, "dbFindRecord").mockReturnValue(null);
        jest.spyOn(dbFunctions, "dbSaveRecord").mockReturnValue(null);
        fetch.mockReturnValueOnce(Promise.resolve({ status: 201, json: () => Promise.resolve({
            summary: baseMockedDbFindRecordPostResponse.recipeSummary,
            title: baseMockedDbFindRecordPostResponse.recipeName
        })}));
        fetch.mockReturnValueOnce(Promise.resolve({ status: 201, json: () => Promise.resolve(mockedInstructions)}));
        const res = await request(app).post("/recipes").send(baseMockedRequestBody);
        expect(res.status).toStrictEqual(200);
        expect(res.body).toEqual(expectedResponse);
    });

    // Input: Valid parameters, recipe does not exist in database and recipeImage not supplied
    // Expected status code: 200
    // Expected behavior: Recipe is created and added to db with default image url
    // Expected output: The saved recipe
    test("Recipe does not exist in db and image not supplied", async () => {
        let expectedResponse = Object.assign({}, baseMockedDbFindRecordPostResponse);
        expectedResponse.instructions = mockedInstructions;
        expectedResponse.recipeImage = "https://visitors-centre.jrc.ec.europa.eu/sites/default/files/thumbnail/kmffq_additional-illo4video_2019_5_fishplate%5B1%5D.jpg"
        jest.spyOn(dbFunctions, "dbFindRecord").mockReturnValue(null);
        jest.spyOn(dbFunctions, "dbSaveRecord").mockReturnValue(null);
        fetch.mockReturnValueOnce(Promise.resolve({ status: 201, json: () => Promise.resolve({
            summary: baseMockedDbFindRecordPostResponse.recipeSummary,
            title: baseMockedDbFindRecordPostResponse.recipeName
        })}));
        fetch.mockReturnValueOnce(Promise.resolve({ status: 201, json: () => Promise.resolve(mockedInstructions)}));
        let mockedRequestBody = Object.assign({}, baseMockedRequestBody);
        mockedRequestBody.recipeImage = null;
        const res = await request(app).post("/recipes").send(mockedRequestBody);
        expect(res.status).toStrictEqual(200);
        expect(res.body).toEqual(expectedResponse);
    });
})