L10a Qualität & Abnahmetests: HTTP-Sicherheitstest (manipulierte IDs aller /api/v1-Routen, Datei-Routen, Uploads, CSRF, Sessions, Login-Sperre, Header) gegen next start
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,7 +9,8 @@
|
||||
import "dotenv/config";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { prisma } from "../src/server/db";
|
||||
import type { SyncOperationInput } from "../src/lib/sync/envelope";
|
||||
import { CONFLICTING_OPS, SYNC_OP_TYPES, type SyncOperationInput } from "../src/lib/sync/envelope";
|
||||
import { EXTERNAL_OPS } from "../src/server/services/sync/external-ops";
|
||||
import { applyOperations } from "../src/server/services/sync/apply";
|
||||
import { storeFieldUpload } from "../src/server/services/field/uploads";
|
||||
import { getFieldBundle } from "../src/server/services/field/queries";
|
||||
@@ -137,10 +138,17 @@ runSuite("E2E Offline-Sync", [SLUG_A, SLUG_B], async () => {
|
||||
section("7. Ungültige Ops, Scope, Mandantentrennung");
|
||||
const invalid = await applyOperations(tech, { deviceId: "e2e-device", operations: [op("note.create", { workOrderId: w, text: "" })] });
|
||||
ok(invalid.results[0].status === "rejected" && invalid.results[0].errorCode === "invalid", "ungültige Payload → rejected invalid");
|
||||
// signature.capture is not registered in services/sync/external-ops.ts (reports lane uses server actions)
|
||||
const unavailable = op("signature.capture", { reportId: "x" });
|
||||
const na = await applyOperations(tech, { deviceId: "e2e-device", operations: [unavailable] });
|
||||
ok(na.results[0].status === "rejected" && (await prisma.syncOperation.count({ where: { clientOpId: unavailable.clientOpId } })) === 0, "nicht verfügbare Op → rejected, nicht gespeichert (später wiederholbar)");
|
||||
// An op type without server handler (neither field handler nor registered in services/sync/external-ops.ts)
|
||||
// must be answered without being stored, so devices can retry after the owning lane is deployed.
|
||||
const FIELD_OPS = ["session.start", "session.pause", "session.resume", "session.end", "work_order.transition", "note.create", "checklist.toggle", "material.upsert", "photo.attach", "voice.attach"];
|
||||
const unregistered = SYNC_OP_TYPES.find((t) => !FIELD_OPS.includes(t) && !EXTERNAL_OPS[t] && !CONFLICTING_OPS.includes(t));
|
||||
if (unregistered) {
|
||||
const unavailable = op(unregistered, { reportId: "x" });
|
||||
const na = await applyOperations(tech, { deviceId: "e2e-device", operations: [unavailable] });
|
||||
ok(na.results[0].status === "rejected" && (await prisma.syncOperation.count({ where: { clientOpId: unavailable.clientOpId } })) === 0, `nicht verfügbare Op ${unregistered} → rejected, nicht gespeichert (später wiederholbar)`);
|
||||
} else {
|
||||
console.log("• alle Sync-Op-Typen sind registriert — Prüfung „nicht verfügbare Op“ entfällt");
|
||||
}
|
||||
const notesBefore = await prisma.activityNote.count({ where: { workOrderId: w } });
|
||||
const outsider = await applyOperations(A.ctx.outsider, { deviceId: "o", operations: [op("note.create", { workOrderId: w, text: "fremd" }), op("session.start", { workOrderId: w, mode: "work" })] });
|
||||
ok(outsider.results.every((x) => x.status === "rejected" && x.errorCode === "not_found"), "Monteur ohne Zuweisung → not_found");
|
||||
|
||||
Reference in New Issue
Block a user