Compare commits
3 Commits
720d42cf71
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
290e3709e2 | ||
|
|
fcafbf1965 | ||
|
|
1821937106 |
@@ -7,6 +7,9 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
|
# Une copie de ce dépôt existe sur les deux instances Gitea : la production ne doit
|
||||||
|
# jamais exécuter le workflow de recette, ni inversement.
|
||||||
|
if: ${{ gitea.server_url == 'https://git.santinova-soft.org' }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Deploy on production server
|
- name: Deploy on production server
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
|
# Une copie de ce dépôt existe sur les deux instances Gitea : la recette ne doit
|
||||||
|
# jamais exécuter le workflow de production, ni inversement.
|
||||||
|
if: ${{ gitea.server_url == 'https://git.recette.santinova-soft.org' }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Deploy on recette server
|
- name: Deploy on recette server
|
||||||
|
|||||||
43
.gitea/workflows/validate.yml
Normal file
43
.gitea/workflows/validate.yml
Normal 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
|
||||||
7
app.json
7
app.json
@@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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": {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|||||||
@@ -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 lorsqu’une 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 d’autorisation 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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|
||||||
|
|||||||
4
todo.md
4
todo.md
@@ -199,6 +199,8 @@
|
|||||||
- [x] Vérifier TypeScript, Vitest et le build de production après refactoring.
|
- [x] Vérifier TypeScript, Vitest et le build de production après refactoring.
|
||||||
|
|
||||||
## Déploiement recette et supervision de classification
|
## Déploiement recette et supervision de classification
|
||||||
- [ ] Déployer le refactoring et appliquer la migration d’unicité des lectures en recette.
|
- [x] Empêcher le workflow de production de s’exécuter depuis le Gitea et le runner de recette.
|
||||||
|
- [x] Déployer le refactoring et appliquer la migration d’unicité des lectures en recette.
|
||||||
- [ ] Tester en recette la persistance des articles lus après déconnexion et reconnexion.
|
- [ ] Tester en recette la persistance des articles lus après déconnexion et reconnexion.
|
||||||
- [x] Ajouter un rapport administrateur des erreurs de classification RSS avec date, flux, article et cause.
|
- [x] Ajouter un rapport administrateur des erreurs de classification RSS avec date, flux, article et cause.
|
||||||
|
- [ ] Corriger le découpage frontend qui empêchait React de s’afficher après le build de recette.
|
||||||
|
|||||||
@@ -167,19 +167,6 @@ export default defineConfig({
|
|||||||
build: {
|
build: {
|
||||||
outDir: path.resolve(import.meta.dirname, "dist/public"),
|
outDir: path.resolve(import.meta.dirname, "dist/public"),
|
||||||
emptyOutDir: true,
|
emptyOutDir: true,
|
||||||
// Conserver un socle initial léger : les bibliothèques d'interface et de données
|
|
||||||
// sont séparées pour être mises en cache indépendamment des écrans métier.
|
|
||||||
rollupOptions: {
|
|
||||||
output: {
|
|
||||||
manualChunks(id) {
|
|
||||||
if (!id.includes("node_modules")) return undefined;
|
|
||||||
if (id.includes("/@trpc/") || id.includes("/@tanstack/")) return "vendor-data";
|
|
||||||
if (id.includes("/react/") || id.includes("/react-dom/") || id.includes("/scheduler/")) return "vendor-react";
|
|
||||||
if (id.includes("/@radix-ui/") || id.includes("/lucide-react/")) return "vendor-ui";
|
|
||||||
return "vendor";
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
host: true,
|
host: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user