feat(eo): restore audioBase64 mode for Gemini batch transcription

- POST /corrections/eo accepts audioBase64 + mimeType (XOR with transcript)
- Gemini transcribeAudio called server-side before correction
- No audio storage (client downloads locally)
- /transcriptions/token kept for future Deepgram live use

Typecheck: OK · Tests: all green

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Hermann_Kitio 2026-04-25 05:59:53 +03:00
parent 14880fe94c
commit 8f8a900449
4 changed files with 352 additions and 31 deletions

View file

@ -73,7 +73,7 @@ describe("POST /corrections/eo — Sprint 4a", () => {
expect(res.status).toBe(400);
});
it("400 si transcript manquant", async () => {
it("400 si ni transcript ni audioBase64 fournis", async () => {
const app = buildApp();
const res = await app.request("/corrections/eo", {
method: "POST",
@ -85,6 +85,36 @@ describe("POST /corrections/eo — Sprint 4a", () => {
expect(body.code).toBe("VALIDATION_ERROR");
});
it("400 si transcript ET audioBase64 fournis simultanément (XOR)", async () => {
const app = buildApp();
const res = await app.request("/corrections/eo", {
method: "POST",
headers: JSON_HEADERS,
body: JSON.stringify({
simulationId: "s1",
tache: "EO_T1",
transcript: "t",
audioBase64: "AAAA",
mimeType: "audio/webm",
}),
});
expect(res.status).toBe(400);
});
it("400 si audioBase64 sans mimeType", async () => {
const app = buildApp();
const res = await app.request("/corrections/eo", {
method: "POST",
headers: JSON_HEADERS,
body: JSON.stringify({
simulationId: "s1",
tache: "EO_T1",
audioBase64: "AAAA",
}),
});
expect(res.status).toBe(400);
});
it("400 si nclc_cible invalide", async () => {
const app = buildApp();
const res = await app.request("/corrections/eo", {
@ -133,6 +163,34 @@ describe("POST /corrections/eo — Sprint 4a", () => {
);
});
it("200 mode batch audio (transmet audioBase64 + mimeType au controller)", async () => {
correctEOMock.mockResolvedValue({
data: { score: 14, nclc: 9, simulation_id: "s-audio", diagnostic: "d" },
});
const app = buildApp();
const res = await app.request("/corrections/eo", {
method: "POST",
headers: JSON_HEADERS,
body: JSON.stringify({
simulationId: "s-audio",
tache: "EO_T1",
audioBase64: "AAAA",
mimeType: "audio/webm",
}),
});
expect(res.status).toBe(200);
expect(correctEOMock).toHaveBeenCalledWith(
expect.objectContaining({
simulationId: "s-audio",
tache: "EO_T1",
nclcCible: 9,
audioBase64: "AAAA",
mimeType: "audio/webm",
}),
expect.any(Object),
);
});
it("200 avec nclc_cible=10 transmis au controller", async () => {
correctEOMock.mockResolvedValue({
data: { score: 16, nclc: 10, simulation_id: "s2", diagnostic: "d" },