Task 20: /inspections/new?voice=<id> zeigt die Sprachnotiz über dem Formular (Abgeschriebenes, Unklares, „Alle übernehmen“ mit Rückfrage) und Vorschläge unter den Feldern mit wörtlichem Zitat; nichts wird ohne Tipp übernommen, die Volkskarte wird nie angehakt. Bei ausgeschalteter Auswertung ist der Text offen zum Abtippen. Speichern schickt voiceCaptureId; bei 409 VOICE_CAPTURE_GONE genau ein neuer Versuch ohne Notiz mit demselben Idempotency-Key (online und aus der Warteschlange); VOICE_CAPTURE_COLONY_MISMATCH nie automatisch, sondern „Ohne Sprachnotiz speichern/senden“. Skalen aus lib/inspections/scales.ts mit Paritätstest zum Backend. Task 22: /settings/voice mit Modell und Fristen (bei ausgeschalteter Auswertung ausdrücklich: nichts geht an einen KI-Dienst), „Alle meine Sprachnotizen löschen“ auf Server und Gerät mit Anzahl; Systemkarte „Sprachnotizen“ (nur system:manage) mit Zählern und Kosten der letzten 7 Tage (Ruling 15); Service Worker cacht /api/v1/voice-captures nie, /voice und /voice/new offline erreichbar. Gates: Frontend lint, typecheck, 3106 Tests, build. Refs #27 Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
96 lines
3.8 KiB
TypeScript
96 lines
3.8 KiB
TypeScript
import { existsSync, readFileSync } from "node:fs";
|
|
import { resolve } from "node:path";
|
|
import { describe, expect, it } from "vitest";
|
|
import {
|
|
DOMAIN_EVENT_LABELS,
|
|
JOB_LABELS,
|
|
JOB_RUN_STATUS_LABELS,
|
|
QUEUE_STALE_WARNING_SECONDS,
|
|
WEBHOOK_DELIVERY_STATUS_LABELS,
|
|
attemptsLabel,
|
|
formatAge,
|
|
} from "./labels";
|
|
import { JOB_NAMES } from "./types";
|
|
|
|
describe("system labels", () => {
|
|
it("labels every job", () => {
|
|
expect(JOB_NAMES.every((job) => JOB_LABELS[job].length > 0)).toBe(true);
|
|
expect(JOB_LABELS.cleanup).toBe("Aufräumen");
|
|
});
|
|
|
|
it("labels the voice transcribe job and both voice events", () => {
|
|
expect(JOB_LABELS["voice-transcribe"]).toBe("Sprachnotizen abschreiben");
|
|
expect(DOMAIN_EVENT_LABELS.VoiceCaptureReady).toBe("Sprachnotiz fertig");
|
|
expect(DOMAIN_EVENT_LABELS.VoiceCaptureFailed).toBe("Sprachnotiz fehlgeschlagen");
|
|
});
|
|
|
|
it("labels the run and delivery states", () => {
|
|
expect(JOB_RUN_STATUS_LABELS.FAILED).toBe("Fehlgeschlagen");
|
|
expect(WEBHOOK_DELIVERY_STATUS_LABELS.DELIVERED).toBe("Zugestellt");
|
|
expect(WEBHOOK_DELIVERY_STATUS_LABELS.PENDING).toBe("Offen");
|
|
expect(DOMAIN_EVENT_LABELS.InspectionRecorded).toBe("Durchsicht erfasst");
|
|
});
|
|
|
|
it("DOMAIN_EVENT_LABELS names ColonySplit, QueenAssigned, the extraction, honey batch and phenology events and SeasonPhaseChanged", () => {
|
|
expect(DOMAIN_EVENT_LABELS).toMatchObject({
|
|
ColonySplit: "Ableger angelegt",
|
|
QueenAssigned: "Königin zugewiesen",
|
|
ExtractionRecorded: "Schleuderung erfasst",
|
|
ExtractionCorrected: "Schleuderung korrigiert",
|
|
HoneyBatchRecorded: "Honigcharge angelegt",
|
|
HoneyBatchCorrected: "Honigcharge korrigiert",
|
|
PhenologyReported: "Pflanzenmeldung erfasst",
|
|
PhenologyCorrected: "Pflanzenmeldung korrigiert",
|
|
SeasonPhaseChanged: "Saisonabschnitt gewechselt",
|
|
});
|
|
});
|
|
|
|
it("warns after ten minutes", () => {
|
|
expect(QUEUE_STALE_WARNING_SECONDS).toBe(600);
|
|
});
|
|
|
|
it("pluralises attempts", () => {
|
|
expect(attemptsLabel(1)).toBe("1 Versuch");
|
|
expect(attemptsLabel(0)).toBe("0 Versuche");
|
|
expect(attemptsLabel(8)).toBe("8 Versuche");
|
|
});
|
|
|
|
it("formats ages in German", () => {
|
|
expect(formatAge(5)).toBe("vor wenigen Sekunden");
|
|
expect(formatAge(180)).toBe("vor 3 Min.");
|
|
expect(formatAge(7300)).toBe("vor 2 Std.");
|
|
expect(formatAge(2 * 86400 + 10)).toBe("vor 2 Tagen");
|
|
expect(formatAge(86400)).toBe("vor 1 Tag");
|
|
});
|
|
});
|
|
|
|
describe("DOMAIN_EVENT_LABELS completeness", () => {
|
|
it("names PhenologyRecomputeRequested and WinterCheckRecorded", () => {
|
|
expect(DOMAIN_EVENT_LABELS.PhenologyRecomputeRequested).toBe("Saison neu berechnen angefordert");
|
|
expect(DOMAIN_EVENT_LABELS.WinterCheckRecorded).toBe("Winterkontrolle erfasst");
|
|
});
|
|
|
|
it("has a German label for every backend domain event type", () => {
|
|
const catalog = resolve(process.cwd(), "../ApiCore-Backend/src/domain-events/domain-event-types.ts");
|
|
if (!existsSync(catalog)) return; // frontend checked out alone
|
|
const source = readFileSync(catalog, "utf8");
|
|
const block = source.slice(source.indexOf("const CATALOG"), source.indexOf("DOMAIN_EVENT_TYPES"));
|
|
const types = [...block.matchAll(/^\s+([A-Z][A-Za-z]+): true,/gm)].map((match) => match[1]);
|
|
expect(types.length).toBeGreaterThan(20);
|
|
expect(types.filter((type) => !(type in DOMAIN_EVENT_LABELS))).toEqual([]);
|
|
});
|
|
|
|
it("DOMAIN_EVENT_LABELS names ColonyCreated, ColonyMoved, ApiaryChanged and TskReferenceDayReached", () => {
|
|
expect(DOMAIN_EVENT_LABELS).toMatchObject({
|
|
ColonyCreated: "Volk angelegt",
|
|
ColonyMoved: "Volk umgesetzt",
|
|
ApiaryChanged: "Stand geändert",
|
|
TskReferenceDayReached: "TSK-Stichtag erreicht",
|
|
});
|
|
});
|
|
|
|
it("names HiveChanged", () => {
|
|
expect(DOMAIN_EVENT_LABELS.HiveChanged).toBe("Beute geändert");
|
|
});
|
|
});
|