feat: Sprachnotizen in Synchronisierung, Warteschlange und Abmelden (T7 L5 Task 18)
Wartende Sprachnotizen werden nach Einträgen und Fotos hochgeladen, je Notiz ein fester Idempotency-Key; endgültige 4xx markieren die Notiz mit festem deutschem Text, 401/408/429/5xx/Netz warten auf den nächsten Lauf, „wird bearbeitet“ wird übersprungen. Warteschlangen-Sheet mit Abschnitt „Sprachnotizen“ (Verwerfen je Notiz nur für den Besitzer, alle verwerfen mit Rückfrage, Hinweis bei zu vielen offenen Notizen), Offline-Band zählt nur wartende Notizen, Abmelden fragt bei wartenden Notizen nach. Zählen liest keine Audiodaten. Gates: Frontend lint, typecheck, 2886 Tests. Refs #27 Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
0c7147f626
commit
e614ded967
18 files changed
+1201
-30
No files matched your search
@@ -9,6 +9,7 @@ vi.mock("@/lib/treatments/api", () => ({ createTreatment: vi.fn() }));
|
||||
vi.mock("@/lib/documents/api", () => ({ uploadDocument: vi.fn() }));
|
||||
vi.mock("@/lib/phenology/api", () => ({ createPhenologyReport: vi.fn() }));
|
||||
vi.mock("@/lib/hive/api", () => ({ postHiveChange: vi.fn() }));
|
||||
vi.mock("@/lib/voice/api", () => ({ uploadVoiceCapture: vi.fn() }));
|
||||
vi.mock("@/lib/apiaries/offline-pack", () => ({ refreshPacksAfterSync: vi.fn() }));
|
||||
|
||||
// What the inspection drain hands over to the photos, seen at that moment.
|
||||
@@ -131,6 +132,66 @@ describe("syncPendingEntries", () => {
|
||||
expect(await db.getPendingEntries("pending-inspections")).toEqual([]);
|
||||
});
|
||||
|
||||
it("uploads recorded voice notes after the records, inside the run", async () => {
|
||||
const order: string[] = [];
|
||||
const { uploadVoiceCapture } = await import("@/lib/voice/api");
|
||||
vi.mocked(uploadVoiceCapture).mockReset().mockImplementation(async () => {
|
||||
order.push("voice");
|
||||
return { id: "cap-1", status: "QUEUED" as const };
|
||||
});
|
||||
createInspection.mockImplementation(async () => {
|
||||
order.push("inspection");
|
||||
return { id: "x" } as never;
|
||||
});
|
||||
await db.queueInspection(inspection(1), "key-1", "user-1");
|
||||
const raw = await openDB("apicore-offline");
|
||||
await raw.put("pending-voice", {
|
||||
id: "v1",
|
||||
ownerUserId: "user-1",
|
||||
blob: new Blob(["audio"]),
|
||||
mime: "audio/webm",
|
||||
durationSec: 20,
|
||||
colonyId: "colony-1",
|
||||
inspectionDate: "2026-10-16",
|
||||
createdAt: "2026-10-16T08:00:00.000Z",
|
||||
uploadIdempotencyKey: "upload-v1",
|
||||
state: "recorded",
|
||||
});
|
||||
raw.close();
|
||||
|
||||
const result = await sync.syncPendingEntries("user-1");
|
||||
|
||||
expect(order).toEqual(["inspection", "voice"]);
|
||||
expect(result).toEqual({ synced: 2, failed: 0, skippedForeign: 0 });
|
||||
});
|
||||
|
||||
it("a cancelled run sends no voice note", async () => {
|
||||
const { uploadVoiceCapture } = await import("@/lib/voice/api");
|
||||
vi.mocked(uploadVoiceCapture).mockReset();
|
||||
await db.getPendingEntries("pending-inspections");
|
||||
const raw = await openDB("apicore-offline");
|
||||
await raw.put("pending-voice", {
|
||||
id: "v1",
|
||||
ownerUserId: "user-1",
|
||||
blob: new Blob(["audio"]),
|
||||
mime: "audio/webm",
|
||||
durationSec: 20,
|
||||
colonyId: "colony-1",
|
||||
inspectionDate: "2026-10-16",
|
||||
createdAt: "2026-10-16T08:00:00.000Z",
|
||||
uploadIdempotencyKey: "upload-v1",
|
||||
state: "recorded",
|
||||
});
|
||||
raw.close();
|
||||
|
||||
const run = sync.syncPendingEntries("user-1");
|
||||
await sync.cancelSync();
|
||||
await run;
|
||||
sync.resumeSync();
|
||||
|
||||
expect(uploadVoiceCapture).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("offline the plant report is queued and synced later with its key", async () => {
|
||||
const report = {
|
||||
phase: "SALWEIDE",
|
||||
|
||||
Reference in new issue
Block a user