Checkpoint: Création de connecteur web : l’URL est normalisée, la table est présente sur les trois environnements et le flux HTTPS de production a été validé avec le même profil utilisateur (création puis suppression sans résidu). Toute erreur de persistance est désormais remplacée côté navigateur par un message générique ; les logs ne conservent que le code technique MySQL/Drizzle, jamais la requête ni ses paramètres. Recette et production déployées, santé, MySQL, routes HTTP 200 et volumes PDF vérifiés. Suite validée : 75 tests, TypeScript et build.
This commit is contained in:
@@ -1,5 +1,9 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import { WEB_IMPORT_SOURCE_SAVE_ERROR, getSafeWebImportSourceError } from "./webImportSourcesError";
|
import {
|
||||||
|
WEB_IMPORT_SOURCE_SAVE_ERROR,
|
||||||
|
getSafeWebImportSourceError,
|
||||||
|
getWebImportSourceTechnicalCode,
|
||||||
|
} from "./webImportSourcesError";
|
||||||
|
|
||||||
describe("getSafeWebImportSourceError", () => {
|
describe("getSafeWebImportSourceError", () => {
|
||||||
it("never returns the SQL statement or connector parameters to the client", () => {
|
it("never returns the SQL statement or connector parameters to the client", () => {
|
||||||
@@ -14,4 +18,12 @@ describe("getSafeWebImportSourceError", () => {
|
|||||||
expect(safeError.message).not.toContain("insert into");
|
expect(safeError.message).not.toContain("insert into");
|
||||||
expect(safeError.message).not.toContain("secret-value");
|
expect(safeError.message).not.toContain("secret-value");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("retient uniquement le code du pilote, même lorsque celui-ci est imbriqué", () => {
|
||||||
|
const databaseError = Object.assign(new Error("params: user@example.fr, secret-value"), {
|
||||||
|
cause: { code: "ER_NO_SUCH_TABLE", message: "contains secret-value" },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(getWebImportSourceTechnicalCode(databaseError)).toBe("ER_NO_SUCH_TABLE");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,12 +14,24 @@ export function getSafeWebImportSourceError(_error: unknown): TRPCError {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Extrait uniquement le code du pilote, sans conserver son message ni ses paramètres. */
|
||||||
|
export function getWebImportSourceTechnicalCode(error: unknown): string {
|
||||||
|
if (!error || typeof error !== "object") return "unknown";
|
||||||
|
|
||||||
|
const directCode = "code" in error && typeof error.code === "string" ? error.code : null;
|
||||||
|
if (directCode) return directCode;
|
||||||
|
|
||||||
|
const cause = "cause" in error ? error.cause : null;
|
||||||
|
if (cause && typeof cause === "object" && "code" in cause && typeof cause.code === "string") {
|
||||||
|
return cause.code;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
|
||||||
/** Journalise uniquement les métadonnées techniques non sensibles. */
|
/** Journalise uniquement les métadonnées techniques non sensibles. */
|
||||||
export function logWebImportSourceSaveFailure(error: unknown): void {
|
export function logWebImportSourceSaveFailure(error: unknown): void {
|
||||||
const technicalCode =
|
const technicalCode = getWebImportSourceTechnicalCode(error);
|
||||||
error && typeof error === "object" && "code" in error && typeof error.code === "string"
|
|
||||||
? error.code
|
|
||||||
: "unknown";
|
|
||||||
|
|
||||||
console.error("[WebImportSources] Échec d’enregistrement", { technicalCode });
|
console.error("[WebImportSources] Échec d’enregistrement", { technicalCode });
|
||||||
}
|
}
|
||||||
|
|||||||
6
todo.md
6
todo.md
@@ -1,9 +1,9 @@
|
|||||||
# Invoice Analyzer - TODO
|
# Invoice Analyzer - TODO
|
||||||
|
|
||||||
## Incident création connecteur web — nouvelle tentative production — 30/09/2026
|
## Incident création connecteur web — nouvelle tentative production — 30/09/2026
|
||||||
- [ ] Reproduire l’insertion avec le même profil utilisateur sans exposer d’identifiant
|
- [x] Reproduire l’insertion avec le même profil utilisateur sans exposer d’identifiant
|
||||||
- [ ] Identifier l’erreur SQL exacte et empêcher sa remontée avec paramètres sensibles
|
- [ ] Journaliser un code technique non sensible en cas de nouvel échec SQL
|
||||||
- [ ] Corriger, tester et déployer le correctif en recette et production
|
- [ ] Valider et déployer l’instrumentation sans paramètres sensibles
|
||||||
|
|
||||||
## Correction création connecteur web en production — 30/09/2026
|
## Correction création connecteur web en production — 30/09/2026
|
||||||
- [x] Diagnostiquer l’échec SQL d’insertion sans exposer les identifiants
|
- [x] Diagnostiquer l’échec SQL d’insertion sans exposer les identifiants
|
||||||
|
|||||||
Reference in New Issue
Block a user