From 83784ea19dc2b254f7d4024f3c4c05ccf97d1f8a Mon Sep 17 00:00:00 2001 From: Albrecht Degering Date: Fri, 10 Jul 2026 17:50:04 +0200 Subject: [PATCH] Add retention policy option regression coverage --- .../features/privacy/RetentionPolicyManagement.tsx | 6 ++---- webui/src/features/privacy/policyLogic.ts | 12 ++++++++++++ webui/tests/privacy-policy.test.ts | 4 ++++ 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/webui/src/features/privacy/RetentionPolicyManagement.tsx b/webui/src/features/privacy/RetentionPolicyManagement.tsx index 47c7587..c8452a8 100644 --- a/webui/src/features/privacy/RetentionPolicyManagement.tsx +++ b/webui/src/features/privacy/RetentionPolicyManagement.tsx @@ -21,6 +21,7 @@ import FormField from "../../components/FormField"; import ToggleSwitch from "../../components/ToggleSwitch"; import { useUnsavedDraftGuard } from "../../components/UnsavedChangesGuard"; import { + privacyRetentionAuditDetailOptionDisabled, privacyRetentionLocalAllowsLowerLevel, privacyRetentionParentAllowsField } from "./policyLogic"; @@ -70,8 +71,6 @@ type FieldDefinition = { type RawJsonValue = "inherit" | "keep" | "disable"; type AuditDetailValue = "inherit" | PrivacyRetentionPolicy["audit_detail_level"]; -const auditDetailOrder: Record = { full: 0, redacted: 1, minimal: 2 }; - const defaultAllowLowerLevelLimits: PrivacyRetentionLimitPermissions = { store_raw_campaign_json: true, raw_campaign_json_retention_days: true, @@ -517,9 +516,8 @@ function RetentionDaysField({ value, disabled, placeholder, max, onChange }: {va } function AuditDetailSelect({ value, includeInherit, parentValue, disabled, onChange }: {value: AuditDetailValue;includeInherit: boolean;parentValue?: PrivacyRetentionPolicy["audit_detail_level"] | null;disabled: boolean;onChange: (value: AuditDetailValue) => void;}) { - const minimumOrder = parentValue ? auditDetailOrder[parentValue] : 0; function optionDisabled(option: PrivacyRetentionPolicy["audit_detail_level"]): boolean { - return auditDetailOrder[option] < minimumOrder; + return privacyRetentionAuditDetailOptionDisabled(option, parentValue); } return ( diff --git a/webui/src/features/privacy/policyLogic.ts b/webui/src/features/privacy/policyLogic.ts index b7786a7..7e613ae 100644 --- a/webui/src/features/privacy/policyLogic.ts +++ b/webui/src/features/privacy/policyLogic.ts @@ -11,6 +11,10 @@ export type RetentionPolicyLimitCarrier = { allow_lower_level_limits?: Partial> | null; }; +export type PrivacyRetentionAuditDetailLevel = "full" | "redacted" | "minimal"; + +const auditDetailOrder: Record = { full: 0, redacted: 1, minimal: 2 }; + export function privacyRetentionParentAllowsField(parentPolicy: RetentionPolicyLimitCarrier | null, key: PrivacyRetentionPolicyFieldKey): boolean { return !parentPolicy || parentPolicy.allow_lower_level_limits?.[key] !== false; } @@ -27,3 +31,11 @@ export function privacyRetentionLocalAllowsLowerLevel( if (localValue !== undefined) return localValue && privacyRetentionParentAllowsField(parentPolicy, key); return privacyRetentionParentAllowsField(parentPolicy, key); } + +export function privacyRetentionAuditDetailOptionDisabled( + option: PrivacyRetentionAuditDetailLevel, + parentValue?: PrivacyRetentionAuditDetailLevel | null +): boolean { + const minimumOrder = parentValue ? auditDetailOrder[parentValue] : 0; + return auditDetailOrder[option] < minimumOrder; +} diff --git a/webui/tests/privacy-policy.test.ts b/webui/tests/privacy-policy.test.ts index a28264a..b241a51 100644 --- a/webui/tests/privacy-policy.test.ts +++ b/webui/tests/privacy-policy.test.ts @@ -1,6 +1,7 @@ import { type PrivacyRetentionPolicyFieldKey, type RetentionPolicyLimitCarrier, + privacyRetentionAuditDetailOptionDisabled, privacyRetentionLocalAllowsLowerLevel, privacyRetentionParentAllowsField } from "../src/features/privacy/policyLogic"; @@ -32,3 +33,6 @@ assert(!privacyRetentionLocalAllowsLowerLevel(localPatch, parentPolicy, field, f assert(privacyRetentionParentAllowsField(null, field), "missing parent policy should allow local field controls"); assert(privacyRetentionLocalAllowsLowerLevel({}, null, field, false), "unlocked inherited lower-level controls should remain enabled"); assert(!privacyRetentionLocalAllowsLowerLevel({ allow_lower_level_limits: { [field]: false } }, null, field, true), "system policy can disable lower-level choices"); +assert(privacyRetentionAuditDetailOptionDisabled("full", "redacted"), "lower levels cannot widen audit detail from redacted to full"); +assert(!privacyRetentionAuditDetailOptionDisabled("minimal", "redacted"), "lower levels can choose stricter audit detail than the parent"); +assert(!privacyRetentionAuditDetailOptionDisabled("full", null), "system-level audit detail options are all viable");