Skip to content

Commit

Permalink
Additional tests & redaction helpers
Browse files Browse the repository at this point in the history
  • Loading branch information
mhelleborg committed Oct 23, 2024
1 parent 5ab44df commit 2385e8d
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 38 deletions.
6 changes: 2 additions & 4 deletions Source/Events/Redaction/PersonalDataRedactedForEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
/// </summary>
[EventType(Redactions.PersonalDataRedactedId)]
public abstract class PersonalDataRedacted
{
public string EventId { get; init; }
Expand All @@ -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
/// </summary>
[EventType(Redactions.PersonalDataRedactedId)]
public class
PersonalDataRedactedForEvent : PersonalDataRedacted;
[EventType("de1e7e17-bad5-da7a-fad4-fbc6ec3c0ea5")]
public class PersonalDataRedactedForEvent : PersonalDataRedacted;

/// <summary>
/// Extend this type to create a redaction event for a specific event type
Expand Down
91 changes: 70 additions & 21 deletions Source/Events/Redaction/Redactions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,49 +7,98 @@

namespace Dolittle.SDK.Events.Redaction;

/// <summary>
/// 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
/// </summary>
public static class Redactions
{
/// <summary>
/// This is recognized by the runtime, and triggers redaction of the selected personal data
/// </summary>
public const string PersonalDataRedactedPrefix = "de1e7e17-bad5-da7a";
public const string PersonalDataRedactedId = "de1e7e17-bad5-da7a-fad4-fbc6ec3c0ea5";

/// <summary>
/// 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
/// </summary>
/// <param name="reason">Why is the redaction being performed</param>
/// <param name="redactedBy">Who is performing it</param>
/// <typeparam name="TEvent">What is the event that is being redacted</typeparam>
/// <returns>The created event</returns>
/// <exception cref="ArgumentException">On invalid arguments</exception>
public static PersonalDataRedactedForEvent Create<TEvent>(string reason, string redactedBy) where TEvent : class
{
if (!TryCreate<TEvent>(reason, redactedBy, out var redactionEvent, out var error))
{
throw new ArgumentException(error);
}

return redactionEvent;
}

/// <summary>
/// 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
/// </summary>
/// <param name="reason"></param>
/// <param name="redactedBy"></param>
/// <param name="redactionEvent"></param>
/// <param name="error"></param>
/// <typeparam name="TEvent"></typeparam>
/// <returns></returns>
/// <param name="reason">Why is the redaction being performed</param>
/// <param name="redactedBy">Who is performing it</param>
/// <typeparam name="TEvent">What is the event that is being redacted</typeparam>
/// <typeparam name="TRedactionEvent">The custom event type</typeparam>
/// <returns>The created event</returns>
/// <exception cref="ArgumentException">On invalid arguments</exception>
public static TRedactionEvent Create<TEvent, TRedactionEvent>(string reason, string redactedBy)
where TEvent : class
where TRedactionEvent : PersonalDataRedactedForEvent<TEvent>, new()
{
if (!TryCreate<TEvent, TRedactionEvent>(reason, redactedBy, out var redactionEvent, out var error))
{
throw new ArgumentException(error);
}

return redactionEvent;
}

/// <summary>
/// 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
/// </summary>
/// <param name="reason">Why is the redaction being performed</param>
/// <param name="redactedBy">Who is performing it</param>
/// <param name="redactionEvent">The created event</param>
/// <typeparam name="TEvent">What is the event that is being redacted</typeparam>
/// <returns>True if it was created, false otherwise</returns>
/// <exception cref="ArgumentException">On invalid arguments</exception>
public static bool TryCreate<TEvent>(string reason, string redactedBy,
[NotNullWhen(true)] out PersonalDataRedactedForEvent? redactionEvent, [NotNullWhen(false)] out string? error)
where TEvent : class =>
InternalTryCreate<TEvent, PersonalDataRedactedForEvent>(reason, redactedBy, out redactionEvent, out error);


/// <summary>
/// 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
/// </summary>
/// <param name="reason"></param>
/// <param name="redactedBy"></param>
/// <param name="redactionEvent"></param>
/// <param name="error"></param>
/// <typeparam name="TEvent">The redacted event</typeparam>
/// <typeparam name="TRedactionEvent">The redaction event type</typeparam>
/// <returns></returns>
/// <param name="reason">Why is the redaction being performed</param>
/// <param name="redactedBy">Who is performing it</param>
/// <param name="redactionEvent">The created event</param>
/// <typeparam name="TEvent">What is the event that is being redacted</typeparam>
/// <typeparam name="TRedactionEvent">The custom event type</typeparam>
/// <returns>True if it was created, false otherwise</returns>
/// <exception cref="ArgumentException">On invalid arguments</exception>
public static bool TryCreate<TEvent, TRedactionEvent>(string reason, string redactedBy,
[NotNullWhen(true)] out TRedactionEvent? redactionEvent, [NotNullWhen(false)] out string? error)
where TEvent : class
where TRedactionEvent : PersonalDataRedactedForEvent<TEvent>, new() =>
InternalTryCreate<TEvent, TRedactionEvent>(reason, redactedBy, out redactionEvent, out error);
InternalTryCreate<TEvent, TRedactionEvent>(reason, redactedBy, out redactionEvent, out error);



/// <summary>
/// Try to create a PersonalDataRedacted event for a given event type
Expand Down
45 changes: 32 additions & 13 deletions Tests/Events/Redaction/RedactionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,10 @@ public record RedactedRecord(


[EventType("de1e7e17-bad5-da7a-aaaa-fbc6ec3c0ea6")]
public class SomeRecordRedacted : PersonalDataRedactedForEvent<RedactedRecord>;
public class RecordRedacted : PersonalDataRedactedForEvent<RedactedRecord>
{
public string MoreDetailsWeWantToKeep { get; set; }
}

public class RedactionTests
{
Expand Down Expand Up @@ -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<RedactedRecord>(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<string, object?>
{
{ "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";
Expand Down Expand Up @@ -221,22 +243,19 @@ public void WhenCreatingCustomRedactionEvent()
{
var reason = "Some reason";
var redactedBy = "Some person";
var success =
Redactions.TryCreate<RedactedRecord, SomeRecordRedacted>(reason, redactedBy, out var redactionEvent,
out var error);
var evt = Redactions.Create<RedactedRecord, RecordRedacted>(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<string, object?>
evt.Should().NotBeNull();
evt!.EventId.Should().Be("5577fe91-5955-4b93-98b0-6399647ffdf3");
evt.EventAlias.Should().Be(nameof(RedactedRecord));
evt.RedactedProperties.Should().BeEquivalentTo(new Dictionary<string, object?>
{
{ "RedactedParam", null },
{ "AnotherRedactedParam", -999 },
});
redactionEvent.RedactedBy.Should().Be(redactedBy);
redactionEvent.Reason.Should().Be(reason);
evt.RedactedBy.Should().Be(redactedBy);
evt.Reason.Should().Be(reason);
}


Expand Down

0 comments on commit 2385e8d

Please sign in to comment.