< Summary - Heimdall API

Information
Class: ArturRios.Heimdall.Data.EntityMaps.GoogleUserDbMap
Assembly: ArturRios.Heimdall.Data
File(s): /home/ci/agent/workspace/heimdall-api-docs_main/app/src/Infrastructure/ArturRios.Heimdall.Data/EntityMaps/GoogleUserDbMap.cs
Line coverage
100%
Covered lines: 44
Uncovered lines: 0
Coverable lines: 44
Total lines: 96
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage
100%
Covered methods: 2
Fully covered methods: 2
Total methods: 2
Method coverage: 100%
Full method coverage: 100%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
Configure(...)100%11100%
IgnoredAfterInsert(...)100%11100%

File(s)

/home/ci/agent/workspace/heimdall-api-docs_main/app/src/Infrastructure/ArturRios.Heimdall.Data/EntityMaps/GoogleUserDbMap.cs

#LineLine coverage
 1using ArturRios.Heimdall.Domain.Entities;
 2using Microsoft.EntityFrameworkCore;
 3using Microsoft.EntityFrameworkCore.Metadata;
 4using Microsoft.EntityFrameworkCore.Metadata.Builders;
 5
 6namespace ArturRios.Heimdall.Data.EntityMaps;
 7
 8internal static class GoogleUserDbMap
 9{
 10    public static void Configure(this EntityTypeBuilder<GoogleUser> googleUser)
 11    {
 412        googleUser.ToTable("google_user");
 13
 414        googleUser.HasKey(x => x.Id);
 15
 416        googleUser.Property(x => x.PublicId).IsRequired();
 417        googleUser.HasIndex(x => x.PublicId).IsUnique();
 18
 419        googleUser.Property(x => x.GoogleId).IsRequired();
 20
 421        googleUser.Property(x => x.Email).IsRequired();
 22
 423        googleUser.Property(x => x.EmailVerified).HasDefaultValue(false);
 424        googleUser.Property(x => x.IsDeleted).HasDefaultValue(false);
 25
 26        // NFR-20's retention window is measured from DeletedAt, so the anonymisation pass reads it
 27        // on every run: indexed, and filtered to the rows that can possibly be due, which is a small
 28        // fraction of the table.
 429        googleUser.Property(x => x.DeletedAt);
 430        googleUser.Property(x => x.DeletionKind);
 431        googleUser.Property(x => x.AnonymisedAt);
 32
 33        // NFR-23: the basis is required and defaults to Unrecorded, so a row can never be silent
 34        // about it — a null would be indistinguishable from "nobody has looked at this yet".
 435        googleUser.Property(x => x.LegalBasis).HasDefaultValue(0);
 436        googleUser.Property(x => x.PrivacyNoticeVersion);
 437        googleUser.Property(x => x.BasisRecordedAt);
 38
 39        // NFR-24: indexed because every listing filters on it, and the restricted set is a small
 40        // fraction of the table.
 441        googleUser.Property(x => x.ProcessingRestrictedAt);
 442        googleUser.Property(x => x.RestrictionGround);
 443        googleUser.Property(x => x.RestrictionLiftNotifiedAt);
 44
 445        googleUser.HasIndex(x => x.ProcessingRestrictedAt)
 446            .HasFilter("processing_restricted_at IS NOT NULL");
 47
 448        googleUser.HasIndex(x => x.DeletedAt)
 449            .HasFilter("is_deleted = true AND anonymised_at IS NULL");
 50
 51        // The lifecycle columns, as for a person (#237, #205): whether the Google User is deleted, asked
 52        // to be erased, restricted or anonymised. Each changes only through a targeted, conditional write
 53        // in AtomicWrites, so a whole-row save of a copy read before the change — a sign-in's refresh
 54        // racing a deletion, say, or a deletion racing a restriction — cannot put the old state back.
 455        IgnoredAfterInsert(googleUser.Property(x => x.IsDeleted));
 456        IgnoredAfterInsert(googleUser.Property(x => x.DeletedAt));
 457        IgnoredAfterInsert(googleUser.Property(x => x.DeletionKind));
 458        IgnoredAfterInsert(googleUser.Property(x => x.ErasureRequestedAt));
 459        IgnoredAfterInsert(googleUser.Property(x => x.ErasureDueAt));
 460        IgnoredAfterInsert(googleUser.Property(x => x.ErasureBlockedReason));
 461        IgnoredAfterInsert(googleUser.Property(x => x.ProcessingRestrictedAt));
 462        IgnoredAfterInsert(googleUser.Property(x => x.RestrictionGround));
 463        IgnoredAfterInsert(googleUser.Property(x => x.RestrictionLiftNotifiedAt));
 464        IgnoredAfterInsert(googleUser.Property(x => x.AnonymisedAt));
 65
 466        googleUser.Property(x => x.CreatedAt).HasDefaultValueSql("now()");
 467        googleUser.Property(x => x.UpdatedAt).HasDefaultValueSql("now()");
 68
 69        // GoogleId (Google's 'sub' claim) is unique within the scope (FR-GO-08).
 470        googleUser.HasIndex(x => new { x.ScopeId, x.GoogleId }).IsUnique();
 71
 72        // Email is unique within the scope among Google Users (FR-GO-07), case-insensitively — the
 73        // application compares addresses with LOWER(), so an index over the raw column would accept
 74        // a pair the handler had already refused and enforce a rule nobody wrote. The expression
 75        // form is applied in the migration's raw SQL: EF cannot model an index over an expression,
 76        // so this call reserves the name and the migration replaces its definition.
 77        //
 78        // The joint uniqueness with User persons' emails spans two tables and cannot be a unique
 79        // index at all; it stays in the application layer (CreateUserCommandHandler,
 80        // UpdatePersonCommandHandler, GoogleSignInCommandHandler).
 481        googleUser.HasIndex(x => new { x.ScopeId, x.Email })
 482            .IsUnique()
 483            .HasDatabaseName("ix_google_user_scope_id_email");
 84
 85        // A Google User belongs to exactly one scope (FR-GO-06); hard-deleting the scope cascades
 86        // to its Google Users (NFR-14).
 487        googleUser.HasOne(x => x.Scope)
 488            .WithMany(s => s.GoogleUsers)
 489            .HasForeignKey(x => x.ScopeId)
 490            .IsRequired()
 491            .OnDelete(DeleteBehavior.Cascade);
 492    }
 93
 94    private static void IgnoredAfterInsert<T>(PropertyBuilder<T> property) =>
 4095        property.Metadata.SetAfterSaveBehavior(PropertySaveBehavior.Ignore);
 96}