From deec91c5a310003f5a36bdf0f7da95aa913bf1f7 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Thu, 20 Aug 2026 13:48:29 +0200 Subject: [PATCH] =?UTF-8?q?fix(recipes):=20synchronise=20les=20sources=20e?= =?UTF-8?q?n=20base=20au=20d=C3=A9marrage=20de=20l'image=20de=20prod?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Le conteneur de prod ne peuplait jamais la table Source : seed-runtime.ts (l'entrée seed de l'image Docker, exécutée après `prisma migrate deploy`) n'appelait que seedReferenceData(), jamais registerAllRecipeSources()/ syncRecipeSources() — contrairement à prisma/seed.ts (dev). server.ts enregistre bien les adaptateurs dans son propre registre en mémoire, mais c'est un processus distinct de celui qui lance seed-runtime.js dans la chaîne CMD du Dockerfile ; sans ce sync, GET /reference/sources renvoyait toujours [], et HouseholdSettingsPage masquait silencieusement toute la section sources (sources.length === 0 → return null). C'est ce que l'utilisateur a remarqué : impossible de paramétrer les sources visibles du foyer en prod. Vérifié en local : Source/HouseSource vidées, seed-runtime.js compilé relancé exactement comme le ferait le conteneur (migrate deploy déjà appliqué, puis ce script) → les deux sources (TheMealDB, JSON-LD) sont bien resynchronisées. Ajoute aussi la couverture Cypress du parcours "sources" qui manquait : - onboarding.feature : nouveau scénario où le catalogue de sources n'est pas vide — l'étape /onboarding/sources s'affiche et se soumet, au lieu du seul scénario existant qui la voyait toujours skippée (catalogue vide). - household-settings.feature : nouveaux scénarios pour la section sources de /parametres/foyer — affichage + sauvegarde (autosave incluse) quand des sources existent, et disparition complète de la section quand le catalogue est vide. - Nouvelles steps partagées (reference-data.steps.ts pour le catalogue, household-mutations.steps.ts pour la sélection par foyer). Non exécutés localement : Chromium/Electron headless plante au lancement du process GPU dans cet environnement (limitation documentée du README, reproductible sur main, sans lien avec ce changement) — vérifiés par relecture attentive contre le code source réel (libellés de traduction, routes, formes de requête/réponse) et en suivant le même gabarit que les scénarios existants déjà verts en CI. Co-Authored-By: Claude Sonnet 5 --- .claude/launch.json | 6 ++++ apps/api/src/scripts/seed-runtime.ts | 18 ++++++++++- .../cypress/e2e/household-settings.feature | 26 ++++++++++++++++ apps/web/cypress/e2e/onboarding.feature | 25 +++++++++++++++ .../household-mutations.steps.ts | 22 +++++++++++++ .../step_definitions/reference-data.steps.ts | 31 +++++++++++++++++-- 6 files changed, 124 insertions(+), 4 deletions(-) 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, + }, + ], + }); +});