Tests API: couverture Mocha + Cucumber pour le foyer et la suppression de compte (step 4/8)

- house.test.ts réécrit (le foyer n'est plus auto-créé) + POST /house,
  POST /house/join, POST /house/leave, DELETE /house/current,
  DELETE /house/members/:id
- auth.test.ts: signup renvoie houseId=null, DELETE /auth/me (mauvais
  mot de passe, suppression, transfert d'admin)
- planning.test.ts/steps.ts: création explicite du foyer (POST /house)
- household.feature: scénarios créer/rejoindre/quitter/supprimer/
  retirer un membre, via un second agent (CustomWorld.secondAgent)
- auth.feature: scénarios de suppression de compte
This commit is contained in:
Nicolas 2026-08-17 10:40:23 +02:00
parent 7af98756dc
commit 3363cfad75
9 changed files with 516 additions and 23 deletions

View file

@ -33,3 +33,18 @@ Feature: Account creation and login
When I log in with email "alice@example.com" and password "wrong-password"
Then the response status should be 401
And the response error code should be "INVALID_CREDENTIALS"
Scenario: A signed-in user cannot delete their account with the wrong password
Given a profile already exists with email "alice@example.com" and password "correct-horse-battery-staple"
And I log in with email "alice@example.com" and password "correct-horse-battery-staple"
When I delete my account with password "wrong-password"
Then the response status should be 401
And the response error code should be "INVALID_CREDENTIALS"
And I am authenticated as "alice@example.com"
Scenario: A signed-in user deletes their account
Given a profile already exists with email "alice@example.com" and password "correct-horse-battery-staple"
And I log in with email "alice@example.com" and password "correct-horse-battery-staple"
When I delete my account with password "correct-horse-battery-staple"
Then the response status should be 204
And I am no longer authenticated

View file

@ -1,16 +1,67 @@
Feature: Household name
Feature: Household
As a signed-in user
I want to name my household
So that it's recognizable as ours, not the auto-generated default
I want to name my household, invite others to it, and manage its members
So that my whole household can share the same planning
Scenario: A visitor without a session cannot read the household
When I send a GET request to "/house/current"
Then the response status should be 401
And the response error code should be "NOT_AUTHENTICATED"
Scenario: A signed-in user creates a household
Given a profile already exists with email "alice@example.com" and password "correct-horse-battery-staple"
And I log in with email "alice@example.com" and password "correct-horse-battery-staple"
When I create a household named "Chez Alice"
Then the response status should be 201
And my household should be named "Chez Alice"
Scenario: A signed-in user renames their household
Given a profile already exists with email "alice@example.com" and password "correct-horse-battery-staple"
And I log in with email "alice@example.com" and password "correct-horse-battery-staple"
And I have a household named "Foyer de test"
When I rename my household to "Chez les Martin"
Then the response status should be 200
And my household should be named "Chez les Martin"
Scenario: A second user joins a household using its invite code
Given a profile already exists with email "alice@example.com" and password "correct-horse-battery-staple"
And I log in with email "alice@example.com" and password "correct-horse-battery-staple"
And I have a household named "Chez Alice"
And a profile already exists with email "bob@example.com" and password "correct-horse-battery-staple"
When the second user logs in with email "bob@example.com" and password "correct-horse-battery-staple"
And the second user joins my household using its invite code
Then the second user's response status should be 200
And the second user should be a member of my household
Scenario: A non-admin member cannot delete the household
Given a profile already exists with email "alice@example.com" and password "correct-horse-battery-staple"
And I log in with email "alice@example.com" and password "correct-horse-battery-staple"
And I have a household named "Chez Alice"
And a profile already exists with email "bob@example.com" and password "correct-horse-battery-staple"
And the second user logs in with email "bob@example.com" and password "correct-horse-battery-staple"
And the second user joins my household using its invite code
When the second user tries to delete the household
Then the second user's response status should be 403
And the second user's response error code should be "NOT_HOUSE_ADMIN"
Scenario: Adminship transfers to the remaining member when the admin leaves
Given a profile already exists with email "alice@example.com" and password "correct-horse-battery-staple"
And I log in with email "alice@example.com" and password "correct-horse-battery-staple"
And I have a household named "Chez Alice"
And a profile already exists with email "bob@example.com" and password "correct-horse-battery-staple"
And the second user logs in with email "bob@example.com" and password "correct-horse-battery-staple"
And the second user joins my household using its invite code
When I leave the household
Then the response status should be 204
And the second user should be the household's admin
Scenario: The admin removes a member
Given a profile already exists with email "alice@example.com" and password "correct-horse-battery-staple"
And I log in with email "alice@example.com" and password "correct-horse-battery-staple"
And I have a household named "Chez Alice"
And a profile already exists with email "bob@example.com" and password "correct-horse-battery-staple"
And the second user logs in with email "bob@example.com" and password "correct-horse-battery-staple"
And the second user joins my household using its invite code
When I remove the second user from my household
Then the response status should be 200
And the second user should have no household

View file

@ -48,8 +48,20 @@ When(
},
);
When(
"I delete my account with password {string}",
async function (this: CustomWorld, password: string) {
this.response = await this.agent.delete("/auth/me").send({ password });
},
);
Then("I am authenticated as {string}", async function (this: CustomWorld, email: string) {
const res = await this.agent.get("/auth/me");
assert.equal(res.status, 200);
assert.equal(res.body.email, email);
});
Then("I am no longer authenticated", async function (this: CustomWorld) {
const res = await this.agent.get("/auth/me");
assert.equal(res.status, 401);
});

View file

@ -1,12 +1,90 @@
import assert from "node:assert/strict";
import { Then, When } from "@cucumber/cucumber";
import { ErrorCode } from "@batch-cooking/shared";
import { Given, Then, When } from "@cucumber/cucumber";
import type { CustomWorld } from "../support/world.js";
Given("I have a household named {string}", async function (this: CustomWorld, name: string) {
const res = await this.agent.post("/house").send({ name });
assert.equal(res.status, 201, JSON.stringify(res.body));
});
When("I create a household named {string}", async function (this: CustomWorld, name: string) {
this.response = await this.agent.post("/house").send({ name });
});
When("I rename my household to {string}", async function (this: CustomWorld, name: string) {
this.response = await this.agent.patch("/house/current").send({ name });
});
When("I leave the household", async function (this: CustomWorld) {
this.response = await this.agent.post("/house/leave");
});
When("I remove the second user from my household", async function (this: CustomWorld) {
const secondMe = await this.secondAgent.get("/auth/me");
this.response = await this.agent.delete(`/house/members/${secondMe.body.id}`);
});
Then("my household should be named {string}", async function (this: CustomWorld, name: string) {
const res = await this.agent.get("/house/current");
assert.equal(res.body.name, name);
});
// --- Steps involving a second, independently signed-in user ---------------
// The first ("a profile already exists with email ...") step is reused
// as-is for the second user too — it just inserts a row, agent-agnostic.
When(
"the second user logs in with email {string} and password {string}",
async function (this: CustomWorld, email: string, password: string) {
this.secondResponse = await this.secondAgent.post("/auth/login").send({ email, password });
},
);
When(
"the second user joins my household using its invite code",
async function (this: CustomWorld) {
const house = await this.agent.get("/house/current");
this.secondResponse = await this.secondAgent
.post("/house/join")
.send({ inviteCode: house.body.inviteCode });
},
);
When("the second user tries to delete the household", async function (this: CustomWorld) {
this.secondResponse = await this.secondAgent.delete("/house/current");
});
Then(
"the second user's response status should be {int}",
function (this: CustomWorld, status: number) {
assert.equal(this.secondResponse.status, status);
},
);
Then(
"the second user's response error code should be {string}",
function (this: CustomWorld, code: string) {
const expected = ErrorCode[code as keyof typeof ErrorCode];
assert.notEqual(expected, undefined, `Unknown ErrorCode member: "${code}"`);
assert.equal(this.secondResponse.body.code, expected);
},
);
Then("the second user should be a member of my household", async function (this: CustomWorld) {
const house = await this.agent.get("/house/current");
const secondMe = await this.secondAgent.get("/auth/me");
const memberIds = (house.body.members as Array<{ id: number }>).map((member) => member.id);
assert.ok(memberIds.includes(secondMe.body.id));
});
Then("the second user should be the household's admin", async function (this: CustomWorld) {
const secondMe = await this.secondAgent.get("/auth/me");
const house = await this.secondAgent.get("/house/current");
assert.equal(house.body.adminId, secondMe.body.id);
});
Then("the second user should have no household", async function (this: CustomWorld) {
const secondMe = await this.secondAgent.get("/auth/me");
assert.equal(secondMe.body.houseId, null);
});

View file

@ -15,14 +15,14 @@ Then("the current planning response should be empty", function (this: CustomWorl
// the API — there's no "create a planning" endpoint yet (see
// specs/batch-cooking-architecture.md, "Calcul batch-cooking" is still
// TODO), so this is the only way to get a household into a state where it
// has one. Reads the household off the already-authenticated agent (via
// `GET /auth/me`) rather than taking it as a step argument, since the
// scenario never names it explicitly.
// has one. A household is no longer created implicitly at signup, so this
// step creates one via `POST /house` first — the scenario never names it
// explicitly, its name doesn't matter here.
Given(
"my household has a planning covering today with recipe {string} on {string} for {string}",
async function (this: CustomWorld, recipeName: string, weekDay: string, meal: string) {
const me = await this.agent.get("/auth/me");
const houseId: number = me.body.houseId;
const houseRes = await this.agent.post("/house").send({ name: "Foyer de test" });
const houseId: number = houseRes.body.id;
const recipe = await prisma.recipe.create({ data: { name: recipeName } });
const today = new Date();

View file

@ -11,11 +11,15 @@ export class CustomWorld extends World {
app: Express;
agent: ReturnType<typeof request.agent>;
response!: request.Response;
/** A second, independent session (own cookie jar) — only used by scenarios needing two distinct signed-in users, e.g. household invites/admin transfer/member removal. */
secondAgent: ReturnType<typeof request.agent>;
secondResponse!: request.Response;
constructor(options: IWorldOptions) {
super(options);
this.app = createApp();
this.agent = request.agent(this.app);
this.secondAgent = request.agent(this.app);
}
}

View file

@ -38,7 +38,7 @@ describe("Auth", () => {
});
describe("POST /auth/signup", () => {
it("creates a profile and its house, and sets a session cookie", async () => {
it("creates a profile without a household yet, and sets a session cookie", async () => {
const payload = buildSignupPayload();
const res = await request(app).post("/auth/signup").send(payload);
@ -49,7 +49,9 @@ describe("Auth", () => {
email: payload.email,
});
expect(res.body).to.not.have.property("passwordHash");
expect(res.body.houseId).to.be.a("number");
// No household is created at signup anymore — it's an optional
// onboarding step (create/join/skip), see house.test.ts.
expect(res.body.houseId).to.equal(null);
expect(res.headers["set-cookie"]?.[0]).to.include("session=");
});
@ -131,4 +133,75 @@ describe("Auth", () => {
expect(res.body.email).to.equal(payload.email);
});
});
describe("DELETE /auth/me", () => {
it("rejects requests without a session cookie with 401 NOT_AUTHENTICATED", async () => {
const res = await request(app).delete("/auth/me").send({ password: "whatever" });
expect(res.status).to.equal(401);
expect(res.body.code).to.equal(ErrorCode.NOT_AUTHENTICATED);
});
it("rejects a wrong password with 401 INVALID_CREDENTIALS, without deleting the profile", async () => {
const payload = buildSignupPayload();
const agent = request.agent(app);
const signupRes = await agent.post("/auth/signup").send(payload);
const res = await agent.delete("/auth/me").send({ password: "wrong-password" });
expect(res.status).to.equal(401);
expect(res.body.code).to.equal(ErrorCode.INVALID_CREDENTIALS);
expect(
await prisma.userProfile.findUnique({ where: { id: signupRes.body.id } }),
).to.not.equal(null);
});
it("deletes the profile and clears the session cookie", async () => {
const payload = buildSignupPayload();
const agent = request.agent(app);
const signupRes = await agent.post("/auth/signup").send(payload);
const res = await agent.delete("/auth/me").send({ password: payload.password });
expect(res.status).to.equal(204);
expect(await prisma.userProfile.findUnique({ where: { id: signupRes.body.id } })).to.equal(
null,
);
const meRes = await agent.get("/auth/me");
expect(meRes.status).to.equal(401);
});
it("deletes the household along with the account when it's the sole member", async () => {
const payload = buildSignupPayload();
const agent = request.agent(app);
await agent.post("/auth/signup").send(payload);
const houseRes = await agent.post("/house").send({ name: "Chez moi" });
const res = await agent.delete("/auth/me").send({ password: payload.password });
expect(res.status).to.equal(204);
expect(await prisma.house.findUnique({ where: { id: houseRes.body.id } })).to.equal(null);
});
it("transfers adminship to another member before deleting an admin's account", async () => {
const adminPayload = buildSignupPayload();
const adminAgent = request.agent(app);
const houseRes = await adminAgent
.post("/auth/signup")
.send(adminPayload)
.then(() => adminAgent.post("/house").send({ name: "Chez nous" }));
const memberPayload = buildSignupPayload();
const memberAgent = request.agent(app);
const memberSignupRes = await memberAgent.post("/auth/signup").send(memberPayload);
await memberAgent.post("/house/join").send({ inviteCode: houseRes.body.inviteCode });
const res = await adminAgent.delete("/auth/me").send({ password: adminPayload.password });
expect(res.status).to.equal(204);
const house = await prisma.house.findUnique({ where: { id: houseRes.body.id } });
expect(house?.adminId).to.equal(memberSignupRes.body.id);
});
});
});

View file

@ -1,6 +1,7 @@
import { ErrorCode, type SignupInput } from "@batch-cooking/shared";
import { faker } from "@faker-js/faker";
import { expect } from "chai";
import type { Express } from "express";
import request from "supertest";
import { createApp } from "../src/app.js";
import { prisma } from "../src/db/prisma.js";
@ -17,6 +18,13 @@ function buildSignupPayload(): SignupInput {
};
}
/** Signs up a fresh profile on a brand new agent (its own cookie jar) and returns both. */
async function signupAgent(app: Express) {
const agent = request.agent(app);
const res = await agent.post("/auth/signup").send(buildSignupPayload());
return { agent, profile: res.body };
}
describe("Household", () => {
const app = createApp();
@ -36,14 +44,28 @@ describe("Household", () => {
expect(res.body.code).to.equal(ErrorCode.NOT_AUTHENTICATED);
});
it("returns the household created at signup", async () => {
const agent = request.agent(app);
const signupRes = await agent.post("/auth/signup").send(buildSignupPayload());
it("returns null when the profile has no household yet", async () => {
const { agent } = await signupAgent(app);
const res = await agent.get("/house/current");
expect(res.status).to.equal(200);
expect(res.body).to.deep.equal({ id: signupRes.body.houseId, name: res.body.name });
expect(res.body).to.equal(null);
});
it("returns the household with its admin and member list, once created", async () => {
const { agent, profile } = await signupAgent(app);
await agent.post("/house").send({ name: "Chez Alice" });
const res = await agent.get("/house/current");
expect(res.status).to.equal(200);
expect(res.body.name).to.equal("Chez Alice");
expect(res.body.adminId).to.equal(profile.id);
expect(res.body.inviteCode).to.match(/^[A-Z2-9]{8}$/);
expect(res.body.members).to.deep.equal([
{ id: profile.id, firstName: profile.firstName, lastName: profile.lastName },
]);
});
});
@ -55,9 +77,18 @@ describe("Household", () => {
expect(res.body.code).to.equal(ErrorCode.NOT_AUTHENTICATED);
});
it("rejects renaming when the profile has no household yet with 404 HOUSE_NOT_FOUND", async () => {
const { agent } = await signupAgent(app);
const res = await agent.patch("/house/current").send({ name: "Chez nous" });
expect(res.status).to.equal(404);
expect(res.body.code).to.equal(ErrorCode.HOUSE_NOT_FOUND);
});
it("renames the household", async () => {
const agent = request.agent(app);
await agent.post("/auth/signup").send(buildSignupPayload());
const { agent } = await signupAgent(app);
await agent.post("/house").send({ name: "Chez Alice" });
const res = await agent.patch("/house/current").send({ name: "Chez les Dupont" });
@ -69,8 +100,8 @@ describe("Household", () => {
});
it("rejects an empty name with 400 VALIDATION_ERROR", async () => {
const agent = request.agent(app);
await agent.post("/auth/signup").send(buildSignupPayload());
const { agent } = await signupAgent(app);
await agent.post("/house").send({ name: "Chez Alice" });
const res = await agent.patch("/house/current").send({ name: "" });
@ -78,4 +109,231 @@ describe("Household", () => {
expect(res.body.code).to.equal(ErrorCode.VALIDATION_ERROR);
});
});
describe("POST /house", () => {
it("rejects requests without a session cookie with 401 NOT_AUTHENTICATED", async () => {
const res = await request(app).post("/house").send({ name: "Chez nous" });
expect(res.status).to.equal(401);
expect(res.body.code).to.equal(ErrorCode.NOT_AUTHENTICATED);
});
it("creates a household with the caller as its admin", async () => {
const { agent, profile } = await signupAgent(app);
const res = await agent.post("/house").send({ name: "Chez Alice" });
expect(res.status).to.equal(201);
expect(res.body.name).to.equal("Chez Alice");
expect(res.body.adminId).to.equal(profile.id);
const me = await agent.get("/auth/me");
expect(me.body.houseId).to.equal(res.body.id);
});
it("rejects an empty name with 400 VALIDATION_ERROR", async () => {
const { agent } = await signupAgent(app);
const res = await agent.post("/house").send({ name: "" });
expect(res.status).to.equal(400);
expect(res.body.code).to.equal(ErrorCode.VALIDATION_ERROR);
});
it("rejects creating a second household with 409 ALREADY_HAS_HOUSE", async () => {
const { agent } = await signupAgent(app);
await agent.post("/house").send({ name: "Chez Alice" });
const res = await agent.post("/house").send({ name: "Chez Alice bis" });
expect(res.status).to.equal(409);
expect(res.body.code).to.equal(ErrorCode.ALREADY_HAS_HOUSE);
});
});
describe("POST /house/join", () => {
it("rejects requests without a session cookie with 401 NOT_AUTHENTICATED", async () => {
const res = await request(app).post("/house/join").send({ inviteCode: "ABCDEFGH" });
expect(res.status).to.equal(401);
expect(res.body.code).to.equal(ErrorCode.NOT_AUTHENTICATED);
});
it("joins an existing household by invite code", async () => {
const { agent: adminAgent } = await signupAgent(app);
const created = await adminAgent.post("/house").send({ name: "Chez Alice" });
const { agent: joinerAgent, profile: joiner } = await signupAgent(app);
const res = await joinerAgent
.post("/house/join")
.send({ inviteCode: created.body.inviteCode });
expect(res.status).to.equal(200);
expect(res.body.id).to.equal(created.body.id);
expect(res.body.members.map((m: { id: number }) => m.id)).to.include(joiner.id);
});
it("rejects an unknown invite code with 404 INVITE_CODE_NOT_FOUND", async () => {
const { agent } = await signupAgent(app);
const res = await agent.post("/house/join").send({ inviteCode: "ZZZZZZZZ" });
expect(res.status).to.equal(404);
expect(res.body.code).to.equal(ErrorCode.INVITE_CODE_NOT_FOUND);
});
it("rejects joining when the profile already belongs to a household with 409 ALREADY_HAS_HOUSE", async () => {
const { agent: adminAgent } = await signupAgent(app);
const created = await adminAgent.post("/house").send({ name: "Chez Alice" });
const { agent } = await signupAgent(app);
await agent.post("/house").send({ name: "Chez Bob" });
const res = await agent.post("/house/join").send({ inviteCode: created.body.inviteCode });
expect(res.status).to.equal(409);
expect(res.body.code).to.equal(ErrorCode.ALREADY_HAS_HOUSE);
});
});
describe("POST /house/leave", () => {
it("rejects requests without a session cookie with 401 NOT_AUTHENTICATED", async () => {
const res = await request(app).post("/house/leave");
expect(res.status).to.equal(401);
expect(res.body.code).to.equal(ErrorCode.NOT_AUTHENTICATED);
});
it("rejects leaving when the profile has no household with 404 HOUSE_NOT_FOUND", async () => {
const { agent } = await signupAgent(app);
const res = await agent.post("/house/leave");
expect(res.status).to.equal(404);
expect(res.body.code).to.equal(ErrorCode.HOUSE_NOT_FOUND);
});
it("deletes the household when its sole member leaves", async () => {
const { agent } = await signupAgent(app);
const created = await agent.post("/house").send({ name: "Chez Alice" });
const res = await agent.post("/house/leave");
expect(res.status).to.equal(204);
expect(await prisma.house.findUnique({ where: { id: created.body.id } })).to.equal(null);
});
it("transfers adminship to the remaining member when the admin leaves", async () => {
const { agent: adminAgent } = await signupAgent(app);
const created = await adminAgent.post("/house").send({ name: "Chez Alice" });
const { agent: memberAgent, profile: member } = await signupAgent(app);
await memberAgent.post("/house/join").send({ inviteCode: created.body.inviteCode });
const res = await adminAgent.post("/house/leave");
expect(res.status).to.equal(204);
const house = await prisma.house.findUnique({ where: { id: created.body.id } });
expect(house?.adminId).to.equal(member.id);
});
});
describe("DELETE /house/current", () => {
it("rejects requests without a session cookie with 401 NOT_AUTHENTICATED", async () => {
const res = await request(app).delete("/house/current");
expect(res.status).to.equal(401);
expect(res.body.code).to.equal(ErrorCode.NOT_AUTHENTICATED);
});
it("rejects deleting when the profile has no household with 404 HOUSE_NOT_FOUND", async () => {
const { agent } = await signupAgent(app);
const res = await agent.delete("/house/current");
expect(res.status).to.equal(404);
expect(res.body.code).to.equal(ErrorCode.HOUSE_NOT_FOUND);
});
it("rejects a non-admin member with 403 NOT_HOUSE_ADMIN", async () => {
const { agent: adminAgent } = await signupAgent(app);
const created = await adminAgent.post("/house").send({ name: "Chez Alice" });
const { agent: memberAgent } = await signupAgent(app);
await memberAgent.post("/house/join").send({ inviteCode: created.body.inviteCode });
const res = await memberAgent.delete("/house/current");
expect(res.status).to.equal(403);
expect(res.body.code).to.equal(ErrorCode.NOT_HOUSE_ADMIN);
});
it("deletes the household for every member, cascading its plannings", async () => {
const { agent: adminAgent } = await signupAgent(app);
const created = await adminAgent.post("/house").send({ name: "Chez Alice" });
const { agent: memberAgent, profile: member } = await signupAgent(app);
await memberAgent.post("/house/join").send({ inviteCode: created.body.inviteCode });
const planning = await prisma.planning.create({
data: {
houseId: created.body.id,
startDate: new Date(Date.UTC(2000, 0, 1)),
finishDate: new Date(Date.UTC(2000, 0, 7)),
},
});
const res = await adminAgent.delete("/house/current");
expect(res.status).to.equal(204);
expect(await prisma.house.findUnique({ where: { id: created.body.id } })).to.equal(null);
expect(await prisma.planning.findUnique({ where: { id: planning.id } })).to.equal(null);
const memberProfile = await prisma.userProfile.findUniqueOrThrow({
where: { id: member.id },
});
expect(memberProfile.houseId).to.equal(null);
});
});
describe("DELETE /house/members/:memberId", () => {
it("rejects requests without a session cookie with 401 NOT_AUTHENTICATED", async () => {
const res = await request(app).delete("/house/members/1");
expect(res.status).to.equal(401);
expect(res.body.code).to.equal(ErrorCode.NOT_AUTHENTICATED);
});
it("rejects a non-admin member with 403 NOT_HOUSE_ADMIN", async () => {
const { agent: adminAgent, profile: admin } = await signupAgent(app);
const created = await adminAgent.post("/house").send({ name: "Chez Alice" });
const { agent: memberAgent } = await signupAgent(app);
await memberAgent.post("/house/join").send({ inviteCode: created.body.inviteCode });
const res = await memberAgent.delete(`/house/members/${admin.id}`);
expect(res.status).to.equal(403);
expect(res.body.code).to.equal(ErrorCode.NOT_HOUSE_ADMIN);
});
it("rejects the admin trying to remove themselves with 400 VALIDATION_ERROR", async () => {
const { agent: adminAgent, profile: admin } = await signupAgent(app);
await adminAgent.post("/house").send({ name: "Chez Alice" });
const res = await adminAgent.delete(`/house/members/${admin.id}`);
expect(res.status).to.equal(400);
expect(res.body.code).to.equal(ErrorCode.VALIDATION_ERROR);
});
it("removes the targeted member from the household", async () => {
const { agent: adminAgent } = await signupAgent(app);
const created = await adminAgent.post("/house").send({ name: "Chez Alice" });
const { agent: memberAgent, profile: member } = await signupAgent(app);
await memberAgent.post("/house/join").send({ inviteCode: created.body.inviteCode });
const res = await adminAgent.delete(`/house/members/${member.id}`);
expect(res.status).to.equal(200);
expect(res.body.members.map((m: { id: number }) => m.id)).to.not.include(member.id);
const memberProfile = await prisma.userProfile.findUniqueOrThrow({
where: { id: member.id },
});
expect(memberProfile.houseId).to.equal(null);
});
});
});

View file

@ -49,8 +49,9 @@ describe("Planning", () => {
it("returns the household's planning covering today, with recipes resolved", async () => {
const agent = request.agent(app);
const signupRes = await agent.post("/auth/signup").send(buildSignupPayload());
const houseId: number = signupRes.body.houseId;
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 today = new Date();
@ -80,8 +81,9 @@ describe("Planning", () => {
it("returns null when the household's planning does not cover today", async () => {
const agent = request.agent(app);
const signupRes = await agent.post("/auth/signup").send(buildSignupPayload());
const houseId: number = signupRes.body.houseId;
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 as "current".
await prisma.planning.create({