Add retention policy option regression coverage
Some checks failed
Dependency Audit / dependency-audit (push) Has been cancelled
Module Matrix / module-matrix (push) Has been cancelled

This commit is contained in:
2026-07-10 17:50:04 +02:00
parent 81f0f649b5
commit 83784ea19d
3 changed files with 18 additions and 4 deletions

View File

@@ -21,6 +21,7 @@ import FormField from "../../components/FormField";
import ToggleSwitch from "../../components/ToggleSwitch"; import ToggleSwitch from "../../components/ToggleSwitch";
import { useUnsavedDraftGuard } from "../../components/UnsavedChangesGuard"; import { useUnsavedDraftGuard } from "../../components/UnsavedChangesGuard";
import { import {
privacyRetentionAuditDetailOptionDisabled,
privacyRetentionLocalAllowsLowerLevel, privacyRetentionLocalAllowsLowerLevel,
privacyRetentionParentAllowsField } from privacyRetentionParentAllowsField } from
"./policyLogic"; "./policyLogic";
@@ -70,8 +71,6 @@ type FieldDefinition = {
type RawJsonValue = "inherit" | "keep" | "disable"; type RawJsonValue = "inherit" | "keep" | "disable";
type AuditDetailValue = "inherit" | PrivacyRetentionPolicy["audit_detail_level"]; type AuditDetailValue = "inherit" | PrivacyRetentionPolicy["audit_detail_level"];
const auditDetailOrder: Record<PrivacyRetentionPolicy["audit_detail_level"], number> = { full: 0, redacted: 1, minimal: 2 };
const defaultAllowLowerLevelLimits: PrivacyRetentionLimitPermissions = { const defaultAllowLowerLevelLimits: PrivacyRetentionLimitPermissions = {
store_raw_campaign_json: true, store_raw_campaign_json: true,
raw_campaign_json_retention_days: 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;}) { 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 { function optionDisabled(option: PrivacyRetentionPolicy["audit_detail_level"]): boolean {
return auditDetailOrder[option] < minimumOrder; return privacyRetentionAuditDetailOptionDisabled(option, parentValue);
} }
return ( return (

View File

@@ -11,6 +11,10 @@ export type RetentionPolicyLimitCarrier = {
allow_lower_level_limits?: Partial<Record<PrivacyRetentionPolicyFieldKey, boolean>> | null; allow_lower_level_limits?: Partial<Record<PrivacyRetentionPolicyFieldKey, boolean>> | null;
}; };
export type PrivacyRetentionAuditDetailLevel = "full" | "redacted" | "minimal";
const auditDetailOrder: Record<PrivacyRetentionAuditDetailLevel, number> = { full: 0, redacted: 1, minimal: 2 };
export function privacyRetentionParentAllowsField(parentPolicy: RetentionPolicyLimitCarrier | null, key: PrivacyRetentionPolicyFieldKey): boolean { export function privacyRetentionParentAllowsField(parentPolicy: RetentionPolicyLimitCarrier | null, key: PrivacyRetentionPolicyFieldKey): boolean {
return !parentPolicy || parentPolicy.allow_lower_level_limits?.[key] !== false; return !parentPolicy || parentPolicy.allow_lower_level_limits?.[key] !== false;
} }
@@ -27,3 +31,11 @@ export function privacyRetentionLocalAllowsLowerLevel(
if (localValue !== undefined) return localValue && privacyRetentionParentAllowsField(parentPolicy, key); if (localValue !== undefined) return localValue && privacyRetentionParentAllowsField(parentPolicy, key);
return 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;
}

View File

@@ -1,6 +1,7 @@
import { import {
type PrivacyRetentionPolicyFieldKey, type PrivacyRetentionPolicyFieldKey,
type RetentionPolicyLimitCarrier, type RetentionPolicyLimitCarrier,
privacyRetentionAuditDetailOptionDisabled,
privacyRetentionLocalAllowsLowerLevel, privacyRetentionLocalAllowsLowerLevel,
privacyRetentionParentAllowsField privacyRetentionParentAllowsField
} from "../src/features/privacy/policyLogic"; } 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(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({}, 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(!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");