feat(ci): ajouter validation et garde-fou de déploiement
All checks were successful
Deploy Production / deploy (push) Has been skipped
Validation applicative / TypeScript, tests et build (push) Successful in 4m19s
Deploy Recette / deploy (push) Successful in 4m59s

This commit is contained in:
Manus Automation
2026-08-20 16:40:12 +00:00
parent fcafbf1965
commit 290e3709e2
6 changed files with 105 additions and 14 deletions

View File

@@ -0,0 +1,43 @@
name: Validation applicative
on:
push:
branches: [main]
paths-ignore:
- "**.md"
- "docs/**"
pull_request:
branches: [main]
paths-ignore:
- "**.md"
- "docs/**"
workflow_dispatch:
jobs:
verify:
name: TypeScript, tests et build
runs-on: ci-node22
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- name: Calculer la clé de cache pnpm
id: pnpm-cache-key
shell: bash
run: |
echo "store=$(pnpm store path --silent)" >> "$GITHUB_OUTPUT"
echo "lock=$(sha256sum pnpm-lock.yaml | cut -d ' ' -f 1)" >> "$GITHUB_OUTPUT"
- name: Restaurer le store pnpm
uses: actions/cache@v4
with:
path: ${{ steps.pnpm-cache-key.outputs.store }}
key: pnpm-${{ runner.os }}-${{ steps.pnpm-cache-key.outputs.lock }}
restore-keys: |
pnpm-${{ runner.os }}-
- name: Installer les dépendances verrouillées
run: pnpm install --frozen-lockfile --prefer-offline
- name: Vérifier TypeScript, tests et build
run: pnpm verify

View File

@@ -6,8 +6,11 @@
"recette": "https://veille.recette.santinova-soft.org", "recette": "https://veille.recette.santinova-soft.org",
"prod": "https://veille.santinova-soft.org" "prod": "https://veille.santinova-soft.org"
}, },
"containerName": "veille-reglementaire", "containerName": "veille-reglementaire-recette",
"image": "images/veille-reglementaire.png", "image": "images/veille-reglementaire.png",
"giteaRepo": "veille-reglementaire", "giteaRepo": "veille-reglementaire",
"giteaOwner": "manus-admin" "giteaOwner": "manus-admin",
"ci": {
"required": true
}
} }

View File

@@ -10,6 +10,7 @@
"check": "tsc --noEmit", "check": "tsc --noEmit",
"format": "prettier --write .", "format": "prettier --write .",
"test": "vitest run", "test": "vitest run",
"verify": "pnpm check && pnpm test && pnpm build",
"db:push": "drizzle-kit generate && drizzle-kit migrate" "db:push": "drizzle-kit generate && drizzle-kit migrate"
}, },
"dependencies": { "dependencies": {

View File

@@ -246,4 +246,8 @@ async function startServer() {
}); });
} }
// Les routeurs importent certaines fonctions de ce module. Vitest ne doit jamais
// démarrer un serveur HTTP à cet effet, sinon les suites parallèles se disputent un port.
if (!process.env.VITEST) {
startServer().catch(console.error); startServer().catch(console.error);
}

View File

@@ -1,21 +1,47 @@
import { describe, it, expect } from "vitest"; import { afterAll, beforeEach, describe, expect, it, vi } from "vitest";
import { isAzureAdConfigured, getAzureAuthUrl } from "./azureAuth";
const azureTestConfig = {
tenantId: "00000000-0000-0000-0000-000000000001",
clientId: "00000000-0000-0000-0000-000000000002",
clientSecret: "test-secret-not-used-for-url",
};
const originalAzureEnv = {
tenantId: process.env.AZURE_AD_TENANT_ID,
clientId: process.env.AZURE_AD_CLIENT_ID,
clientSecret: process.env.AZURE_AD_CLIENT_SECRET,
redirectUri: process.env.AZURE_AD_REDIRECT_URI,
};
describe("Azure AD configuration", () => { describe("Azure AD configuration", () => {
it("should detect Azure AD as configured when env vars are set", () => { beforeEach(() => {
// Les variables sont injectées via webdev_request_secrets // Chaque test utilise une instance MSAL neuve et ne dépend jamais des secrets CI.
vi.resetModules();
process.env.AZURE_AD_TENANT_ID = azureTestConfig.tenantId;
process.env.AZURE_AD_CLIENT_ID = azureTestConfig.clientId;
process.env.AZURE_AD_CLIENT_SECRET = azureTestConfig.clientSecret;
process.env.AZURE_AD_REDIRECT_URI = "https://example.test/api/auth/azure/callback";
});
afterAll(() => {
process.env.AZURE_AD_TENANT_ID = originalAzureEnv.tenantId;
process.env.AZURE_AD_CLIENT_ID = originalAzureEnv.clientId;
process.env.AZURE_AD_CLIENT_SECRET = originalAzureEnv.clientSecret;
process.env.AZURE_AD_REDIRECT_URI = originalAzureEnv.redirectUri;
});
it("détecte Azure AD lorsquune configuration complète est fournie", async () => {
const { isAzureAdConfigured } = await import("./azureAuth");
const configured = isAzureAdConfigured(); const configured = isAzureAdConfigured();
expect(configured).toBe(true); expect(configured).toBe(true);
}); });
it("should generate a valid Azure AD auth URL", async () => { it("génère une URL dautorisation Azure AD à partir de la configuration de test", async () => {
if (!isAzureAdConfigured()) { const { getAzureAuthUrl } = await import("./azureAuth");
console.warn("Azure AD not configured, skipping URL test");
return;
}
const url = await getAzureAuthUrl(); const url = await getAzureAuthUrl();
expect(url).toContain("login.microsoftonline.com"); expect(url).toContain("login.microsoftonline.com");
expect(url).toContain("oauth2/v2.0/authorize"); expect(url).toContain("oauth2/v2.0/authorize");
expect(url).toContain("f496da82-e18f-4567-bf05-8551ae6669b2"); // client_id expect(url).toContain(azureTestConfig.tenantId);
expect(url).toContain(azureTestConfig.clientId);
}); });
}); });

View File

@@ -1,4 +1,18 @@
import { describe, expect, it, vi, beforeEach } from "vitest"; import { describe, expect, it, vi, beforeEach } from "vitest";
// Les tests de contrat tRPC ne doivent jamais dépendre d'une base ou d'une migration
// disponible dans l'environnement CI. Les quatre lectures publiques sont donc isolées.
vi.mock("./db", async () => {
const actual = await vi.importActual<typeof import("./db")>("./db");
return {
...actual,
getVeilleItems: vi.fn(async () => ({ items: [], total: 0 })),
getVeilleDistinctValues: vi.fn(async () => ({ categories: [], niveaux: [], territoires: [] })),
getAapItems: vi.fn(async () => ({ items: [], total: 0 })),
getAapDistinctValues: vi.fn(async () => ({ regions: [], departements: [] })),
};
});
import { appRouter } from "./routers"; import { appRouter } from "./routers";
import type { TrpcContext } from "./_core/context"; import type { TrpcContext } from "./_core/context";