Arbeidspuls/src/integrity.test.ts
2026-05-27 22:51:28 +02:00

196 lines
7.4 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { attachIntegrity, buildCanonicalReportPayload, canonicalizeReportForIntegrity, createExportIntegrity, verifyEntryIntegrity } from "./integrity";
import type { Report } from "./types";
const report: Report = {
id: "rapport-1",
report_time: "08:00:00",
report_date: "2026-05-27",
created_at: "2026-05-27T06:00:00.000Z",
type: "work_report",
work_ability: 4,
energy_level: 3,
mental_clarity: 4,
symptom_burden: 2,
effort_strain: 3,
status: "kan_fortsette",
physical_energy: 3,
mental_energy: 3,
task_types: [],
main_limitations: [],
helpful_accommodations: []
};
describe("integritetskontroll", () => {
it("legger til og validerer integritetsmetadata", () => {
const signed = attachIntegrity(report);
const verified = verifyEntryIntegrity(signed);
expect(signed.integrity?.payload_hash_sha256).toBeTruthy();
expect(signed.integrity?.payload_hash_sha256).toHaveLength(64);
expect(signed.integrity?.algorithm).toBe("sha256");
expect(verified.integrity?.verification_status).toBe("valid");
});
it("markerer endret rapport som mulig endret", () => {
const signed = attachIntegrity(report);
const tampered = { ...signed, energy_level: 1 as const };
const verified = verifyEntryIntegrity(tampered);
expect(verified.integrity?.verification_status).toBe("modified");
});
it("validerer uendret eksport og import uten falsk modified", () => {
const signed = attachIntegrity({ ...report, total_score_percent: 72 });
const exported = JSON.stringify({ reports: [signed] });
const imported = JSON.parse(exported).reports[0] as Report;
const verified = verifyEntryIntegrity(imported);
expect(verified.integrity?.verification_status).toBe("valid");
});
it("påvirkes ikke av nøkkelrekkefølge eller appgenerert verifiseringsstatus", () => {
const signed = attachIntegrity(report);
const reordered = {
type: signed.type,
created_at: signed.created_at,
id: signed.id,
report_date: signed.report_date,
report_time: signed.report_time,
status: signed.status,
effort_strain: signed.effort_strain,
symptom_burden: signed.symptom_burden,
mental_clarity: signed.mental_clarity,
energy_level: signed.energy_level,
work_ability: signed.work_ability,
physical_energy: signed.physical_energy,
mental_energy: signed.mental_energy,
helpful_accommodations: signed.helpful_accommodations,
main_limitations: signed.main_limitations,
task_types: signed.task_types,
integrity: { ...signed.integrity!, verification_status: "modified" as const }
};
expect(canonicalizeReportForIntegrity(reordered)).toBe(canonicalizeReportForIntegrity(signed));
expect(verifyEntryIntegrity(reordered).integrity?.verification_status).toBe("valid");
});
it("fjerner hele integrity-objektet fra canonical payload", () => {
const signed = attachIntegrity(report);
const withChangedIntegrity = {
...signed,
integrity: {
...signed.integrity!,
verification_status: "modified" as const,
verification_reason: "Endret lokal forklaring",
signed_at: "2099-01-01T00:00:00.000Z"
}
};
expect(buildCanonicalReportPayload(withChangedIntegrity)).toBe(buildCanonicalReportPayload(report));
expect(verifyEntryIntegrity(withChangedIntegrity).integrity?.verification_status).toBe("valid");
});
it("ignorerer rekalkulert totalscore og veiledernotater i integritetskontrollen", () => {
const signed = attachIntegrity(report);
const withLocalFields = {
...signed,
total_score_percent: 12,
evaluator_notes: [{ id: "note-1", text: "Lokalt notat" }],
report_review_note: "Lokalt notat",
normalized_period_score: 55
} as Report;
expect(verifyEntryIntegrity(withLocalFields).integrity?.verification_status).toBe("valid");
});
it("validerer rapport med arbeidssted og legacy energidetaljer uten false modified", () => {
const raw = {
id: "aac87f66-2d39-4a6f-8169-7d39aa5fc0e6",
created_at: "2026-05-27T18:36:17.482Z",
report_date: "2026-05-27",
report_time: "20:36:17",
type: "work_report",
workplace: "Hjemme",
work_start_time: "12:30",
work_end_time: "20:00",
work_ability: 4,
energy_level: 3,
mental_clarity: 4,
symptom_burden: 1,
effort_strain: 2,
status: "kan_fortsette",
physical_energy: 3,
mental_energy: 4,
physical_energy_detail: 3,
mental_energy_detail: 4,
perceived_productivity: 4,
task_completion: "som_forventet",
task_types: ["sitting", "computer_work", "concentration"],
main_limitations: ["stress_anxiety"],
helpful_accommodations: [],
total_score_percent: 81
} as Report;
const signed = attachIntegrity(raw);
const imported = JSON.parse(JSON.stringify(signed)) as Report;
expect(buildCanonicalReportPayload(imported)).not.toContain("physical_energy_detail");
expect(buildCanonicalReportPayload(imported)).not.toContain("mental_energy_detail");
expect(verifyEntryIntegrity(imported).integrity?.verification_status).toBe("valid");
});
it("bruker legacy energidetaljer som fallback når foretrukket felt mangler", () => {
const preferred = attachIntegrity({ ...report, physical_energy: 2, mental_energy: 4 });
const aliasOnly = {
...report,
physical_energy: undefined,
mental_energy: undefined,
physical_energy_detail: 2,
mental_energy_detail: 4
} as unknown as Report;
expect(buildCanonicalReportPayload(aliasOnly)).toBe(buildCanonicalReportPayload(preferred));
expect(verifyEntryIntegrity({ ...aliasOnly, integrity: preferred.integrity }).integrity?.verification_status).toBe("valid");
});
it("oppdager faktisk endring av rapporterte felt", () => {
const signed = attachIntegrity({ ...report, note: "Opprinnelig kommentar" });
expect(verifyEntryIntegrity({ ...signed, work_ability: 1 }).integrity?.verification_status).toBe("modified");
expect(verifyEntryIntegrity({ ...signed, energy_level: 1 }).integrity?.verification_status).toBe("modified");
expect(verifyEntryIntegrity({ ...signed, symptom_burden: 5 }).integrity?.verification_status).toBe("modified");
expect(verifyEntryIntegrity({ ...signed, workplace: "Annet sted" }).integrity?.verification_status).toBe("modified");
expect(verifyEntryIntegrity({ ...signed, note: "Endret kommentar" }).integrity?.verification_status).toBe("modified");
});
it("markerer eldre local-fnv1a-64 metadata som unsupported, ikke modified", () => {
const legacy = {
...report,
integrity: {
report_id: report.id,
canonicalization: "stable-json-v2" as const,
algorithm: "local-fnv1a-64" as const,
payload_hash_sha256: "1234",
signed_at: "2026-05-27T06:00:00.000Z"
}
} as Report;
expect(verifyEntryIntegrity(legacy).integrity?.verification_status).toBe("unsupported");
});
it("markerer rapport uten metadata som ikke verifisert", () => {
const verified = verifyEntryIntegrity(report);
expect(verified.integrity?.verification_status).toBe("missing");
});
it("lager eksportintegritet for manifest", () => {
const signed = attachIntegrity(report);
const integrity = createExportIntegrity([signed]);
expect(integrity.schema).toBe("arbeidsevne-export-integrity-v1");
expect(integrity.manifest_hash_sha256).toBeTruthy();
});
});