batchCooking/apps/api/test/planning.test.ts
Nicolas 9b2b2c2e28 API: GET /planning?date= remplace GET /planning/current (step 2/4)
- getPlanningForDate(houseId, date: DateTime) — paramétré au lieu de
  toujours "aujourd'hui", même logique de recherche sinon
- GET /planning?date=YYYY-MM-DD, validation de forme (zod) puis de
  validité calendaire (parseDateOnly, 400 VALIDATION_ERROR sinon) —
  un seul endpoint générique au lieu de deux qui se recouvrent
- Tests Mocha + Cucumber adaptés, + cas date manquante/malformée/
  impossible et "semaine différente d'aujourd'hui"
2026-08-17 14:17:46 +02:00

174 lines
6.4 KiB
TypeScript

import { DateTime } from "@batch-cooking/date-tools";
import { ErrorCode, type SignupInput } from "@batch-cooking/shared";
import { faker } from "@faker-js/faker";
import { expect } from "chai";
import request from "supertest";
import { createApp } from "../src/app.js";
import { prisma } from "../src/db/prisma.js";
import { resetDatabase } from "../test-support/reset-db.js";
/** See `auth.test.ts` — same rationale for generating rather than hardcoding. */
function buildSignupPayload(): SignupInput {
const firstName = faker.person.firstName();
const lastName = faker.person.lastName();
return {
firstName,
lastName,
email: faker.internet.email({ firstName, lastName }).toLowerCase(),
password: faker.internet.password({ length: 16 }),
};
}
/** Today, as the `YYYY-MM-DD` string `GET /planning`'s `?date=` expects. */
function today(): string {
return isoDate(DateTime.utc());
}
/** `toISODate()` only returns `null` for an invalid `DateTime` — never the case for the always-valid values built in this file. */
function isoDate(date: DateTime): string {
const iso = date.toISODate();
if (iso === null) throw new Error("Unexpectedly invalid DateTime in a test helper");
return iso;
}
describe("Planning", () => {
const app = createApp();
beforeEach(async () => {
await resetDatabase();
});
after(async () => {
await prisma.$disconnect();
});
describe("GET /planning", () => {
it("rejects requests without a session cookie with 401 NOT_AUTHENTICATED", async () => {
const res = await request(app).get("/planning").query({ date: today() });
expect(res.status).to.equal(401);
expect(res.body.code).to.equal(ErrorCode.NOT_AUTHENTICATED);
});
it("rejects a missing date with 400 VALIDATION_ERROR", async () => {
const agent = request.agent(app);
await agent.post("/auth/signup").send(buildSignupPayload());
const res = await agent.get("/planning");
expect(res.status).to.equal(400);
expect(res.body.code).to.equal(ErrorCode.VALIDATION_ERROR);
});
it("rejects a malformed date with 400 VALIDATION_ERROR", async () => {
const agent = request.agent(app);
await agent.post("/auth/signup").send(buildSignupPayload());
const res = await agent.get("/planning").query({ date: "not-a-date" });
expect(res.status).to.equal(400);
expect(res.body.code).to.equal(ErrorCode.VALIDATION_ERROR);
});
it("rejects a date shaped right but calendarially impossible with 400 VALIDATION_ERROR", async () => {
const agent = request.agent(app);
await agent.post("/auth/signup").send(buildSignupPayload());
const res = await agent.get("/planning").query({ date: "2026-02-30" });
expect(res.status).to.equal(400);
expect(res.body.code).to.equal(ErrorCode.VALIDATION_ERROR);
});
it("returns null when the household has no planning covering that date", async () => {
const agent = request.agent(app);
await agent.post("/auth/signup").send(buildSignupPayload());
const res = await agent.get("/planning").query({ date: today() });
expect(res.status).to.equal(200);
expect(res.body).to.equal(null);
});
it("returns the household's planning covering that date, with recipes resolved", async () => {
const agent = request.agent(app);
await agent.post("/auth/signup").send(buildSignupPayload());
const houseRes = await agent.post("/house").send({ name: "Chez moi" });
const houseId: number = houseRes.body.id;
const recipe = await prisma.recipe.create({ data: { name: "Ratatouille" } });
const now = new Date();
const planning = await prisma.planning.create({
data: {
houseId,
startDate: new Date(
Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() - 2),
),
finishDate: new Date(
Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() + 2),
),
},
});
await prisma.planningItem.create({
data: { planningId: planning.id, weekDay: "lundi", meal: "diner", recipeId: recipe.id },
});
const res = await agent.get("/planning").query({ date: today() });
expect(res.status).to.equal(200);
expect(res.body.id).to.equal(planning.id);
expect(res.body.items).to.have.length(1);
expect(res.body.items[0]).to.include({ weekDay: "lundi", meal: "diner" });
expect(res.body.items[0].recipe).to.include({ id: recipe.id, name: "Ratatouille" });
});
it("returns null when the household's planning does not cover that date", async () => {
const agent = request.agent(app);
await agent.post("/auth/signup").send(buildSignupPayload());
const houseRes = await agent.post("/house").send({ name: "Chez moi" });
const houseId: number = houseRes.body.id;
// A planning entirely in the past — shouldn't be picked up for today.
await prisma.planning.create({
data: {
houseId,
startDate: new Date(Date.UTC(2000, 0, 1)),
finishDate: new Date(Date.UTC(2000, 0, 7)),
},
});
const res = await agent.get("/planning").query({ date: today() });
expect(res.status).to.equal(200);
expect(res.body).to.equal(null);
});
it("returns a different week's planning when asked for a date outside the current one", async () => {
const agent = request.agent(app);
await agent.post("/auth/signup").send(buildSignupPayload());
const houseRes = await agent.post("/house").send({ name: "Chez moi" });
const houseId: number = houseRes.body.id;
const recipe = await prisma.recipe.create({ data: { name: "Curry de lentilles" } });
const nextWeek = DateTime.utc().plus({ weeks: 1 });
const planning = await prisma.planning.create({
data: {
houseId,
startDate: nextWeek.startOf("week").toJSDate(),
finishDate: nextWeek.endOf("week").startOf("day").toJSDate(),
},
});
await prisma.planningItem.create({
data: { planningId: planning.id, weekDay: "mardi", meal: "dejeuner", recipeId: recipe.id },
});
const res = await agent.get("/planning").query({ date: isoDate(nextWeek) });
expect(res.status).to.equal(200);
expect(res.body.id).to.equal(planning.id);
const thisWeekRes = await agent.get("/planning").query({ date: today() });
expect(thisWeekRes.body).to.equal(null);
});
});
});