< Summary - Heimdall API

Information
Class: ArturRios.Heimdall.WebApi.Retention.IdentityAnonymisationService
Assembly: ArturRios.Heimdall.WebApi
File(s): /home/runner/work/heimdall-api/heimdall-api/src/Presentation/ArturRios.Heimdall.WebApi/Retention/IdentityAnonymisationService.cs
Line coverage
22%
Covered lines: 4
Uncovered lines: 14
Coverable lines: 18
Total lines: 56
Line coverage: 22.2%
Branch coverage
0%
Covered branches: 0
Total branches: 4
Branch coverage: 0%
Method coverage
66%
Covered methods: 2
Fully covered methods: 2
Total methods: 3
Method coverage: 66.6%
Full method coverage: 66.6%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
get_StartupDescription()100%11100%
RunAsync()0%2040%

File(s)

/home/runner/work/heimdall-api/heimdall-api/src/Presentation/ArturRios.Heimdall.WebApi/Retention/IdentityAnonymisationService.cs

#LineLine coverage
 1using ArturRios.Heimdall.Command.Input;
 2using ArturRios.Heimdall.Command.Output;
 3using ArturRios.Heimdall.Shared.Retention;
 4using ArturRios.Mediator.Command;
 5
 6namespace ArturRios.Heimdall.WebApi.Retention;
 7
 8/// <summary>
 9///     Runs <see cref="AnonymiseExpiredDeletionsCommand" /> on an interval, so a logically deleted
 10///     identity reaches a terminal state instead of sitting in the database indefinitely (NFR-20).
 11/// </summary>
 12/// <remarks>
 13///     This is the pass that actually discharges GDPR Art. 17 and LGPD Art. 16 for an identity, so
 14///     unlike the token purge it logs every run that changed something, however small.
 15/// </remarks>
 16public class IdentityAnonymisationService(
 17    IServiceScopeFactory scopeFactory,
 18    DataRetentionOptions retention,
 19    ILogger<IdentityAnonymisationService> logger)
 89920    : ScheduledPassService(scopeFactory, retention.PurgeInterval, logger)
 21{
 22    protected override string StartupDescription =>
 89923        $"Identity anonymisation scheduled every {retention.PurgeInterval}, anonymising records " +
 89924        $"{retention.SubjectErasureDeadline} after a requested erasure and " +
 89925        $"{retention.AdministrativeDeletionWindow} after an administrative deletion";
 26
 27    protected override async Task<string?> RunAsync(IServiceProvider scope)
 28    {
 029        var mediator = scope.GetRequiredService<CommandMediator>();
 30
 031        var result = await mediator
 032            .ExecuteCommandAsync<AnonymiseExpiredDeletionsCommand, AnonymiseExpiredDeletionsCommandOutput>(
 033                new AnonymiseExpiredDeletionsCommand());
 34
 035        if (!result.Success)
 36        {
 037            return $"Identity anonymisation failed: {string.Join("; ", result.Errors)}";
 38        }
 39
 040        var anonymised = result.Data!;
 41
 42        // The identifiers are named, not just counted: this line is the erasure ledger (NFR-25),
 43        // and it is what makes a restore reconcilable. A backup taken before a subject asked
 44        // reinstates them with no trace of the request, so the list has to survive somewhere the
 45        // restore cannot roll back — which means outside the database, in the logs.
 46        //
 47        // Safe to write and to ship off the host precisely because these identities are now
 48        // anonymised: a PublicId that resolves to nobody is not personal data.
 049        return anonymised.TotalAnonymised == 0
 050            ? null
 051            : $"Anonymised {anonymised.TotalAnonymised} identities past their retention window: " +
 052              $"{anonymised.PersonsAnonymised} persons, {anonymised.GoogleUsersAnonymised} Google Users, " +
 053              $"removing {anonymised.DependentsRemoved} dependent rows. " +
 054              $"Erasure ledger: {string.Join(",", anonymised.AnonymisedIds)}";
 055    }
 56}