diff --git a/.env b/.env index 66b6db15..07d37726 100644 --- a/.env +++ b/.env @@ -1,19 +1,21 @@ # AGENTCONNECT_OIDC_CLIENT_ID="____-____-____-____-____" +AGENTCONNECT_OIDC_ID_TOKEN_SIGNED_RESPONSE_ALG="ES256" AGENTCONNECT_OIDC_ISSUER="http://localhost:6100/api/v2" AGENTCONNECT_OIDC_SCOPE="openid given_name usual_name email" AGENTCONNECT_OIDC_SECRET_ID="________________________" +AGENTCONNECT_OIDC_USERINFO_SIGNED_RESPONSE_ALG="ES256" ALLOWED_USERS="user@yopmail.com" API_AUTH_PASSWORD="________________________" API_AUTH_URL="http://localhost:6300" API_AUTH_USERNAME="________________________" CONSOLA_LEVEL="4" +COOKIE_ENCRYPTION_KEY="password_at_least_32_characters_long" DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres?schema=public" ENTREPRISE_API_GOUV_TOKEN="____.____.____" ENTREPRISE_API_GOUV_URL="http://localhost:6200" HOST="https://hyyypertool-preprod.moncomptepro.beta.gouv.fr/proxy/localhost:3000" SENTRY_DNS="https://____@errors.data.gouv.fr/____" -TZ="Europe/Paris" ZAMMAD_TOKEN="____" ZAMMAD_URL="https://support.etalab.gouv.fr" diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index b86a775b..e7fa917f 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -19,12 +19,6 @@ jobs: run: working-directory: "e2e" - env: - MOCKED_APP_MONCOMPTEPRO_BETA_GOUV_FR: http://127.0.0.1:6300 - MOCKED_AUTH_AGENTCONNECT_GOUV_FR: http://127.0.0.1:6100 - MOCKED_DATABASE_URL: postgres://postgres:postgres@localhost:5432/postgres - MOCKED_ENTREPRISE_API_GOUV_URL: http://127.0.0.1:6200 - steps: - name: Checkout uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 @@ -47,13 +41,13 @@ jobs: run: | docker compose ps echo "Fake https://auth.agentconnect.gouv.fr" - curl -sSf -XGET ${{ env.MOCKED_AUTH_AGENTCONNECT_GOUV_FR }}/readyz + curl -sSf -XGET http://127.0.0.1:6100/readyz echo echo "Fake https://entreprise.api.gouv.fr ready" - curl -sSf -XGET ${{ env.MOCKED_ENTREPRISE_API_GOUV_URL }}/readyz + curl -sSf -XGET http://127.0.0.1:6200/readyz echo echo "Fake https://app.moncomptepro.beta.gouv.fr ready" - curl -sSf -XGET ${{ env.MOCKED_APP_MONCOMPTEPRO_BETA_GOUV_FR }}/readyz + curl -sSf -XGET http://127.0.0.1:6300/readyz working-directory: "." - name: cypress-io/github-action needs package-lock.json @@ -63,24 +57,7 @@ jobs: - name: Cypress run uses: cypress-io/github-action@8d3918616d8ac34caa2b49afc8b408b6a872a6f5 # v6.7.1 env: - AGENTCONNECT_OIDC_CLIENT_ID: ${{ secrets.AGENTCONNECT_OIDC_CLIENT_ID }} - AGENTCONNECT_OIDC_ISSUER: "${{ env.MOCKED_AUTH_AGENTCONNECT_GOUV_FR }}/api/v2" - AGENTCONNECT_OIDC_SCOPE: "openid given_name usual_name email" - AGENTCONNECT_OIDC_SECRET_ID: ${{ secrets.AGENTCONNECT_OIDC_SECRET_ID }} CONSOLA_LEVEL: "2" - ALLOWED_USERS: "user@yopmail.com" - API_AUTH_PASSWORD: "???" - API_AUTH_URL: "${{ env.MOCKED_APP_MONCOMPTEPRO_BETA_GOUV_FR }}" - API_AUTH_USERNAME: "???" - DATABASE_URL: "${{ env.MOCKED_DATABASE_URL }}" - DO_NOT_SEND_MAIL: "true" - ENTREPRISE_API_GOUV_TOKEN: "???" - ENTREPRISE_API_GOUV_URL: "${{ env.MOCKED_ENTREPRISE_API_GOUV_URL }}" - HOST: "https://hyyypertool-preprod.moncomptepro.beta.gouv.fr/proxy/localhost:3000" - SENTRY_DNS: "https://123456789@errors.data.gouv.fr//XX" - TZ: "Europe/Paris" - ZAMMAD_TOKEN: "???" - ZAMMAD_URL: "https://support.etalab.gouv.fr" DEPLOY_ENV: "production" NODE_ENV: "development" with: diff --git a/e2e/features/users/jean_bon.feature b/e2e/features/users/jean_bon.feature index 27c9d4f1..bd0b09e6 100644 --- a/e2e/features/users/jean_bon.feature +++ b/e2e/features/users/jean_bon.feature @@ -27,4 +27,4 @@ Fonctionnalité: Page utilisateur with moderations # Scénario: Organisations de Raphael Alors je vois "Jean est enregistré(e) dans les modérations suivantes :" - * je vois la ligne de table "11/11/2011 11:11:11 AM" + * je vois la ligne de table "11/11/2011 11:11:11" diff --git a/packages/~/app/core/src/config/env.ts b/packages/~/app/core/src/config/env.ts index 64797504..8699f430 100644 --- a/packages/~/app/core/src/config/env.ts +++ b/packages/~/app/core/src/config/env.ts @@ -9,8 +9,7 @@ import { z } from "zod"; // dotenv.config({ - override: true, - path: [".env", ".env.local", `.env.${env["NODE_ENV"]}.local`], + path: [`.env.${process.env.NODE_ENV}.local`, ".env.local", ".env"], }); const pkg = await import(join(cwd(), "package.json")); @@ -61,7 +60,9 @@ export const app_env = z.object({ ENTREPRISE_API_GOUV_URL: z.string().trim().url(), GIT_SHA: GIT_SHA_SHEMA, HOST: z.string().trim().url().optional(), - NODE_ENV: z.enum(["development", "production"]).default("development"), + NODE_ENV: z + .enum(["development", "production", "test"]) + .default("development"), PORT: z.coerce.number().default(3000), SENTRY_DNS: z.string().trim().url().optional(), VERSION: z.string().default( diff --git a/packages/~/app/middleware/src/set_config.test.ts b/packages/~/app/middleware/src/set_config.test.ts new file mode 100644 index 00000000..d0b56c62 --- /dev/null +++ b/packages/~/app/middleware/src/set_config.test.ts @@ -0,0 +1,81 @@ +// + +import { expect, mock, test } from "bun:test"; +import { Hono } from "hono"; +import { ZodError } from "zod"; +import { parse } from "dotenv"; +import { readFile } from "node:fs/promises"; +import { set_config } from "./set_config"; + +// + +const default_dotenv = parse(await readFile(".env", { encoding: "utf8" })); + +test("should set config", async () => { + const app = new Hono().get( + "/", + set_config(), + async ({ json, var: { config } }) => { + return json(config); + }, + ); + + const res = await app.request("/"); + + expect(res.status).toBe(200); + expect(await res.json()).toEqual( + expect.objectContaining({ + PUBLIC_ASSETS_PATH: expect.stringContaining("/public/built"), + ASSETS_PATH: expect.stringContaining("/assets/"), + DEPLOY_ENV: "preview", + NODE_ENV: "test", + PORT: 3000, + }), + ); +}); + +test("❌ bubble missing env variable", async () => { + mock.module("hono/adapter", () => { + return { + env: () => ({ ...default_dotenv, NODE_ENV: "🌵" }), + }; + }); + + const app = new Hono() + .get("/", set_config(), async ({ json, var: { config } }) => { + return json(config); + }) + .onError((e) => { + throw e; + }); + + expect(async () => { + await app.request("/"); + }).toThrow( + new ZodError([ + { + received: "🌵", + code: "invalid_enum_value", + options: ["development", "production", "test"], + path: ["env", "NODE_ENV"], + message: + "Invalid enum value. Expected 'development' | 'production' | 'test', received '🌵'", + }, + ]), + ); +}); + +test("should set mocked config", async () => { + const app = new Hono().get( + "/", + set_config({ VERSION: "🧌" }), + async ({ json, var: { config } }) => { + return json(config); + }, + ); + + const res = await app.request("/"); + + expect(res.status).toBe(200); + expect(await res.json()).toEqual({ VERSION: "🧌" }); +}); diff --git a/packages/~/app/middleware/src/set_config.ts b/packages/~/app/middleware/src/set_config.ts index 1b09acbb..5ec7a6c3 100644 --- a/packages/~/app/middleware/src/set_config.ts +++ b/packages/~/app/middleware/src/set_config.ts @@ -1,9 +1,9 @@ // import type { App_Config } from "@~/app.core/config"; -import dotenv from "dotenv"; +import { app_env } from "@~/app.core/config/env"; import type { Env, MiddlewareHandler } from "hono"; -import { env } from "node:process"; +import { env } from "hono/adapter"; // @@ -17,14 +17,9 @@ export function set_config( }; } - dotenv.config({ - override: true, - path: [".env", ".env.local", `.env.${env.NODE_ENV}.local`], - }); - - return async function set_config_middleware({ set }, next) { - const { app_env } = await import("@~/app.core/config/env"); - const app_config = app_env.parse(env, { + return async function set_config_middleware(c, next) { + const { set } = c; + const app_config = app_env.parse(env(c), { path: ["env"], }); const ASSETS_PATH = `/assets/${app_config.VERSION}` as const; diff --git a/packages/~/users/api/src/:id/moderations/ModerationTable.tsx b/packages/~/users/api/src/:id/moderations/ModerationTable.tsx index 9b307fc4..12f25067 100644 --- a/packages/~/users/api/src/:id/moderations/ModerationTable.tsx +++ b/packages/~/users/api/src/:id/moderations/ModerationTable.tsx @@ -1,5 +1,6 @@ // +import { LocalTime } from "@~/app.ui/time/LocalTime"; import { row } from "@~/app.ui/table"; import { urls } from "@~/app.urls"; import { moderation_type_to_emoji } from "@~/moderations.lib/moderation_type.mapper"; @@ -53,9 +54,8 @@ export function ModerationTable({ ) .when( () => name === "created_at", - (value: string): string => { - const date = new Date(value); - return `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`; + (value: string) => { + return ; }, ) .otherwise((value) => value)}