- apps/api: les tests Planning (mocha et cucumber) lisaient l'horloge
systeme (new Date()/DateTime.utc()) pour construire leurs fixtures et
interroger /planning, ce qui les rendait non deterministes. Ajoute
test-support/reference-date.ts (TEST_REFERENCE_DATE, une date UTC
fixe) et l'utilise dans planning.test.ts / planning.steps.ts a la
place du systeme.
- apps/web: nouveau style global pour tous les radio/checkbox de
l'app (theme-select, allergy-select, onboarding) - "carte
selectionnable" : le controle natif reste reel/accessible mais
visuellement cache, toute la ligne devient la surface interactive
(bordure + fond teinte + coche au survol/selection). Corrige au
passage le bug de fond qui causait le desalignement des radios sur
/parametres/preferences-utilisateur (la regle generique
input, select { width: 100% } de profile-forms.scss s'appliquait
aussi aux checkbox/radio) et une regression de font-weight ou les
lignes non selectionnees du theme apparaissaient en gras comme si
elles l'etaient.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
170 lines
6.4 KiB
TypeScript
170 lines
6.4 KiB
TypeScript
import type { 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 { TEST_REFERENCE_DATE } from "../test-support/reference-date.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 }),
|
|
};
|
|
}
|
|
|
|
/** The fixed test "today", as the `YYYY-MM-DD` string `GET /planning`'s `?date=` expects. */
|
|
function today(): string {
|
|
return isoDate(TEST_REFERENCE_DATE);
|
|
}
|
|
|
|
/** `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 planning = await prisma.planning.create({
|
|
data: {
|
|
houseId,
|
|
startDate: TEST_REFERENCE_DATE.minus({ days: 2 }).toJSDate(),
|
|
finishDate: TEST_REFERENCE_DATE.plus({ days: 2 }).toJSDate(),
|
|
},
|
|
});
|
|
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 = TEST_REFERENCE_DATE.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);
|
|
});
|
|
});
|
|
});
|