Skip to content

src/__tests__/errors.test.ts โ€‹

From HW1: SliceDrop API ยท original: si-679-f-26-hw1-jessicadonoho/src/__tests__/errors.test.ts ยท all files in this project

ts
import { beforeEach, describe, expect, it, vi } from "vitest";
import request from "supertest";
import { app } from "../app";
import { CUSTOMER_TOKEN } from "../constants";
import { _resetOrders } from "../data/orders";

beforeEach(() => { _resetOrders(); });

const asCustomer = () => request(app).post("/orders").set("Authorization", `Bearer ${CUSTOMER_TOKEN}`);

describe("404", () => {
  it("answers a path no router handles", async () => {
    const res = await request(app).get("/nope");
    expect(res.status).toBe(404);
    expect(res.body.error).toBeDefined();
  });
});

describe("400 on an invalid order", () => {
  it("reports every problem it found, not just the first", async () => {
    const res = await asCustomer().send({});
    expect(res.status).toBe(400);
    expect(Array.isArray(res.body.errors)).toBe(true);
    expect(res.body.errors.length).toBeGreaterThanOrEqual(2);
  });

  it("rejects an unknown menu item", async () => {
    const res = await asCustomer().send({
      customerName: "Ada", items: [{ menuItemId: "sushi", quantity: 1 }]
    });
    expect(res.status).toBe(400);
  });

  it("rejects a quantity below 1", async () => {
    const res = await asCustomer().send({
      customerName: "Ada", items: [{ menuItemId: "soda", quantity: 0 }]
    });
    expect(res.status).toBe(400);
  });

  it("rejects a crust the item does not offer", async () => {
    const res = await asCustomer().send({
      customerName: "Ada", items: [{ menuItemId: "hawaiian", quantity: 1, crust: "stuffed" }]
    });
    expect(res.status).toBe(400);
  });

  it("rejects a size the item does not offer", async () => {
    const res = await asCustomer().send({
      customerName: "Ada", items: [{ menuItemId: "soda", quantity: 1, size: "bathtub" }]
    });
    expect(res.status).toBe(400);
  });
});

describe("500 when our own code throws", () => {
  // Forcing a real server-side crash: make the given addOrder blow up, then
  // send a request that is otherwise perfectly valid. A 400 here would mean
  // the error handler is blaming the client for our bug.
  it("does not report a server crash as a client error", async () => {
    vi.resetModules();
    vi.doMock("../data/orders", async () => {
      const real = await vi.importActual<typeof import("../data/orders")>("../data/orders");
      return { ...real, addOrder: () => { throw new Error("boom"); } };
    });

    // A dynamic import() is ESM, and under NodeNext those need the file
    // extension -- unlike the static imports at the top of this file.
    const { app: freshApp } = await import("../app.js");
    const res = await request(freshApp).post("/orders")
      .set("Authorization", `Bearer ${CUSTOMER_TOKEN}`)
      .send({ customerName: "Ada", items: [{ menuItemId: "soda", quantity: 1 }] });

    vi.doUnmock("../data/orders");
    vi.resetModules();

    expect(res.status).toBe(500);
  });
});

describe("400 on malformed JSON", () => {
  it("answers the client, not a crash", async () => {
    const res = await asCustomer()
      .set("Content-Type", "application/json").send("{nope");
    expect(res.status).toBe(400);
    expect(res.body.error).toBeDefined();
  });
});