From 2385e8d4c18695a9a220f1fa17c016168f285ee7 Mon Sep 17 00:00:00 2001 From: Magne Helleborg Date: Wed, 23 Oct 2024 08:28:25 +0200 Subject: [PATCH] Additional tests & redaction helpers --- .../Redaction/PersonalDataRedactedForEvent.cs | 6 +- Source/Events/Redaction/Redactions.cs | 91 ++++++++++++++----- Tests/Events/Redaction/RedactionTests.cs | 45 ++++++--- 3 files changed, 104 insertions(+), 38 deletions(-) diff --git a/Source/Events/Redaction/PersonalDataRedactedForEvent.cs b/Source/Events/Redaction/PersonalDataRedactedForEvent.cs index 166609a1..99ae2602 100644 --- a/Source/Events/Redaction/PersonalDataRedactedForEvent.cs +++ b/Source/Events/Redaction/PersonalDataRedactedForEvent.cs @@ -9,7 +9,6 @@ namespace Dolittle.SDK.Events.Redaction; /// Event that triggers redaction of the given personal data /// It will target the given event type and redact the properties specified within the EventSourceId of the event /// -[EventType(Redactions.PersonalDataRedactedId)] public abstract class PersonalDataRedacted { public string EventId { get; init; } @@ -29,9 +28,8 @@ public abstract class PersonalDataRedacted /// Event that triggers redaction of the given personal data /// This is a built-in event type that is recognized by the runtime /// -[EventType(Redactions.PersonalDataRedactedId)] -public class - PersonalDataRedactedForEvent : PersonalDataRedacted; +[EventType("de1e7e17-bad5-da7a-fad4-fbc6ec3c0ea5")] +public class PersonalDataRedactedForEvent : PersonalDataRedacted; /// /// Extend this type to create a redaction event for a specific event type diff --git a/Source/Events/Redaction/Redactions.cs b/Source/Events/Redaction/Redactions.cs index 664a1a4e..85b2c24f 100644 --- a/Source/Events/Redaction/Redactions.cs +++ b/Source/Events/Redaction/Redactions.cs @@ -7,49 +7,98 @@ namespace Dolittle.SDK.Events.Redaction; +/// +/// This is a helper class for creating redaction events. +/// Redaction events apply to a specific event type, for a given EventSourceId. +/// They will retroactively redact the personal data that is defined by the redacted fields in the event type +/// Read models will not be force replayed automatically, so projections should listen to relevant redaction events +/// and update their state accordingly +/// public static class Redactions { /// /// This is recognized by the runtime, and triggers redaction of the selected personal data /// public const string PersonalDataRedactedPrefix = "de1e7e17-bad5-da7a"; - public const string PersonalDataRedactedId = "de1e7e17-bad5-da7a-fad4-fbc6ec3c0ea5"; + /// + /// Create a PersonalDataRedacted event for a given event type + /// This is the built-in redaction event, you might want to use a custom redaction event for your event type + /// Must include reason and redactedBy, and the event type must have redacted fields defined + /// + /// Why is the redaction being performed + /// Who is performing it + /// What is the event that is being redacted + /// The created event + /// On invalid arguments + public static PersonalDataRedactedForEvent Create(string reason, string redactedBy) where TEvent : class + { + if (!TryCreate(reason, redactedBy, out var redactionEvent, out var error)) + { + throw new ArgumentException(error); + } + + return redactionEvent; + } /// - /// Try to create a PersonalDataRedacted event for a given event type - /// Must include reason and redactedBy + /// Create a PersonalDataRedacted event for a given event type + /// This will create the custom event type that you define, which should inherit from PersonalDataRedactedForEvent + /// Must include reason and redactedBy, and the event type must have redacted fields defined /// - /// - /// - /// - /// - /// - /// + /// Why is the redaction being performed + /// Who is performing it + /// What is the event that is being redacted + /// The custom event type + /// The created event + /// On invalid arguments + public static TRedactionEvent Create(string reason, string redactedBy) + where TEvent : class + where TRedactionEvent : PersonalDataRedactedForEvent, new() + { + if (!TryCreate(reason, redactedBy, out var redactionEvent, out var error)) + { + throw new ArgumentException(error); + } + + return redactionEvent; + } + + /// + /// Create a PersonalDataRedacted event for a given event type + /// This is the built-in redaction event, you might want to use a custom redaction event for your event type + /// Must include reason and redactedBy, and the event type must have redacted fields defined + /// + /// Why is the redaction being performed + /// Who is performing it + /// The created event + /// What is the event that is being redacted + /// True if it was created, false otherwise + /// On invalid arguments public static bool TryCreate(string reason, string redactedBy, [NotNullWhen(true)] out PersonalDataRedactedForEvent? redactionEvent, [NotNullWhen(false)] out string? error) where TEvent : class => InternalTryCreate(reason, redactedBy, out redactionEvent, out error); - + /// - /// Try to create a PersonalDataRedacted event for a given event type - /// Must include reason and redactedBy + /// Create a PersonalDataRedacted event for a given event type + /// This will create the custom event type that you define, which should inherit from PersonalDataRedactedForEvent + /// Must include reason and redactedBy, and the event type must have redacted fields defined /// - /// - /// - /// - /// - /// The redacted event - /// The redaction event type - /// + /// Why is the redaction being performed + /// Who is performing it + /// The created event + /// What is the event that is being redacted + /// The custom event type + /// True if it was created, false otherwise + /// On invalid arguments public static bool TryCreate(string reason, string redactedBy, [NotNullWhen(true)] out TRedactionEvent? redactionEvent, [NotNullWhen(false)] out string? error) where TEvent : class where TRedactionEvent : PersonalDataRedactedForEvent, new() => - InternalTryCreate(reason, redactedBy, out redactionEvent, out error); + InternalTryCreate(reason, redactedBy, out redactionEvent, out error); - /// /// Try to create a PersonalDataRedacted event for a given event type diff --git a/Tests/Events/Redaction/RedactionTests.cs b/Tests/Events/Redaction/RedactionTests.cs index f91c1a28..cf430b7a 100644 --- a/Tests/Events/Redaction/RedactionTests.cs +++ b/Tests/Events/Redaction/RedactionTests.cs @@ -113,7 +113,10 @@ public record RedactedRecord( [EventType("de1e7e17-bad5-da7a-aaaa-fbc6ec3c0ea6")] -public class SomeRecordRedacted : PersonalDataRedactedForEvent; +public class RecordRedacted : PersonalDataRedactedForEvent +{ + public string MoreDetailsWeWantToKeep { get; set; } +} public class RedactionTests { @@ -193,7 +196,26 @@ public void WhenRecordHasNoRedactableProperties() } [Fact] - public void WhenCreatingRedactionEvent() + public void WhenCreatingRedactionRecord() + { + var reason = "Some reason"; + var redactedBy = "Some person"; + var evt = Redactions.Create(reason, redactedBy); + + evt.Should().NotBeNull(); + evt!.EventId.Should().Be("5577fe91-5955-4b93-98b0-6399647ffdf3"); + evt.EventAlias.Should().Be(nameof(RedactedRecord)); + evt.RedactedProperties.Should().BeEquivalentTo(new Dictionary + { + { "RedactedParam", null }, + { "AnotherRedactedParam", -999 }, + }); + evt.RedactedBy.Should().Be(redactedBy); + evt.Reason.Should().Be(reason); + } + + [Fact] + public void WhenTryCreatingRedactionEvent() { var reason = "Some reason"; var redactedBy = "Some person"; @@ -221,22 +243,19 @@ public void WhenCreatingCustomRedactionEvent() { var reason = "Some reason"; var redactedBy = "Some person"; - var success = - Redactions.TryCreate(reason, redactedBy, out var redactionEvent, - out var error); + var evt = Redactions.Create(reason, redactedBy); + evt!.MoreDetailsWeWantToKeep = "Some details"; - success.Should().BeTrue(); - error.Should().BeNull(); - redactionEvent.Should().NotBeNull(); - redactionEvent!.EventId.Should().Be("5577fe91-5955-4b93-98b0-6399647ffdf3"); - redactionEvent.EventAlias.Should().Be(nameof(RedactedRecord)); - redactionEvent.RedactedProperties.Should().BeEquivalentTo(new Dictionary + evt.Should().NotBeNull(); + evt!.EventId.Should().Be("5577fe91-5955-4b93-98b0-6399647ffdf3"); + evt.EventAlias.Should().Be(nameof(RedactedRecord)); + evt.RedactedProperties.Should().BeEquivalentTo(new Dictionary { { "RedactedParam", null }, { "AnotherRedactedParam", -999 }, }); - redactionEvent.RedactedBy.Should().Be(redactedBy); - redactionEvent.Reason.Should().Be(reason); + evt.RedactedBy.Should().Be(redactedBy); + evt.Reason.Should().Be(reason); }