< Summary - Heimdall API

Information
Class: ArturRios.Heimdall.Command.Services.LegalBasisRecorder
Assembly: ArturRios.Heimdall.Command
File(s): /home/runner/work/heimdall-api/heimdall-api/src/Application/ArturRios.Heimdall.Command/Services/LegalBasisRecorder.cs
Line coverage
100%
Covered lines: 14
Uncovered lines: 0
Coverable lines: 14
Total lines: 77
Line coverage: 100%
Branch coverage
100%
Covered branches: 12
Total branches: 12
Branch coverage: 100%
Method coverage
100%
Covered methods: 3
Fully covered methods: 3
Total methods: 3
Method coverage: 100%
Full method coverage: 100%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
Record(...)100%11100%
Record(...)100%11100%
Resolve(...)100%1212100%

File(s)

/home/runner/work/heimdall-api/heimdall-api/src/Application/ArturRios.Heimdall.Command/Services/LegalBasisRecorder.cs

#LineLine coverage
 1using ArturRios.Heimdall.Domain.Entities;
 2using ArturRios.Heimdall.Domain.Enums;
 3
 4namespace ArturRios.Heimdall.Command.Services;
 5
 6/// <summary>
 7///     Stamps the lawful basis and the privacy notice version onto an identity as it is created
 8///     (NFR-23).
 9/// </summary>
 10/// <remarks>
 11///     <para>
 12///         GDPR Art. 5(2) and LGPD Art. 8 §2 put the burden of demonstrating the basis on the
 13///         controller. Demonstrating it means being able to say, of a particular person, what the
 14///         basis was and what they were told — which a system that derives the answer at read time
 15///         cannot do, because it answers what the basis is *now*.
 16///     </para>
 17///     <para>
 18///         The basis comes from the scope where the scope declares one, because the scope is the
 19///         tenant boundary and the tenant is usually the controller for the identities inside it.
 20///         Where it does not, contract performance applies: the identity exists to run a service the
 21///         person is party to, which is what every other row of the record already says.
 22///     </para>
 23/// </remarks>
 24public static class LegalBasisRecorder
 25{
 26    /// <summary>
 27    ///     The version of the privacy notice this build ships. Bumped whenever the notice changes
 28    ///     materially, so an identity records the version actually in force when it was created.
 29    /// </summary>
 30    /// <remarks>
 31    ///     Must match the version header of <c>docs/requirements/Privacy Notice.md</c>.
 32    ///     <c>PrivacyNoticeVersionTests</c> fails the build if the two drift, because a recorded
 33    ///     version that names a document nobody can produce is worse than no record: it looks like
 34    ///     evidence of what somebody was told while pointing at the wrong text.
 35    /// </remarks>
 36    public const string CurrentPrivacyNoticeVersion = "1.2";
 37
 38    /// <summary>The basis applied when a scope declares none, and for identities outside any scope.</summary>
 39    public const LegalBases DefaultBasis = LegalBases.ContractPerformance;
 40
 41    /// <summary>Records the basis and notice version on a person being created.</summary>
 42    public static void Record(Person person, Scope? scope, DateTime at)
 43    {
 3744        person.LegalBasis = (int)Resolve(scope);
 3745        person.PrivacyNoticeVersion = CurrentPrivacyNoticeVersion;
 3746        person.BasisRecordedAt = at;
 3747    }
 48
 49    /// <summary>Records the basis and notice version on a Google User being created.</summary>
 50    public static void Record(GoogleUser googleUser, Scope? scope, DateTime at)
 51    {
 2052        googleUser.LegalBasis = (int)Resolve(scope);
 2053        googleUser.PrivacyNoticeVersion = CurrentPrivacyNoticeVersion;
 2054        googleUser.BasisRecordedAt = at;
 2055    }
 56
 57    /// <summary>
 58    ///     The basis a scope declares, or the default. <see cref="LegalBases.Consent" /> is never
 59    ///     returned even if somehow stored: it needs a withdrawal path that does not exist, and
 60    ///     honouring a stored value here would let a deployment record a basis it could not honour.
 61    ///     The validators refuse it on the way in; this refuses it on the way out too, because a
 62    ///     guard that exists in one direction only is one migration away from being bypassed.
 63    /// </summary>
 64    public static LegalBases Resolve(Scope? scope)
 65    {
 5766        if (scope?.DefaultLegalBasis is not { } declared)
 67        {
 5268            return DefaultBasis;
 69        }
 70
 571        var basis = (LegalBases)declared;
 72
 573        return basis is LegalBases.Consent or LegalBases.Unrecorded || !Enum.IsDefined(basis)
 574            ? DefaultBasis
 575            : basis;
 76    }
 77}