diff --git a/.claude/launch.json b/.claude/launch.json index 28829a9..ac69df6 100644 --- a/.claude/launch.json +++ b/.claude/launch.json @@ -6,6 +6,12 @@ "runtimeExecutable": "pnpm", "runtimeArgs": ["--filter", "web", "dev"], "port": 5173 + }, + { + "name": "api", + "runtimeExecutable": "pnpm", + "runtimeArgs": ["--filter", "api", "dev"], + "port": 3000 } ] } diff --git a/apps/api/src/scripts/seed-runtime.ts b/apps/api/src/scripts/seed-runtime.ts index 5044d91..f80ed01 100644 --- a/apps/api/src/scripts/seed-runtime.ts +++ b/apps/api/src/scripts/seed-runtime.ts @@ -1,5 +1,7 @@ import { prisma } from "../db/prisma.js"; +import { syncRecipeSources } from "../db/recipe-source-sync.js"; import { seedReferenceData } from "../db/reference-seed-data.js"; +import { registerAllRecipeSources } from "../sources/index.js"; /** * Runtime seed entry point for the production Docker image — run via @@ -14,11 +16,25 @@ import { seedReferenceData } from "../db/reference-seed-data.js"; * alongside everything else, and it runs under plain `node`, no tsx * needed at runtime. * + * Also registers and syncs the recipe-source registry + * (`registerAllRecipeSources`/`syncRecipeSources`) — mirroring + * `prisma/seed.ts`'s own two calls. Without this, `server.ts`'s own + * `registerAllRecipeSources()` call only populates *that* process' in-memory + * registry (each `node` invocation in the Docker CMD chain is a separate + * process), so the `Source` table itself would stay permanently empty in + * production and `GET /reference/sources` would always return `[]` — which + * is exactly what silently hid the whole "sources" section of + * `HouseholdSettingsPage` (`apps/web`) until this was added. + * * Safe to run on every container start: `seedReferenceData` upserts by - * each row's unique name, so re-running it against a database that + * each row's unique name, and `syncRecipeSources` is equally idempotent + * (see its own doc comment) — re-running both against a database that * already has this data is a no-op. */ +registerAllRecipeSources(); + seedReferenceData(prisma) + .then(() => syncRecipeSources(prisma)) .then(() => prisma.$disconnect()) .catch(async (err) => { console.error(err); diff --git a/apps/web/cypress/e2e/household-settings.feature b/apps/web/cypress/e2e/household-settings.feature index 078cdc7..6334a6f 100644 --- a/apps/web/cypress/e2e/household-settings.feature +++ b/apps/web/cypress/e2e/household-settings.feature @@ -53,6 +53,32 @@ Feature: Household settings Then the household deletion request should have been made And I should see "Créer un foyer" + Scenario: Shows and saves the household's enabled recipe sources + Given I am signed in as "Alice" "Martin" + And my household id is 1 + And the household request returns the two-member household + And the sources reference list has options + And the household's enabled sources are empty + And saving the source selection will succeed + When I visit "/parametres/foyer" + Then I should see the section "Sources disponibles" + And I should see "TheMealDB" + And I should see "Officielle" + And I should see "Import générique (JSON-LD)" + And I should see "Non officielle" + When I check the checkbox "TheMealDB" + Then the source selection update request should have been made with source id 1 + And I should see "Enregistré ✓" + + Scenario: Hides the sources section entirely when no source is implemented yet + Given I am signed in as "Alice" "Martin" + And my household id is 1 + And the household request returns the two-member household + And the sources reference list is empty + And the household's enabled sources are empty + When I visit "/parametres/foyer" + Then I should not see "Sources de recettes" + Scenario: Leaves the household Given I am signed in as "Bob" "Dupont" And my user id is 2 diff --git a/apps/web/cypress/e2e/onboarding.feature b/apps/web/cypress/e2e/onboarding.feature index 2997214..82efcbe 100644 --- a/apps/web/cypress/e2e/onboarding.feature +++ b/apps/web/cypress/e2e/onboarding.feature @@ -52,6 +52,31 @@ Feature: Onboarding wizard Then the allergies update request should have been made with no allergy ids And the URL should be the home page + Scenario: Shows and saves the sources step instead of skipping it, when sources are available + Given the diets reference list is empty + And selecting the diet will succeed + And the household request returns no household + And creating a household will succeed + And the sources reference list has options + And the household's enabled sources are empty + And saving the source selection will succeed + And the allergies reference list is empty + And updating allergies will succeed + And I have signed up + When I click the button "Continuer" + Then the URL should include "/onboarding/foyer" + When I fill in the "houseName" field with "Chez Alice" + And I click the button "Créer" + Then the household creation request should have been made with name "Chez Alice" + And the URL should include "/onboarding/sources" + And I should see "Étape 3 sur 4" + And I should see the section "Sources disponibles" + And I should see "TheMealDB" + When I check the checkbox "TheMealDB" + And I click the button "Continuer" + Then the source selection update request should have been made with source id 1 + And the URL should include "/onboarding/allergenes" + Scenario: Lets the household step be completed by joining an existing household instead of creating one Given the diets reference list is empty And selecting the diet will succeed diff --git a/apps/web/cypress/support/step_definitions/household-mutations.steps.ts b/apps/web/cypress/support/step_definitions/household-mutations.steps.ts index 82c6aa4..4dc0862 100644 --- a/apps/web/cypress/support/step_definitions/household-mutations.steps.ts +++ b/apps/web/cypress/support/step_definitions/household-mutations.steps.ts @@ -61,3 +61,25 @@ Then( cy.wait("@joinHouse").its("request.body").should("deep.equal", { inviteCode: code }); }, ); + +// Shared between onboarding.feature's sources step and household-settings.feature's +// sources section — both read/write the same `/house/current/sources` endpoint. + +Given("the household's enabled sources are empty", () => { + cy.intercept("GET", "**/house/current/sources", { statusCode: 200, body: [] }); +}); + +Given("saving the source selection will succeed", () => { + cy.intercept("PATCH", "**/house/current/sources", (req) => { + req.reply({ statusCode: 200, body: req.body.sourceIds }); + }).as("updateSources"); +}); + +Then( + "the source selection update request should have been made with source id {int}", + (id: number) => { + cy.wait("@updateSources") + .its("request.body") + .should("deep.equal", { sourceIds: [id] }); + }, +); diff --git a/apps/web/cypress/support/step_definitions/reference-data.steps.ts b/apps/web/cypress/support/step_definitions/reference-data.steps.ts index 263bf19..edcb805 100644 --- a/apps/web/cypress/support/step_definitions/reference-data.steps.ts +++ b/apps/web/cypress/support/step_definitions/reference-data.steps.ts @@ -35,9 +35,34 @@ Given("the allergies reference list is empty", () => { // Every onboarding scenario that reaches the household step also reaches // `/onboarding/sources` right after (when a household got created/joined — // see `OnboardingHouseholdPage`'s `goToNextStep`), which reads this before -// self-skipping to `/onboarding/allergenes`. No "has options" counterpart -// yet — no source is implemented in the app itself, so there's nothing -// real to mock a populated catalog with. +// self-skipping to `/onboarding/allergenes` if it comes back empty. Given("the sources reference list is empty", () => { cy.intercept("GET", "**/reference/sources", { statusCode: 200, body: [] }); }); + +// Mirrors what's actually seeded (`reference-seed-data.ts`'s +// `registerAllRecipeSources`/`syncRecipeSources`) — one official API source +// with an icon, one unofficial scraper without one — so the sources step +// (onboarding and `/parametres/foyer` alike) has something real to show +// instead of self-skipping. +Given("the sources reference list has options", () => { + cy.intercept("GET", "**/reference/sources", { + statusCode: 200, + body: [ + { + id: 1, + key: "theMealDb", + name: "TheMealDB", + official: true, + iconUrl: "https://www.themealdb.com/images/logo.svg", + }, + { + id: 2, + key: "jsonLdRecipe", + name: "Import générique (JSON-LD)", + official: false, + iconUrl: null, + }, + ], + }); +});