From eda02f22a5adebb0f53e6ce937d681d82fef4757 Mon Sep 17 00:00:00 2001 From: Glenn Clarke Date: Wed, 22 May 2024 16:25:39 +0100 Subject: [PATCH 01/81] PC-1083: Adds migration and new model for consortium data. Adds a list of consortia to user model. Contains further documentation to make it clear that full Consortia and Local Authority Data is located in the HUG2 Public site codebase. --- HerPortal.BusinessLogic/Models/Consortium.cs | 14 ++++ .../Models/ConsortiumData.cs | 4 ++ .../Models/LocalAuthority.cs | 5 ++ .../Models/LocalAuthorityData.cs | 5 ++ HerPortal.BusinessLogic/Models/User.cs | 1 + HerPortal.Data/HerDbContext.cs | 1 + ...ConsortiumModelAndUserListOfConsortiums.cs | 64 +++++++++++++++++++ .../Migrations/HerDbContextModelSnapshot.cs | 46 +++++++++++++ 8 files changed, 140 insertions(+) create mode 100644 HerPortal.BusinessLogic/Models/Consortium.cs create mode 100644 HerPortal.Data/Migrations/20240522134042_AddConsortiumModelAndUserListOfConsortiums.cs diff --git a/HerPortal.BusinessLogic/Models/Consortium.cs b/HerPortal.BusinessLogic/Models/Consortium.cs new file mode 100644 index 0000000..bdb36c7 --- /dev/null +++ b/HerPortal.BusinessLogic/Models/Consortium.cs @@ -0,0 +1,14 @@ +namespace HerPortal.BusinessLogic.Models; + +/// +/// This model is not a full model for Consortium Data. +/// It should be considered as a reference to the full data for a Consortium. +/// The full data (including an the Consortium's relationship to LAs) can be found in the HUG2 Public Website codebase +/// +public class Consortium +{ + public int Id { get; set; } + public string ConsortiumCode { get; set; } + + public List Users { get; set; } +} \ No newline at end of file diff --git a/HerPortal.BusinessLogic/Models/ConsortiumData.cs b/HerPortal.BusinessLogic/Models/ConsortiumData.cs index 4c1dd2d..7a81cf7 100644 --- a/HerPortal.BusinessLogic/Models/ConsortiumData.cs +++ b/HerPortal.BusinessLogic/Models/ConsortiumData.cs @@ -1,5 +1,9 @@ namespace HerPortal.BusinessLogic.Models; +/// +/// This class does not contain full Consortium Data. +/// The full data (including an the Consortium's relationship to LAs) can be found in the HUG2 Public Website codebase +/// public static class ConsortiumData { public static readonly Dictionary ConsortiumNamesByConsortiumCode = new() diff --git a/HerPortal.BusinessLogic/Models/LocalAuthority.cs b/HerPortal.BusinessLogic/Models/LocalAuthority.cs index 792dc13..27a40b0 100644 --- a/HerPortal.BusinessLogic/Models/LocalAuthority.cs +++ b/HerPortal.BusinessLogic/Models/LocalAuthority.cs @@ -1,5 +1,10 @@ namespace HerPortal.BusinessLogic.Models; +/// +/// This model is not a full model for Local Authority Data. +/// It should be considered as a reference to the full data for a Local Authority. +/// The full data (including an LAs relationship to a Consortium) can be found in the HUG2 Public Website codebase +/// public class LocalAuthority { public int Id { get; set; } diff --git a/HerPortal.BusinessLogic/Models/LocalAuthorityData.cs b/HerPortal.BusinessLogic/Models/LocalAuthorityData.cs index 3eaa043..fb6b56c 100644 --- a/HerPortal.BusinessLogic/Models/LocalAuthorityData.cs +++ b/HerPortal.BusinessLogic/Models/LocalAuthorityData.cs @@ -1,5 +1,10 @@ namespace HerPortal.BusinessLogic.Models; +/// +/// This model is not a full model for Local Authority Data. +/// It should be considered as a reference to the full data for a Local Authority. +/// The full data (including an LAs relationship to a Consortium) can be found in the HUG2 Public Website codebase +/// public static class LocalAuthorityData { // The mapping from custodian code to name comes from the publicly available "Local custodian codes" download link diff --git a/HerPortal.BusinessLogic/Models/User.cs b/HerPortal.BusinessLogic/Models/User.cs index 22b3a7a..bc3a93f 100644 --- a/HerPortal.BusinessLogic/Models/User.cs +++ b/HerPortal.BusinessLogic/Models/User.cs @@ -7,4 +7,5 @@ public class User public bool HasLoggedIn { get; set; } public List LocalAuthorities { get; set; } + public List Consortia { get; set; } } diff --git a/HerPortal.Data/HerDbContext.cs b/HerPortal.Data/HerDbContext.cs index 4bf136d..a7784e6 100644 --- a/HerPortal.Data/HerDbContext.cs +++ b/HerPortal.Data/HerDbContext.cs @@ -11,6 +11,7 @@ public class HerDbContext : DbContext, IDataProtectionKeyContext public DbSet AuditDownloads { get; set; } public DbSet CsvFileDownloads { get; set; } public DbSet LocalAuthorities { get; set; } + public DbSet Consortia { get; set; } public DbSet Users { get; set; } public HerDbContext(DbContextOptions options) : base(options) diff --git a/HerPortal.Data/Migrations/20240522134042_AddConsortiumModelAndUserListOfConsortiums.cs b/HerPortal.Data/Migrations/20240522134042_AddConsortiumModelAndUserListOfConsortiums.cs new file mode 100644 index 0000000..0c8444b --- /dev/null +++ b/HerPortal.Data/Migrations/20240522134042_AddConsortiumModelAndUserListOfConsortiums.cs @@ -0,0 +1,64 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace HerPortal.Data.Migrations +{ + public partial class AddConsortiumModelAndUserListOfConsortiums : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Consortia", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ConsortiumCode = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Consortia", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "ConsortiumUser", + columns: table => new + { + ConsortiaId = table.Column(type: "integer", nullable: false), + UsersId = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ConsortiumUser", x => new { x.ConsortiaId, x.UsersId }); + table.ForeignKey( + name: "FK_ConsortiumUser_Consortia_ConsortiaId", + column: x => x.ConsortiaId, + principalTable: "Consortia", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_ConsortiumUser_Users_UsersId", + column: x => x.UsersId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_ConsortiumUser_UsersId", + table: "ConsortiumUser", + column: "UsersId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "ConsortiumUser"); + + migrationBuilder.DropTable( + name: "Consortia"); + } + } +} diff --git a/HerPortal.Data/Migrations/HerDbContextModelSnapshot.cs b/HerPortal.Data/Migrations/HerDbContextModelSnapshot.cs index d1e2d3d..037b9d6 100644 --- a/HerPortal.Data/Migrations/HerDbContextModelSnapshot.cs +++ b/HerPortal.Data/Migrations/HerDbContextModelSnapshot.cs @@ -22,6 +22,21 @@ protected override void BuildModel(ModelBuilder modelBuilder) NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + modelBuilder.Entity("ConsortiumUser", b => + { + b.Property("ConsortiaId") + .HasColumnType("integer"); + + b.Property("UsersId") + .HasColumnType("integer"); + + b.HasKey("ConsortiaId", "UsersId"); + + b.HasIndex("UsersId"); + + b.ToTable("ConsortiumUser"); + }); + modelBuilder.Entity("HerPortal.BusinessLogic.Models.AuditDownload", b => { b.Property("Id") @@ -52,6 +67,22 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("AuditDownloads"); }); + modelBuilder.Entity("HerPortal.BusinessLogic.Models.Consortium", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ConsortiumCode") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Consortia"); + }); + modelBuilder.Entity("HerPortal.BusinessLogic.Models.CsvFileDownload", b => { b.Property("CustodianCode") @@ -166,6 +197,21 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("DataProtectionKeys"); }); + modelBuilder.Entity("ConsortiumUser", b => + { + b.HasOne("HerPortal.BusinessLogic.Models.Consortium", null) + .WithMany() + .HasForeignKey("ConsortiaId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("HerPortal.BusinessLogic.Models.User", null) + .WithMany() + .HasForeignKey("UsersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + modelBuilder.Entity("HerPortal.BusinessLogic.Models.CsvFileDownload", b => { b.HasOne("HerPortal.BusinessLogic.Models.User", "User") From 18d7f120100ed9e965d6e60bae97169abf08aeb4 Mon Sep 17 00:00:00 2001 From: Glenn Clarke Date: Wed, 22 May 2024 16:33:37 +0100 Subject: [PATCH 02/81] PC-1083: Indenting cleanup --- HerPortal.BusinessLogic/Models/Consortium.cs | 6 +++--- HerPortal.BusinessLogic/Models/ConsortiumData.cs | 4 ++-- HerPortal.BusinessLogic/Models/LocalAuthority.cs | 6 +++--- HerPortal.BusinessLogic/Models/LocalAuthorityData.cs | 6 +++--- HerPortal.BusinessLogic/Models/User.cs | 4 ++-- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/HerPortal.BusinessLogic/Models/Consortium.cs b/HerPortal.BusinessLogic/Models/Consortium.cs index bdb36c7..62fd4cb 100644 --- a/HerPortal.BusinessLogic/Models/Consortium.cs +++ b/HerPortal.BusinessLogic/Models/Consortium.cs @@ -1,9 +1,9 @@ namespace HerPortal.BusinessLogic.Models; /// -/// This model is not a full model for Consortium Data. -/// It should be considered as a reference to the full data for a Consortium. -/// The full data (including an the Consortium's relationship to LAs) can be found in the HUG2 Public Website codebase +/// This model is not a full model for Consortium Data. +/// It should be considered as a reference to the full data for a Consortium. +/// The full data (including an the Consortium's relationship to LAs) can be found in the HUG2 Public Website codebase /// public class Consortium { diff --git a/HerPortal.BusinessLogic/Models/ConsortiumData.cs b/HerPortal.BusinessLogic/Models/ConsortiumData.cs index 7a81cf7..e4dbbb0 100644 --- a/HerPortal.BusinessLogic/Models/ConsortiumData.cs +++ b/HerPortal.BusinessLogic/Models/ConsortiumData.cs @@ -1,8 +1,8 @@ namespace HerPortal.BusinessLogic.Models; /// -/// This class does not contain full Consortium Data. -/// The full data (including an the Consortium's relationship to LAs) can be found in the HUG2 Public Website codebase +/// This class does not contain full Consortium Data. +/// The full data (including an the Consortium's relationship to LAs) can be found in the HUG2 Public Website codebase /// public static class ConsortiumData { diff --git a/HerPortal.BusinessLogic/Models/LocalAuthority.cs b/HerPortal.BusinessLogic/Models/LocalAuthority.cs index 27a40b0..47181ca 100644 --- a/HerPortal.BusinessLogic/Models/LocalAuthority.cs +++ b/HerPortal.BusinessLogic/Models/LocalAuthority.cs @@ -1,9 +1,9 @@ namespace HerPortal.BusinessLogic.Models; /// -/// This model is not a full model for Local Authority Data. -/// It should be considered as a reference to the full data for a Local Authority. -/// The full data (including an LAs relationship to a Consortium) can be found in the HUG2 Public Website codebase +/// This model is not a full model for Local Authority Data. +/// It should be considered as a reference to the full data for a Local Authority. +/// The full data (including an LAs relationship to a Consortium) can be found in the HUG2 Public Website codebase /// public class LocalAuthority { diff --git a/HerPortal.BusinessLogic/Models/LocalAuthorityData.cs b/HerPortal.BusinessLogic/Models/LocalAuthorityData.cs index fb6b56c..b6e04f6 100644 --- a/HerPortal.BusinessLogic/Models/LocalAuthorityData.cs +++ b/HerPortal.BusinessLogic/Models/LocalAuthorityData.cs @@ -1,9 +1,9 @@ namespace HerPortal.BusinessLogic.Models; /// -/// This model is not a full model for Local Authority Data. -/// It should be considered as a reference to the full data for a Local Authority. -/// The full data (including an LAs relationship to a Consortium) can be found in the HUG2 Public Website codebase +/// This model is not a full model for Local Authority Data. +/// It should be considered as a reference to the full data for a Local Authority. +/// The full data (including an LAs relationship to a Consortium) can be found in the HUG2 Public Website codebase /// public static class LocalAuthorityData { diff --git a/HerPortal.BusinessLogic/Models/User.cs b/HerPortal.BusinessLogic/Models/User.cs index bc3a93f..096cc6a 100644 --- a/HerPortal.BusinessLogic/Models/User.cs +++ b/HerPortal.BusinessLogic/Models/User.cs @@ -5,7 +5,7 @@ public class User public int Id { get; set; } public string EmailAddress { get; set; } public bool HasLoggedIn { get; set; } - + public List LocalAuthorities { get; set; } public List Consortia { get; set; } -} +} \ No newline at end of file From 0b872d86d6aecda83fbf529c5bf31fb5e58a3953 Mon Sep 17 00:00:00 2001 From: Glenn Clarke Date: Wed, 22 May 2024 16:36:28 +0100 Subject: [PATCH 03/81] PC-1083: Added missing designer file --- ...mModelAndUserListOfConsortiums.Designer.cs | 245 ++++++++++++++++++ 1 file changed, 245 insertions(+) create mode 100644 HerPortal.Data/Migrations/20240522134042_AddConsortiumModelAndUserListOfConsortiums.Designer.cs diff --git a/HerPortal.Data/Migrations/20240522134042_AddConsortiumModelAndUserListOfConsortiums.Designer.cs b/HerPortal.Data/Migrations/20240522134042_AddConsortiumModelAndUserListOfConsortiums.Designer.cs new file mode 100644 index 0000000..044f5a8 --- /dev/null +++ b/HerPortal.Data/Migrations/20240522134042_AddConsortiumModelAndUserListOfConsortiums.Designer.cs @@ -0,0 +1,245 @@ +// +using System; +using HerPortal.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace HerPortal.Data.Migrations +{ + [DbContext(typeof(HerDbContext))] + [Migration("20240522134042_AddConsortiumModelAndUserListOfConsortiums")] + partial class AddConsortiumModelAndUserListOfConsortiums + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.16") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("ConsortiumUser", b => + { + b.Property("ConsortiaId") + .HasColumnType("integer"); + + b.Property("UsersId") + .HasColumnType("integer"); + + b.HasKey("ConsortiaId", "UsersId"); + + b.HasIndex("UsersId"); + + b.ToTable("ConsortiumUser"); + }); + + modelBuilder.Entity("HerPortal.BusinessLogic.Models.AuditDownload", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CustodianCode") + .IsRequired() + .HasColumnType("text"); + + b.Property("Month") + .HasColumnType("integer"); + + b.Property("Timestamp") + .HasColumnType("timestamp without time zone"); + + b.Property("UserEmail") + .IsRequired() + .HasColumnType("text"); + + b.Property("Year") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("AuditDownloads"); + }); + + modelBuilder.Entity("HerPortal.BusinessLogic.Models.Consortium", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ConsortiumCode") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Consortia"); + }); + + modelBuilder.Entity("HerPortal.BusinessLogic.Models.CsvFileDownload", b => + { + b.Property("CustodianCode") + .HasColumnType("text"); + + b.Property("Year") + .HasColumnType("integer"); + + b.Property("Month") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("LastDownloaded") + .HasColumnType("timestamp without time zone"); + + b.Property("xmin") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid"); + + b.HasKey("CustodianCode", "Year", "Month", "UserId"); + + b.HasIndex("UserId"); + + b.ToTable("CsvFileDownloads"); + }); + + modelBuilder.Entity("HerPortal.BusinessLogic.Models.LocalAuthority", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CustodianCode") + .HasColumnType("text"); + + b.Property("xmin") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid"); + + b.HasKey("Id"); + + b.HasIndex("CustodianCode") + .IsUnique(); + + b.ToTable("LocalAuthorities"); + }); + + modelBuilder.Entity("HerPortal.BusinessLogic.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("EmailAddress") + .HasColumnType("text"); + + b.Property("HasLoggedIn") + .HasColumnType("boolean"); + + b.Property("xmin") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid"); + + b.HasKey("Id"); + + b.HasIndex("EmailAddress") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("LocalAuthorityUser", b => + { + b.Property("LocalAuthoritiesId") + .HasColumnType("integer"); + + b.Property("UsersId") + .HasColumnType("integer"); + + b.HasKey("LocalAuthoritiesId", "UsersId"); + + b.HasIndex("UsersId"); + + b.ToTable("LocalAuthorityUser"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FriendlyName") + .HasColumnType("text"); + + b.Property("Xml") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("DataProtectionKeys"); + }); + + modelBuilder.Entity("ConsortiumUser", b => + { + b.HasOne("HerPortal.BusinessLogic.Models.Consortium", null) + .WithMany() + .HasForeignKey("ConsortiaId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("HerPortal.BusinessLogic.Models.User", null) + .WithMany() + .HasForeignKey("UsersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("HerPortal.BusinessLogic.Models.CsvFileDownload", b => + { + b.HasOne("HerPortal.BusinessLogic.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("LocalAuthorityUser", b => + { + b.HasOne("HerPortal.BusinessLogic.Models.LocalAuthority", null) + .WithMany() + .HasForeignKey("LocalAuthoritiesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("HerPortal.BusinessLogic.Models.User", null) + .WithMany() + .HasForeignKey("UsersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} From b60340a0b64a6eb51c95ffcb863a81d928925a79 Mon Sep 17 00:00:00 2001 From: Glenn Clarke Date: Wed, 22 May 2024 16:44:32 +0100 Subject: [PATCH 04/81] PC-1083: Added notes about how changing custodian codes or consortium codes will require mapping or migrations to ensure correct relationships are maintained --- HerPortal.BusinessLogic/Models/ConsortiumData.cs | 1 + HerPortal.BusinessLogic/Models/LocalAuthorityData.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/HerPortal.BusinessLogic/Models/ConsortiumData.cs b/HerPortal.BusinessLogic/Models/ConsortiumData.cs index e4dbbb0..4b33bc0 100644 --- a/HerPortal.BusinessLogic/Models/ConsortiumData.cs +++ b/HerPortal.BusinessLogic/Models/ConsortiumData.cs @@ -6,6 +6,7 @@ /// public static class ConsortiumData { + // If Consortium Codes change, migrations or mapping will be required to maintain correct relationships public static readonly Dictionary ConsortiumNamesByConsortiumCode = new() { { "C_0002", "Blackpool" }, diff --git a/HerPortal.BusinessLogic/Models/LocalAuthorityData.cs b/HerPortal.BusinessLogic/Models/LocalAuthorityData.cs index b6e04f6..1362ccb 100644 --- a/HerPortal.BusinessLogic/Models/LocalAuthorityData.cs +++ b/HerPortal.BusinessLogic/Models/LocalAuthorityData.cs @@ -7,6 +7,7 @@ /// public static class LocalAuthorityData { + // If Custodian Codes change, migrations or mapping will be required to maintain correct relationships // The mapping from custodian code to name comes from the publicly available "Local custodian codes" download link // on https://www.ordnancesurvey.co.uk/business-government/tools-support/addressbase-support public static readonly Dictionary LocalAuthorityNamesByCustodianCode = new() From adb3c5a03f4eb8da499f711a085ff65efd6deb06 Mon Sep 17 00:00:00 2001 From: Glenn Clarke Date: Wed, 22 May 2024 16:46:32 +0100 Subject: [PATCH 05/81] PC-1083: Added additional information to mapping/migration notes --- HerPortal.BusinessLogic/Models/ConsortiumData.cs | 3 ++- HerPortal.BusinessLogic/Models/LocalAuthorityData.cs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/HerPortal.BusinessLogic/Models/ConsortiumData.cs b/HerPortal.BusinessLogic/Models/ConsortiumData.cs index 4b33bc0..7c97449 100644 --- a/HerPortal.BusinessLogic/Models/ConsortiumData.cs +++ b/HerPortal.BusinessLogic/Models/ConsortiumData.cs @@ -6,7 +6,8 @@ /// public static class ConsortiumData { - // If Consortium Codes change, migrations or mapping will be required to maintain correct relationships + // If Consortium Codes change, migrations or mapping will be required to maintain + // correct relationships with the referenced LA in the HUG2 Public site codebase public static readonly Dictionary ConsortiumNamesByConsortiumCode = new() { { "C_0002", "Blackpool" }, diff --git a/HerPortal.BusinessLogic/Models/LocalAuthorityData.cs b/HerPortal.BusinessLogic/Models/LocalAuthorityData.cs index 1362ccb..64bc1fa 100644 --- a/HerPortal.BusinessLogic/Models/LocalAuthorityData.cs +++ b/HerPortal.BusinessLogic/Models/LocalAuthorityData.cs @@ -7,7 +7,8 @@ /// public static class LocalAuthorityData { - // If Custodian Codes change, migrations or mapping will be required to maintain correct relationships + // If Custodian Codes change, migrations or mapping will be required to maintain + // correct relationships with the referenced LA in the HUG2 Public site codebase // The mapping from custodian code to name comes from the publicly available "Local custodian codes" download link // on https://www.ordnancesurvey.co.uk/business-government/tools-support/addressbase-support public static readonly Dictionary LocalAuthorityNamesByCustodianCode = new() From 75a78a44c7ab1196bdd8f9f0a0c23f4cd7cfd78c Mon Sep 17 00:00:00 2001 From: Glenn Clarke Date: Thu, 23 May 2024 11:14:30 +0100 Subject: [PATCH 06/81] PC-1083: Updated comments for clarity. Adding links to improve comment usability. --- HerPortal.BusinessLogic/Models/Consortium.cs | 1 + .../Models/ConsortiumData.cs | 10 +++++---- .../Models/LocalAuthority.cs | 4 +++- .../Models/LocalAuthorityData.cs | 22 +++++++++++++------ 4 files changed, 25 insertions(+), 12 deletions(-) diff --git a/HerPortal.BusinessLogic/Models/Consortium.cs b/HerPortal.BusinessLogic/Models/Consortium.cs index 62fd4cb..4144287 100644 --- a/HerPortal.BusinessLogic/Models/Consortium.cs +++ b/HerPortal.BusinessLogic/Models/Consortium.cs @@ -4,6 +4,7 @@ /// This model is not a full model for Consortium Data. /// It should be considered as a reference to the full data for a Consortium. /// The full data (including an the Consortium's relationship to LAs) can be found in the HUG2 Public Website codebase +/// /// public class Consortium { diff --git a/HerPortal.BusinessLogic/Models/ConsortiumData.cs b/HerPortal.BusinessLogic/Models/ConsortiumData.cs index 7c97449..89efaea 100644 --- a/HerPortal.BusinessLogic/Models/ConsortiumData.cs +++ b/HerPortal.BusinessLogic/Models/ConsortiumData.cs @@ -1,13 +1,15 @@ namespace HerPortal.BusinessLogic.Models; /// -/// This class does not contain full Consortium Data. -/// The full data (including an the Consortium's relationship to LAs) can be found in the HUG2 Public Website codebase +/// LA-Consortium mapping data can be found both in , and in the HUG2 Public Website codebase's LocalAuthorityData.cs. +/// Link to HUG2 Public Website codebase's LocalAuthorityData.cs +/// Note: Mappings in the above files are expected to be consistent between both repos /// public static class ConsortiumData { - // If Consortium Codes change, migrations or mapping will be required to maintain - // correct relationships with the referenced LA in the HUG2 Public site codebase + /// Ensure that the code mapping in is updated if any custodian codes change + /// If a Consortium Code changes, we will need to perform a migration to map old users of the code to + /// use the new code, and begin using the new code. public static readonly Dictionary ConsortiumNamesByConsortiumCode = new() { { "C_0002", "Blackpool" }, diff --git a/HerPortal.BusinessLogic/Models/LocalAuthority.cs b/HerPortal.BusinessLogic/Models/LocalAuthority.cs index 47181ca..b15bf72 100644 --- a/HerPortal.BusinessLogic/Models/LocalAuthority.cs +++ b/HerPortal.BusinessLogic/Models/LocalAuthority.cs @@ -3,7 +3,9 @@ /// /// This model is not a full model for Local Authority Data. /// It should be considered as a reference to the full data for a Local Authority. -/// The full data (including an LAs relationship to a Consortium) can be found in the HUG2 Public Website codebase +/// Data for LA Relationships to Consortiums can be found in LocalAuthorityData.cs +/// The full data for LAs (including an LAs relationship to a Consortium) can be found in the HUG2 Public Website codebase. +/// Link to HUG2 Public Website codebase's LocalAuthorityData.cs /// public class LocalAuthority { diff --git a/HerPortal.BusinessLogic/Models/LocalAuthorityData.cs b/HerPortal.BusinessLogic/Models/LocalAuthorityData.cs index 64bc1fa..5fa2bb6 100644 --- a/HerPortal.BusinessLogic/Models/LocalAuthorityData.cs +++ b/HerPortal.BusinessLogic/Models/LocalAuthorityData.cs @@ -1,16 +1,20 @@ namespace HerPortal.BusinessLogic.Models; /// -/// This model is not a full model for Local Authority Data. -/// It should be considered as a reference to the full data for a Local Authority. -/// The full data (including an LAs relationship to a Consortium) can be found in the HUG2 Public Website codebase +/// This class does not contain all data for Local Authorities. +/// Full data (including an LAs relationship to a Consortium) can be found in the HUG2 Public Website codebase +/// HUG2 Public Website codebase +/// The LA-Consortium mapping contained here must be the same as the LocalAuthorityData.cs file in the +/// public website repository. /// public static class LocalAuthorityData { - // If Custodian Codes change, migrations or mapping will be required to maintain - // correct relationships with the referenced LA in the HUG2 Public site codebase - // The mapping from custodian code to name comes from the publicly available "Local custodian codes" download link - // on https://www.ordnancesurvey.co.uk/business-government/tools-support/addressbase-support + + /// If Custodian Codes change, mappings in + /// will need to be updated and a migration will need to be done to ensure that correct relationships are maintained + /// and users' relationships with Local Authorities stay consistent. + /// The mapping from custodian code to name comes from the publicly available "Local custodian codes" download link + /// on https://www.ordnancesurvey.co.uk/business-government/tools-support/addressbase-support public static readonly Dictionary LocalAuthorityNamesByCustodianCode = new() { { "9052", "Aberdeenshire" }, @@ -383,6 +387,10 @@ public static class LocalAuthorityData { "2741", "York" }, }; + /// If a Consortium Code changes, we will need to perform a migration to map old users of the code to + /// use the new code, and begin using the new code. + /// The mappings contained here should be consistent with the mapping in the HUG2 Public Website Repo's LocalAuthorityData.cs + /// HUG2 Public Website codebase public static readonly Dictionary LocalAuthorityConsortiumCodeByCustodianCode = new() { { "3805", "C_0031" }, From 6ac54908c44dd93b84458ebf23016dc2629f9768 Mon Sep 17 00:00:00 2001 From: Glenn Clarke Date: Thu, 23 May 2024 17:16:48 +0100 Subject: [PATCH 07/81] PC-1083: Further updates to comments for clarity. Adding more links to improve comment usability. --- HerPortal.BusinessLogic/Models/ConsortiumData.cs | 4 +--- HerPortal.BusinessLogic/Models/LocalAuthority.cs | 2 +- .../Models/LocalAuthorityData.cs | 13 +++++++------ 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/HerPortal.BusinessLogic/Models/ConsortiumData.cs b/HerPortal.BusinessLogic/Models/ConsortiumData.cs index 89efaea..cd48b5f 100644 --- a/HerPortal.BusinessLogic/Models/ConsortiumData.cs +++ b/HerPortal.BusinessLogic/Models/ConsortiumData.cs @@ -7,9 +7,7 @@ /// public static class ConsortiumData { - /// Ensure that the code mapping in is updated if any custodian codes change - /// If a Consortium Code changes, we will need to perform a migration to map old users of the code to - /// use the new code, and begin using the new code. + /// If any Custodian/Consortium codes are changed, consider remapping required in public static readonly Dictionary ConsortiumNamesByConsortiumCode = new() { { "C_0002", "Blackpool" }, diff --git a/HerPortal.BusinessLogic/Models/LocalAuthority.cs b/HerPortal.BusinessLogic/Models/LocalAuthority.cs index b15bf72..e7dcca2 100644 --- a/HerPortal.BusinessLogic/Models/LocalAuthority.cs +++ b/HerPortal.BusinessLogic/Models/LocalAuthority.cs @@ -4,7 +4,7 @@ /// This model is not a full model for Local Authority Data. /// It should be considered as a reference to the full data for a Local Authority. /// Data for LA Relationships to Consortiums can be found in LocalAuthorityData.cs -/// The full data for LAs (including an LAs relationship to a Consortium) can be found in the HUG2 Public Website codebase. +/// The full data for LAs (including an LAs relationship to a Consortium) can also be found in the HUG2 Public Website codebase. /// Link to HUG2 Public Website codebase's LocalAuthorityData.cs /// public class LocalAuthority diff --git a/HerPortal.BusinessLogic/Models/LocalAuthorityData.cs b/HerPortal.BusinessLogic/Models/LocalAuthorityData.cs index 5fa2bb6..54b7a0a 100644 --- a/HerPortal.BusinessLogic/Models/LocalAuthorityData.cs +++ b/HerPortal.BusinessLogic/Models/LocalAuthorityData.cs @@ -9,12 +9,12 @@ /// public static class LocalAuthorityData { - - /// If Custodian Codes change, mappings in - /// will need to be updated and a migration will need to be done to ensure that correct relationships are maintained - /// and users' relationships with Local Authorities stay consistent. + /// If Custodian Codes change (through LAs merging etc.) we need to ensure + /// consistency with the HUG2 Public Website data, and consider any remapping that may be required. + /// HUG2 Public Website codebase /// The mapping from custodian code to name comes from the publicly available "Local custodian codes" download link /// on https://www.ordnancesurvey.co.uk/business-government/tools-support/addressbase-support + /// public static readonly Dictionary LocalAuthorityNamesByCustodianCode = new() { { "9052", "Aberdeenshire" }, @@ -387,8 +387,9 @@ public static class LocalAuthorityData { "2741", "York" }, }; - /// If a Consortium Code changes, we will need to perform a migration to map old users of the code to - /// use the new code, and begin using the new code. + /// If Custodian Codes change (through LAs merging etc.) we need to ensure consistency with the + /// HUG2 Public Website data, and we may need to map old codes to new ones for existing users. + /// HUG2 Public Website codebase /// The mappings contained here should be consistent with the mapping in the HUG2 Public Website Repo's LocalAuthorityData.cs /// HUG2 Public Website codebase public static readonly Dictionary LocalAuthorityConsortiumCodeByCustodianCode = new() From 5fd5af1b0ef263f2004ee05f12963ee434902259 Mon Sep 17 00:00:00 2001 From: Glenn Clarke Date: Thu, 23 May 2024 17:24:51 +0100 Subject: [PATCH 08/81] PC-1083: Update to make comment more readable --- HerPortal.BusinessLogic/Models/ConsortiumData.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HerPortal.BusinessLogic/Models/ConsortiumData.cs b/HerPortal.BusinessLogic/Models/ConsortiumData.cs index cd48b5f..0ea1068 100644 --- a/HerPortal.BusinessLogic/Models/ConsortiumData.cs +++ b/HerPortal.BusinessLogic/Models/ConsortiumData.cs @@ -7,7 +7,7 @@ /// public static class ConsortiumData { - /// If any Custodian/Consortium codes are changed, consider remapping required in + /// If any Custodian/Consortium codes are changed, consider if remapping is required in public static readonly Dictionary ConsortiumNamesByConsortiumCode = new() { { "C_0002", "Blackpool" }, From 72dace0fc90e29c8156ded0c9133505741c65ce1 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Tue, 28 May 2024 11:27:54 +0100 Subject: [PATCH 09/81] PC-1085: add structure for a user to be created with consortiums --- HerPortal.ManagementShell/AdminAction.cs | 9 +++---- .../DatabaseOperation.cs | 13 ++++++++-- .../IDatabaseOperation.cs | 3 ++- HerPortal.ManagementShell/Program.cs | 2 +- .../ManagementShell/AdminActionTests.cs | 24 ++++++++++--------- 5 files changed, 32 insertions(+), 19 deletions(-) diff --git a/HerPortal.ManagementShell/AdminAction.cs b/HerPortal.ManagementShell/AdminAction.cs index 6b960a4..9b9e038 100644 --- a/HerPortal.ManagementShell/AdminAction.cs +++ b/HerPortal.ManagementShell/AdminAction.cs @@ -83,10 +83,11 @@ private Enum GetUserStatus(User? userOrNull) return userOrNull == null ? UserStatus.New : UserStatus.Active; } - private void TryCreateUser(string userEmailAddress, string[]? custodianCodes) + private void TryCreateUser(string userEmailAddress, string[]? custodianCodes, string[]? consortiumCodes) { var lasToAdd = dbOperation.GetLas(custodianCodes ?? Array.Empty()); - dbOperation.CreateUserOrLogError(userEmailAddress, lasToAdd); + var consortiaToAdd = dbOperation.GetConsortia(consortiumCodes ?? Array.Empty()); + dbOperation.CreateUserOrLogError(userEmailAddress, lasToAdd, consortiaToAdd); } public void TryRemoveUser(User? user) @@ -155,7 +156,7 @@ public void RemoveLas(User? user, string[]? custodianCodes) dbOperation.RemoveLasFromUser(user, lasToRemove); } - public void CreateOrUpdateUser(string? userEmailAddress, string[] custodianCodes) + public void CreateOrUpdateUserWithLas(string? userEmailAddress, string[] custodianCodes) { if (userEmailAddress == null) { @@ -185,7 +186,7 @@ public void CreateOrUpdateUser(string? userEmailAddress, string[] custodianCodes } else if (userStatus.Equals(UserStatus.New)) { - TryCreateUser(userEmailAddress, custodianCodes); + TryCreateUser(userEmailAddress, custodianCodes, null); } } } diff --git a/HerPortal.ManagementShell/DatabaseOperation.cs b/HerPortal.ManagementShell/DatabaseOperation.cs index f4a556f..686a617 100644 --- a/HerPortal.ManagementShell/DatabaseOperation.cs +++ b/HerPortal.ManagementShell/DatabaseOperation.cs @@ -32,6 +32,14 @@ public List GetLas(string[] custodianCodes) .ToList(); } + public List GetConsortia(string[] custodianCodes) + { + return custodianCodes + .Select(code => dbContext.Consortia + .Single(la => la.ConsortiumCode == code)) + .ToList(); + } + public void RemoveUserOrLogError(User user) { using var dbContextTransaction = dbContext.Database.BeginTransaction(); @@ -50,7 +58,7 @@ public void RemoveUserOrLogError(User user) } } - public void CreateUserOrLogError(string userEmailAddress, List localAuthorities) + public void CreateUserOrLogError(string userEmailAddress, List localAuthorities, List consortia) { using var dbContextTransaction = dbContext.Database.BeginTransaction(); try @@ -59,7 +67,8 @@ public void CreateUserOrLogError(string userEmailAddress, List l { EmailAddress = userEmailAddress, HasLoggedIn = false, - LocalAuthorities = localAuthorities + LocalAuthorities = localAuthorities, + Consortia = consortia }; dbContext.Add(newLaUser); dbContext.SaveChanges(); diff --git a/HerPortal.ManagementShell/IDatabaseOperation.cs b/HerPortal.ManagementShell/IDatabaseOperation.cs index aca6dd2..8dcdea2 100644 --- a/HerPortal.ManagementShell/IDatabaseOperation.cs +++ b/HerPortal.ManagementShell/IDatabaseOperation.cs @@ -6,8 +6,9 @@ public interface IDatabaseOperation { public List GetUsersWithLocalAuthorities(); public List GetLas(string[] custodianCodes); + public List GetConsortia(string[] consortiumCodes); public void RemoveUserOrLogError(User user); - public void CreateUserOrLogError(string userEmailAddress, List localAuthorities); + public void CreateUserOrLogError(string userEmailAddress, List localAuthorities, List consortia); public void AddLasToUser(User user, List localAuthorities); public void RemoveLasFromUser(User user, List localAuthorities); } \ No newline at end of file diff --git a/HerPortal.ManagementShell/Program.cs b/HerPortal.ManagementShell/Program.cs index f87078a..ca5137d 100644 --- a/HerPortal.ManagementShell/Program.cs +++ b/HerPortal.ManagementShell/Program.cs @@ -53,7 +53,7 @@ public static void Main(string[] args) adminAction.RemoveLas(adminAction.GetUser(userEmailAddress), custodianCodes); return; case Subcommand.AddLas: - adminAction.CreateOrUpdateUser(userEmailAddress, custodianCodes); + adminAction.CreateOrUpdateUserWithLas(userEmailAddress, custodianCodes); return; default: outputProvider.Output("Invalid terminal command entered. Please refer to the documentation"); diff --git a/HerPortal.UnitTests/ManagementShell/AdminActionTests.cs b/HerPortal.UnitTests/ManagementShell/AdminActionTests.cs index 956a408..9226afb 100644 --- a/HerPortal.UnitTests/ManagementShell/AdminActionTests.cs +++ b/HerPortal.UnitTests/ManagementShell/AdminActionTests.cs @@ -55,7 +55,7 @@ public void ConfirmsCustodianCodesWhenUpdating() SetupConfirmCustodianCodes(custodianCodes, userEmailAddress, true); // Act - underTest.CreateOrUpdateUser(userEmailAddress, custodianCodes); + underTest.CreateOrUpdateUserWithLas(userEmailAddress, custodianCodes); // Assert mockOutputProvider.Verify(mock => mock.Confirm(It.IsAny()), Times.Once()); @@ -84,10 +84,11 @@ public void CreatesNewUser_IfUserNotFoundByDbOperation() mockDatabaseOperation.Setup(mock => mock.GetLas(custodianCodes)).Returns(las); // Act - underTest.CreateOrUpdateUser(userEmailAddress, custodianCodes); + underTest.CreateOrUpdateUserWithLas(userEmailAddress, custodianCodes); // Assert - mockDatabaseOperation.Verify(mock => mock.CreateUserOrLogError(userEmailAddress, las), Times.Once()); + mockDatabaseOperation.Verify( + mock => mock.CreateUserOrLogError(userEmailAddress, las, It.IsAny>()), Times.Once()); } [Test] @@ -124,7 +125,7 @@ public void AddsLasToExistingUser_IfUserFoundByDbOperation() mockDatabaseOperation.Setup(mock => mock.GetLas(custodianCodes)).Returns(lasToAdd); // Act - underTest.CreateOrUpdateUser(userEmailAddress, custodianCodes); + underTest.CreateOrUpdateUserWithLas(userEmailAddress, custodianCodes); // Assert mockDatabaseOperation.Verify(mock => mock.AddLasToUser(users[0], lasToAdd)); @@ -192,7 +193,7 @@ public void DisplaysErrorMessage_WhenNoLasSpecified_IfUserExists() SetupConfirmCustodianCodes(Array.Empty(), userEmailAddress, true); // Act - underTest.CreateOrUpdateUser(userEmailAddress, Array.Empty()); + underTest.CreateOrUpdateUserWithLas(userEmailAddress, Array.Empty()); // Assert mockOutputProvider.Verify(mock => mock.Output(It.IsAny())); @@ -242,7 +243,7 @@ public void DisplaysInnerExceptionMessage_IfCustodianCode_IsNotFoundInDict() var listWithCustodianCodeNotInDict = new [] { "1111" }; // Act - underTest.CreateOrUpdateUser(userEmailAddress, listWithCustodianCodeNotInDict); + underTest.CreateOrUpdateUserWithLas(userEmailAddress, listWithCustodianCodeNotInDict); // Assert mockOutputProvider.Verify(mock => mock.Output(It.IsAny())); @@ -298,11 +299,12 @@ public void DoesNotCallDbOperation_IfConfirmKeyNotPressed() }; // Act - underTest.CreateOrUpdateUser(userEmailAddress, custodianCodes); + underTest.CreateOrUpdateUserWithLas(userEmailAddress, custodianCodes); // Assert mockOutputProvider.Verify(mock => mock.Output("Process cancelled, no changes were made to the database")); - mockDatabaseOperation.Verify(mock => mock.CreateUserOrLogError(userEmailAddress, lasToAdd), Times.Never()); + mockDatabaseOperation.Verify( + mock => mock.CreateUserOrLogError(userEmailAddress, lasToAdd, It.IsAny>()), Times.Never()); } [Test] @@ -318,7 +320,7 @@ public void AsksForConfirmation() var custodianCodes = new[] { "9052"}; // Act - underTest.CreateOrUpdateUser(userEmailAddress, custodianCodes); + underTest.CreateOrUpdateUserWithLas(userEmailAddress, custodianCodes); // Assert mockOutputProvider.Verify(mock => mock.Confirm("Please confirm (y/n)"), Times.Once); @@ -336,7 +338,7 @@ public void DisplaysCorrectUserStatus_WhenUserActive() mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthorities()).Returns(users); var custodianCodes = new[] { "9052"}; // Act - underTest.CreateOrUpdateUser(userEmailAddress, custodianCodes); + underTest.CreateOrUpdateUserWithLas(userEmailAddress, custodianCodes); // Assert mockOutputProvider.Verify(mock => mock.Output("User found in database. LAs will be added to their account"), Times.Once()); @@ -354,7 +356,7 @@ public void DisplaysCorrectUserStatus_WhenUserInactive() mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthorities()).Returns(users); var custodianCodes = new[] { "9052"}; // Act - underTest.CreateOrUpdateUser(userEmailAddress, custodianCodes); + underTest.CreateOrUpdateUserWithLas(userEmailAddress, custodianCodes); // Assert mockOutputProvider.Verify(mock => mock.Output("User not found in database. A new user will be created"), Times.Once()); From dfab47f72c106dece45510c3dc8d750185d3f9ac Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Tue, 28 May 2024 13:51:00 +0100 Subject: [PATCH 10/81] PC-1085: add new command to add consortia to an account --- HerPortal.ManagementShell/AdminAction.cs | 119 +++++-- .../DatabaseOperation.cs | 32 +- .../IDatabaseOperation.cs | 3 +- HerPortal.ManagementShell/Program.cs | 14 +- HerPortal.UnitTests/Builders/UserBuilder.cs | 6 + .../ManagementShell/AdminActionTests.cs | 292 ++++++++++++++++-- 6 files changed, 411 insertions(+), 55 deletions(-) diff --git a/HerPortal.ManagementShell/AdminAction.cs b/HerPortal.ManagementShell/AdminAction.cs index 9b9e038..08c7f1d 100644 --- a/HerPortal.ManagementShell/AdminAction.cs +++ b/HerPortal.ManagementShell/AdminAction.cs @@ -13,6 +13,7 @@ public enum UserStatus private readonly IDatabaseOperation dbOperation; private readonly IOutputProvider outputProvider; private readonly Dictionary custodianCodeToLaDict = LocalAuthorityData.LocalAuthorityNamesByCustodianCode; + private readonly Dictionary consortiumCodeToConsortiumDict = ConsortiumData.ConsortiumNamesByConsortiumCode; public AdminAction(IDatabaseOperation dbOperation, IOutputProvider outputProvider) { @@ -22,7 +23,7 @@ public AdminAction(IDatabaseOperation dbOperation, IOutputProvider outputProvide public User? GetUser(string emailAddress) { - var portalUsers = dbOperation.GetUsersWithLocalAuthorities(); + var portalUsers = dbOperation.GetUsersWithLocalAuthoritiesAndConsortia(); return portalUsers.SingleOrDefault(user => string.Equals ( @@ -32,28 +33,57 @@ public AdminAction(IDatabaseOperation dbOperation, IOutputProvider outputProvide )); } + private void PrintCodes(string[] codes, Dictionary codeToNameDict) + { + if (codes.Length < 1) + { + outputProvider.Output("(None)"); + } + + foreach (var code in codes) + { + var name = codeToNameDict[code]; + outputProvider.Output($"{code}: {name}"); + } + } + private bool ConfirmCustodianCodes(string userEmailAddress, string[] codes) { outputProvider.Output( $"You are changing permissions for user {userEmailAddress} for the following local authorities:"); - if (codes.Length < 1) + try + { + PrintCodes(codes, custodianCodeToLaDict); + } + catch (Exception e) + { + outputProvider.Output($"{e.Message} Process terminated"); + return false; + } + + var hasUserConfirmed = outputProvider.Confirm("Please confirm (y/n)"); + if (!hasUserConfirmed) { - outputProvider.Output("(No LAs specified)"); + outputProvider.Output("Process cancelled, no changes were made to the database"); } - foreach (var code in codes) + return hasUserConfirmed; + } + + private bool ConfirmConsortiumCodes(string? userEmailAddress, string[] codes) + { + outputProvider.Output( + $"You are changing permissions for user {userEmailAddress} for the following consortiums:"); + + try { - try - { - var localAuthority = custodianCodeToLaDict[code]; - outputProvider.Output($"{code}: {localAuthority}"); - } - catch (Exception e) - { - outputProvider.Output($"{e.Message} Process terminated"); - return false; - } + PrintCodes(codes, consortiumCodeToConsortiumDict); + } + catch (Exception e) + { + outputProvider.Output($"{e.Message} Process terminated"); + return false; } var hasUserConfirmed = outputProvider.Confirm("Please confirm (y/n)"); @@ -78,7 +108,7 @@ private void DisplayUserStatus(Enum status) } } - private Enum GetUserStatus(User? userOrNull) + private UserStatus GetUserStatus(User? userOrNull) { return userOrNull == null ? UserStatus.New : UserStatus.Active; } @@ -155,15 +185,27 @@ public void RemoveLas(User? user, string[]? custodianCodes) dbOperation.RemoveLasFromUser(user, lasToRemove); } - - public void CreateOrUpdateUserWithLas(string? userEmailAddress, string[] custodianCodes) + + private void AddConsortia(User? user, string[]? consortiumCodes) { - if (userEmailAddress == null) + if (user == null) { - outputProvider.Output("Please specify user E-mail address to create or update"); + outputProvider.Output("User not found"); return; } + if (consortiumCodes == null || consortiumCodes.Length < 1) + { + outputProvider.Output("Please specify consortium codes to add to user"); + return; + } + + var consortiaToAdd = dbOperation.GetConsortia(consortiumCodes); + dbOperation.AddConsortiaToUser(user, consortiaToAdd); + } + + private (User? user, UserStatus userStatus) SetupUser(string userEmailAddress) + { var user = GetUser(userEmailAddress); var userStatus = GetUserStatus(user); DisplayUserStatus(userStatus); @@ -175,6 +217,19 @@ public void CreateOrUpdateUserWithLas(string? userEmailAddress, string[] custodi outputProvider.Output("Take a moment to double check the following list and only continue if you are certain this user should have access to these LAs."); outputProvider.Output("NB: in particular, you should only do this for LAs that have signed their DSA contracts!"); outputProvider.Output(""); + + return (user, userStatus); + } + + public void CreateOrUpdateUserWithLas(string? userEmailAddress, string[] custodianCodes) + { + if (userEmailAddress == null) + { + outputProvider.Output("Please specify user E-mail address to create or update"); + return; + } + + var (user, userStatus) = SetupUser(userEmailAddress); var confirmation = ConfirmCustodianCodes(userEmailAddress, custodianCodes); @@ -190,4 +245,30 @@ public void CreateOrUpdateUserWithLas(string? userEmailAddress, string[] custodi } } } + + public void CreateOrUpdateUserWithConsortia(string? userEmailAddress, string[] consortiumCodes) + { + if (userEmailAddress == null) + { + outputProvider.Output("Please specify user E-mail address to create or update"); + return; + } + + var (user, userStatus) = SetupUser(userEmailAddress); + + var confirmation = ConfirmConsortiumCodes(userEmailAddress, consortiumCodes); + + if (confirmation) + { + switch (userStatus) + { + case UserStatus.Active: + AddConsortia(user, consortiumCodes); + break; + case UserStatus.New: + TryCreateUser(userEmailAddress, null, consortiumCodes); + break; + } + } + } } \ No newline at end of file diff --git a/HerPortal.ManagementShell/DatabaseOperation.cs b/HerPortal.ManagementShell/DatabaseOperation.cs index 686a617..b90586b 100644 --- a/HerPortal.ManagementShell/DatabaseOperation.cs +++ b/HerPortal.ManagementShell/DatabaseOperation.cs @@ -17,10 +17,11 @@ public DatabaseOperation(HerDbContext dbContext, OutputProvider outputProvider) this.outputProvider = outputProvider; } - public List GetUsersWithLocalAuthorities() + public List GetUsersWithLocalAuthoritiesAndConsortia() { return dbContext.Users .Include(user => user.LocalAuthorities) + .Include(user => user.Consortia) .ToList(); } @@ -131,4 +132,33 @@ public void AddLasToUser(User user, List localAuthorities) dbContextTransaction.Rollback(); } } + + public void AddConsortiaToUser(User user, List consortia) + { + using var dbContextTransaction = dbContext.Database.BeginTransaction(); + try + { + foreach (var consortium in consortia) + { + try + { + user?.Consortia.Add(consortium); + } + catch (Exception e) + { + outputProvider.Output(e.Message); + throw; + } + } + + dbContext.SaveChanges(); + outputProvider.Output("Operation successful"); + dbContextTransaction.Commit(); + } + catch (Exception e) + { + outputProvider.Output($"Rollback following error in transaction: {e.InnerException?.Message}"); + dbContextTransaction.Rollback(); + } + } } \ No newline at end of file diff --git a/HerPortal.ManagementShell/IDatabaseOperation.cs b/HerPortal.ManagementShell/IDatabaseOperation.cs index 8dcdea2..dc92bfc 100644 --- a/HerPortal.ManagementShell/IDatabaseOperation.cs +++ b/HerPortal.ManagementShell/IDatabaseOperation.cs @@ -4,11 +4,12 @@ namespace HerPortal.ManagementShell; public interface IDatabaseOperation { - public List GetUsersWithLocalAuthorities(); + public List GetUsersWithLocalAuthoritiesAndConsortia(); public List GetLas(string[] custodianCodes); public List GetConsortia(string[] consortiumCodes); public void RemoveUserOrLogError(User user); public void CreateUserOrLogError(string userEmailAddress, List localAuthorities, List consortia); public void AddLasToUser(User user, List localAuthorities); + public void AddConsortiaToUser(User user, List consortia); public void RemoveLasFromUser(User user, List localAuthorities); } \ No newline at end of file diff --git a/HerPortal.ManagementShell/Program.cs b/HerPortal.ManagementShell/Program.cs index ca5137d..701a681 100644 --- a/HerPortal.ManagementShell/Program.cs +++ b/HerPortal.ManagementShell/Program.cs @@ -11,7 +11,8 @@ private enum Subcommand { AddLas, RemoveLas, - RemoveUser + RemoveUser, + AddConsortia } public static void Main(string[] args) { @@ -28,13 +29,13 @@ public static void Main(string[] args) Subcommand command; var userEmailAddress = ""; - var custodianCodes = Array.Empty(); + var codes = Array.Empty(); try { command = Enum.Parse(args[0], true); userEmailAddress = args[1]; - custodianCodes = args.Skip(2).ToArray(); + codes = args.Skip(2).ToArray(); } catch (Exception) { @@ -50,11 +51,14 @@ public static void Main(string[] args) adminAction.TryRemoveUser(adminAction.GetUser(userEmailAddress)); return; case Subcommand.RemoveLas: - adminAction.RemoveLas(adminAction.GetUser(userEmailAddress), custodianCodes); + adminAction.RemoveLas(adminAction.GetUser(userEmailAddress), codes); return; case Subcommand.AddLas: - adminAction.CreateOrUpdateUserWithLas(userEmailAddress, custodianCodes); + adminAction.CreateOrUpdateUserWithLas(userEmailAddress, codes); return; + case Subcommand.AddConsortia: + adminAction.CreateOrUpdateUserWithConsortia(userEmailAddress, codes); + break; default: outputProvider.Output("Invalid terminal command entered. Please refer to the documentation"); return; diff --git a/HerPortal.UnitTests/Builders/UserBuilder.cs b/HerPortal.UnitTests/Builders/UserBuilder.cs index 18f4f48..823bd8e 100644 --- a/HerPortal.UnitTests/Builders/UserBuilder.cs +++ b/HerPortal.UnitTests/Builders/UserBuilder.cs @@ -29,6 +29,12 @@ public UserBuilder WithLocalAuthorities(List localAuthorities) return this; } + public UserBuilder WithConsortia(List consortia) + { + user.Consortia = consortia; + return this; + } + public UserBuilder WithHasLoggedIn(bool hasLoggedIn) { user.HasLoggedIn = hasLoggedIn; diff --git a/HerPortal.UnitTests/ManagementShell/AdminActionTests.cs b/HerPortal.UnitTests/ManagementShell/AdminActionTests.cs index 9226afb..8128530 100644 --- a/HerPortal.UnitTests/ManagementShell/AdminActionTests.cs +++ b/HerPortal.UnitTests/ManagementShell/AdminActionTests.cs @@ -23,7 +23,7 @@ public void Setup() } [Test] - public void FindsExistingUserCaseInsensitively_IfInDatabase() + public void GetUser_FindsExistingUserCaseInsensitively_IfInDatabase() { // Arrange var users = new List @@ -32,7 +32,7 @@ public void FindsExistingUserCaseInsensitively_IfInDatabase() new UserBuilder("existinguser2@email.com").Build() }; const string userEmailAddress = "ExistingUser@email.com"; - mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthorities()).Returns(users); + mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); // Act var returnedUser = underTest.GetUser(userEmailAddress); @@ -42,7 +42,7 @@ public void FindsExistingUserCaseInsensitively_IfInDatabase() } [Test] - public void ConfirmsCustodianCodesWhenUpdating() + public void CreateOrUpdateUserWithLas_ConfirmsCustodianCodesWhenUpdating() { // Arrange const string userEmailAddress = "existinguser@email.com"; @@ -50,7 +50,7 @@ public void ConfirmsCustodianCodesWhenUpdating() { new UserBuilder("existinguser@email.com").Build() }; - mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthorities()).Returns(users); + mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); var custodianCodes = new[] { "9052", "2525" }; SetupConfirmCustodianCodes(custodianCodes, userEmailAddress, true); @@ -62,7 +62,7 @@ public void ConfirmsCustodianCodesWhenUpdating() } [Test] - public void CreatesNewUser_IfUserNotFoundByDbOperation() + public void CreateOrUpdateUserWithLas_CreatesNewUser_IfUserNotFoundByDbOperation() { // Arrange const string userEmailAddress = "newuser@email.com"; @@ -70,7 +70,7 @@ public void CreatesNewUser_IfUserNotFoundByDbOperation() { new UserBuilder("existinguser@email.com").Build() }; - mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthorities()).Returns(users); + mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); var custodianCodes = new[] { "9052"}; SetupConfirmCustodianCodes(custodianCodes, userEmailAddress, true); var las = new List @@ -92,7 +92,7 @@ public void CreatesNewUser_IfUserNotFoundByDbOperation() } [Test] - public void AddsLasToExistingUser_IfUserFoundByDbOperation() + public void CreateOrUpdateUserWithLas_AddsLasToExistingUser_IfUserFoundByDbOperation() { // Arrange var currentLa = new List @@ -109,7 +109,7 @@ public void AddsLasToExistingUser_IfUserFoundByDbOperation() { new UserBuilder("existinguser@email.com").WithLocalAuthorities(currentLa).Build() }; - mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthorities()).Returns(users); + mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); var custodianCodes = new[] { "9052"}; SetupConfirmCustodianCodes(custodianCodes, userEmailAddress, true); @@ -132,7 +132,7 @@ public void AddsLasToExistingUser_IfUserFoundByDbOperation() } [Test] - public void RemovesLasFromExistingUser_IfUserFoundByDbOperation() + public void RemoveLas_RemovesLasFromExistingUser_IfUserFoundByDbOperation() { // Arrange var laToRemove = new LocalAuthority @@ -150,7 +150,7 @@ public void RemovesLasFromExistingUser_IfUserFoundByDbOperation() var userEmailAddress = "existinguser@email.com"; var user = new UserBuilder(userEmailAddress).WithLocalAuthorities(new List { laToRemove, laToKeep }).Build(); var users = new List { user }; - mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthorities()).Returns(users); + mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); var custodianCodes = new[] { laToRemove.CustodianCode }; SetupConfirmCustodianCodes(custodianCodes, userEmailAddress, true); @@ -163,14 +163,14 @@ public void RemovesLasFromExistingUser_IfUserFoundByDbOperation() } [Test] - public void RemoveUser_IfUserFound_WhenThereIsDeletionConfirmation() + public void TryRemoveUser_IfUserFound_WhenThereIsDeletionConfirmation() { // Arrange var users = new List { new UserBuilder("existinguser@email.com").Build() }; - mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthorities()).Returns(users); + mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); mockOutputProvider.Setup(mock => mock.Confirm(It.IsAny())).Returns(true); // Act @@ -181,7 +181,7 @@ public void RemoveUser_IfUserFound_WhenThereIsDeletionConfirmation() } [Test] - public void DisplaysErrorMessage_WhenNoLasSpecified_IfUserExists() + public void CreateOrUpdateUserWithLas_DisplaysErrorMessage_WhenNoLasSpecified_IfUserExists() { // Arrange var userEmailAddress = "existinguser@email.com"; @@ -189,7 +189,7 @@ public void DisplaysErrorMessage_WhenNoLasSpecified_IfUserExists() { new UserBuilder("existinguser@email.com").Build() }; - mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthorities()).Returns(users); + mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); SetupConfirmCustodianCodes(Array.Empty(), userEmailAddress, true); // Act @@ -200,7 +200,7 @@ public void DisplaysErrorMessage_WhenNoLasSpecified_IfUserExists() } [Test] - public void DisplaysErrorMessage_IfCustodianCodeToRemove_DoesNotMatchAnyOfExistingUsersLas() + public void RemoveLas_DisplaysErrorMessage_IfCustodianCodeToRemove_DoesNotMatchAnyOfExistingUsersLas() { // Arrange var laToRemove = new LocalAuthority @@ -218,7 +218,7 @@ public void DisplaysErrorMessage_IfCustodianCodeToRemove_DoesNotMatchAnyOfExisti var userEmailAddress = "existinguser@email.com"; var user = new UserBuilder(userEmailAddress).WithLocalAuthorities(new List { usersCurrentLa }).Build(); var users = new List { user }; - mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthorities()).Returns(users); + mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); var custodianCodes = new[] { laToRemove.CustodianCode }; SetupConfirmCustodianCodes(custodianCodes, userEmailAddress, true); @@ -231,7 +231,7 @@ public void DisplaysErrorMessage_IfCustodianCodeToRemove_DoesNotMatchAnyOfExisti } [Test] - public void DisplaysInnerExceptionMessage_IfCustodianCode_IsNotFoundInDict() + public void CreateOrUpdateUserWithLas_DisplaysInnerExceptionMessage_IfCustodianCode_IsNotFoundInDict() { // Arrange const string userEmailAddress = "existinguser@email.com"; @@ -239,18 +239,19 @@ public void DisplaysInnerExceptionMessage_IfCustodianCode_IsNotFoundInDict() { new UserBuilder("existinguser@email.com").Build() }; - mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthorities()).Returns(users); + mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); var listWithCustodianCodeNotInDict = new [] { "1111" }; // Act underTest.CreateOrUpdateUserWithLas(userEmailAddress, listWithCustodianCodeNotInDict); // Assert - mockOutputProvider.Verify(mock => mock.Output(It.IsAny())); + mockOutputProvider.Verify(mock => + mock.Output("The given key '1111' was not present in the dictionary. Process terminated")); } [Test] - public void DisplaysErrorMessage_WhenRemovingLas_IfUserNotInDatabase() + public void RemoveLas_DisplaysErrorMessage_WhenRemovingLas_IfUserNotInDatabase() { // Arrange const string userEmailAddress = "usernotindb@email.com"; @@ -258,7 +259,7 @@ public void DisplaysErrorMessage_WhenRemovingLas_IfUserNotInDatabase() { new UserBuilder("userindb@email.com").Build() }; - mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthorities()).Returns(users); + mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); var custodianCodes = new[] { "9052"}; SetupConfirmCustodianCodes(custodianCodes, userEmailAddress, false); var lasToAdd = new List @@ -278,7 +279,7 @@ public void DisplaysErrorMessage_WhenRemovingLas_IfUserNotInDatabase() } [Test] - public void DoesNotCallDbOperation_IfConfirmKeyNotPressed() + public void CreateOrUpdateUserWithLas_DoesNotCallDbOperation_IfConfirmKeyNotPressed() { // Arrange const string userEmailAddress = "newuser@email.com"; @@ -286,7 +287,7 @@ public void DoesNotCallDbOperation_IfConfirmKeyNotPressed() { new UserBuilder("existinguser@email.com").Build() }; - mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthorities()).Returns(users); + mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); var custodianCodes = new[] { "9052"}; SetupConfirmCustodianCodes(custodianCodes, userEmailAddress, false); var lasToAdd = new List @@ -308,7 +309,7 @@ public void DoesNotCallDbOperation_IfConfirmKeyNotPressed() } [Test] - public void AsksForConfirmation() + public void CreateOrUpdateUserWithLas_AsksForConfirmation() { // Arrange const string userEmailAddress = "newuser@email.com"; @@ -316,7 +317,7 @@ public void AsksForConfirmation() { new UserBuilder("existinguser@email.com").Build() }; - mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthorities()).Returns(users); + mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); var custodianCodes = new[] { "9052"}; // Act @@ -327,7 +328,7 @@ public void AsksForConfirmation() } [Test] - public void DisplaysCorrectUserStatus_WhenUserActive() + public void CreateOrUpdateUserWithLas_DisplaysCorrectUserStatus_WhenUserActive() { // Arrange const string userEmailAddress = "existinguser@email.com"; @@ -335,7 +336,7 @@ public void DisplaysCorrectUserStatus_WhenUserActive() { new UserBuilder("existinguser@email.com").Build() }; - mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthorities()).Returns(users); + mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); var custodianCodes = new[] { "9052"}; // Act underTest.CreateOrUpdateUserWithLas(userEmailAddress, custodianCodes); @@ -345,7 +346,7 @@ public void DisplaysCorrectUserStatus_WhenUserActive() } [Test] - public void DisplaysCorrectUserStatus_WhenUserInactive() + public void CreateOrUpdateUserWithLas_DisplaysCorrectUserStatus_WhenUserInactive() { // Arrange const string userEmailAddress = "newuser@email.com"; @@ -353,7 +354,7 @@ public void DisplaysCorrectUserStatus_WhenUserInactive() { new UserBuilder("existinguser@email.com").Build() }; - mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthorities()).Returns(users); + mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); var custodianCodes = new[] { "9052"}; // Act underTest.CreateOrUpdateUserWithLas(userEmailAddress, custodianCodes); @@ -375,4 +376,237 @@ private void SetupConfirmCustodianCodes(IEnumerable custodianCodes, stri mockOutputProvider.Setup(op => op.Confirm("Please confirm (y/n)")).Returns(confirmation); } + + [Test] + public void CreateOrUpdateUserWithConsortia_ConfirmsCustodianCodesWhenUpdating() + { + // Arrange + const string userEmailAddress = "existinguser@email.com"; + var users = new List + { + new UserBuilder("existinguser@email.com").Build() + }; + mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); + var custodianCodes = new[] { "C_0002", "C_0003" }; + SetupConfirmCustodianCodes(custodianCodes, userEmailAddress, true); + + // Act + underTest.CreateOrUpdateUserWithConsortia(userEmailAddress, custodianCodes); + + // Assert + mockOutputProvider.Verify(mock => mock.Confirm(It.IsAny()), Times.Once()); + } + + [Test] + public void CreateOrUpdateUserWithConsortia_CreatesNewUser_IfUserNotFoundByDbOperation() + { + // Arrange + const string userEmailAddress = "newuser@email.com"; + var users = new List + { + new UserBuilder("existinguser@email.com").Build() + }; + mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); + var consortiumCodes = new[] { "C_0002" }; + SetupConfirmCustodianCodes(consortiumCodes, userEmailAddress, true); + var consortia = new List + { + new() + { + Id = 1, + ConsortiumCode = "C_0002" + } + }; + mockDatabaseOperation.Setup(mock => mock.GetConsortia(consortiumCodes)).Returns(consortia); + + // Act + underTest.CreateOrUpdateUserWithConsortia(userEmailAddress, consortiumCodes); + + // Assert + mockDatabaseOperation.Verify( + mock => mock.CreateUserOrLogError(userEmailAddress, It.IsAny>(), consortia), Times.Once()); + } + + [Test] + public void CreateOrUpdateUserWithConsortia_AddsLasToExistingUser_IfUserFoundByDbOperation() + { + // Arrange + var currentConsortia = new List + { + new() + { + Id = 2, + ConsortiumCode = "C_0002" + } + }; + const string userEmailAddress = "existinguser@email.com"; + + var users = new List + { + new UserBuilder("existinguser@email.com").WithConsortia(currentConsortia).Build() + }; + mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); + + var custodianCodes = new[] { "C_0002" }; + SetupConfirmCustodianCodes(custodianCodes, userEmailAddress, true); + + var consortiaToAdd = new List + { + new() + { + Id = 1, + ConsortiumCode = "C_0003" + } + }; + mockDatabaseOperation.Setup(mock => mock.GetConsortia(custodianCodes)).Returns(consortiaToAdd); + + // Act + underTest.CreateOrUpdateUserWithConsortia(userEmailAddress, custodianCodes); + + // Assert + mockDatabaseOperation.Verify(mock => mock.AddConsortiaToUser(users[0], consortiaToAdd)); + } + + + [Test] + public void CreateOrUpdateUserWithConsortia_DisplaysErrorMessage_WhenNoLasSpecified_IfUserExists() + { + // Arrange + var userEmailAddress = "existinguser@email.com"; + var users = new List + { + new UserBuilder("existinguser@email.com").Build() + }; + mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); + SetupConfirmCustodianCodes(Array.Empty(), userEmailAddress, true); + + // Act + underTest.CreateOrUpdateUserWithConsortia(userEmailAddress, Array.Empty()); + + // Assert + mockOutputProvider.Verify(mock => mock.Output(It.IsAny())); + } + + + [Test] + public void CreateOrUpdateUserWithConsortia_DisplaysInnerExceptionMessage_IfCustodianCode_IsNotFoundInDict() + { + // Arrange + const string userEmailAddress = "existinguser@email.com"; + var users = new List + { + new UserBuilder("existinguser@email.com").Build() + }; + mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); + var listWithCustodianCodeNotInDict = new [] { "C_1111" }; + + // Act + underTest.CreateOrUpdateUserWithConsortia(userEmailAddress, listWithCustodianCodeNotInDict); + + // Assert + mockOutputProvider.Verify(mock => + mock.Output("The given key 'C_1111' was not present in the dictionary. Process terminated")); + } + + + [Test] + public void CreateOrUpdateUserWithConsortia_DoesNotCallDbOperation_IfConfirmKeyNotPressed() + { + // Arrange + const string userEmailAddress = "newuser@email.com"; + var users = new List + { + new UserBuilder("existinguser@email.com").Build() + }; + mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); + var consortiumCodes = new[] { "C_0002" }; + SetupConfirmConsortiumCodes(consortiumCodes, userEmailAddress, false); + var consortiaToAdd = new List + { + new() + { + Id = 1, + ConsortiumCode = "C_0002" + } + }; + + // Act + underTest.CreateOrUpdateUserWithConsortia(userEmailAddress, consortiumCodes); + + // Assert + mockOutputProvider.Verify(mock => mock.Output("Process cancelled, no changes were made to the database")); + mockDatabaseOperation.Verify( + mock => mock.CreateUserOrLogError(userEmailAddress, It.IsAny>(), consortiaToAdd), Times.Never()); + + } + + [Test] + public void CreateOrUpdateUserWithConsortia_AsksForConfirmation() + { + // Arrange + const string userEmailAddress = "newuser@email.com"; + var users = new List + { + new UserBuilder("existinguser@email.com").Build() + }; + mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); + var consortiumCodes = new[] { "C_0002" }; + + // Act + underTest.CreateOrUpdateUserWithConsortia(userEmailAddress, consortiumCodes); + + // Assert + mockOutputProvider.Verify(mock => mock.Confirm("Please confirm (y/n)"), Times.Once); + } + + [Test] + public void CreateOrUpdateUserWithConsortia_DisplaysCorrectUserStatus_WhenUserActive() + { + // Arrange + const string userEmailAddress = "existinguser@email.com"; + var users = new List + { + new UserBuilder("existinguser@email.com").Build() + }; + + mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); + var consortiumCodes = new[] { "C_0002" }; + // Act + underTest.CreateOrUpdateUserWithConsortia(userEmailAddress, consortiumCodes); + + // Assert + mockOutputProvider.Verify(mock => mock.Output("User found in database. LAs will be added to their account"), Times.Once()); + } + + [Test] + public void CreateOrUpdateUserWithConsortia_DisplaysCorrectUserStatus_WhenUserInactive() + { + // Arrange + const string userEmailAddress = "newuser@email.com"; + var users = new List + { + new UserBuilder("existinguser@email.com").Build() + }; + mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); + var consortiumCodes = new[] { "C_0002" }; + // Act + underTest.CreateOrUpdateUserWithConsortia(userEmailAddress, consortiumCodes); + + // Assert + mockOutputProvider.Verify(mock => mock.Output("User not found in database. A new user will be created"), Times.Once()); + } + + private void SetupConfirmConsortiumCodes(IEnumerable consortiumCodes, string userEmailAddress, bool confirmation) + { + mockOutputProvider + .Setup(op => + op.Output( + $"You are changing permissions for user {userEmailAddress} for the following consortiums: ")); + foreach (var code in consortiumCodes) + { + mockOutputProvider.Setup(op => op.Output("Code: Consortium")); + } + + mockOutputProvider.Setup(op => op.Confirm("Please confirm (y/n)")).Returns(confirmation); + } } \ No newline at end of file From 5e534bacb4b8a88cfe89e23f96e298f24dfc0fcb Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Tue, 28 May 2024 15:30:24 +0100 Subject: [PATCH 11/81] PC-1085: add warnings when managing LA access --- HerPortal.ManagementShell/AdminAction.cs | 66 ++++++++++++---- HerPortal.UnitTests/Builders/UserBuilder.cs | 3 +- .../ManagementShell/AdminActionTests.cs | 77 ++++++++++++++++++- 3 files changed, 129 insertions(+), 17 deletions(-) diff --git a/HerPortal.ManagementShell/AdminAction.cs b/HerPortal.ManagementShell/AdminAction.cs index 08c7f1d..88a4bab 100644 --- a/HerPortal.ManagementShell/AdminAction.cs +++ b/HerPortal.ManagementShell/AdminAction.cs @@ -12,8 +12,10 @@ public enum UserStatus private readonly IDatabaseOperation dbOperation; private readonly IOutputProvider outputProvider; - private readonly Dictionary custodianCodeToLaDict = LocalAuthorityData.LocalAuthorityNamesByCustodianCode; - private readonly Dictionary consortiumCodeToConsortiumDict = ConsortiumData.ConsortiumNamesByConsortiumCode; + private readonly Dictionary custodianCodeToLaNameDict = LocalAuthorityData.LocalAuthorityNamesByCustodianCode; + private readonly Dictionary custodianCodeToConsortiumCodeDict = LocalAuthorityData.LocalAuthorityConsortiumCodeByCustodianCode; + private readonly Dictionary consortiumCodeToConsortiumNameDict = ConsortiumData.ConsortiumNamesByConsortiumCode; + private readonly Dictionary> consortiumCodeToCustodianCodesDict = ConsortiumData.ConsortiumCustodianCodesIdsByConsortiumCode; public AdminAction(IDatabaseOperation dbOperation, IOutputProvider outputProvider) { @@ -33,9 +35,9 @@ public AdminAction(IDatabaseOperation dbOperation, IOutputProvider outputProvide )); } - private void PrintCodes(string[] codes, Dictionary codeToNameDict) + private void PrintCodes(IReadOnlyCollection codes, IReadOnlyDictionary codeToNameDict) { - if (codes.Length < 1) + if (codes.Count < 1) { outputProvider.Output("(None)"); } @@ -47,14 +49,33 @@ private void PrintCodes(string[] codes, Dictionary codeToNameDic } } - private bool ConfirmCustodianCodes(string userEmailAddress, string[] codes) + private bool ConfirmCustodianCodes(string userEmailAddress, string[] custodianCodes, User? user) { outputProvider.Output( - $"You are changing permissions for user {userEmailAddress} for the following local authorities:"); + $"You are changing permissions for user {userEmailAddress} for the following Local Authorities:"); try { - PrintCodes(codes, custodianCodeToLaDict); + if (user != null) + { + // flag the need to not add LAs that are in consortia the user owns + var custodianCodesOfConsortia = user.Consortia + .SelectMany(consortium => + consortiumCodeToCustodianCodesDict[consortium.ConsortiumCode]); + var custodianCodeInOwnedConsortiumGrouping = custodianCodes.ToLookup(custodianCodesOfConsortia.Contains); + var custodianCodesNotInOwnedConsortium = custodianCodeInOwnedConsortiumGrouping[false].ToList(); + var custodianCodesInOwnedConsortium = custodianCodeInOwnedConsortiumGrouping[true].ToList(); + + outputProvider.Output("Add the following Local Authorities:"); + PrintCodes(custodianCodesNotInOwnedConsortium, custodianCodeToLaNameDict); + outputProvider.Output("Ignore the following Local Authorities already in owned Consortia:"); + PrintCodes(custodianCodesInOwnedConsortium, custodianCodeToLaNameDict); + } + else + { + outputProvider.Output("Add the following Local Authorities:"); + PrintCodes(custodianCodes, custodianCodeToLaNameDict); + } } catch (Exception e) { @@ -71,14 +92,33 @@ private bool ConfirmCustodianCodes(string userEmailAddress, string[] codes) return hasUserConfirmed; } - private bool ConfirmConsortiumCodes(string? userEmailAddress, string[] codes) + private bool ConfirmConsortiumCodes(string? userEmailAddress, string[] consortiumCodes, User? user) { outputProvider.Output( - $"You are changing permissions for user {userEmailAddress} for the following consortiums:"); + $"You are changing permissions for user {userEmailAddress} for the following Consortia:"); try { - PrintCodes(codes, consortiumCodeToConsortiumDict); + if (user != null) + { + outputProvider.Output("Add the following Consortia:"); + PrintCodes(consortiumCodes, consortiumCodeToConsortiumNameDict); + + // flag the need to remove access for any LAs in the new consortia + var ownedLaInConsortia = user.LocalAuthorities + .Where(localAuthority => + consortiumCodes.Contains(custodianCodeToConsortiumCodeDict[localAuthority.CustodianCode])) + .Select(localAuthority => localAuthority.CustodianCode) + .ToList(); + + outputProvider.Output("Remove the following Local Authorities in these Consortia:"); + PrintCodes(ownedLaInConsortia, custodianCodeToLaNameDict); + } + else + { + outputProvider.Output("Add the following Consortia:"); + PrintCodes(consortiumCodes, consortiumCodeToConsortiumNameDict); + } } catch (Exception e) { @@ -169,7 +209,7 @@ public void RemoveLas(User? user, string[]? custodianCodes) return; } - var userConfirmation = ConfirmCustodianCodes(user.EmailAddress, custodianCodes); + var userConfirmation = ConfirmCustodianCodes(user.EmailAddress, custodianCodes, user); if (!userConfirmation) { return; @@ -231,7 +271,7 @@ public void CreateOrUpdateUserWithLas(string? userEmailAddress, string[] custodi var (user, userStatus) = SetupUser(userEmailAddress); - var confirmation = ConfirmCustodianCodes(userEmailAddress, custodianCodes); + var confirmation = ConfirmCustodianCodes(userEmailAddress, custodianCodes, user); if (confirmation) { @@ -256,7 +296,7 @@ public void CreateOrUpdateUserWithConsortia(string? userEmailAddress, string[] c var (user, userStatus) = SetupUser(userEmailAddress); - var confirmation = ConfirmConsortiumCodes(userEmailAddress, consortiumCodes); + var confirmation = ConfirmConsortiumCodes(userEmailAddress, consortiumCodes, user); if (confirmation) { diff --git a/HerPortal.UnitTests/Builders/UserBuilder.cs b/HerPortal.UnitTests/Builders/UserBuilder.cs index 823bd8e..de92b4d 100644 --- a/HerPortal.UnitTests/Builders/UserBuilder.cs +++ b/HerPortal.UnitTests/Builders/UserBuilder.cs @@ -14,7 +14,8 @@ public UserBuilder(string emailAddress) Id = 13, EmailAddress = emailAddress, HasLoggedIn = true, - LocalAuthorities = new List() + LocalAuthorities = new List(), + Consortia = new List() }; } diff --git a/HerPortal.UnitTests/ManagementShell/AdminActionTests.cs b/HerPortal.UnitTests/ManagementShell/AdminActionTests.cs index 8128530..c429472 100644 --- a/HerPortal.UnitTests/ManagementShell/AdminActionTests.cs +++ b/HerPortal.UnitTests/ManagementShell/AdminActionTests.cs @@ -111,7 +111,7 @@ public void CreateOrUpdateUserWithLas_AddsLasToExistingUser_IfUserFoundByDbOpera }; mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); - var custodianCodes = new[] { "9052"}; + var custodianCodes = new[] { "9052" }; SetupConfirmCustodianCodes(custodianCodes, userEmailAddress, true); var lasToAdd = new List @@ -355,7 +355,7 @@ public void CreateOrUpdateUserWithLas_DisplaysCorrectUserStatus_WhenUserInactive new UserBuilder("existinguser@email.com").Build() }; mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); - var custodianCodes = new[] { "9052"}; + var custodianCodes = new[] { "9052" }; // Act underTest.CreateOrUpdateUserWithLas(userEmailAddress, custodianCodes); @@ -363,16 +363,41 @@ public void CreateOrUpdateUserWithLas_DisplaysCorrectUserStatus_WhenUserInactive mockOutputProvider.Verify(mock => mock.Output("User not found in database. A new user will be created"), Times.Once()); } + [Test] + public void CreateOrUpdateUserWithLas_WhenGivenLocalAuthorities_PrintsThem() + { + // Arrange + const string userEmailAddress = "newuser@email.com"; + var users = new List + { + new UserBuilder("existinguser@email.com").Build() + }; + mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); + var custodianCodes = new[] { "9052", "3805" }; + // Act + underTest.CreateOrUpdateUserWithLas(userEmailAddress, custodianCodes); + + // Assert + mockOutputProvider.Verify(mock => mock.Output("9052: Aberdeenshire"), Times.Once()); + mockOutputProvider.Verify(mock => mock.Output("3805: Adur"), Times.Once()); + } + private void SetupConfirmCustodianCodes(IEnumerable custodianCodes, string userEmailAddress, bool confirmation) { mockOutputProvider .Setup(op => op.Output( - $"You are changing permissions for user {userEmailAddress} for the following local authorities: ")); + $"You are changing permissions for user {userEmailAddress} for the following Local Authorities:")); + mockOutputProvider + .Setup(op => op.Output("Add the following Local Authorities:")); foreach (var code in custodianCodes) { mockOutputProvider.Setup(op => op.Output("Code: Local Authority")); } + mockOutputProvider + .Setup(op => + op.Output("Ignore the following Local Authorities already in owned Consortia:")); + mockOutputProvider.Setup(op => op.Output("(None)")); mockOutputProvider.Setup(op => op.Confirm("Please confirm (y/n)")).Returns(confirmation); } @@ -596,6 +621,52 @@ public void CreateOrUpdateUserWithConsortia_DisplaysCorrectUserStatus_WhenUserIn mockOutputProvider.Verify(mock => mock.Output("User not found in database. A new user will be created"), Times.Once()); } + [Test] + public void CreateOrUpdateUserWithConsortia_WhenGivenConsortia_PrintsThem() + { + // Arrange + const string userEmailAddress = "newuser@email.com"; + var users = new List + { + new UserBuilder("existinguser@email.com").Build() + }; + mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); + var consortiumCodes = new[] { "C_0002", "C_0003" }; + // Act + underTest.CreateOrUpdateUserWithConsortia(userEmailAddress, consortiumCodes); + + // Assert + mockOutputProvider.Verify(mock => mock.Output("C_0002: Blackpool"), Times.Once()); + mockOutputProvider.Verify(mock => mock.Output("C_0003: Bristol"), Times.Once()); + } + + [Test] + public void CreateOrUpdateUserWithConsortia_WhenUserOwnsLocalAuthorityInConsortium_PrintsThem() + { + // Arrange + var currentLa = new List + { + new() + { + Id = 2, + CustodianCode = "2372" + } + }; + + const string userEmailAddress = "existinguser@email.com"; + var users = new List + { + new UserBuilder("existinguser@email.com").WithLocalAuthorities(currentLa).Build() + }; + mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); + var consortiumCodes = new[] { "C_0002" }; + // Act + underTest.CreateOrUpdateUserWithConsortia(userEmailAddress, consortiumCodes); + + // Assert + mockOutputProvider.Verify(mock => mock.Output("2372: Blackburn With Darwen"), Times.Once()); + } + private void SetupConfirmConsortiumCodes(IEnumerable consortiumCodes, string userEmailAddress, bool confirmation) { mockOutputProvider From 3cc32da5bd901858706724465b68f303e2a07595 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Tue, 28 May 2024 16:00:04 +0100 Subject: [PATCH 12/81] PC-1085: update database operations dont add the filtered LAs if in a consortium remove LAs when adding the consortium theyre in --- HerPortal.ManagementShell/AdminAction.cs | 42 +++++++++++++------ .../DatabaseOperation.cs | 29 ++++++++++++- .../IDatabaseOperation.cs | 5 ++- 3 files changed, 60 insertions(+), 16 deletions(-) diff --git a/HerPortal.ManagementShell/AdminAction.cs b/HerPortal.ManagementShell/AdminAction.cs index 88a4bab..168412d 100644 --- a/HerPortal.ManagementShell/AdminAction.cs +++ b/HerPortal.ManagementShell/AdminAction.cs @@ -59,10 +59,8 @@ private bool ConfirmCustodianCodes(string userEmailAddress, string[] custodianCo if (user != null) { // flag the need to not add LAs that are in consortia the user owns - var custodianCodesOfConsortia = user.Consortia - .SelectMany(consortium => - consortiumCodeToCustodianCodesDict[consortium.ConsortiumCode]); - var custodianCodeInOwnedConsortiumGrouping = custodianCodes.ToLookup(custodianCodesOfConsortia.Contains); + var custodianCodeInOwnedConsortiumGrouping = custodianCodes.ToLookup(custodianCode => + CustodianCodeIsInOwnedConsortium(user, custodianCode)); var custodianCodesNotInOwnedConsortium = custodianCodeInOwnedConsortiumGrouping[false].ToList(); var custodianCodesInOwnedConsortium = custodianCodeInOwnedConsortiumGrouping[true].ToList(); @@ -92,6 +90,14 @@ private bool ConfirmCustodianCodes(string userEmailAddress, string[] custodianCo return hasUserConfirmed; } + private bool CustodianCodeIsInOwnedConsortium(User user, string custodianCode) + { + var custodianCodesOfConsortia = user.Consortia + .SelectMany(consortium => + consortiumCodeToCustodianCodesDict[consortium.ConsortiumCode]); + return custodianCodesOfConsortia.Contains(custodianCode); + } + private bool ConfirmConsortiumCodes(string? userEmailAddress, string[] consortiumCodes, User? user) { outputProvider.Output( @@ -105,14 +111,10 @@ private bool ConfirmConsortiumCodes(string? userEmailAddress, string[] consortiu PrintCodes(consortiumCodes, consortiumCodeToConsortiumNameDict); // flag the need to remove access for any LAs in the new consortia - var ownedLaInConsortia = user.LocalAuthorities - .Where(localAuthority => - consortiumCodes.Contains(custodianCodeToConsortiumCodeDict[localAuthority.CustodianCode])) - .Select(localAuthority => localAuthority.CustodianCode) - .ToList(); + var ownedCustodianCodesInConsortia = GetOwnedCustodianCodesInConsortia(user, consortiumCodes); outputProvider.Output("Remove the following Local Authorities in these Consortia:"); - PrintCodes(ownedLaInConsortia, custodianCodeToLaNameDict); + PrintCodes(ownedCustodianCodesInConsortia, custodianCodeToLaNameDict); } else { @@ -135,6 +137,15 @@ private bool ConfirmConsortiumCodes(string? userEmailAddress, string[] consortiu return hasUserConfirmed; } + private List GetOwnedCustodianCodesInConsortia(User user, string[] consortiumCodes) + { + return user.LocalAuthorities + .Where(localAuthority => + consortiumCodes.Contains(custodianCodeToConsortiumCodeDict[localAuthority.CustodianCode])) + .Select(localAuthority => localAuthority.CustodianCode) + .ToList(); + } + private void DisplayUserStatus(Enum status) { switch (status) @@ -191,7 +202,10 @@ private void AddLas(User? user, string[]? custodianCodes) return; } - var lasToAdd = dbOperation.GetLas(custodianCodes); + var filteredCustodianCodes = custodianCodes + .Where(custodianCode => !CustodianCodeIsInOwnedConsortium(user, custodianCode)); + + var lasToAdd = dbOperation.GetLas(filteredCustodianCodes); dbOperation.AddLasToUser(user, lasToAdd); } @@ -241,7 +255,11 @@ private void AddConsortia(User? user, string[]? consortiumCodes) } var consortiaToAdd = dbOperation.GetConsortia(consortiumCodes); - dbOperation.AddConsortiaToUser(user, consortiaToAdd); + + var ownedCustodianCodesInConsortia = GetOwnedCustodianCodesInConsortia(user, consortiumCodes); + var lasToRemove = dbOperation.GetLas(ownedCustodianCodesInConsortia); + + dbOperation.AddConsortiaAndRemoveLasFromUser(user, consortiaToAdd, lasToRemove); } private (User? user, UserStatus userStatus) SetupUser(string userEmailAddress) diff --git a/HerPortal.ManagementShell/DatabaseOperation.cs b/HerPortal.ManagementShell/DatabaseOperation.cs index b90586b..31a38c4 100644 --- a/HerPortal.ManagementShell/DatabaseOperation.cs +++ b/HerPortal.ManagementShell/DatabaseOperation.cs @@ -25,7 +25,7 @@ public List GetUsersWithLocalAuthoritiesAndConsortia() .ToList(); } - public List GetLas(string[] custodianCodes) + public List GetLas(IEnumerable custodianCodes) { return custodianCodes .Select(code => dbContext.LocalAuthorities @@ -33,7 +33,7 @@ public List GetLas(string[] custodianCodes) .ToList(); } - public List GetConsortia(string[] custodianCodes) + public List GetConsortia(IEnumerable custodianCodes) { return custodianCodes .Select(code => dbContext.Consortia @@ -104,6 +104,31 @@ public void RemoveLasFromUser(User user, List lasToRemove) } } + public void AddConsortiaAndRemoveLasFromUser(User user, List consortia, List localAuthorities) + { + using var dbContextTransaction = dbContext.Database.BeginTransaction(); + try + { + foreach (var consortium in consortia) + { + user?.Consortia.Add(consortium); + } + foreach (var localAuthority in localAuthorities) + { + user?.LocalAuthorities.Remove(localAuthority); + } + + dbContext.SaveChanges(); + outputProvider.Output("Operation successful"); + dbContextTransaction.Commit(); + } + catch (Exception e) + { + outputProvider.Output($"Rollback following error in transaction: {e.InnerException?.Message}"); + dbContextTransaction.Rollback(); + } + } + public void AddLasToUser(User user, List localAuthorities) { using var dbContextTransaction = dbContext.Database.BeginTransaction(); diff --git a/HerPortal.ManagementShell/IDatabaseOperation.cs b/HerPortal.ManagementShell/IDatabaseOperation.cs index dc92bfc..a4e4b30 100644 --- a/HerPortal.ManagementShell/IDatabaseOperation.cs +++ b/HerPortal.ManagementShell/IDatabaseOperation.cs @@ -5,11 +5,12 @@ namespace HerPortal.ManagementShell; public interface IDatabaseOperation { public List GetUsersWithLocalAuthoritiesAndConsortia(); - public List GetLas(string[] custodianCodes); - public List GetConsortia(string[] consortiumCodes); + public List GetLas(IEnumerable custodianCodes); + public List GetConsortia(IEnumerable consortiumCodes); public void RemoveUserOrLogError(User user); public void CreateUserOrLogError(string userEmailAddress, List localAuthorities, List consortia); public void AddLasToUser(User user, List localAuthorities); public void AddConsortiaToUser(User user, List consortia); public void RemoveLasFromUser(User user, List localAuthorities); + public void AddConsortiaAndRemoveLasFromUser(User user, List consortia, List localAuthorities); } \ No newline at end of file From 4b8a863e9e1684df57511b8f49eb536197199901 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Tue, 28 May 2024 16:05:55 +0100 Subject: [PATCH 13/81] PC-1085: consolidate to less restrictive collections of strings --- HerPortal.ManagementShell/AdminAction.cs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/HerPortal.ManagementShell/AdminAction.cs b/HerPortal.ManagementShell/AdminAction.cs index 168412d..6f509ba 100644 --- a/HerPortal.ManagementShell/AdminAction.cs +++ b/HerPortal.ManagementShell/AdminAction.cs @@ -49,7 +49,7 @@ private void PrintCodes(IReadOnlyCollection codes, IReadOnlyDictionary custodianCodes, User? user) { outputProvider.Output( $"You are changing permissions for user {userEmailAddress} for the following Local Authorities:"); @@ -98,7 +98,7 @@ private bool CustodianCodeIsInOwnedConsortium(User user, string custodianCode) return custodianCodesOfConsortia.Contains(custodianCode); } - private bool ConfirmConsortiumCodes(string? userEmailAddress, string[] consortiumCodes, User? user) + private bool ConfirmConsortiumCodes(string? userEmailAddress, IReadOnlyCollection consortiumCodes, User? user) { outputProvider.Output( $"You are changing permissions for user {userEmailAddress} for the following Consortia:"); @@ -137,7 +137,7 @@ private bool ConfirmConsortiumCodes(string? userEmailAddress, string[] consortiu return hasUserConfirmed; } - private List GetOwnedCustodianCodesInConsortia(User user, string[] consortiumCodes) + private List GetOwnedCustodianCodesInConsortia(User user, IEnumerable consortiumCodes) { return user.LocalAuthorities .Where(localAuthority => @@ -164,7 +164,7 @@ private UserStatus GetUserStatus(User? userOrNull) return userOrNull == null ? UserStatus.New : UserStatus.Active; } - private void TryCreateUser(string userEmailAddress, string[]? custodianCodes, string[]? consortiumCodes) + private void TryCreateUser(string userEmailAddress, IEnumerable? custodianCodes, IEnumerable? consortiumCodes) { var lasToAdd = dbOperation.GetLas(custodianCodes ?? Array.Empty()); var consortiaToAdd = dbOperation.GetConsortia(consortiumCodes ?? Array.Empty()); @@ -188,7 +188,7 @@ public void TryRemoveUser(User? user) dbOperation.RemoveUserOrLogError(user); } - private void AddLas(User? user, string[]? custodianCodes) + private void AddLas(User? user, IReadOnlyCollection? custodianCodes) { if (user == null) { @@ -196,7 +196,7 @@ private void AddLas(User? user, string[]? custodianCodes) return; } - if (custodianCodes == null || custodianCodes.Length < 1) + if (custodianCodes == null || custodianCodes.Count < 1) { outputProvider.Output("Please specify custodian codes to add to user"); return; @@ -209,7 +209,7 @@ private void AddLas(User? user, string[]? custodianCodes) dbOperation.AddLasToUser(user, lasToAdd); } - public void RemoveLas(User? user, string[]? custodianCodes) + public void RemoveLas(User? user, IReadOnlyCollection? custodianCodes) { if (user == null) { @@ -217,7 +217,7 @@ public void RemoveLas(User? user, string[]? custodianCodes) return; } - if (custodianCodes == null || custodianCodes.Length < 1) + if (custodianCodes == null || custodianCodes.Count < 1) { outputProvider.Output("Please specify custodian codes to remove from user"); return; @@ -240,7 +240,7 @@ public void RemoveLas(User? user, string[]? custodianCodes) dbOperation.RemoveLasFromUser(user, lasToRemove); } - private void AddConsortia(User? user, string[]? consortiumCodes) + private void AddConsortia(User? user, IReadOnlyCollection? consortiumCodes) { if (user == null) { @@ -248,7 +248,7 @@ private void AddConsortia(User? user, string[]? consortiumCodes) return; } - if (consortiumCodes == null || consortiumCodes.Length < 1) + if (consortiumCodes == null || consortiumCodes.Count < 1) { outputProvider.Output("Please specify consortium codes to add to user"); return; @@ -279,7 +279,7 @@ private void AddConsortia(User? user, string[]? consortiumCodes) return (user, userStatus); } - public void CreateOrUpdateUserWithLas(string? userEmailAddress, string[] custodianCodes) + public void CreateOrUpdateUserWithLas(string? userEmailAddress, IReadOnlyCollection custodianCodes) { if (userEmailAddress == null) { @@ -304,7 +304,7 @@ public void CreateOrUpdateUserWithLas(string? userEmailAddress, string[] custodi } } - public void CreateOrUpdateUserWithConsortia(string? userEmailAddress, string[] consortiumCodes) + public void CreateOrUpdateUserWithConsortia(string? userEmailAddress, IReadOnlyCollection consortiumCodes) { if (userEmailAddress == null) { From dc520897fb2e4182de8eb568693d7bb11a68763c Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Tue, 28 May 2024 16:23:06 +0100 Subject: [PATCH 14/81] PC-1085: allow consortia to be remove --- HerPortal.ManagementShell/AdminAction.cs | 55 ++++++++++++++++--- .../DatabaseOperation.cs | 21 +++++++ .../IDatabaseOperation.cs | 1 + HerPortal.ManagementShell/Program.cs | 6 +- 4 files changed, 75 insertions(+), 8 deletions(-) diff --git a/HerPortal.ManagementShell/AdminAction.cs b/HerPortal.ManagementShell/AdminAction.cs index 6f509ba..0bcbd97 100644 --- a/HerPortal.ManagementShell/AdminAction.cs +++ b/HerPortal.ManagementShell/AdminAction.cs @@ -49,14 +49,19 @@ private void PrintCodes(IReadOnlyCollection codes, IReadOnlyDictionary custodianCodes, User? user) + private bool ConfirmCustodianCodes(string userEmailAddress, IReadOnlyCollection custodianCodes, User? user, bool remove) { outputProvider.Output( $"You are changing permissions for user {userEmailAddress} for the following Local Authorities:"); try { - if (user != null) + if (remove) + { + outputProvider.Output("Remove the following Local Authorities:"); + PrintCodes(custodianCodes, custodianCodeToLaNameDict); + } + else if (user != null) { // flag the need to not add LAs that are in consortia the user owns var custodianCodeInOwnedConsortiumGrouping = custodianCodes.ToLookup(custodianCode => @@ -98,14 +103,19 @@ private bool CustodianCodeIsInOwnedConsortium(User user, string custodianCode) return custodianCodesOfConsortia.Contains(custodianCode); } - private bool ConfirmConsortiumCodes(string? userEmailAddress, IReadOnlyCollection consortiumCodes, User? user) + private bool ConfirmConsortiumCodes(string? userEmailAddress, IReadOnlyCollection consortiumCodes, User? user, bool remove) { outputProvider.Output( $"You are changing permissions for user {userEmailAddress} for the following Consortia:"); try { - if (user != null) + if (remove) + { + outputProvider.Output("Remove the following Consortia:"); + PrintCodes(consortiumCodes, consortiumCodeToConsortiumNameDict); + } + else if (user != null) { outputProvider.Output("Add the following Consortia:"); PrintCodes(consortiumCodes, consortiumCodeToConsortiumNameDict); @@ -223,7 +233,7 @@ public void RemoveLas(User? user, IReadOnlyCollection? custodianCodes) return; } - var userConfirmation = ConfirmCustodianCodes(user.EmailAddress, custodianCodes, user); + var userConfirmation = ConfirmCustodianCodes(user.EmailAddress, custodianCodes, user, true); if (!userConfirmation) { return; @@ -289,7 +299,7 @@ public void CreateOrUpdateUserWithLas(string? userEmailAddress, IReadOnlyCollect var (user, userStatus) = SetupUser(userEmailAddress); - var confirmation = ConfirmCustodianCodes(userEmailAddress, custodianCodes, user); + var confirmation = ConfirmCustodianCodes(userEmailAddress, custodianCodes, user, false); if (confirmation) { @@ -314,7 +324,7 @@ public void CreateOrUpdateUserWithConsortia(string? userEmailAddress, IReadOnlyC var (user, userStatus) = SetupUser(userEmailAddress); - var confirmation = ConfirmConsortiumCodes(userEmailAddress, consortiumCodes, user); + var confirmation = ConfirmConsortiumCodes(userEmailAddress, consortiumCodes, user, false); if (confirmation) { @@ -329,4 +339,35 @@ public void CreateOrUpdateUserWithConsortia(string? userEmailAddress, IReadOnlyC } } } + + public void RemoveConsortia(User? user, IReadOnlyCollection? consortiumCodes) + { + if (user == null) + { + outputProvider.Output("User not found"); + return; + } + + if (consortiumCodes == null || consortiumCodes.Count < 1) + { + outputProvider.Output("Please specify consortium codes to remove from user"); + return; + } + + var userConfirmation = ConfirmConsortiumCodes(user.EmailAddress, consortiumCodes, user, true); + if (!userConfirmation) + { + return; + } + + var consortiaToRemove = user.Consortia.Where(consortium => consortiumCodes.Contains(consortium.ConsortiumCode)).ToList(); + var missingCodes = consortiumCodes.Where(code => !consortiaToRemove.Any(consortium => consortium.ConsortiumCode.Equals(code))).ToList(); + if (missingCodes.Count > 0) + { + outputProvider.Output($"Could not find Consortia attached to {user.EmailAddress} for the following codes: {string.Join(", ", missingCodes)}. Please check your inputs and try again."); + return; + } + + dbOperation.RemoveConsortiaFromUser(user, consortiaToRemove); + } } \ No newline at end of file diff --git a/HerPortal.ManagementShell/DatabaseOperation.cs b/HerPortal.ManagementShell/DatabaseOperation.cs index 31a38c4..d15831f 100644 --- a/HerPortal.ManagementShell/DatabaseOperation.cs +++ b/HerPortal.ManagementShell/DatabaseOperation.cs @@ -129,6 +129,27 @@ public void AddConsortiaAndRemoveLasFromUser(User user, List consort } } + public void RemoveConsortiaFromUser(User user, List consortia) + { + using var dbContextTransaction = dbContext.Database.BeginTransaction(); + try + { + foreach (var consortium in consortia) + { + user?.Consortia.Remove(consortium); + } + + dbContext.SaveChanges(); + outputProvider.Output("Operation successful"); + dbContextTransaction.Commit(); + } + catch (Exception e) + { + outputProvider.Output($"Rollback following error in transaction: {e.InnerException?.Message}"); + dbContextTransaction.Rollback(); + } + } + public void AddLasToUser(User user, List localAuthorities) { using var dbContextTransaction = dbContext.Database.BeginTransaction(); diff --git a/HerPortal.ManagementShell/IDatabaseOperation.cs b/HerPortal.ManagementShell/IDatabaseOperation.cs index a4e4b30..9709f83 100644 --- a/HerPortal.ManagementShell/IDatabaseOperation.cs +++ b/HerPortal.ManagementShell/IDatabaseOperation.cs @@ -13,4 +13,5 @@ public interface IDatabaseOperation public void AddConsortiaToUser(User user, List consortia); public void RemoveLasFromUser(User user, List localAuthorities); public void AddConsortiaAndRemoveLasFromUser(User user, List consortia, List localAuthorities); + public void RemoveConsortiaFromUser(User user, List consortia); } \ No newline at end of file diff --git a/HerPortal.ManagementShell/Program.cs b/HerPortal.ManagementShell/Program.cs index 701a681..2dd2a8e 100644 --- a/HerPortal.ManagementShell/Program.cs +++ b/HerPortal.ManagementShell/Program.cs @@ -12,7 +12,8 @@ private enum Subcommand AddLas, RemoveLas, RemoveUser, - AddConsortia + AddConsortia, + RemoveConsortia } public static void Main(string[] args) { @@ -59,6 +60,9 @@ public static void Main(string[] args) case Subcommand.AddConsortia: adminAction.CreateOrUpdateUserWithConsortia(userEmailAddress, codes); break; + case Subcommand.RemoveConsortia: + adminAction.RemoveConsortia(adminAction.GetUser(userEmailAddress), codes); + break; default: outputProvider.Output("Invalid terminal command entered. Please refer to the documentation"); return; From 990da7b78e1fe6d188eb902ff3580aed2b5abc1b Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Tue, 28 May 2024 16:27:08 +0100 Subject: [PATCH 15/81] PC-1085: reformat test file --- .../ManagementShell/AdminActionTests.cs | 160 +++++++++--------- 1 file changed, 82 insertions(+), 78 deletions(-) diff --git a/HerPortal.UnitTests/ManagementShell/AdminActionTests.cs b/HerPortal.UnitTests/ManagementShell/AdminActionTests.cs index c429472..8b77dec 100644 --- a/HerPortal.UnitTests/ManagementShell/AdminActionTests.cs +++ b/HerPortal.UnitTests/ManagementShell/AdminActionTests.cs @@ -1,17 +1,17 @@ using System; using System.Collections.Generic; using HerPortal.BusinessLogic.Models; +using HerPortal.ManagementShell; using Moq; using NUnit.Framework; using Tests.Builders; -using HerPortal.ManagementShell; namespace Tests.ManagementShell; public class AdminActionTests { - private Mock mockOutputProvider; private Mock mockDatabaseOperation; + private Mock mockOutputProvider; private AdminAction underTest; [SetUp] @@ -71,7 +71,7 @@ public void CreateOrUpdateUserWithLas_CreatesNewUser_IfUserNotFoundByDbOperation new UserBuilder("existinguser@email.com").Build() }; mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); - var custodianCodes = new[] { "9052"}; + var custodianCodes = new[] { "9052" }; SetupConfirmCustodianCodes(custodianCodes, userEmailAddress, true); var las = new List { @@ -82,10 +82,10 @@ public void CreateOrUpdateUserWithLas_CreatesNewUser_IfUserNotFoundByDbOperation } }; mockDatabaseOperation.Setup(mock => mock.GetLas(custodianCodes)).Returns(las); - + // Act underTest.CreateOrUpdateUserWithLas(userEmailAddress, custodianCodes); - + // Assert mockDatabaseOperation.Verify( mock => mock.CreateUserOrLogError(userEmailAddress, las, It.IsAny>()), Times.Once()); @@ -97,7 +97,7 @@ public void CreateOrUpdateUserWithLas_AddsLasToExistingUser_IfUserFoundByDbOpera // Arrange var currentLa = new List { - new LocalAuthority() + new() { Id = 2, CustodianCode = "2525" @@ -110,10 +110,10 @@ public void CreateOrUpdateUserWithLas_AddsLasToExistingUser_IfUserFoundByDbOpera new UserBuilder("existinguser@email.com").WithLocalAuthorities(currentLa).Build() }; mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); - + var custodianCodes = new[] { "9052" }; SetupConfirmCustodianCodes(custodianCodes, userEmailAddress, true); - + var lasToAdd = new List { new() @@ -123,10 +123,10 @@ public void CreateOrUpdateUserWithLas_AddsLasToExistingUser_IfUserFoundByDbOpera } }; mockDatabaseOperation.Setup(mock => mock.GetLas(custodianCodes)).Returns(lasToAdd); - + // Act underTest.CreateOrUpdateUserWithLas(userEmailAddress, custodianCodes); - + // Assert mockDatabaseOperation.Verify(mock => mock.AddLasToUser(users[0], lasToAdd)); } @@ -148,7 +148,8 @@ public void RemoveLas_RemovesLasFromExistingUser_IfUserFoundByDbOperation() }; var userEmailAddress = "existinguser@email.com"; - var user = new UserBuilder(userEmailAddress).WithLocalAuthorities(new List { laToRemove, laToKeep }).Build(); + var user = new UserBuilder(userEmailAddress) + .WithLocalAuthorities(new List { laToRemove, laToKeep }).Build(); var users = new List { user }; mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); @@ -159,7 +160,8 @@ public void RemoveLas_RemovesLasFromExistingUser_IfUserFoundByDbOperation() underTest.RemoveLas(user, custodianCodes); // Assert - mockDatabaseOperation.Verify(mock => mock.RemoveLasFromUser(user, new List { laToRemove }), Times.Once()); + mockDatabaseOperation.Verify(mock => mock.RemoveLasFromUser(user, new List { laToRemove }), + Times.Once()); } [Test] @@ -172,10 +174,10 @@ public void TryRemoveUser_IfUserFound_WhenThereIsDeletionConfirmation() }; mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); mockOutputProvider.Setup(mock => mock.Confirm(It.IsAny())).Returns(true); - + // Act underTest.TryRemoveUser(users[0]); - + // Assert mockDatabaseOperation.Verify(mock => mock.RemoveUserOrLogError(users[0]), Times.Once()); } @@ -194,7 +196,7 @@ public void CreateOrUpdateUserWithLas_DisplaysErrorMessage_WhenNoLasSpecified_If // Act underTest.CreateOrUpdateUserWithLas(userEmailAddress, Array.Empty()); - + // Assert mockOutputProvider.Verify(mock => mock.Output(It.IsAny())); } @@ -214,18 +216,19 @@ public void RemoveLas_DisplaysErrorMessage_IfCustodianCodeToRemove_DoesNotMatchA CustodianCode = "2525", Id = 7 }; - + var userEmailAddress = "existinguser@email.com"; - var user = new UserBuilder(userEmailAddress).WithLocalAuthorities(new List { usersCurrentLa }).Build(); + var user = new UserBuilder(userEmailAddress).WithLocalAuthorities(new List { usersCurrentLa }) + .Build(); var users = new List { user }; mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); var custodianCodes = new[] { laToRemove.CustodianCode }; SetupConfirmCustodianCodes(custodianCodes, userEmailAddress, true); - + // Act underTest.RemoveLas(user, custodianCodes); - + // Assert mockOutputProvider.Verify(mock => mock.Output(It.IsAny())); } @@ -240,11 +243,11 @@ public void CreateOrUpdateUserWithLas_DisplaysInnerExceptionMessage_IfCustodianC new UserBuilder("existinguser@email.com").Build() }; mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); - var listWithCustodianCodeNotInDict = new [] { "1111" }; + var listWithCustodianCodeNotInDict = new[] { "1111" }; // Act underTest.CreateOrUpdateUserWithLas(userEmailAddress, listWithCustodianCodeNotInDict); - + // Assert mockOutputProvider.Verify(mock => mock.Output("The given key '1111' was not present in the dictionary. Process terminated")); @@ -260,7 +263,7 @@ public void RemoveLas_DisplaysErrorMessage_WhenRemovingLas_IfUserNotInDatabase() new UserBuilder("userindb@email.com").Build() }; mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); - var custodianCodes = new[] { "9052"}; + var custodianCodes = new[] { "9052" }; SetupConfirmCustodianCodes(custodianCodes, userEmailAddress, false); var lasToAdd = new List { @@ -270,10 +273,10 @@ public void RemoveLas_DisplaysErrorMessage_WhenRemovingLas_IfUserNotInDatabase() CustodianCode = "9052" } }; - + // Act underTest.RemoveLas(null, custodianCodes); - + // Assert mockOutputProvider.Verify(mock => mock.Output("User not found")); } @@ -288,7 +291,7 @@ public void CreateOrUpdateUserWithLas_DoesNotCallDbOperation_IfConfirmKeyNotPres new UserBuilder("existinguser@email.com").Build() }; mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); - var custodianCodes = new[] { "9052"}; + var custodianCodes = new[] { "9052" }; SetupConfirmCustodianCodes(custodianCodes, userEmailAddress, false); var lasToAdd = new List { @@ -298,16 +301,16 @@ public void CreateOrUpdateUserWithLas_DoesNotCallDbOperation_IfConfirmKeyNotPres CustodianCode = "9052" } }; - + // Act underTest.CreateOrUpdateUserWithLas(userEmailAddress, custodianCodes); - + // Assert mockOutputProvider.Verify(mock => mock.Output("Process cancelled, no changes were made to the database")); mockDatabaseOperation.Verify( mock => mock.CreateUserOrLogError(userEmailAddress, lasToAdd, It.IsAny>()), Times.Never()); } - + [Test] public void CreateOrUpdateUserWithLas_AsksForConfirmation() { @@ -318,15 +321,15 @@ public void CreateOrUpdateUserWithLas_AsksForConfirmation() new UserBuilder("existinguser@email.com").Build() }; mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); - var custodianCodes = new[] { "9052"}; - + var custodianCodes = new[] { "9052" }; + // Act underTest.CreateOrUpdateUserWithLas(userEmailAddress, custodianCodes); // Assert mockOutputProvider.Verify(mock => mock.Confirm("Please confirm (y/n)"), Times.Once); } - + [Test] public void CreateOrUpdateUserWithLas_DisplaysCorrectUserStatus_WhenUserActive() { @@ -337,14 +340,15 @@ public void CreateOrUpdateUserWithLas_DisplaysCorrectUserStatus_WhenUserActive() new UserBuilder("existinguser@email.com").Build() }; mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); - var custodianCodes = new[] { "9052"}; + var custodianCodes = new[] { "9052" }; // Act underTest.CreateOrUpdateUserWithLas(userEmailAddress, custodianCodes); - + // Assert - mockOutputProvider.Verify(mock => mock.Output("User found in database. LAs will be added to their account"), Times.Once()); + mockOutputProvider.Verify(mock => mock.Output("User found in database. LAs will be added to their account"), + Times.Once()); } - + [Test] public void CreateOrUpdateUserWithLas_DisplaysCorrectUserStatus_WhenUserInactive() { @@ -358,11 +362,12 @@ public void CreateOrUpdateUserWithLas_DisplaysCorrectUserStatus_WhenUserInactive var custodianCodes = new[] { "9052" }; // Act underTest.CreateOrUpdateUserWithLas(userEmailAddress, custodianCodes); - + // Assert - mockOutputProvider.Verify(mock => mock.Output("User not found in database. A new user will be created"), Times.Once()); + mockOutputProvider.Verify(mock => mock.Output("User not found in database. A new user will be created"), + Times.Once()); } - + [Test] public void CreateOrUpdateUserWithLas_WhenGivenLocalAuthorities_PrintsThem() { @@ -376,13 +381,14 @@ public void CreateOrUpdateUserWithLas_WhenGivenLocalAuthorities_PrintsThem() var custodianCodes = new[] { "9052", "3805" }; // Act underTest.CreateOrUpdateUserWithLas(userEmailAddress, custodianCodes); - + // Assert mockOutputProvider.Verify(mock => mock.Output("9052: Aberdeenshire"), Times.Once()); mockOutputProvider.Verify(mock => mock.Output("3805: Adur"), Times.Once()); } - - private void SetupConfirmCustodianCodes(IEnumerable custodianCodes, string userEmailAddress, bool confirmation) + + private void SetupConfirmCustodianCodes(IEnumerable custodianCodes, string userEmailAddress, + bool confirmation) { mockOutputProvider .Setup(op => @@ -390,18 +396,15 @@ private void SetupConfirmCustodianCodes(IEnumerable custodianCodes, stri $"You are changing permissions for user {userEmailAddress} for the following Local Authorities:")); mockOutputProvider .Setup(op => op.Output("Add the following Local Authorities:")); - foreach (var code in custodianCodes) - { - mockOutputProvider.Setup(op => op.Output("Code: Local Authority")); - } + foreach (var code in custodianCodes) mockOutputProvider.Setup(op => op.Output("Code: Local Authority")); mockOutputProvider - .Setup(op => + .Setup(op => op.Output("Ignore the following Local Authorities already in owned Consortia:")); mockOutputProvider.Setup(op => op.Output("(None)")); mockOutputProvider.Setup(op => op.Confirm("Please confirm (y/n)")).Returns(confirmation); } - + [Test] public void CreateOrUpdateUserWithConsortia_ConfirmsCustodianCodesWhenUpdating() { @@ -443,13 +446,14 @@ public void CreateOrUpdateUserWithConsortia_CreatesNewUser_IfUserNotFoundByDbOpe } }; mockDatabaseOperation.Setup(mock => mock.GetConsortia(consortiumCodes)).Returns(consortia); - + // Act underTest.CreateOrUpdateUserWithConsortia(userEmailAddress, consortiumCodes); - + // Assert mockDatabaseOperation.Verify( - mock => mock.CreateUserOrLogError(userEmailAddress, It.IsAny>(), consortia), Times.Once()); + mock => mock.CreateUserOrLogError(userEmailAddress, It.IsAny>(), consortia), + Times.Once()); } [Test] @@ -471,10 +475,10 @@ public void CreateOrUpdateUserWithConsortia_AddsLasToExistingUser_IfUserFoundByD new UserBuilder("existinguser@email.com").WithConsortia(currentConsortia).Build() }; mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); - + var custodianCodes = new[] { "C_0002" }; SetupConfirmCustodianCodes(custodianCodes, userEmailAddress, true); - + var consortiaToAdd = new List { new() @@ -484,10 +488,10 @@ public void CreateOrUpdateUserWithConsortia_AddsLasToExistingUser_IfUserFoundByD } }; mockDatabaseOperation.Setup(mock => mock.GetConsortia(custodianCodes)).Returns(consortiaToAdd); - + // Act underTest.CreateOrUpdateUserWithConsortia(userEmailAddress, custodianCodes); - + // Assert mockDatabaseOperation.Verify(mock => mock.AddConsortiaToUser(users[0], consortiaToAdd)); } @@ -507,7 +511,7 @@ public void CreateOrUpdateUserWithConsortia_DisplaysErrorMessage_WhenNoLasSpecif // Act underTest.CreateOrUpdateUserWithConsortia(userEmailAddress, Array.Empty()); - + // Assert mockOutputProvider.Verify(mock => mock.Output(It.IsAny())); } @@ -523,11 +527,11 @@ public void CreateOrUpdateUserWithConsortia_DisplaysInnerExceptionMessage_IfCust new UserBuilder("existinguser@email.com").Build() }; mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); - var listWithCustodianCodeNotInDict = new [] { "C_1111" }; + var listWithCustodianCodeNotInDict = new[] { "C_1111" }; // Act underTest.CreateOrUpdateUserWithConsortia(userEmailAddress, listWithCustodianCodeNotInDict); - + // Assert mockOutputProvider.Verify(mock => mock.Output("The given key 'C_1111' was not present in the dictionary. Process terminated")); @@ -561,8 +565,8 @@ public void CreateOrUpdateUserWithConsortia_DoesNotCallDbOperation_IfConfirmKeyN // Assert mockOutputProvider.Verify(mock => mock.Output("Process cancelled, no changes were made to the database")); mockDatabaseOperation.Verify( - mock => mock.CreateUserOrLogError(userEmailAddress, It.IsAny>(), consortiaToAdd), Times.Never()); - + mock => mock.CreateUserOrLogError(userEmailAddress, It.IsAny>(), consortiaToAdd), + Times.Never()); } [Test] @@ -576,14 +580,14 @@ public void CreateOrUpdateUserWithConsortia_AsksForConfirmation() }; mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); var consortiumCodes = new[] { "C_0002" }; - + // Act underTest.CreateOrUpdateUserWithConsortia(userEmailAddress, consortiumCodes); // Assert mockOutputProvider.Verify(mock => mock.Confirm("Please confirm (y/n)"), Times.Once); } - + [Test] public void CreateOrUpdateUserWithConsortia_DisplaysCorrectUserStatus_WhenUserActive() { @@ -593,16 +597,17 @@ public void CreateOrUpdateUserWithConsortia_DisplaysCorrectUserStatus_WhenUserAc { new UserBuilder("existinguser@email.com").Build() }; - + mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); var consortiumCodes = new[] { "C_0002" }; // Act underTest.CreateOrUpdateUserWithConsortia(userEmailAddress, consortiumCodes); - + // Assert - mockOutputProvider.Verify(mock => mock.Output("User found in database. LAs will be added to their account"), Times.Once()); + mockOutputProvider.Verify(mock => mock.Output("User found in database. LAs will be added to their account"), + Times.Once()); } - + [Test] public void CreateOrUpdateUserWithConsortia_DisplaysCorrectUserStatus_WhenUserInactive() { @@ -616,11 +621,12 @@ public void CreateOrUpdateUserWithConsortia_DisplaysCorrectUserStatus_WhenUserIn var consortiumCodes = new[] { "C_0002" }; // Act underTest.CreateOrUpdateUserWithConsortia(userEmailAddress, consortiumCodes); - + // Assert - mockOutputProvider.Verify(mock => mock.Output("User not found in database. A new user will be created"), Times.Once()); + mockOutputProvider.Verify(mock => mock.Output("User not found in database. A new user will be created"), + Times.Once()); } - + [Test] public void CreateOrUpdateUserWithConsortia_WhenGivenConsortia_PrintsThem() { @@ -634,12 +640,12 @@ public void CreateOrUpdateUserWithConsortia_WhenGivenConsortia_PrintsThem() var consortiumCodes = new[] { "C_0002", "C_0003" }; // Act underTest.CreateOrUpdateUserWithConsortia(userEmailAddress, consortiumCodes); - + // Assert mockOutputProvider.Verify(mock => mock.Output("C_0002: Blackpool"), Times.Once()); mockOutputProvider.Verify(mock => mock.Output("C_0003: Bristol"), Times.Once()); } - + [Test] public void CreateOrUpdateUserWithConsortia_WhenUserOwnsLocalAuthorityInConsortium_PrintsThem() { @@ -652,7 +658,7 @@ public void CreateOrUpdateUserWithConsortia_WhenUserOwnsLocalAuthorityInConsorti CustodianCode = "2372" } }; - + const string userEmailAddress = "existinguser@email.com"; var users = new List { @@ -662,21 +668,19 @@ public void CreateOrUpdateUserWithConsortia_WhenUserOwnsLocalAuthorityInConsorti var consortiumCodes = new[] { "C_0002" }; // Act underTest.CreateOrUpdateUserWithConsortia(userEmailAddress, consortiumCodes); - + // Assert mockOutputProvider.Verify(mock => mock.Output("2372: Blackburn With Darwen"), Times.Once()); } - - private void SetupConfirmConsortiumCodes(IEnumerable consortiumCodes, string userEmailAddress, bool confirmation) + + private void SetupConfirmConsortiumCodes(IEnumerable consortiumCodes, string userEmailAddress, + bool confirmation) { mockOutputProvider .Setup(op => op.Output( $"You are changing permissions for user {userEmailAddress} for the following consortiums: ")); - foreach (var code in consortiumCodes) - { - mockOutputProvider.Setup(op => op.Output("Code: Consortium")); - } + foreach (var code in consortiumCodes) mockOutputProvider.Setup(op => op.Output("Code: Consortium")); mockOutputProvider.Setup(op => op.Confirm("Please confirm (y/n)")).Returns(confirmation); } From d2db801a597e554e4756f1ce6ac7ab3730f23fb6 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Tue, 28 May 2024 17:04:52 +0100 Subject: [PATCH 16/81] PC-1085: add tests for managing of LAs related to consortiums --- .../ManagementShell/AdminActionTests.cs | 86 ++++++++++++++++++- 1 file changed, 85 insertions(+), 1 deletion(-) diff --git a/HerPortal.UnitTests/ManagementShell/AdminActionTests.cs b/HerPortal.UnitTests/ManagementShell/AdminActionTests.cs index 8b77dec..f32948e 100644 --- a/HerPortal.UnitTests/ManagementShell/AdminActionTests.cs +++ b/HerPortal.UnitTests/ManagementShell/AdminActionTests.cs @@ -131,6 +131,48 @@ public void CreateOrUpdateUserWithLas_AddsLasToExistingUser_IfUserFoundByDbOpera mockDatabaseOperation.Verify(mock => mock.AddLasToUser(users[0], lasToAdd)); } + [Test] + public void CreateOrUpdateUserWithLas_AddsLasToExistingUser_IgnoresLasInOwnedConsortia() + { + // Arrange + var currentConsortia = new List + { + new() + { + Id = 2, + ConsortiumCode = "C_0002" + } + }; + const string userEmailAddress = "existinguser@email.com"; + + var users = new List + { + new UserBuilder("existinguser@email.com").WithConsortia(currentConsortia).Build() + }; + mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); + + // 2372 is in consortium C_0002 + var custodianCodes = new[] { "9052", "2372" }; + var filteredCustodianCodes = new[] { "9052" }; + SetupConfirmCustodianCodes(filteredCustodianCodes, userEmailAddress, true); + + var lasToAdd = new List + { + new() + { + Id = 1, + CustodianCode = "9052" + } + }; + mockDatabaseOperation.Setup(mock => mock.GetLas(filteredCustodianCodes)).Returns(lasToAdd); + + // Act + underTest.CreateOrUpdateUserWithLas(userEmailAddress, custodianCodes); + + // Assert + mockDatabaseOperation.Verify(mock => mock.AddLasToUser(users[0], lasToAdd)); + } + [Test] public void RemoveLas_RemovesLasFromExistingUser_IfUserFoundByDbOperation() { @@ -476,7 +518,7 @@ public void CreateOrUpdateUserWithConsortia_AddsLasToExistingUser_IfUserFoundByD }; mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); - var custodianCodes = new[] { "C_0002" }; + var custodianCodes = new[] { "C_0003" }; SetupConfirmCustodianCodes(custodianCodes, userEmailAddress, true); var consortiaToAdd = new List @@ -673,6 +715,48 @@ public void CreateOrUpdateUserWithConsortia_WhenUserOwnsLocalAuthorityInConsorti mockOutputProvider.Verify(mock => mock.Output("2372: Blackburn With Darwen"), Times.Once()); } + [Test] + public void CreateOrUpdateUserWithConsortia_WhenUserHasLasInConsortia_RemovesThem() + { + // Arrange + var currentLa = new List + { + new() + { + Id = 2, + CustodianCode = "2372" + } + }; + const string userEmailAddress = "existinguser@email.com"; + + var users = new List + { + new UserBuilder("existinguser@email.com").WithLocalAuthorities(currentLa).Build() + }; + mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); + + var consortiumCodes = new[] { "C_0002" }; + var custodianCodesToRemove = new[] { "2372" }; + SetupConfirmCustodianCodes(consortiumCodes, userEmailAddress, true); + + var consortiaToAdd = new List + { + new() + { + Id = 1, + ConsortiumCode = "C_0002" + } + }; + mockDatabaseOperation.Setup(mock => mock.GetConsortia(consortiumCodes)).Returns(consortiaToAdd); + mockDatabaseOperation.Setup(mock => mock.GetLas(custodianCodesToRemove)).Returns(currentLa); + + // Act + underTest.CreateOrUpdateUserWithConsortia(userEmailAddress, consortiumCodes); + + // Assert + mockDatabaseOperation.Verify(mock => mock.AddConsortiaAndRemoveLasFromUser(users[0], consortiaToAdd, currentLa)); + } + private void SetupConfirmConsortiumCodes(IEnumerable consortiumCodes, string userEmailAddress, bool confirmation) { From 715e9931d1eef4340f2e88226298f14c9ac5a166 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Tue, 28 May 2024 17:16:27 +0100 Subject: [PATCH 17/81] PC-1085: add tests for removing consortia --- .../ManagementShell/AdminActionTests.cs | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/HerPortal.UnitTests/ManagementShell/AdminActionTests.cs b/HerPortal.UnitTests/ManagementShell/AdminActionTests.cs index f32948e..ebc6fc7 100644 --- a/HerPortal.UnitTests/ManagementShell/AdminActionTests.cs +++ b/HerPortal.UnitTests/ManagementShell/AdminActionTests.cs @@ -756,6 +756,93 @@ public void CreateOrUpdateUserWithConsortia_WhenUserHasLasInConsortia_RemovesThe // Assert mockDatabaseOperation.Verify(mock => mock.AddConsortiaAndRemoveLasFromUser(users[0], consortiaToAdd, currentLa)); } + + [Test] + public void RemoveConsortia_RemovesLasFromExistingUser_IfUserFoundByDbOperation() + { + // Arrange + var consortiumToRemove = new Consortium + { + ConsortiumCode = "C_0002", + Id = 123 + }; + + var consortiumToKeep = new Consortium + { + ConsortiumCode = "C_0003", + Id = 456 + }; + + var userEmailAddress = "existinguser@email.com"; + var user = new UserBuilder(userEmailAddress) + .WithConsortia(new List { consortiumToRemove, consortiumToKeep }).Build(); + var users = new List { user }; + mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); + + var custodianCodes = new[] { consortiumToRemove.ConsortiumCode }; + SetupConfirmConsortiumCodes(custodianCodes, userEmailAddress, true); + + // Act + underTest.RemoveConsortia(user, custodianCodes); + + // Assert + mockDatabaseOperation.Verify(mock => mock.RemoveConsortiaFromUser(user, new List { consortiumToRemove }), + Times.Once()); + } + + [Test] + public void RemoveConsortia_DisplaysErrorMessage_IfCustodianCodeToRemove_DoesNotMatchAnyOfExistingUsersLas() + { + // Arrange + var consortiumToRemove = new Consortium + { + ConsortiumCode = "C_0002", + Id = 123 + }; + + var usersCurrentConsortium = new Consortium + { + ConsortiumCode = "C_0003", + Id = 7 + }; + + var userEmailAddress = "existinguser@email.com"; + var user = new UserBuilder(userEmailAddress).WithConsortia(new List { usersCurrentConsortium }) + .Build(); + var users = new List { user }; + mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); + + var consortiumCodes = new[] { consortiumToRemove.ConsortiumCode }; + SetupConfirmConsortiumCodes(consortiumCodes, userEmailAddress, true); + + // Act + underTest.RemoveConsortia(user, consortiumCodes); + + // Assert + mockOutputProvider.Verify(mock => + mock.Output( + "Could not find Consortia attached to existinguser@email.com for the following codes: C_0002. Please check your inputs and try again.")); + } + + [Test] + public void RemoveConsortia_DisplaysErrorMessage_WhenRemovingLas_IfUserNotInDatabase() + { + // Arrange + const string userEmailAddress = "usernotindb@email.com"; + var users = new List + { + new UserBuilder("userindb@email.com").Build() + }; + mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); + var consortiumCodes = new[] { "C_0002" }; + SetupConfirmConsortiumCodes(consortiumCodes, userEmailAddress, false); + + // Act + underTest.RemoveConsortia(null, consortiumCodes); + + // Assert + mockOutputProvider.Verify(mock => mock.Output("User not found")); + } private void SetupConfirmConsortiumCodes(IEnumerable consortiumCodes, string userEmailAddress, bool confirmation) From 558d4566d482704a94510a8465fff3682ddf12f6 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Tue, 28 May 2024 17:22:38 +0100 Subject: [PATCH 18/81] PC-1085: remove SetupConfirmCustodianCodes it didn't seem to do anything removed the consortium counterpart as well --- .../ManagementShell/AdminActionTests.cs | 66 +++++-------------- 1 file changed, 18 insertions(+), 48 deletions(-) diff --git a/HerPortal.UnitTests/ManagementShell/AdminActionTests.cs b/HerPortal.UnitTests/ManagementShell/AdminActionTests.cs index ebc6fc7..3e7f433 100644 --- a/HerPortal.UnitTests/ManagementShell/AdminActionTests.cs +++ b/HerPortal.UnitTests/ManagementShell/AdminActionTests.cs @@ -52,7 +52,7 @@ public void CreateOrUpdateUserWithLas_ConfirmsCustodianCodesWhenUpdating() }; mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); var custodianCodes = new[] { "9052", "2525" }; - SetupConfirmCustodianCodes(custodianCodes, userEmailAddress, true); + mockOutputProvider.Setup(op => op.Confirm("Please confirm (y/n)")).Returns(true); // Act underTest.CreateOrUpdateUserWithLas(userEmailAddress, custodianCodes); @@ -72,7 +72,7 @@ public void CreateOrUpdateUserWithLas_CreatesNewUser_IfUserNotFoundByDbOperation }; mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); var custodianCodes = new[] { "9052" }; - SetupConfirmCustodianCodes(custodianCodes, userEmailAddress, true); + mockOutputProvider.Setup(op => op.Confirm("Please confirm (y/n)")).Returns(true); var las = new List { new() @@ -112,7 +112,7 @@ public void CreateOrUpdateUserWithLas_AddsLasToExistingUser_IfUserFoundByDbOpera mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); var custodianCodes = new[] { "9052" }; - SetupConfirmCustodianCodes(custodianCodes, userEmailAddress, true); + mockOutputProvider.Setup(op => op.Confirm("Please confirm (y/n)")).Returns(true); var lasToAdd = new List { @@ -154,7 +154,7 @@ public void CreateOrUpdateUserWithLas_AddsLasToExistingUser_IgnoresLasInOwnedCon // 2372 is in consortium C_0002 var custodianCodes = new[] { "9052", "2372" }; var filteredCustodianCodes = new[] { "9052" }; - SetupConfirmCustodianCodes(filteredCustodianCodes, userEmailAddress, true); + mockOutputProvider.Setup(op => op.Confirm("Please confirm (y/n)")).Returns(true); var lasToAdd = new List { @@ -196,7 +196,7 @@ public void RemoveLas_RemovesLasFromExistingUser_IfUserFoundByDbOperation() mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); var custodianCodes = new[] { laToRemove.CustodianCode }; - SetupConfirmCustodianCodes(custodianCodes, userEmailAddress, true); + mockOutputProvider.Setup(op => op.Confirm("Please confirm (y/n)")).Returns(true); // Act underTest.RemoveLas(user, custodianCodes); @@ -234,7 +234,7 @@ public void CreateOrUpdateUserWithLas_DisplaysErrorMessage_WhenNoLasSpecified_If new UserBuilder("existinguser@email.com").Build() }; mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); - SetupConfirmCustodianCodes(Array.Empty(), userEmailAddress, true); + mockOutputProvider.Setup(op => op.Confirm("Please confirm (y/n)")).Returns(true); // Act underTest.CreateOrUpdateUserWithLas(userEmailAddress, Array.Empty()); @@ -266,7 +266,7 @@ public void RemoveLas_DisplaysErrorMessage_IfCustodianCodeToRemove_DoesNotMatchA mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); var custodianCodes = new[] { laToRemove.CustodianCode }; - SetupConfirmCustodianCodes(custodianCodes, userEmailAddress, true); + mockOutputProvider.Setup(op => op.Confirm("Please confirm (y/n)")).Returns(true); // Act underTest.RemoveLas(user, custodianCodes); @@ -306,7 +306,7 @@ public void RemoveLas_DisplaysErrorMessage_WhenRemovingLas_IfUserNotInDatabase() }; mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); var custodianCodes = new[] { "9052" }; - SetupConfirmCustodianCodes(custodianCodes, userEmailAddress, false); + mockOutputProvider.Setup(op => op.Confirm("Please confirm (y/n)")).Returns(true); var lasToAdd = new List { new() @@ -334,7 +334,7 @@ public void CreateOrUpdateUserWithLas_DoesNotCallDbOperation_IfConfirmKeyNotPres }; mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); var custodianCodes = new[] { "9052" }; - SetupConfirmCustodianCodes(custodianCodes, userEmailAddress, false); + mockOutputProvider.Setup(op => op.Confirm("Please confirm (y/n)")).Returns(false); var lasToAdd = new List { new() @@ -429,24 +429,6 @@ public void CreateOrUpdateUserWithLas_WhenGivenLocalAuthorities_PrintsThem() mockOutputProvider.Verify(mock => mock.Output("3805: Adur"), Times.Once()); } - private void SetupConfirmCustodianCodes(IEnumerable custodianCodes, string userEmailAddress, - bool confirmation) - { - mockOutputProvider - .Setup(op => - op.Output( - $"You are changing permissions for user {userEmailAddress} for the following Local Authorities:")); - mockOutputProvider - .Setup(op => op.Output("Add the following Local Authorities:")); - foreach (var code in custodianCodes) mockOutputProvider.Setup(op => op.Output("Code: Local Authority")); - mockOutputProvider - .Setup(op => - op.Output("Ignore the following Local Authorities already in owned Consortia:")); - mockOutputProvider.Setup(op => op.Output("(None)")); - - mockOutputProvider.Setup(op => op.Confirm("Please confirm (y/n)")).Returns(confirmation); - } - [Test] public void CreateOrUpdateUserWithConsortia_ConfirmsCustodianCodesWhenUpdating() { @@ -458,7 +440,7 @@ public void CreateOrUpdateUserWithConsortia_ConfirmsCustodianCodesWhenUpdating() }; mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); var custodianCodes = new[] { "C_0002", "C_0003" }; - SetupConfirmCustodianCodes(custodianCodes, userEmailAddress, true); + mockOutputProvider.Setup(op => op.Confirm("Please confirm (y/n)")).Returns(true); // Act underTest.CreateOrUpdateUserWithConsortia(userEmailAddress, custodianCodes); @@ -478,7 +460,7 @@ public void CreateOrUpdateUserWithConsortia_CreatesNewUser_IfUserNotFoundByDbOpe }; mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); var consortiumCodes = new[] { "C_0002" }; - SetupConfirmCustodianCodes(consortiumCodes, userEmailAddress, true); + mockOutputProvider.Setup(op => op.Confirm("Please confirm (y/n)")).Returns(true); var consortia = new List { new() @@ -519,7 +501,7 @@ public void CreateOrUpdateUserWithConsortia_AddsLasToExistingUser_IfUserFoundByD mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); var custodianCodes = new[] { "C_0003" }; - SetupConfirmCustodianCodes(custodianCodes, userEmailAddress, true); + mockOutputProvider.Setup(op => op.Confirm("Please confirm (y/n)")).Returns(true); var consortiaToAdd = new List { @@ -549,7 +531,7 @@ public void CreateOrUpdateUserWithConsortia_DisplaysErrorMessage_WhenNoLasSpecif new UserBuilder("existinguser@email.com").Build() }; mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); - SetupConfirmCustodianCodes(Array.Empty(), userEmailAddress, true); + mockOutputProvider.Setup(op => op.Confirm("Please confirm (y/n)")).Returns(true); // Act underTest.CreateOrUpdateUserWithConsortia(userEmailAddress, Array.Empty()); @@ -591,7 +573,7 @@ public void CreateOrUpdateUserWithConsortia_DoesNotCallDbOperation_IfConfirmKeyN }; mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); var consortiumCodes = new[] { "C_0002" }; - SetupConfirmConsortiumCodes(consortiumCodes, userEmailAddress, false); + mockOutputProvider.Setup(op => op.Confirm("Please confirm (y/n)")).Returns(false); var consortiaToAdd = new List { new() @@ -737,7 +719,7 @@ public void CreateOrUpdateUserWithConsortia_WhenUserHasLasInConsortia_RemovesThe var consortiumCodes = new[] { "C_0002" }; var custodianCodesToRemove = new[] { "2372" }; - SetupConfirmCustodianCodes(consortiumCodes, userEmailAddress, true); + mockOutputProvider.Setup(op => op.Confirm("Please confirm (y/n)")).Returns(true); var consortiaToAdd = new List { @@ -780,7 +762,7 @@ public void RemoveConsortia_RemovesLasFromExistingUser_IfUserFoundByDbOperation( mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); var custodianCodes = new[] { consortiumToRemove.ConsortiumCode }; - SetupConfirmConsortiumCodes(custodianCodes, userEmailAddress, true); + mockOutputProvider.Setup(op => op.Confirm("Please confirm (y/n)")).Returns(true); // Act underTest.RemoveConsortia(user, custodianCodes); @@ -813,7 +795,7 @@ public void RemoveConsortia_DisplaysErrorMessage_IfCustodianCodeToRemove_DoesNot mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); var consortiumCodes = new[] { consortiumToRemove.ConsortiumCode }; - SetupConfirmConsortiumCodes(consortiumCodes, userEmailAddress, true); + mockOutputProvider.Setup(op => op.Confirm("Please confirm (y/n)")).Returns(true); // Act underTest.RemoveConsortia(user, consortiumCodes); @@ -835,7 +817,7 @@ public void RemoveConsortia_DisplaysErrorMessage_WhenRemovingLas_IfUserNotInData }; mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); var consortiumCodes = new[] { "C_0002" }; - SetupConfirmConsortiumCodes(consortiumCodes, userEmailAddress, false); + mockOutputProvider.Setup(op => op.Confirm("Please confirm (y/n)")).Returns(true); // Act underTest.RemoveConsortia(null, consortiumCodes); @@ -843,16 +825,4 @@ public void RemoveConsortia_DisplaysErrorMessage_WhenRemovingLas_IfUserNotInData // Assert mockOutputProvider.Verify(mock => mock.Output("User not found")); } - - private void SetupConfirmConsortiumCodes(IEnumerable consortiumCodes, string userEmailAddress, - bool confirmation) - { - mockOutputProvider - .Setup(op => - op.Output( - $"You are changing permissions for user {userEmailAddress} for the following consortiums: ")); - foreach (var code in consortiumCodes) mockOutputProvider.Setup(op => op.Output("Code: Consortium")); - - mockOutputProvider.Setup(op => op.Confirm("Please confirm (y/n)")).Returns(confirmation); - } } \ No newline at end of file From 21198cf6d77fdb6e5944af2349be18fc5402410c Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Tue, 28 May 2024 17:42:16 +0100 Subject: [PATCH 19/81] PC-1085: add some handling for when the added LA/consortium isn't in DB --- HerPortal.ManagementShell/AdminAction.cs | 32 +++++++++++++++++-- .../DatabaseOperation.cs | 16 ++++++++-- .../IDatabaseOperation.cs | 4 +-- 3 files changed, 44 insertions(+), 8 deletions(-) diff --git a/HerPortal.ManagementShell/AdminAction.cs b/HerPortal.ManagementShell/AdminAction.cs index 0bcbd97..cb235c4 100644 --- a/HerPortal.ManagementShell/AdminAction.cs +++ b/HerPortal.ManagementShell/AdminAction.cs @@ -174,10 +174,22 @@ private UserStatus GetUserStatus(User? userOrNull) return userOrNull == null ? UserStatus.New : UserStatus.Active; } - private void TryCreateUser(string userEmailAddress, IEnumerable? custodianCodes, IEnumerable? consortiumCodes) + private void TryCreateUser(string userEmailAddress, IReadOnlyCollection? custodianCodes, IReadOnlyCollection? consortiumCodes) { var lasToAdd = dbOperation.GetLas(custodianCodes ?? Array.Empty()); var consortiaToAdd = dbOperation.GetConsortia(consortiumCodes ?? Array.Empty()); + + if (lasToAdd == null) + { + outputProvider.Output("Unrecognised LA. Make sure all codes are present in the database."); + return; + } + if (consortiaToAdd == null) + { + outputProvider.Output("Unrecognised Consortium. Make sure all codes are present in the database."); + return; + } + dbOperation.CreateUserOrLogError(userEmailAddress, lasToAdd, consortiaToAdd); } @@ -213,9 +225,17 @@ private void AddLas(User? user, IReadOnlyCollection? custodianCodes) } var filteredCustodianCodes = custodianCodes - .Where(custodianCode => !CustodianCodeIsInOwnedConsortium(user, custodianCode)); + .Where(custodianCode => !CustodianCodeIsInOwnedConsortium(user, custodianCode)) + .ToList(); var lasToAdd = dbOperation.GetLas(filteredCustodianCodes); + + if (lasToAdd == null) + { + outputProvider.Output("Unrecognised LA. Make sure all codes are present in the database."); + return; + } + dbOperation.AddLasToUser(user, lasToAdd); } @@ -267,7 +287,13 @@ private void AddConsortia(User? user, IReadOnlyCollection? consortiumCod var consortiaToAdd = dbOperation.GetConsortia(consortiumCodes); var ownedCustodianCodesInConsortia = GetOwnedCustodianCodesInConsortia(user, consortiumCodes); - var lasToRemove = dbOperation.GetLas(ownedCustodianCodesInConsortia); + var lasToRemove = dbOperation.GetLas(ownedCustodianCodesInConsortia) ?? new List(); + + if (consortiaToAdd == null) + { + outputProvider.Output("Unrecognised Consortium. Make sure all codes are present in the database."); + return; + } dbOperation.AddConsortiaAndRemoveLasFromUser(user, consortiaToAdd, lasToRemove); } diff --git a/HerPortal.ManagementShell/DatabaseOperation.cs b/HerPortal.ManagementShell/DatabaseOperation.cs index d15831f..ba8d3bd 100644 --- a/HerPortal.ManagementShell/DatabaseOperation.cs +++ b/HerPortal.ManagementShell/DatabaseOperation.cs @@ -25,17 +25,27 @@ public List GetUsersWithLocalAuthoritiesAndConsortia() .ToList(); } - public List GetLas(IEnumerable custodianCodes) + public List? GetLas(IReadOnlyCollection custodianCodes) { + if (custodianCodes.Any(code => !dbContext.LocalAuthorities.Any(la => la.CustodianCode == code))) + { + return null; + } + return custodianCodes .Select(code => dbContext.LocalAuthorities .Single(la => la.CustodianCode == code)) .ToList(); } - public List GetConsortia(IEnumerable custodianCodes) + public List? GetConsortia(IReadOnlyCollection consortiumCodes) { - return custodianCodes + if (consortiumCodes.Any(code => !dbContext.Consortia.Any(la => la.ConsortiumCode == code))) + { + return null; + } + + return consortiumCodes .Select(code => dbContext.Consortia .Single(la => la.ConsortiumCode == code)) .ToList(); diff --git a/HerPortal.ManagementShell/IDatabaseOperation.cs b/HerPortal.ManagementShell/IDatabaseOperation.cs index 9709f83..e60d09e 100644 --- a/HerPortal.ManagementShell/IDatabaseOperation.cs +++ b/HerPortal.ManagementShell/IDatabaseOperation.cs @@ -5,8 +5,8 @@ namespace HerPortal.ManagementShell; public interface IDatabaseOperation { public List GetUsersWithLocalAuthoritiesAndConsortia(); - public List GetLas(IEnumerable custodianCodes); - public List GetConsortia(IEnumerable consortiumCodes); + public List? GetLas(IReadOnlyCollection custodianCodes); + public List? GetConsortia(IReadOnlyCollection consortiumCodes); public void RemoveUserOrLogError(User user); public void CreateUserOrLogError(string userEmailAddress, List localAuthorities, List consortia); public void AddLasToUser(User user, List localAuthorities); From f43857f1af451c4b072704c3d440c35a3c3138f5 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Wed, 29 May 2024 09:19:30 +0100 Subject: [PATCH 20/81] PC-1085: final naming improvements and code format --- HerPortal.ManagementShell/AdminAction.cs | 131 +++++++++--------- .../DatabaseOperation.cs | 44 ++---- .../IDatabaseOperation.cs | 10 +- 3 files changed, 85 insertions(+), 100 deletions(-) diff --git a/HerPortal.ManagementShell/AdminAction.cs b/HerPortal.ManagementShell/AdminAction.cs index cb235c4..5c4d933 100644 --- a/HerPortal.ManagementShell/AdminAction.cs +++ b/HerPortal.ManagementShell/AdminAction.cs @@ -10,12 +10,20 @@ public enum UserStatus Active } + private readonly Dictionary consortiumCodeToConsortiumNameDict = + ConsortiumData.ConsortiumNamesByConsortiumCode; + + private readonly Dictionary> consortiumCodeToCustodianCodesDict = + ConsortiumData.ConsortiumCustodianCodesIdsByConsortiumCode; + + private readonly Dictionary custodianCodeToConsortiumCodeDict = + LocalAuthorityData.LocalAuthorityConsortiumCodeByCustodianCode; + + private readonly Dictionary custodianCodeToLaNameDict = + LocalAuthorityData.LocalAuthorityNamesByCustodianCode; + private readonly IDatabaseOperation dbOperation; private readonly IOutputProvider outputProvider; - private readonly Dictionary custodianCodeToLaNameDict = LocalAuthorityData.LocalAuthorityNamesByCustodianCode; - private readonly Dictionary custodianCodeToConsortiumCodeDict = LocalAuthorityData.LocalAuthorityConsortiumCodeByCustodianCode; - private readonly Dictionary consortiumCodeToConsortiumNameDict = ConsortiumData.ConsortiumNamesByConsortiumCode; - private readonly Dictionary> consortiumCodeToCustodianCodesDict = ConsortiumData.ConsortiumCustodianCodesIdsByConsortiumCode; public AdminAction(IDatabaseOperation dbOperation, IOutputProvider outputProvider) { @@ -37,10 +45,7 @@ public AdminAction(IDatabaseOperation dbOperation, IOutputProvider outputProvide private void PrintCodes(IReadOnlyCollection codes, IReadOnlyDictionary codeToNameDict) { - if (codes.Count < 1) - { - outputProvider.Output("(None)"); - } + if (codes.Count < 1) outputProvider.Output("(None)"); foreach (var code in codes) { @@ -49,7 +54,8 @@ private void PrintCodes(IReadOnlyCollection codes, IReadOnlyDictionary custodianCodes, User? user, bool remove) + private bool ConfirmCustodianCodes(string userEmailAddress, IReadOnlyCollection custodianCodes, User? user, + bool remove) { outputProvider.Output( $"You are changing permissions for user {userEmailAddress} for the following Local Authorities:"); @@ -68,7 +74,7 @@ private bool ConfirmCustodianCodes(string userEmailAddress, IReadOnlyCollection< CustodianCodeIsInOwnedConsortium(user, custodianCode)); var custodianCodesNotInOwnedConsortium = custodianCodeInOwnedConsortiumGrouping[false].ToList(); var custodianCodesInOwnedConsortium = custodianCodeInOwnedConsortiumGrouping[true].ToList(); - + outputProvider.Output("Add the following Local Authorities:"); PrintCodes(custodianCodesNotInOwnedConsortium, custodianCodeToLaNameDict); outputProvider.Output("Ignore the following Local Authorities already in owned Consortia:"); @@ -79,18 +85,15 @@ private bool ConfirmCustodianCodes(string userEmailAddress, IReadOnlyCollection< outputProvider.Output("Add the following Local Authorities:"); PrintCodes(custodianCodes, custodianCodeToLaNameDict); } - } + } catch (Exception e) { outputProvider.Output($"{e.Message} Process terminated"); return false; } - + var hasUserConfirmed = outputProvider.Confirm("Please confirm (y/n)"); - if (!hasUserConfirmed) - { - outputProvider.Output("Process cancelled, no changes were made to the database"); - } + if (!hasUserConfirmed) outputProvider.Output("Process cancelled, no changes were made to the database"); return hasUserConfirmed; } @@ -103,7 +106,8 @@ private bool CustodianCodeIsInOwnedConsortium(User user, string custodianCode) return custodianCodesOfConsortia.Contains(custodianCode); } - private bool ConfirmConsortiumCodes(string? userEmailAddress, IReadOnlyCollection consortiumCodes, User? user, bool remove) + private bool ConfirmConsortiumCodes(string? userEmailAddress, IReadOnlyCollection consortiumCodes, + User? user, bool remove) { outputProvider.Output( $"You are changing permissions for user {userEmailAddress} for the following Consortia:"); @@ -119,10 +123,10 @@ private bool ConfirmConsortiumCodes(string? userEmailAddress, IReadOnlyCollectio { outputProvider.Output("Add the following Consortia:"); PrintCodes(consortiumCodes, consortiumCodeToConsortiumNameDict); - + // flag the need to remove access for any LAs in the new consortia var ownedCustodianCodesInConsortia = GetOwnedCustodianCodesInConsortia(user, consortiumCodes); - + outputProvider.Output("Remove the following Local Authorities in these Consortia:"); PrintCodes(ownedCustodianCodesInConsortia, custodianCodeToLaNameDict); } @@ -131,7 +135,7 @@ private bool ConfirmConsortiumCodes(string? userEmailAddress, IReadOnlyCollectio outputProvider.Output("Add the following Consortia:"); PrintCodes(consortiumCodes, consortiumCodeToConsortiumNameDict); } - } + } catch (Exception e) { outputProvider.Output($"{e.Message} Process terminated"); @@ -139,10 +143,7 @@ private bool ConfirmConsortiumCodes(string? userEmailAddress, IReadOnlyCollectio } var hasUserConfirmed = outputProvider.Confirm("Please confirm (y/n)"); - if (!hasUserConfirmed) - { - outputProvider.Output("Process cancelled, no changes were made to the database"); - } + if (!hasUserConfirmed) outputProvider.Output("Process cancelled, no changes were made to the database"); return hasUserConfirmed; } @@ -174,7 +175,8 @@ private UserStatus GetUserStatus(User? userOrNull) return userOrNull == null ? UserStatus.New : UserStatus.Active; } - private void TryCreateUser(string userEmailAddress, IReadOnlyCollection? custodianCodes, IReadOnlyCollection? consortiumCodes) + private void TryCreateUser(string userEmailAddress, IReadOnlyCollection? custodianCodes, + IReadOnlyCollection? consortiumCodes) { var lasToAdd = dbOperation.GetLas(custodianCodes ?? Array.Empty()); var consortiaToAdd = dbOperation.GetConsortia(consortiumCodes ?? Array.Empty()); @@ -184,12 +186,13 @@ private void TryCreateUser(string userEmailAddress, IReadOnlyCollection? outputProvider.Output("Unrecognised LA. Make sure all codes are present in the database."); return; } + if (consortiaToAdd == null) { outputProvider.Output("Unrecognised Consortium. Make sure all codes are present in the database."); return; } - + dbOperation.CreateUserOrLogError(userEmailAddress, lasToAdd, consortiaToAdd); } @@ -203,13 +206,11 @@ public void TryRemoveUser(User? user) var deletionConfirmation = outputProvider.Confirm( $"Attention! This will delete user {user.EmailAddress} and all associated rows from the database. Are you sure you want to commit this transaction? (y/n)"); - if (!deletionConfirmation) - { - return; - } + if (!deletionConfirmation) return; dbOperation.RemoveUserOrLogError(user); } + private void AddLas(User? user, IReadOnlyCollection? custodianCodes) { if (user == null) @@ -235,7 +236,7 @@ private void AddLas(User? user, IReadOnlyCollection? custodianCodes) outputProvider.Output("Unrecognised LA. Make sure all codes are present in the database."); return; } - + dbOperation.AddLasToUser(user, lasToAdd); } @@ -254,22 +255,20 @@ public void RemoveLas(User? user, IReadOnlyCollection? custodianCodes) } var userConfirmation = ConfirmCustodianCodes(user.EmailAddress, custodianCodes, user, true); - if (!userConfirmation) - { - return; - } + if (!userConfirmation) return; var lasToRemove = user.LocalAuthorities.Where(la => custodianCodes.Contains(la.CustodianCode)).ToList(); var missingCodes = custodianCodes.Where(code => !lasToRemove.Any(la => la.CustodianCode.Equals(code))).ToList(); if (missingCodes.Count > 0) { - outputProvider.Output($"Could not find LAs attached to {user.EmailAddress} for the following codes: {string.Join(", ", missingCodes)}. Please check your inputs and try again."); + outputProvider.Output( + $"Could not find LAs attached to {user.EmailAddress} for the following codes: {string.Join(", ", missingCodes)}. Please check your inputs and try again."); return; } dbOperation.RemoveLasFromUser(user, lasToRemove); } - + private void AddConsortia(User? user, IReadOnlyCollection? consortiumCodes) { if (user == null) @@ -285,20 +284,20 @@ private void AddConsortia(User? user, IReadOnlyCollection? consortiumCod } var consortiaToAdd = dbOperation.GetConsortia(consortiumCodes); - + var ownedCustodianCodesInConsortia = GetOwnedCustodianCodesInConsortia(user, consortiumCodes); var lasToRemove = dbOperation.GetLas(ownedCustodianCodesInConsortia) ?? new List(); - + if (consortiaToAdd == null) { outputProvider.Output("Unrecognised Consortium. Make sure all codes are present in the database."); return; } - + dbOperation.AddConsortiaAndRemoveLasFromUser(user, consortiaToAdd, lasToRemove); } - private (User? user, UserStatus userStatus) SetupUser(string userEmailAddress) + private (User? user, UserStatus userStatus) CheckUserStatus(string userEmailAddress) { var user = GetUser(userEmailAddress); var userStatus = GetUserStatus(user); @@ -307,11 +306,14 @@ private void AddConsortia(User? user, IReadOnlyCollection? consortiumCod outputProvider.Output("!!! ATTENTION! READ CAREFULLY OR RISK A DATA BREACH !!!"); outputProvider.Output(""); - outputProvider.Output("You are about to grant a user permission to read PERSONALLY IDENTIFIABLE INFORMATION submitted to LAs."); - outputProvider.Output("Take a moment to double check the following list and only continue if you are certain this user should have access to these LAs."); - outputProvider.Output("NB: in particular, you should only do this for LAs that have signed their DSA contracts!"); + outputProvider.Output( + "You are about to grant a user permission to read PERSONALLY IDENTIFIABLE INFORMATION submitted to LAs."); + outputProvider.Output( + "Take a moment to double check the following list and only continue if you are certain this user should have access to these LAs."); + outputProvider.Output( + "NB: in particular, you should only do this for LAs that have signed their DSA contracts!"); outputProvider.Output(""); - + return (user, userStatus); } @@ -322,22 +324,21 @@ public void CreateOrUpdateUserWithLas(string? userEmailAddress, IReadOnlyCollect outputProvider.Output("Please specify user E-mail address to create or update"); return; } - - var (user, userStatus) = SetupUser(userEmailAddress); + + var (user, userStatus) = CheckUserStatus(userEmailAddress); var confirmation = ConfirmCustodianCodes(userEmailAddress, custodianCodes, user, false); if (confirmation) - { - if (userStatus.Equals(UserStatus.Active)) - { - AddLas(user, custodianCodes); - } - else if (userStatus.Equals(UserStatus.New)) + switch (userStatus) { - TryCreateUser(userEmailAddress, custodianCodes, null); + case UserStatus.Active: + AddLas(user, custodianCodes); + break; + case UserStatus.New: + TryCreateUser(userEmailAddress, custodianCodes, null); + break; } - } } public void CreateOrUpdateUserWithConsortia(string? userEmailAddress, IReadOnlyCollection consortiumCodes) @@ -347,13 +348,12 @@ public void CreateOrUpdateUserWithConsortia(string? userEmailAddress, IReadOnlyC outputProvider.Output("Please specify user E-mail address to create or update"); return; } - - var (user, userStatus) = SetupUser(userEmailAddress); + + var (user, userStatus) = CheckUserStatus(userEmailAddress); var confirmation = ConfirmConsortiumCodes(userEmailAddress, consortiumCodes, user, false); if (confirmation) - { switch (userStatus) { case UserStatus.Active: @@ -363,7 +363,6 @@ public void CreateOrUpdateUserWithConsortia(string? userEmailAddress, IReadOnlyC TryCreateUser(userEmailAddress, null, consortiumCodes); break; } - } } public void RemoveConsortia(User? user, IReadOnlyCollection? consortiumCodes) @@ -381,16 +380,16 @@ public void RemoveConsortia(User? user, IReadOnlyCollection? consortiumC } var userConfirmation = ConfirmConsortiumCodes(user.EmailAddress, consortiumCodes, user, true); - if (!userConfirmation) - { - return; - } + if (!userConfirmation) return; - var consortiaToRemove = user.Consortia.Where(consortium => consortiumCodes.Contains(consortium.ConsortiumCode)).ToList(); - var missingCodes = consortiumCodes.Where(code => !consortiaToRemove.Any(consortium => consortium.ConsortiumCode.Equals(code))).ToList(); + var consortiaToRemove = user.Consortia.Where(consortium => consortiumCodes.Contains(consortium.ConsortiumCode)) + .ToList(); + var missingCodes = consortiumCodes + .Where(code => !consortiaToRemove.Any(consortium => consortium.ConsortiumCode.Equals(code))).ToList(); if (missingCodes.Count > 0) { - outputProvider.Output($"Could not find Consortia attached to {user.EmailAddress} for the following codes: {string.Join(", ", missingCodes)}. Please check your inputs and try again."); + outputProvider.Output( + $"Could not find Consortia attached to {user.EmailAddress} for the following codes: {string.Join(", ", missingCodes)}. Please check your inputs and try again."); return; } diff --git a/HerPortal.ManagementShell/DatabaseOperation.cs b/HerPortal.ManagementShell/DatabaseOperation.cs index ba8d3bd..5e70f01 100644 --- a/HerPortal.ManagementShell/DatabaseOperation.cs +++ b/HerPortal.ManagementShell/DatabaseOperation.cs @@ -27,11 +27,8 @@ public List GetUsersWithLocalAuthoritiesAndConsortia() public List? GetLas(IReadOnlyCollection custodianCodes) { - if (custodianCodes.Any(code => !dbContext.LocalAuthorities.Any(la => la.CustodianCode == code))) - { - return null; - } - + if (custodianCodes.Any(code => !dbContext.LocalAuthorities.Any(la => la.CustodianCode == code))) return null; + return custodianCodes .Select(code => dbContext.LocalAuthorities .Single(la => la.CustodianCode == code)) @@ -40,11 +37,8 @@ public List GetUsersWithLocalAuthoritiesAndConsortia() public List? GetConsortia(IReadOnlyCollection consortiumCodes) { - if (consortiumCodes.Any(code => !dbContext.Consortia.Any(la => la.ConsortiumCode == code))) - { - return null; - } - + if (consortiumCodes.Any(code => !dbContext.Consortia.Any(la => la.ConsortiumCode == code))) return null; + return consortiumCodes .Select(code => dbContext.Consortia .Single(la => la.ConsortiumCode == code)) @@ -69,7 +63,8 @@ public void RemoveUserOrLogError(User user) } } - public void CreateUserOrLogError(string userEmailAddress, List localAuthorities, List consortia) + public void CreateUserOrLogError(string userEmailAddress, List localAuthorities, + List consortia) { using var dbContextTransaction = dbContext.Database.BeginTransaction(); try @@ -98,10 +93,7 @@ public void RemoveLasFromUser(User user, List lasToRemove) using var dbContextTransaction = dbContext.Database.BeginTransaction(); try { - foreach (var la in lasToRemove) - { - user?.LocalAuthorities.Remove(la); - } + foreach (var la in lasToRemove) user?.LocalAuthorities.Remove(la); dbContext.SaveChanges(); outputProvider.Output("Operation successful"); @@ -114,19 +106,14 @@ public void RemoveLasFromUser(User user, List lasToRemove) } } - public void AddConsortiaAndRemoveLasFromUser(User user, List consortia, List localAuthorities) + public void AddConsortiaAndRemoveLasFromUser(User user, List consortia, + List localAuthorities) { using var dbContextTransaction = dbContext.Database.BeginTransaction(); try { - foreach (var consortium in consortia) - { - user?.Consortia.Add(consortium); - } - foreach (var localAuthority in localAuthorities) - { - user?.LocalAuthorities.Remove(localAuthority); - } + foreach (var consortium in consortia) user?.Consortia.Add(consortium); + foreach (var localAuthority in localAuthorities) user?.LocalAuthorities.Remove(localAuthority); dbContext.SaveChanges(); outputProvider.Output("Operation successful"); @@ -144,10 +131,7 @@ public void RemoveConsortiaFromUser(User user, List consortia) using var dbContextTransaction = dbContext.Database.BeginTransaction(); try { - foreach (var consortium in consortia) - { - user?.Consortia.Remove(consortium); - } + foreach (var consortium in consortia) user?.Consortia.Remove(consortium); dbContext.SaveChanges(); outputProvider.Output("Operation successful"); @@ -166,7 +150,6 @@ public void AddLasToUser(User user, List localAuthorities) try { foreach (var la in localAuthorities) - { try { user?.LocalAuthorities.Add(la); @@ -176,7 +159,6 @@ public void AddLasToUser(User user, List localAuthorities) outputProvider.Output(e.Message); throw; } - } dbContext.SaveChanges(); outputProvider.Output("Operation successful"); @@ -195,7 +177,6 @@ public void AddConsortiaToUser(User user, List consortia) try { foreach (var consortium in consortia) - { try { user?.Consortia.Add(consortium); @@ -205,7 +186,6 @@ public void AddConsortiaToUser(User user, List consortia) outputProvider.Output(e.Message); throw; } - } dbContext.SaveChanges(); outputProvider.Output("Operation successful"); diff --git a/HerPortal.ManagementShell/IDatabaseOperation.cs b/HerPortal.ManagementShell/IDatabaseOperation.cs index e60d09e..dfa148e 100644 --- a/HerPortal.ManagementShell/IDatabaseOperation.cs +++ b/HerPortal.ManagementShell/IDatabaseOperation.cs @@ -8,10 +8,16 @@ public interface IDatabaseOperation public List? GetLas(IReadOnlyCollection custodianCodes); public List? GetConsortia(IReadOnlyCollection consortiumCodes); public void RemoveUserOrLogError(User user); - public void CreateUserOrLogError(string userEmailAddress, List localAuthorities, List consortia); + + public void CreateUserOrLogError(string userEmailAddress, List localAuthorities, + List consortia); + public void AddLasToUser(User user, List localAuthorities); public void AddConsortiaToUser(User user, List consortia); public void RemoveLasFromUser(User user, List localAuthorities); - public void AddConsortiaAndRemoveLasFromUser(User user, List consortia, List localAuthorities); + + public void AddConsortiaAndRemoveLasFromUser(User user, List consortia, + List localAuthorities); + public void RemoveConsortiaFromUser(User user, List consortia); } \ No newline at end of file From 39bd576869e0c3978bf72a655d486d286ffb7fb2 Mon Sep 17 00:00:00 2001 From: Glenn Clarke Date: Tue, 28 May 2024 15:57:11 +0100 Subject: [PATCH 21/81] PC-1084: Updated user to have method to get all administrated custodian codes including custodian codes of all consortiums they administrate. Updated usages of original user's custodian codes to new method. Updated data access provider to include the user's consortiums when retrieving information from the DB. Updated tests to account for consortiums being included. --- HerPortal.BusinessLogic/Models/User.cs | 11 +++++++++++ .../Services/CsvFileService/CsvFileService.cs | 4 ++-- HerPortal.Data/DataAccessProvider.cs | 9 +++++++-- HerPortal.UnitTests/Builders/UserBuilder.cs | 3 ++- .../Helpers/ValidConsortiumGenerator.cs | 17 +++++++++++++++++ .../Website/Models/HomepageViewModelTests.cs | 14 +++++++++----- HerPortal/Models/HomepageViewModel.cs | 12 ++++++------ 7 files changed, 54 insertions(+), 16 deletions(-) create mode 100644 HerPortal.UnitTests/Helpers/ValidConsortiumGenerator.cs diff --git a/HerPortal.BusinessLogic/Models/User.cs b/HerPortal.BusinessLogic/Models/User.cs index 096cc6a..4138188 100644 --- a/HerPortal.BusinessLogic/Models/User.cs +++ b/HerPortal.BusinessLogic/Models/User.cs @@ -8,4 +8,15 @@ public class User public List LocalAuthorities { get; set; } public List Consortia { get; set; } + + public List GetAdministratedCustodianCodes() + { + var consortiumCodes = Consortia.Select(consortium => consortium.ConsortiumCode).ToList(); + var custodianCodes = LocalAuthorities.Select(la => la.CustodianCode).ToList(); + return LocalAuthorityData.LocalAuthorityConsortiumCodeByCustodianCode + .Where(codes => consortiumCodes.Contains(codes.Value)) + .Select(codes => codes.Key) + .Union(custodianCodes) + .ToList(); + } } \ No newline at end of file diff --git a/HerPortal.BusinessLogic/Services/CsvFileService/CsvFileService.cs b/HerPortal.BusinessLogic/Services/CsvFileService/CsvFileService.cs index b40ec2e..9cbee75 100644 --- a/HerPortal.BusinessLogic/Services/CsvFileService/CsvFileService.cs +++ b/HerPortal.BusinessLogic/Services/CsvFileService/CsvFileService.cs @@ -83,7 +83,7 @@ public async Task> GetFileDataForUserAsync(string userE { // Make sure that we only return file data for files that the user currently has access to var user = await dataAccessProvider.GetUserByEmailAsync(userEmailAddress); - var currentCustodianCodes = user.LocalAuthorities.Select(la => la.CustodianCode); + var currentCustodianCodes = user.GetAdministratedCustodianCodes(); var downloads = await dataAccessProvider.GetCsvFileDownloadDataForUserAsync(user.Id); var files = new List(); @@ -167,7 +167,7 @@ public async Task GetLocalAuthorityFileForDownloadAsync(string custodian { // Important! First ensure the logged-in user is allowed to access this data var userData = await dataAccessProvider.GetUserByEmailAsync(userEmailAddress); - if (!userData.LocalAuthorities.Any(la => la.CustodianCode == custodianCode)) + if (userData.GetAdministratedCustodianCodes().All(custodianCodes => custodianCodes != custodianCode)) { // We don't want to log the User's email address for GDPR reasons, but the ID is fine. throw new SecurityException( diff --git a/HerPortal.Data/DataAccessProvider.cs b/HerPortal.Data/DataAccessProvider.cs index 4debcc9..e8e7573 100644 --- a/HerPortal.Data/DataAccessProvider.cs +++ b/HerPortal.Data/DataAccessProvider.cs @@ -17,6 +17,7 @@ public async Task GetUserByEmailAsync(string emailAddress) { var users = await context.Users .Include(u => u.LocalAuthorities) + .Include(u => u.Consortia) // In order to compare email addresses case-insensitively, we bring the whole table into memory here // to perform the comparison in C#, since Entity Framework doesn't allow for the StringComparison // overload. However, since we don't expect this table to be monstrously huge this is acceptable @@ -45,6 +46,7 @@ public async Task> GetAllActiveUsersAsync() return await context.Users .Where(u => u.HasLoggedIn) .Include(u => u.LocalAuthorities) + .Include(u => u.Consortia) .ToListAsync(); } @@ -106,11 +108,14 @@ public async Task MarkCsvFileAsDownloadedAsync(string custodianCode, int year, i public List GetConsortiumCodesForUser(User user) { var userLocalAuthorities = user.LocalAuthorities.Select(la => la.CustodianCode); - + var userConsortia = user.Consortia.Select(consortium => consortium.ConsortiumCode); + // user is a consortium manager if they are a manager of all LAs in that consortium - return ConsortiumData.ConsortiumCustodianCodesIdsByConsortiumCode + return ConsortiumData.ConsortiumCustodianCodesIdsByConsortiumCode .Where(pair => pair.Value.All(consortiumLa => userLocalAuthorities.Contains(consortiumLa))) .Select(pair => pair.Key) + //Include all explicitly listed consortium codes + .Union(userConsortia) .ToList(); } } diff --git a/HerPortal.UnitTests/Builders/UserBuilder.cs b/HerPortal.UnitTests/Builders/UserBuilder.cs index 18f4f48..1f39325 100644 --- a/HerPortal.UnitTests/Builders/UserBuilder.cs +++ b/HerPortal.UnitTests/Builders/UserBuilder.cs @@ -14,7 +14,8 @@ public UserBuilder(string emailAddress) Id = 13, EmailAddress = emailAddress, HasLoggedIn = true, - LocalAuthorities = new List() + LocalAuthorities = new List(), + Consortia = new List() }; } diff --git a/HerPortal.UnitTests/Helpers/ValidConsortiumGenerator.cs b/HerPortal.UnitTests/Helpers/ValidConsortiumGenerator.cs new file mode 100644 index 0000000..f42d96d --- /dev/null +++ b/HerPortal.UnitTests/Helpers/ValidConsortiumGenerator.cs @@ -0,0 +1,17 @@ +namespace Tests.Helpers; + +using System.Collections.Generic; +using System.Linq; +using HerPortal.BusinessLogic.Models; + +public class ValidConsortiumGenerator +{ + public static IEnumerable GetConsortiaWithDifferentCodes(int count) + { + return ConsortiumData + .ConsortiumNamesByConsortiumCode + .Keys + .Take(count) + .Select(cc => new Consortium { ConsortiumCode = cc }); + } +} \ No newline at end of file diff --git a/HerPortal.UnitTests/Website/Models/HomepageViewModelTests.cs b/HerPortal.UnitTests/Website/Models/HomepageViewModelTests.cs index 5da2565..36f4f95 100644 --- a/HerPortal.UnitTests/Website/Models/HomepageViewModelTests.cs +++ b/HerPortal.UnitTests/Website/Models/HomepageViewModelTests.cs @@ -39,6 +39,7 @@ bool shouldShowBanner { HasLoggedIn = hasUserLoggedIn, LocalAuthorities = new List(), + Consortia = new List() }; // Act @@ -48,14 +49,16 @@ bool shouldShowBanner viewModel.ShouldShowBanner.Should().Be(shouldShowBanner); } - [TestCase(1, false)] - [TestCase(2, true)] - [TestCase(3, true)] - [TestCase(4, true)] - [TestCase(100, true)] + [TestCase(1, 0, false)] + [TestCase(2, 0, true)] + [TestCase(3, 0, true)] + [TestCase(4, 0,true)] + [TestCase(100, 0,true)] + [TestCase(0, 1, true)] public void HomepageViewModel_OnlyWhenUserHasOneLocalAuthority_ShouldNotShowFilters ( int numberOfLas, + int numberOfConsortia, bool expected ) { // Arrange @@ -65,6 +68,7 @@ bool expected LocalAuthorities = ValidLocalAuthorityGenerator .GetLocalAuthoritiesWithDifferentCodes(numberOfLas) .ToList(), + Consortia = ValidConsortiumGenerator.GetConsortiaWithDifferentCodes(numberOfConsortia).ToList() }; // Act diff --git a/HerPortal/Models/HomepageViewModel.cs b/HerPortal/Models/HomepageViewModel.cs index 3f118f7..9d04306 100644 --- a/HerPortal/Models/HomepageViewModel.cs +++ b/HerPortal/Models/HomepageViewModel.cs @@ -68,13 +68,13 @@ public HomepageViewModel( List consortiumCodes ) { - var checkboxLabels = user.LocalAuthorities - .Select(la => new KeyValuePair + var checkboxLabels = user.GetAdministratedCustodianCodes() + .Select(custodianCode => new KeyValuePair ( - la.CustodianCode, + custodianCode, new LabelViewModel { - Text = LocalAuthorityData.LocalAuthorityNamesByCustodianCode[la.CustodianCode], + Text = LocalAuthorityData.LocalAuthorityNamesByCustodianCode[custodianCode], } ) ) @@ -89,9 +89,9 @@ List consortiumCodes ))); ShouldShowBanner = !user.HasLoggedIn; - ShouldShowFilters = user.LocalAuthorities.Count >= 2; + ShouldShowFilters = user.GetAdministratedCustodianCodes().Count >= 2; Codes = new List(); - Codes.AddRange(user.LocalAuthorities.Select(la => la.CustodianCode)); + Codes.AddRange(user.GetAdministratedCustodianCodes()); Codes.AddRange(consortiumCodes); LocalAuthorityCheckboxLabels = new Dictionary(checkboxLabels .OrderBy(kvp => kvp.Value.Text) From 6e218e9c052615d0fff9f5771e6458b3f5137fbb Mon Sep 17 00:00:00 2001 From: Glenn Clarke Date: Tue, 28 May 2024 16:18:58 +0100 Subject: [PATCH 22/81] PC-1084: Whitespace cleanup. Helper class is now static. --- HerPortal.BusinessLogic/Models/User.cs | 2 +- HerPortal.Data/DataAccessProvider.cs | 4 ++-- HerPortal.UnitTests/Helpers/ValidConsortiumGenerator.cs | 2 +- .../Website/Models/HomepageViewModelTests.cs | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/HerPortal.BusinessLogic/Models/User.cs b/HerPortal.BusinessLogic/Models/User.cs index 4138188..7bd5781 100644 --- a/HerPortal.BusinessLogic/Models/User.cs +++ b/HerPortal.BusinessLogic/Models/User.cs @@ -8,7 +8,7 @@ public class User public List LocalAuthorities { get; set; } public List Consortia { get; set; } - + public List GetAdministratedCustodianCodes() { var consortiumCodes = Consortia.Select(consortium => consortium.ConsortiumCode).ToList(); diff --git a/HerPortal.Data/DataAccessProvider.cs b/HerPortal.Data/DataAccessProvider.cs index e8e7573..64bc3a3 100644 --- a/HerPortal.Data/DataAccessProvider.cs +++ b/HerPortal.Data/DataAccessProvider.cs @@ -109,9 +109,9 @@ public List GetConsortiumCodesForUser(User user) { var userLocalAuthorities = user.LocalAuthorities.Select(la => la.CustodianCode); var userConsortia = user.Consortia.Select(consortium => consortium.ConsortiumCode); - + // user is a consortium manager if they are a manager of all LAs in that consortium - return ConsortiumData.ConsortiumCustodianCodesIdsByConsortiumCode + return ConsortiumData.ConsortiumCustodianCodesIdsByConsortiumCode .Where(pair => pair.Value.All(consortiumLa => userLocalAuthorities.Contains(consortiumLa))) .Select(pair => pair.Key) //Include all explicitly listed consortium codes diff --git a/HerPortal.UnitTests/Helpers/ValidConsortiumGenerator.cs b/HerPortal.UnitTests/Helpers/ValidConsortiumGenerator.cs index f42d96d..f5609b6 100644 --- a/HerPortal.UnitTests/Helpers/ValidConsortiumGenerator.cs +++ b/HerPortal.UnitTests/Helpers/ValidConsortiumGenerator.cs @@ -4,7 +4,7 @@ using System.Linq; using HerPortal.BusinessLogic.Models; -public class ValidConsortiumGenerator +public static class ValidConsortiumGenerator { public static IEnumerable GetConsortiaWithDifferentCodes(int count) { diff --git a/HerPortal.UnitTests/Website/Models/HomepageViewModelTests.cs b/HerPortal.UnitTests/Website/Models/HomepageViewModelTests.cs index 36f4f95..87eea86 100644 --- a/HerPortal.UnitTests/Website/Models/HomepageViewModelTests.cs +++ b/HerPortal.UnitTests/Website/Models/HomepageViewModelTests.cs @@ -48,12 +48,12 @@ bool shouldShowBanner // Assert viewModel.ShouldShowBanner.Should().Be(shouldShowBanner); } - + [TestCase(1, 0, false)] [TestCase(2, 0, true)] [TestCase(3, 0, true)] - [TestCase(4, 0,true)] - [TestCase(100, 0,true)] + [TestCase(4, 0, true)] + [TestCase(100, 0, true)] [TestCase(0, 1, true)] public void HomepageViewModel_OnlyWhenUserHasOneLocalAuthority_ShouldNotShowFilters ( From 491bfafa332f37adaec25281973b301dc291667b Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Thu, 30 May 2024 09:56:39 +0100 Subject: [PATCH 23/81] PC-1085: add back braces cut by lint --- HerPortal.ManagementShell/AdminAction.cs | 34 +++++++++++++++---- .../DatabaseOperation.cs | 10 ++++-- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/HerPortal.ManagementShell/AdminAction.cs b/HerPortal.ManagementShell/AdminAction.cs index 5c4d933..a08111e 100644 --- a/HerPortal.ManagementShell/AdminAction.cs +++ b/HerPortal.ManagementShell/AdminAction.cs @@ -45,7 +45,10 @@ public AdminAction(IDatabaseOperation dbOperation, IOutputProvider outputProvide private void PrintCodes(IReadOnlyCollection codes, IReadOnlyDictionary codeToNameDict) { - if (codes.Count < 1) outputProvider.Output("(None)"); + if (codes.Count < 1) + { + outputProvider.Output("(None)"); + } foreach (var code in codes) { @@ -93,7 +96,10 @@ private bool ConfirmCustodianCodes(string userEmailAddress, IReadOnlyCollection< } var hasUserConfirmed = outputProvider.Confirm("Please confirm (y/n)"); - if (!hasUserConfirmed) outputProvider.Output("Process cancelled, no changes were made to the database"); + if (!hasUserConfirmed) + { + outputProvider.Output("Process cancelled, no changes were made to the database"); + } return hasUserConfirmed; } @@ -143,7 +149,10 @@ private bool ConfirmConsortiumCodes(string? userEmailAddress, IReadOnlyCollectio } var hasUserConfirmed = outputProvider.Confirm("Please confirm (y/n)"); - if (!hasUserConfirmed) outputProvider.Output("Process cancelled, no changes were made to the database"); + if (!hasUserConfirmed) + { + outputProvider.Output("Process cancelled, no changes were made to the database"); + } return hasUserConfirmed; } @@ -206,7 +215,10 @@ public void TryRemoveUser(User? user) var deletionConfirmation = outputProvider.Confirm( $"Attention! This will delete user {user.EmailAddress} and all associated rows from the database. Are you sure you want to commit this transaction? (y/n)"); - if (!deletionConfirmation) return; + if (!deletionConfirmation) + { + return; + } dbOperation.RemoveUserOrLogError(user); } @@ -255,7 +267,10 @@ public void RemoveLas(User? user, IReadOnlyCollection? custodianCodes) } var userConfirmation = ConfirmCustodianCodes(user.EmailAddress, custodianCodes, user, true); - if (!userConfirmation) return; + if (!userConfirmation) + { + return; + } var lasToRemove = user.LocalAuthorities.Where(la => custodianCodes.Contains(la.CustodianCode)).ToList(); var missingCodes = custodianCodes.Where(code => !lasToRemove.Any(la => la.CustodianCode.Equals(code))).ToList(); @@ -330,6 +345,7 @@ public void CreateOrUpdateUserWithLas(string? userEmailAddress, IReadOnlyCollect var confirmation = ConfirmCustodianCodes(userEmailAddress, custodianCodes, user, false); if (confirmation) + { switch (userStatus) { case UserStatus.Active: @@ -339,6 +355,7 @@ public void CreateOrUpdateUserWithLas(string? userEmailAddress, IReadOnlyCollect TryCreateUser(userEmailAddress, custodianCodes, null); break; } + } } public void CreateOrUpdateUserWithConsortia(string? userEmailAddress, IReadOnlyCollection consortiumCodes) @@ -354,6 +371,7 @@ public void CreateOrUpdateUserWithConsortia(string? userEmailAddress, IReadOnlyC var confirmation = ConfirmConsortiumCodes(userEmailAddress, consortiumCodes, user, false); if (confirmation) + { switch (userStatus) { case UserStatus.Active: @@ -363,6 +381,7 @@ public void CreateOrUpdateUserWithConsortia(string? userEmailAddress, IReadOnlyC TryCreateUser(userEmailAddress, null, consortiumCodes); break; } + } } public void RemoveConsortia(User? user, IReadOnlyCollection? consortiumCodes) @@ -380,7 +399,10 @@ public void RemoveConsortia(User? user, IReadOnlyCollection? consortiumC } var userConfirmation = ConfirmConsortiumCodes(user.EmailAddress, consortiumCodes, user, true); - if (!userConfirmation) return; + if (!userConfirmation) + { + return; + } var consortiaToRemove = user.Consortia.Where(consortium => consortiumCodes.Contains(consortium.ConsortiumCode)) .ToList(); diff --git a/HerPortal.ManagementShell/DatabaseOperation.cs b/HerPortal.ManagementShell/DatabaseOperation.cs index 5e70f01..6beab1e 100644 --- a/HerPortal.ManagementShell/DatabaseOperation.cs +++ b/HerPortal.ManagementShell/DatabaseOperation.cs @@ -27,7 +27,10 @@ public List GetUsersWithLocalAuthoritiesAndConsortia() public List? GetLas(IReadOnlyCollection custodianCodes) { - if (custodianCodes.Any(code => !dbContext.LocalAuthorities.Any(la => la.CustodianCode == code))) return null; + if (custodianCodes.Any(code => !dbContext.LocalAuthorities.Any(la => la.CustodianCode == code))) + { + return null; + } return custodianCodes .Select(code => dbContext.LocalAuthorities @@ -37,7 +40,10 @@ public List GetUsersWithLocalAuthoritiesAndConsortia() public List? GetConsortia(IReadOnlyCollection consortiumCodes) { - if (consortiumCodes.Any(code => !dbContext.Consortia.Any(la => la.ConsortiumCode == code))) return null; + if (consortiumCodes.Any(code => !dbContext.Consortia.Any(la => la.ConsortiumCode == code))) + { + return null; + } return consortiumCodes .Select(code => dbContext.Consortia From 7f1a56376b21ee741150552a50e41a4e82da3e4b Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Thu, 30 May 2024 11:44:36 +0100 Subject: [PATCH 24/81] PC-1085: print consortium name with LA name when knowing the consortium name is useful for the special exceptions --- HerPortal.ManagementShell/AdminAction.cs | 28 +++++++++++++++--------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/HerPortal.ManagementShell/AdminAction.cs b/HerPortal.ManagementShell/AdminAction.cs index a08111e..d3c317a 100644 --- a/HerPortal.ManagementShell/AdminAction.cs +++ b/HerPortal.ManagementShell/AdminAction.cs @@ -19,6 +19,12 @@ public enum UserStatus private readonly Dictionary custodianCodeToConsortiumCodeDict = LocalAuthorityData.LocalAuthorityConsortiumCodeByCustodianCode; + private readonly Dictionary custodianCodeToConsortiumNameDict = + LocalAuthorityData.LocalAuthorityConsortiumCodeByCustodianCode + .ToDictionary( + kvp => kvp.Key, + kvp => ConsortiumData.ConsortiumNamesByConsortiumCode[kvp.Value]); + private readonly Dictionary custodianCodeToLaNameDict = LocalAuthorityData.LocalAuthorityNamesByCustodianCode; @@ -43,7 +49,7 @@ public AdminAction(IDatabaseOperation dbOperation, IOutputProvider outputProvide )); } - private void PrintCodes(IReadOnlyCollection codes, IReadOnlyDictionary codeToNameDict) + private void PrintCodes(IReadOnlyCollection codes, Func codeToName) { if (codes.Count < 1) { @@ -52,7 +58,7 @@ private void PrintCodes(IReadOnlyCollection codes, IReadOnlyDictionary custodianCodeToLaNameDict[code]); } else if (user != null) { @@ -79,14 +85,15 @@ private bool ConfirmCustodianCodes(string userEmailAddress, IReadOnlyCollection< var custodianCodesInOwnedConsortium = custodianCodeInOwnedConsortiumGrouping[true].ToList(); outputProvider.Output("Add the following Local Authorities:"); - PrintCodes(custodianCodesNotInOwnedConsortium, custodianCodeToLaNameDict); + PrintCodes(custodianCodesNotInOwnedConsortium, code => custodianCodeToLaNameDict[code]); outputProvider.Output("Ignore the following Local Authorities already in owned Consortia:"); - PrintCodes(custodianCodesInOwnedConsortium, custodianCodeToLaNameDict); + PrintCodes(custodianCodesInOwnedConsortium, code => + $"{custodianCodeToLaNameDict[code]} ({custodianCodeToConsortiumNameDict[code]})"); } else { outputProvider.Output("Add the following Local Authorities:"); - PrintCodes(custodianCodes, custodianCodeToLaNameDict); + PrintCodes(custodianCodes, code => custodianCodeToLaNameDict[code]); } } catch (Exception e) @@ -123,23 +130,24 @@ private bool ConfirmConsortiumCodes(string? userEmailAddress, IReadOnlyCollectio if (remove) { outputProvider.Output("Remove the following Consortia:"); - PrintCodes(consortiumCodes, consortiumCodeToConsortiumNameDict); + PrintCodes(consortiumCodes, code => consortiumCodeToConsortiumNameDict[code]); } else if (user != null) { outputProvider.Output("Add the following Consortia:"); - PrintCodes(consortiumCodes, consortiumCodeToConsortiumNameDict); + PrintCodes(consortiumCodes, code => consortiumCodeToConsortiumNameDict[code]); // flag the need to remove access for any LAs in the new consortia var ownedCustodianCodesInConsortia = GetOwnedCustodianCodesInConsortia(user, consortiumCodes); outputProvider.Output("Remove the following Local Authorities in these Consortia:"); - PrintCodes(ownedCustodianCodesInConsortia, custodianCodeToLaNameDict); + PrintCodes(ownedCustodianCodesInConsortia, code => + $"{custodianCodeToLaNameDict[code]} ({custodianCodeToConsortiumNameDict[code]})"); } else { outputProvider.Output("Add the following Consortia:"); - PrintCodes(consortiumCodes, consortiumCodeToConsortiumNameDict); + PrintCodes(consortiumCodes, code => consortiumCodeToConsortiumNameDict[code]); } } catch (Exception e) From a729bbacf0ebc803db29b0fede93a5775cae645f Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Thu, 30 May 2024 12:05:10 +0100 Subject: [PATCH 25/81] PC-1085: use AddConsortiaToUser if possible previously would always use the more complex method --- HerPortal.ManagementShell/AdminAction.cs | 9 ++++++++- HerPortal.UnitTests/ManagementShell/AdminActionTests.cs | 6 +++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/HerPortal.ManagementShell/AdminAction.cs b/HerPortal.ManagementShell/AdminAction.cs index d3c317a..621ac6c 100644 --- a/HerPortal.ManagementShell/AdminAction.cs +++ b/HerPortal.ManagementShell/AdminAction.cs @@ -317,7 +317,14 @@ private void AddConsortia(User? user, IReadOnlyCollection? consortiumCod return; } - dbOperation.AddConsortiaAndRemoveLasFromUser(user, consortiaToAdd, lasToRemove); + if (lasToRemove.Count > 0) + { + dbOperation.AddConsortiaAndRemoveLasFromUser(user, consortiaToAdd, lasToRemove); + } + else + { + dbOperation.AddConsortiaToUser(user, consortiaToAdd); + } } private (User? user, UserStatus userStatus) CheckUserStatus(string userEmailAddress) diff --git a/HerPortal.UnitTests/ManagementShell/AdminActionTests.cs b/HerPortal.UnitTests/ManagementShell/AdminActionTests.cs index 3e7f433..865f1c9 100644 --- a/HerPortal.UnitTests/ManagementShell/AdminActionTests.cs +++ b/HerPortal.UnitTests/ManagementShell/AdminActionTests.cs @@ -500,7 +500,7 @@ public void CreateOrUpdateUserWithConsortia_AddsLasToExistingUser_IfUserFoundByD }; mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); - var custodianCodes = new[] { "C_0003" }; + var consortiumCodes = new[] { "C_0003" }; mockOutputProvider.Setup(op => op.Confirm("Please confirm (y/n)")).Returns(true); var consortiaToAdd = new List @@ -511,10 +511,10 @@ public void CreateOrUpdateUserWithConsortia_AddsLasToExistingUser_IfUserFoundByD ConsortiumCode = "C_0003" } }; - mockDatabaseOperation.Setup(mock => mock.GetConsortia(custodianCodes)).Returns(consortiaToAdd); + mockDatabaseOperation.Setup(mock => mock.GetConsortia(consortiumCodes)).Returns(consortiaToAdd); // Act - underTest.CreateOrUpdateUserWithConsortia(userEmailAddress, custodianCodes); + underTest.CreateOrUpdateUserWithConsortia(userEmailAddress, consortiumCodes); // Assert mockDatabaseOperation.Verify(mock => mock.AddConsortiaToUser(users[0], consortiaToAdd)); From 93f0616104b02e3f35a156b21042f8450129a3b9 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Thu, 30 May 2024 12:06:44 +0100 Subject: [PATCH 26/81] PC-1085: use ==0 over <1 --- HerPortal.ManagementShell/AdminAction.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/HerPortal.ManagementShell/AdminAction.cs b/HerPortal.ManagementShell/AdminAction.cs index 621ac6c..6047337 100644 --- a/HerPortal.ManagementShell/AdminAction.cs +++ b/HerPortal.ManagementShell/AdminAction.cs @@ -260,7 +260,7 @@ private void AddLas(User? user, IReadOnlyCollection? custodianCodes) dbOperation.AddLasToUser(user, lasToAdd); } - public void RemoveLas(User? user, IReadOnlyCollection? custodianCodes) + public void RemoveLas(User? user, IReadOnlyCollection custodianCodes) { if (user == null) { @@ -268,7 +268,7 @@ public void RemoveLas(User? user, IReadOnlyCollection? custodianCodes) return; } - if (custodianCodes == null || custodianCodes.Count < 1) + if (custodianCodes.Count == 0) { outputProvider.Output("Please specify custodian codes to remove from user"); return; @@ -399,7 +399,7 @@ public void CreateOrUpdateUserWithConsortia(string? userEmailAddress, IReadOnlyC } } - public void RemoveConsortia(User? user, IReadOnlyCollection? consortiumCodes) + public void RemoveConsortia(User? user, IReadOnlyCollection consortiumCodes) { if (user == null) { @@ -407,7 +407,7 @@ public void RemoveConsortia(User? user, IReadOnlyCollection? consortiumC return; } - if (consortiumCodes == null || consortiumCodes.Count < 1) + if (consortiumCodes.Count == 0) { outputProvider.Output("Please specify consortium codes to remove from user"); return; From 6196d6396e721009a3d442e91b7ae15cce8782fd Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Thu, 30 May 2024 12:20:39 +0100 Subject: [PATCH 27/81] PC-1085: patch a few failing tests --- HerPortal.UnitTests/ManagementShell/AdminActionTests.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/HerPortal.UnitTests/ManagementShell/AdminActionTests.cs b/HerPortal.UnitTests/ManagementShell/AdminActionTests.cs index 865f1c9..5bd1956 100644 --- a/HerPortal.UnitTests/ManagementShell/AdminActionTests.cs +++ b/HerPortal.UnitTests/ManagementShell/AdminActionTests.cs @@ -82,6 +82,7 @@ public void CreateOrUpdateUserWithLas_CreatesNewUser_IfUserNotFoundByDbOperation } }; mockDatabaseOperation.Setup(mock => mock.GetLas(custodianCodes)).Returns(las); + mockDatabaseOperation.Setup(mock => mock.GetConsortia(It.IsAny())).Returns(new List()); // Act underTest.CreateOrUpdateUserWithLas(userEmailAddress, custodianCodes); @@ -470,6 +471,7 @@ public void CreateOrUpdateUserWithConsortia_CreatesNewUser_IfUserNotFoundByDbOpe } }; mockDatabaseOperation.Setup(mock => mock.GetConsortia(consortiumCodes)).Returns(consortia); + mockDatabaseOperation.Setup(mock => mock.GetLas(It.IsAny())).Returns(new List()); // Act underTest.CreateOrUpdateUserWithConsortia(userEmailAddress, consortiumCodes); @@ -694,7 +696,7 @@ public void CreateOrUpdateUserWithConsortia_WhenUserOwnsLocalAuthorityInConsorti underTest.CreateOrUpdateUserWithConsortia(userEmailAddress, consortiumCodes); // Assert - mockOutputProvider.Verify(mock => mock.Output("2372: Blackburn With Darwen"), Times.Once()); + mockOutputProvider.Verify(mock => mock.Output("2372: Blackburn With Darwen (Blackpool)"), Times.Once()); } [Test] From a6bf2f72e14ed32226fbef36a3b79130410751fa Mon Sep 17 00:00:00 2001 From: Glenn Clarke Date: Thu, 30 May 2024 12:25:50 +0100 Subject: [PATCH 28/81] PC-1084: Renamed and simplified many functions. Updated tests to use new custodian code retrieval. Removed unnecessary functions. Removed having all LAs in a consortium giving user Custodian admin rights. --- .../IDataAccessProvider.cs | 1 - HerPortal.BusinessLogic/Models/User.cs | 9 +-- .../Services/CsvFileService/CsvFileService.cs | 70 +++++++++++-------- .../Services/UserService.cs | 5 -- HerPortal.Data/DataAccessProvider.cs | 16 ----- HerPortal.UnitTests/Builders/UserBuilder.cs | 6 ++ .../CsvFileService/CsvFileServiceTests.cs | 31 ++++---- .../Controllers/HomeControllerTests.cs | 2 +- .../Website/Models/HomepageViewModelTests.cs | 39 ++++++++--- HerPortal/Controllers/HomeController.cs | 5 +- HerPortal/Models/HomepageViewModel.cs | 19 ++--- 11 files changed, 109 insertions(+), 94 deletions(-) diff --git a/HerPortal.BusinessLogic/IDataAccessProvider.cs b/HerPortal.BusinessLogic/IDataAccessProvider.cs index 6350e78..1c21e0b 100644 --- a/HerPortal.BusinessLogic/IDataAccessProvider.cs +++ b/HerPortal.BusinessLogic/IDataAccessProvider.cs @@ -9,5 +9,4 @@ public interface IDataAccessProvider public Task> GetAllActiveUsersAsync(); public Task> GetCsvFileDownloadDataForUserAsync(int userId); public Task MarkCsvFileAsDownloadedAsync(string custodianCode, int year, int month, int userId); - public List GetConsortiumCodesForUser(User user); } diff --git a/HerPortal.BusinessLogic/Models/User.cs b/HerPortal.BusinessLogic/Models/User.cs index 7bd5781..2c95822 100644 --- a/HerPortal.BusinessLogic/Models/User.cs +++ b/HerPortal.BusinessLogic/Models/User.cs @@ -9,13 +9,14 @@ public class User public List LocalAuthorities { get; set; } public List Consortia { get; set; } - public List GetAdministratedCustodianCodes() + public List GetAdministeredCustodianCodes() { var consortiumCodes = Consortia.Select(consortium => consortium.ConsortiumCode).ToList(); var custodianCodes = LocalAuthorities.Select(la => la.CustodianCode).ToList(); - return LocalAuthorityData.LocalAuthorityConsortiumCodeByCustodianCode - .Where(codes => consortiumCodes.Contains(codes.Value)) - .Select(codes => codes.Key) + + return consortiumCodes.SelectMany(consortiumCode => + ConsortiumData.ConsortiumCustodianCodesIdsByConsortiumCode[consortiumCode]) + .Distinct() .Union(custodianCodes) .ToList(); } diff --git a/HerPortal.BusinessLogic/Services/CsvFileService/CsvFileService.cs b/HerPortal.BusinessLogic/Services/CsvFileService/CsvFileService.cs index 9cbee75..882b1e7 100644 --- a/HerPortal.BusinessLogic/Services/CsvFileService/CsvFileService.cs +++ b/HerPortal.BusinessLogic/Services/CsvFileService/CsvFileService.cs @@ -83,13 +83,46 @@ public async Task> GetFileDataForUserAsync(string userE { // Make sure that we only return file data for files that the user currently has access to var user = await dataAccessProvider.GetUserByEmailAsync(userEmailAddress); - var currentCustodianCodes = user.GetAdministratedCustodianCodes(); + var custodianCodes = user.GetAdministeredCustodianCodes(); + var consortiumCodes = user.Consortia.Select(c => c.ConsortiumCode).ToList(); - var downloads = await dataAccessProvider.GetCsvFileDownloadDataForUserAsync(user.Id); - var files = new List(); + var localAuthoritiesFileData = await BuildCsvFileDataForLocalAuthorities(user, custodianCodes); + var consortiaTransformedFileData = TransformFileDataForConsortia(consortiumCodes, localAuthoritiesFileData); + + return consortiaTransformedFileData + .OrderByDescending(f => new DateOnly(f.Year, f.Month, 1)) + .ThenByDescending(f => f is ConsortiumCsvFileData) + .ThenBy(f => f.Name); + } - var consortiumCodes = dataAccessProvider.GetConsortiumCodesForUser(user); + private List TransformFileDataForConsortia(List consortiumCodes, List files) + { + files.AddRange( + files + .Where(file => LocalAuthorityData.LocalAuthorityConsortiumCodeByCustodianCode.ContainsKey(file.Code)) + .GroupBy(file => (LocalAuthorityData.LocalAuthorityConsortiumCodeByCustodianCode[file.Code], file.Month, + file.Year)) + .Where(grouping => consortiumCodes.Contains(grouping.Key.Item1)) + .Select(grouping => new ConsortiumCsvFileData( + grouping.Key.Item1, + grouping.Key.Month, + grouping.Key.Year, + grouping + .Select(fileData => fileData.LastUpdated) + .Max(), // there are csv updates as new as + grouping + .Select(fileData => fileData.LastDownloaded) + .Min() // there are csvs undownloaded for as long as + ) + ) + ); + return files; + } + private async Task> BuildCsvFileDataForLocalAuthorities(User user, List currentCustodianCodes) + { + var files = new List(); + var downloads = await dataAccessProvider.GetCsvFileDownloadDataForUserAsync(user.Id); foreach (var custodianCode in currentCustodianCodes) { var s3Objects = await s3FileReader.GetS3ObjectsByCustodianCodeAsync(custodianCode); @@ -113,30 +146,7 @@ public async Task> GetFileDataForUserAsync(string userE )); } - files.AddRange( - files - .Where(file => LocalAuthorityData.LocalAuthorityConsortiumCodeByCustodianCode.ContainsKey(file.Code)) - .GroupBy(file => (LocalAuthorityData.LocalAuthorityConsortiumCodeByCustodianCode[file.Code], file.Month, - file.Year)) - .Where(grouping => consortiumCodes.Contains(grouping.Key.Item1)) - .Select(grouping => new ConsortiumCsvFileData( - grouping.Key.Item1, - grouping.Key.Month, - grouping.Key.Year, - grouping - .Select(fileData => fileData.LastUpdated) - .Max(), // there are csv updates as new as - grouping - .Select(fileData => fileData.LastDownloaded) - .Min() // there are csvs undownloaded for as long as - ) - ) - ); - - return files - .OrderByDescending(f => new DateOnly(f.Year, f.Month, 1)) - .ThenByDescending(f => f is ConsortiumCsvFileData) - .ThenBy(f => f.Name); + return files; } // Page number starts at 1 @@ -167,7 +177,7 @@ public async Task GetLocalAuthorityFileForDownloadAsync(string custodian { // Important! First ensure the logged-in user is allowed to access this data var userData = await dataAccessProvider.GetUserByEmailAsync(userEmailAddress); - if (userData.GetAdministratedCustodianCodes().All(custodianCodes => custodianCodes != custodianCode)) + if (!userData.GetAdministeredCustodianCodes().Contains(custodianCode)) { // We don't want to log the User's email address for GDPR reasons, but the ID is fine. throw new SecurityException( @@ -194,7 +204,7 @@ public async Task GetConsortiumFileForDownloadAsync(string consortiumCod { // Important! First ensure the logged-in user is allowed to access this data var userData = await dataAccessProvider.GetUserByEmailAsync(userEmailAddress); - var consortiumCodes = dataAccessProvider.GetConsortiumCodesForUser(userData); + var consortiumCodes = userData.Consortia.Select(c => c.ConsortiumCode).ToList(); if (!consortiumCodes.Contains(consortiumCode)) { diff --git a/HerPortal.BusinessLogic/Services/UserService.cs b/HerPortal.BusinessLogic/Services/UserService.cs index c0639d2..304b1b8 100644 --- a/HerPortal.BusinessLogic/Services/UserService.cs +++ b/HerPortal.BusinessLogic/Services/UserService.cs @@ -22,9 +22,4 @@ public async Task MarkUserAsHavingLoggedInAsync(int userId) { await dataAccessProvider.MarkUserAsHavingLoggedInAsync(userId); } - - public List GetConsortiumCodesForUser(User user) - { - return dataAccessProvider.GetConsortiumCodesForUser(user); - } } diff --git a/HerPortal.Data/DataAccessProvider.cs b/HerPortal.Data/DataAccessProvider.cs index 64bc3a3..4f3514a 100644 --- a/HerPortal.Data/DataAccessProvider.cs +++ b/HerPortal.Data/DataAccessProvider.cs @@ -45,8 +45,6 @@ public async Task> GetAllActiveUsersAsync() { return await context.Users .Where(u => u.HasLoggedIn) - .Include(u => u.LocalAuthorities) - .Include(u => u.Consortia) .ToListAsync(); } @@ -104,18 +102,4 @@ public async Task MarkCsvFileAsDownloadedAsync(string custodianCode, int year, i await context.SaveChangesAsync(); } - - public List GetConsortiumCodesForUser(User user) - { - var userLocalAuthorities = user.LocalAuthorities.Select(la => la.CustodianCode); - var userConsortia = user.Consortia.Select(consortium => consortium.ConsortiumCode); - - // user is a consortium manager if they are a manager of all LAs in that consortium - return ConsortiumData.ConsortiumCustodianCodesIdsByConsortiumCode - .Where(pair => pair.Value.All(consortiumLa => userLocalAuthorities.Contains(consortiumLa))) - .Select(pair => pair.Key) - //Include all explicitly listed consortium codes - .Union(userConsortia) - .ToList(); - } } diff --git a/HerPortal.UnitTests/Builders/UserBuilder.cs b/HerPortal.UnitTests/Builders/UserBuilder.cs index 1f39325..bce629c 100644 --- a/HerPortal.UnitTests/Builders/UserBuilder.cs +++ b/HerPortal.UnitTests/Builders/UserBuilder.cs @@ -29,6 +29,12 @@ public UserBuilder WithLocalAuthorities(List localAuthorities) user.LocalAuthorities = localAuthorities; return this; } + + public UserBuilder WithConsortia(List consortia) + { + user.Consortia = consortia; + return this; + } public UserBuilder WithHasLoggedIn(bool hasLoggedIn) { diff --git a/HerPortal.UnitTests/BusinessLogic/Services/CsvFileService/CsvFileServiceTests.cs b/HerPortal.UnitTests/BusinessLogic/Services/CsvFileService/CsvFileServiceTests.cs index 4047c6a..2547ce2 100644 --- a/HerPortal.UnitTests/BusinessLogic/Services/CsvFileService/CsvFileServiceTests.cs +++ b/HerPortal.UnitTests/BusinessLogic/Services/CsvFileService/CsvFileServiceTests.cs @@ -57,7 +57,6 @@ public async Task GetFileDataForUserAsync_WhenCalledWithNeverDownloadedFiles_Ret var user = GetUserWithLas("114", "910"); mockDataAccessProvider.Setup(dap => dap.GetUserByEmailAsync(user.EmailAddress)).ReturnsAsync(user); - mockDataAccessProvider.Setup(dap => dap.GetConsortiumCodesForUser(user)).Returns(new List()); mockDataAccessProvider .Setup(dap => dap.GetCsvFileDownloadDataForUserAsync(user.Id)) @@ -98,7 +97,6 @@ public async Task GetFileDataForUserAsync_WhenCalledWithDownloadedFile_ReturnsFi var user = GetUserWithLas("114"); mockDataAccessProvider.Setup(dap => dap.GetUserByEmailAsync(user.EmailAddress)).ReturnsAsync(user); - mockDataAccessProvider.Setup(dap => dap.GetConsortiumCodesForUser(user)).Returns(new List()); mockDataAccessProvider .Setup(dap => dap.GetCsvFileDownloadDataForUserAsync(user.Id)) @@ -139,7 +137,6 @@ public async Task GetFileDataForUserAsync_WhenCalledWithInaccessibleFiles_Return var user = GetUserWithLas("114"); mockDataAccessProvider.Setup(dap => dap.GetUserByEmailAsync(user.EmailAddress)).ReturnsAsync(user); - mockDataAccessProvider.Setup(dap => dap.GetConsortiumCodesForUser(user)).Returns(new List()); mockDataAccessProvider .Setup(dap => dap.GetCsvFileDownloadDataForUserAsync(user.Id)) @@ -193,7 +190,6 @@ public async Task GetPaginatedFileDataForUserAsync_WhenCalledWithOutFilters_Retu var user = GetUserWithLas("114", "910"); mockDataAccessProvider.Setup(dap => dap.GetUserByEmailAsync(user.EmailAddress)).ReturnsAsync(user); - mockDataAccessProvider.Setup(dap => dap.GetConsortiumCodesForUser(user)).Returns(new List()); mockDataAccessProvider .Setup(dap => dap.GetCsvFileDownloadDataForUserAsync(user.Id)) @@ -248,7 +244,6 @@ public async Task GetPaginatedFileDataForUserAsync_WhenCalledWithFilters_Filters var user = GetUserWithLas("114", "910"); mockDataAccessProvider.Setup(dap => dap.GetUserByEmailAsync(user.EmailAddress)).ReturnsAsync(user); - mockDataAccessProvider.Setup(dap => dap.GetConsortiumCodesForUser(user)).Returns(new List()); mockDataAccessProvider .Setup(dap => dap.GetCsvFileDownloadDataForUserAsync(user.Id)) @@ -302,7 +297,6 @@ public async Task GetPaginatedFileDataForUserAsync_WhenCalledWithForPage2_Return var user = GetUserWithLas("114"); mockDataAccessProvider.Setup(dap => dap.GetUserByEmailAsync(user.EmailAddress)).ReturnsAsync(user); - mockDataAccessProvider.Setup(dap => dap.GetConsortiumCodesForUser(user)).Returns(new List()); mockDataAccessProvider .Setup(dap => dap.GetCsvFileDownloadDataForUserAsync(user.Id)) @@ -352,7 +346,6 @@ public async Task GetPaginatedFileDataForUserAsync_WhenCalledForUserWithUndownlo var user = GetUserWithLas("114"); mockDataAccessProvider.Setup(dap => dap.GetUserByEmailAsync(user.EmailAddress)).ReturnsAsync(user); - mockDataAccessProvider.Setup(dap => dap.GetConsortiumCodesForUser(user)).Returns(new List()); mockDataAccessProvider .Setup(dap => dap.GetCsvFileDownloadDataForUserAsync(user.Id)) @@ -389,7 +382,6 @@ public async Task GetPaginatedFileDataForUserAsync_WhenCalledForUserWithoutUndow var user = GetUserWithLas("114"); mockDataAccessProvider.Setup(dap => dap.GetUserByEmailAsync(user.EmailAddress)).ReturnsAsync(user); - mockDataAccessProvider.Setup(dap => dap.GetConsortiumCodesForUser(user)).Returns(new List()); mockDataAccessProvider .Setup(dap => dap.GetCsvFileDownloadDataForUserAsync(user.Id)) @@ -423,10 +415,9 @@ public async Task GetPaginatedFileDataForUserAsync_WhenCalledForUserWithoutUndow public async Task GetFileDataForUserAsync_WhenCalledWithConsortium_ReturnsFileData() { // Arrange - var user = GetUserWithLas("660", "665"); + var user = GetUserWithConsortia("C_0008"); mockDataAccessProvider.Setup(dap => dap.GetUserByEmailAsync(user.EmailAddress)).ReturnsAsync(user); - mockDataAccessProvider.Setup(dap => dap.GetConsortiumCodesForUser(user)).Returns(new List{"C_0008"}); mockDataAccessProvider .Setup(dap => dap.GetCsvFileDownloadDataForUserAsync(user.Id)) @@ -481,10 +472,9 @@ public async Task GetFileDataForUserAsync_WhenCalledWithConsortium_ReturnsFileDa public async Task GetFileDataForUserAsync_WhenCalledWithConsortium_ReturnsFileData_WithCorrectConsortiumDates() { // Arrange - var user = GetUserWithLas("660", "665"); + var user = GetUserWithConsortia("C_0008"); mockDataAccessProvider.Setup(dap => dap.GetUserByEmailAsync(user.EmailAddress)).ReturnsAsync(user); - mockDataAccessProvider.Setup(dap => dap.GetConsortiumCodesForUser(user)).Returns(new List{"C_0008"}); mockDataAccessProvider .Setup(dap => dap.GetCsvFileDownloadDataForUserAsync(user.Id)) @@ -539,10 +529,9 @@ public async Task GetFileDataForUserAsync_WhenCalledWithConsortium_ReturnsFileDa public async Task GetFileDataForUserAsync_WhenCalledWithConsortium_ReturnsFileData_EvenIfNotAllLasReportInTheMonth() { // Arrange - var user = GetUserWithLas("660", "665"); - + var user = GetUserWithConsortia("C_0008"); + mockDataAccessProvider.Setup(dap => dap.GetUserByEmailAsync(user.EmailAddress)).ReturnsAsync(user); - mockDataAccessProvider.Setup(dap => dap.GetConsortiumCodesForUser(user)).Returns(new List{"C_0008"}); mockDataAccessProvider .Setup(dap => dap.GetCsvFileDownloadDataForUserAsync(user.Id)) @@ -577,12 +566,22 @@ public async Task GetFileDataForUserAsync_WhenCalledWithConsortium_ReturnsFileDa result.Should().BeEquivalentTo(expectedResult); } + private User GetUserWithConsortia(params string[] consortia) + { + return new UserBuilder("test@example.com") + .WithConsortia( + consortia.Select(consortium => new Consortium + { + ConsortiumCode = consortium + }).ToList()) + .Build(); + } private User GetUserWithLas(params string[] las) { return new UserBuilder("test@example.com") .WithLocalAuthorities( - las.Select(la => new LocalAuthority() + las.Select(la => new LocalAuthority { CustodianCode = la }).ToList()) diff --git a/HerPortal.UnitTests/Website/Controllers/HomeControllerTests.cs b/HerPortal.UnitTests/Website/Controllers/HomeControllerTests.cs index 8e60264..b28eaed 100644 --- a/HerPortal.UnitTests/Website/Controllers/HomeControllerTests.cs +++ b/HerPortal.UnitTests/Website/Controllers/HomeControllerTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using HerPortal.BusinessLogic; using HerPortal.BusinessLogic.Models; @@ -62,7 +63,6 @@ public async Task Index_WhenUserViewsForTheFirstTime_SetsLoggedInFlag() mockDataAccessProvider .Setup(dap => dap.GetUserByEmailAsync(EmailAddress)) .ReturnsAsync(user); - mockDataAccessProvider.Setup(dap => dap.GetConsortiumCodesForUser(user)).Returns(new List()); mockCsvFileService .Setup(cfg => cfg.GetPaginatedFileDataForUserAsync(user.EmailAddress, new List { "114"}, 1, 20)) .ReturnsAsync(fileData); diff --git a/HerPortal.UnitTests/Website/Models/HomepageViewModelTests.cs b/HerPortal.UnitTests/Website/Models/HomepageViewModelTests.cs index 87eea86..2e26e08 100644 --- a/HerPortal.UnitTests/Website/Models/HomepageViewModelTests.cs +++ b/HerPortal.UnitTests/Website/Models/HomepageViewModelTests.cs @@ -43,19 +43,42 @@ bool shouldShowBanner }; // Act - var viewModel = new HomepageViewModel(user, new PaginatedFileData(), GetDummyPageLink, GetDummyDownloadLink, new List()); + var viewModel = new HomepageViewModel(user, new PaginatedFileData(), GetDummyPageLink, GetDummyDownloadLink); // Assert viewModel.ShouldShowBanner.Should().Be(shouldShowBanner); } - [TestCase(1, 0, false)] - [TestCase(2, 0, true)] - [TestCase(3, 0, true)] - [TestCase(4, 0, true)] - [TestCase(100, 0, true)] - [TestCase(0, 1, true)] + [TestCase(1, false)] + [TestCase(2, true)] + [TestCase(3, true)] + [TestCase(4, true)] + [TestCase(100, true)] public void HomepageViewModel_OnlyWhenUserHasOneLocalAuthority_ShouldNotShowFilters + ( + int numberOfLas, + bool expected + ) { + // Arrange + var user = new User + { + HasLoggedIn = true, + LocalAuthorities = ValidLocalAuthorityGenerator + .GetLocalAuthoritiesWithDifferentCodes(numberOfLas) + .ToList(), + Consortia = ValidConsortiumGenerator.GetConsortiaWithDifferentCodes(0).ToList() + }; + + // Act + var viewModel = new HomepageViewModel(user, new PaginatedFileData(), GetDummyPageLink, GetDummyDownloadLink); + + // Assert + viewModel.ShouldShowFilters.Should().Be(expected); + } + + [TestCase(0, 1, true)] + [TestCase(1, 1, true)] + public void HomepageViewModel_WhenUserHasConsortium_ShouldShowFilters ( int numberOfLas, int numberOfConsortia, @@ -72,7 +95,7 @@ bool expected }; // Act - var viewModel = new HomepageViewModel(user, new PaginatedFileData(), GetDummyPageLink, GetDummyDownloadLink, new List()); + var viewModel = new HomepageViewModel(user, new PaginatedFileData(), GetDummyPageLink, GetDummyDownloadLink); // Assert viewModel.ShouldShowFilters.Should().Be(expected); diff --git a/HerPortal/Controllers/HomeController.cs b/HerPortal/Controllers/HomeController.cs index b185b95..134e596 100644 --- a/HerPortal/Controllers/HomeController.cs +++ b/HerPortal/Controllers/HomeController.cs @@ -58,15 +58,12 @@ string GetDownloadLink(CsvFileData abstractCsvFileData) }; } - var consortiumCodes = userService.GetConsortiumCodesForUser(userData); - var homepageViewModel = new HomepageViewModel ( userData, csvFilePage, GetPageLink, - GetDownloadLink, - consortiumCodes + GetDownloadLink ); if (!userData.HasLoggedIn) diff --git a/HerPortal/Models/HomepageViewModel.cs b/HerPortal/Models/HomepageViewModel.cs index 9d04306..99a9a7f 100644 --- a/HerPortal/Models/HomepageViewModel.cs +++ b/HerPortal/Models/HomepageViewModel.cs @@ -64,34 +64,35 @@ public HomepageViewModel( User user, PaginatedFileData paginatedFileData, Func pageLinkGenerator, - Func downloadLinkGenerator, - List consortiumCodes + Func downloadLinkGenerator ) { - var checkboxLabels = user.GetAdministratedCustodianCodes() + var administeredCustodianCodes = user.GetAdministeredCustodianCodes(); + var consortiumCodes = user.Consortia.Select(c => c.ConsortiumCode).ToList(); + var checkboxLabels = administeredCustodianCodes .Select(custodianCode => new KeyValuePair ( custodianCode, new LabelViewModel { - Text = LocalAuthorityData.LocalAuthorityNamesByCustodianCode[custodianCode], + Text = LocalAuthorityData.LocalAuthorityNamesByCustodianCode[custodianCode] } ) ) .ToList(); - + checkboxLabels.AddRange(consortiumCodes.Select(consortiumCode => new KeyValuePair( consortiumCode, new LabelViewModel { Text = $"{ConsortiumData.ConsortiumNamesByConsortiumCode[consortiumCode]} (Consortium)" } - ))); - + ))); + ShouldShowBanner = !user.HasLoggedIn; - ShouldShowFilters = user.GetAdministratedCustodianCodes().Count >= 2; + ShouldShowFilters = administeredCustodianCodes.Count >= 2; Codes = new List(); - Codes.AddRange(user.GetAdministratedCustodianCodes()); + Codes.AddRange(administeredCustodianCodes); Codes.AddRange(consortiumCodes); LocalAuthorityCheckboxLabels = new Dictionary(checkboxLabels .OrderBy(kvp => kvp.Value.Text) From 37b15da1098d460c2805e801c42b3db5bcd36e7f Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Thu, 30 May 2024 13:58:15 +0100 Subject: [PATCH 29/81] PC-1085: use an exception over optional type a bit more sensible as this outcome is a bit more unusual --- HerPortal.ManagementShell/AdminAction.cs | 67 +++++++++---------- .../DatabaseOperation.cs | 14 ++-- .../IDatabaseOperation.cs | 4 +- 3 files changed, 42 insertions(+), 43 deletions(-) diff --git a/HerPortal.ManagementShell/AdminAction.cs b/HerPortal.ManagementShell/AdminAction.cs index 6047337..72417a8 100644 --- a/HerPortal.ManagementShell/AdminAction.cs +++ b/HerPortal.ManagementShell/AdminAction.cs @@ -195,22 +195,17 @@ private UserStatus GetUserStatus(User? userOrNull) private void TryCreateUser(string userEmailAddress, IReadOnlyCollection? custodianCodes, IReadOnlyCollection? consortiumCodes) { - var lasToAdd = dbOperation.GetLas(custodianCodes ?? Array.Empty()); - var consortiaToAdd = dbOperation.GetConsortia(consortiumCodes ?? Array.Empty()); - - if (lasToAdd == null) + try { - outputProvider.Output("Unrecognised LA. Make sure all codes are present in the database."); - return; + var lasToAdd = dbOperation.GetLas(custodianCodes ?? Array.Empty()); + var consortiaToAdd = dbOperation.GetConsortia(consortiumCodes ?? Array.Empty()); + + dbOperation.CreateUserOrLogError(userEmailAddress, lasToAdd, consortiaToAdd); } - - if (consortiaToAdd == null) + catch (KeyNotFoundException keyNotFoundException) { - outputProvider.Output("Unrecognised Consortium. Make sure all codes are present in the database."); - return; + outputProvider.Output($"Could not create user: {keyNotFoundException.Message}"); } - - dbOperation.CreateUserOrLogError(userEmailAddress, lasToAdd, consortiaToAdd); } public void TryRemoveUser(User? user) @@ -245,19 +240,20 @@ private void AddLas(User? user, IReadOnlyCollection? custodianCodes) return; } - var filteredCustodianCodes = custodianCodes - .Where(custodianCode => !CustodianCodeIsInOwnedConsortium(user, custodianCode)) - .ToList(); + try + { + var filteredCustodianCodes = custodianCodes + .Where(custodianCode => !CustodianCodeIsInOwnedConsortium(user, custodianCode)) + .ToList(); - var lasToAdd = dbOperation.GetLas(filteredCustodianCodes); + var lasToAdd = dbOperation.GetLas(filteredCustodianCodes); - if (lasToAdd == null) + dbOperation.AddLasToUser(user, lasToAdd); + } + catch (KeyNotFoundException keyNotFoundException) { - outputProvider.Output("Unrecognised LA. Make sure all codes are present in the database."); - return; + outputProvider.Output($"Could not add LAs to user: {keyNotFoundException.Message}"); } - - dbOperation.AddLasToUser(user, lasToAdd); } public void RemoveLas(User? user, IReadOnlyCollection custodianCodes) @@ -306,24 +302,25 @@ private void AddConsortia(User? user, IReadOnlyCollection? consortiumCod return; } - var consortiaToAdd = dbOperation.GetConsortia(consortiumCodes); - - var ownedCustodianCodesInConsortia = GetOwnedCustodianCodesInConsortia(user, consortiumCodes); - var lasToRemove = dbOperation.GetLas(ownedCustodianCodesInConsortia) ?? new List(); - - if (consortiaToAdd == null) + try { - outputProvider.Output("Unrecognised Consortium. Make sure all codes are present in the database."); - return; - } + var consortiaToAdd = dbOperation.GetConsortia(consortiumCodes); - if (lasToRemove.Count > 0) - { - dbOperation.AddConsortiaAndRemoveLasFromUser(user, consortiaToAdd, lasToRemove); + var ownedCustodianCodesInConsortia = GetOwnedCustodianCodesInConsortia(user, consortiumCodes); + var lasToRemove = dbOperation.GetLas(ownedCustodianCodesInConsortia); + + if (lasToRemove.Count > 0) + { + dbOperation.AddConsortiaAndRemoveLasFromUser(user, consortiaToAdd, lasToRemove); + } + else + { + dbOperation.AddConsortiaToUser(user, consortiaToAdd); + } } - else + catch (KeyNotFoundException keyNotFoundException) { - dbOperation.AddConsortiaToUser(user, consortiaToAdd); + outputProvider.Output($"Could not add Consortia to user: {keyNotFoundException.Message}"); } } diff --git a/HerPortal.ManagementShell/DatabaseOperation.cs b/HerPortal.ManagementShell/DatabaseOperation.cs index 6beab1e..a3f7523 100644 --- a/HerPortal.ManagementShell/DatabaseOperation.cs +++ b/HerPortal.ManagementShell/DatabaseOperation.cs @@ -25,11 +25,12 @@ public List GetUsersWithLocalAuthoritiesAndConsortia() .ToList(); } - public List? GetLas(IReadOnlyCollection custodianCodes) + public List GetLas(IReadOnlyCollection custodianCodes) { - if (custodianCodes.Any(code => !dbContext.LocalAuthorities.Any(la => la.CustodianCode == code))) + var missingCustodianCode = custodianCodes.SingleOrDefault(code => !dbContext.LocalAuthorities.Any(la => la.CustodianCode == code), null); + if (missingCustodianCode != null) { - return null; + throw new KeyNotFoundException($"Custodian Code {missingCustodianCode} not found."); } return custodianCodes @@ -38,11 +39,12 @@ public List GetUsersWithLocalAuthoritiesAndConsortia() .ToList(); } - public List? GetConsortia(IReadOnlyCollection consortiumCodes) + public List GetConsortia(IReadOnlyCollection consortiumCodes) { - if (consortiumCodes.Any(code => !dbContext.Consortia.Any(la => la.ConsortiumCode == code))) + var missingConsortiumCode = consortiumCodes.SingleOrDefault(code => !dbContext.Consortia.Any(la => la.ConsortiumCode == code), null); + if (missingConsortiumCode != null) { - return null; + throw new KeyNotFoundException($"Consortium Code {missingConsortiumCode} not found."); } return consortiumCodes diff --git a/HerPortal.ManagementShell/IDatabaseOperation.cs b/HerPortal.ManagementShell/IDatabaseOperation.cs index dfa148e..3975262 100644 --- a/HerPortal.ManagementShell/IDatabaseOperation.cs +++ b/HerPortal.ManagementShell/IDatabaseOperation.cs @@ -5,8 +5,8 @@ namespace HerPortal.ManagementShell; public interface IDatabaseOperation { public List GetUsersWithLocalAuthoritiesAndConsortia(); - public List? GetLas(IReadOnlyCollection custodianCodes); - public List? GetConsortia(IReadOnlyCollection consortiumCodes); + public List GetLas(IReadOnlyCollection custodianCodes); + public List GetConsortia(IReadOnlyCollection consortiumCodes); public void RemoveUserOrLogError(User user); public void CreateUserOrLogError(string userEmailAddress, List localAuthorities, From cd163da9c2ccea7a43277bfe6f11d7519d1f5322 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Thu, 30 May 2024 14:13:37 +0100 Subject: [PATCH 30/81] PC-1085: fix tests as null return for GetLas matters now --- HerPortal.UnitTests/ManagementShell/AdminActionTests.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/HerPortal.UnitTests/ManagementShell/AdminActionTests.cs b/HerPortal.UnitTests/ManagementShell/AdminActionTests.cs index 5bd1956..510b4f6 100644 --- a/HerPortal.UnitTests/ManagementShell/AdminActionTests.cs +++ b/HerPortal.UnitTests/ManagementShell/AdminActionTests.cs @@ -442,6 +442,7 @@ public void CreateOrUpdateUserWithConsortia_ConfirmsCustodianCodesWhenUpdating() mockDatabaseOperation.Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()).Returns(users); var custodianCodes = new[] { "C_0002", "C_0003" }; mockOutputProvider.Setup(op => op.Confirm("Please confirm (y/n)")).Returns(true); + mockDatabaseOperation.Setup(mock => mock.GetLas(It.IsAny>())).Returns(new List()); // Act underTest.CreateOrUpdateUserWithConsortia(userEmailAddress, custodianCodes); @@ -513,6 +514,7 @@ public void CreateOrUpdateUserWithConsortia_AddsLasToExistingUser_IfUserFoundByD ConsortiumCode = "C_0003" } }; + mockDatabaseOperation.Setup(mock => mock.GetLas(It.IsAny>())).Returns(new List()); mockDatabaseOperation.Setup(mock => mock.GetConsortia(consortiumCodes)).Returns(consortiaToAdd); // Act From 49032a5deef2fa60e17158e1565772b2da28a500 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Thu, 30 May 2024 14:15:07 +0100 Subject: [PATCH 31/81] PC-1085: wrap database calls reduces a lot of repeated code --- .../DatabaseOperation.cs | 94 +++++-------------- 1 file changed, 22 insertions(+), 72 deletions(-) diff --git a/HerPortal.ManagementShell/DatabaseOperation.cs b/HerPortal.ManagementShell/DatabaseOperation.cs index a3f7523..602c04a 100644 --- a/HerPortal.ManagementShell/DatabaseOperation.cs +++ b/HerPortal.ManagementShell/DatabaseOperation.cs @@ -55,27 +55,17 @@ public List GetConsortia(IReadOnlyCollection consortiumCodes public void RemoveUserOrLogError(User user) { - using var dbContextTransaction = dbContext.Database.BeginTransaction(); - try + PerformTransaction(() => { // removing a user also deletes all associated rows in the LocalAuthorityUser table dbContext.Users.Remove(user); - dbContext.SaveChanges(); - dbContextTransaction.Commit(); - outputProvider.Output($"Operation successful. User {user.EmailAddress} was deleted"); - } - catch (Exception e) - { - dbContextTransaction.Rollback(); - outputProvider.Output($"Rollback following error in transaction: {e.InnerException?.Message}"); - } + }); } public void CreateUserOrLogError(string userEmailAddress, List localAuthorities, List consortia) { - using var dbContextTransaction = dbContext.Database.BeginTransaction(); - try + PerformTransaction(() => { var newLaUser = new User { @@ -85,77 +75,38 @@ public void CreateUserOrLogError(string userEmailAddress, List l Consortia = consortia }; dbContext.Add(newLaUser); - dbContext.SaveChanges(); - outputProvider.Output("Operation successful"); - dbContextTransaction.Commit(); - } - catch (Exception e) - { - outputProvider.Output($"Rollback following error in transaction: {e.InnerException?.Message}"); - dbContextTransaction.Rollback(); - } + }); } public void RemoveLasFromUser(User user, List lasToRemove) { - using var dbContextTransaction = dbContext.Database.BeginTransaction(); - try + PerformTransaction(() => { foreach (var la in lasToRemove) user?.LocalAuthorities.Remove(la); - - dbContext.SaveChanges(); - outputProvider.Output("Operation successful"); - dbContextTransaction.Commit(); - } - catch (Exception e) - { - outputProvider.Output($"Rollback following error in transaction: {e.InnerException?.Message}"); - dbContextTransaction.Rollback(); - } + }); } public void AddConsortiaAndRemoveLasFromUser(User user, List consortia, List localAuthorities) { - using var dbContextTransaction = dbContext.Database.BeginTransaction(); - try + PerformTransaction(() => { foreach (var consortium in consortia) user?.Consortia.Add(consortium); foreach (var localAuthority in localAuthorities) user?.LocalAuthorities.Remove(localAuthority); - - dbContext.SaveChanges(); - outputProvider.Output("Operation successful"); - dbContextTransaction.Commit(); - } - catch (Exception e) - { - outputProvider.Output($"Rollback following error in transaction: {e.InnerException?.Message}"); - dbContextTransaction.Rollback(); - } + }); } public void RemoveConsortiaFromUser(User user, List consortia) { - using var dbContextTransaction = dbContext.Database.BeginTransaction(); - try + PerformTransaction(() => { foreach (var consortium in consortia) user?.Consortia.Remove(consortium); - - dbContext.SaveChanges(); - outputProvider.Output("Operation successful"); - dbContextTransaction.Commit(); - } - catch (Exception e) - { - outputProvider.Output($"Rollback following error in transaction: {e.InnerException?.Message}"); - dbContextTransaction.Rollback(); - } + }); } public void AddLasToUser(User user, List localAuthorities) { - using var dbContextTransaction = dbContext.Database.BeginTransaction(); - try + PerformTransaction(() => { foreach (var la in localAuthorities) try @@ -167,22 +118,12 @@ public void AddLasToUser(User user, List localAuthorities) outputProvider.Output(e.Message); throw; } - - dbContext.SaveChanges(); - outputProvider.Output("Operation successful"); - dbContextTransaction.Commit(); - } - catch (Exception e) - { - outputProvider.Output($"Rollback following error in transaction: {e.InnerException?.Message}"); - dbContextTransaction.Rollback(); - } + }); } public void AddConsortiaToUser(User user, List consortia) { - using var dbContextTransaction = dbContext.Database.BeginTransaction(); - try + PerformTransaction(() => { foreach (var consortium in consortia) try @@ -194,7 +135,16 @@ public void AddConsortiaToUser(User user, List consortia) outputProvider.Output(e.Message); throw; } + }); + } + private void PerformTransaction(Action transaction) + { + using var dbContextTransaction = dbContext.Database.BeginTransaction(); + try + { + transaction(); + dbContext.SaveChanges(); outputProvider.Output("Operation successful"); dbContextTransaction.Commit(); From b9f33ec30edb185a0a0bab5c93839c40925358fb Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Thu, 30 May 2024 14:22:34 +0100 Subject: [PATCH 32/81] PC-1085: strip the braces again --- HerPortal.ManagementShell/AdminAction.cs | 40 ++++--------------- .../DatabaseOperation.cs | 13 +++--- 2 files changed, 13 insertions(+), 40 deletions(-) diff --git a/HerPortal.ManagementShell/AdminAction.cs b/HerPortal.ManagementShell/AdminAction.cs index 72417a8..d062a6f 100644 --- a/HerPortal.ManagementShell/AdminAction.cs +++ b/HerPortal.ManagementShell/AdminAction.cs @@ -51,10 +51,7 @@ public AdminAction(IDatabaseOperation dbOperation, IOutputProvider outputProvide private void PrintCodes(IReadOnlyCollection codes, Func codeToName) { - if (codes.Count < 1) - { - outputProvider.Output("(None)"); - } + if (codes.Count < 1) outputProvider.Output("(None)"); foreach (var code in codes) { @@ -103,10 +100,7 @@ private bool ConfirmCustodianCodes(string userEmailAddress, IReadOnlyCollection< } var hasUserConfirmed = outputProvider.Confirm("Please confirm (y/n)"); - if (!hasUserConfirmed) - { - outputProvider.Output("Process cancelled, no changes were made to the database"); - } + if (!hasUserConfirmed) outputProvider.Output("Process cancelled, no changes were made to the database"); return hasUserConfirmed; } @@ -157,10 +151,7 @@ private bool ConfirmConsortiumCodes(string? userEmailAddress, IReadOnlyCollectio } var hasUserConfirmed = outputProvider.Confirm("Please confirm (y/n)"); - if (!hasUserConfirmed) - { - outputProvider.Output("Process cancelled, no changes were made to the database"); - } + if (!hasUserConfirmed) outputProvider.Output("Process cancelled, no changes were made to the database"); return hasUserConfirmed; } @@ -199,7 +190,7 @@ private void TryCreateUser(string userEmailAddress, IReadOnlyCollection? { var lasToAdd = dbOperation.GetLas(custodianCodes ?? Array.Empty()); var consortiaToAdd = dbOperation.GetConsortia(consortiumCodes ?? Array.Empty()); - + dbOperation.CreateUserOrLogError(userEmailAddress, lasToAdd, consortiaToAdd); } catch (KeyNotFoundException keyNotFoundException) @@ -218,10 +209,7 @@ public void TryRemoveUser(User? user) var deletionConfirmation = outputProvider.Confirm( $"Attention! This will delete user {user.EmailAddress} and all associated rows from the database. Are you sure you want to commit this transaction? (y/n)"); - if (!deletionConfirmation) - { - return; - } + if (!deletionConfirmation) return; dbOperation.RemoveUserOrLogError(user); } @@ -271,10 +259,7 @@ public void RemoveLas(User? user, IReadOnlyCollection custodianCodes) } var userConfirmation = ConfirmCustodianCodes(user.EmailAddress, custodianCodes, user, true); - if (!userConfirmation) - { - return; - } + if (!userConfirmation) return; var lasToRemove = user.LocalAuthorities.Where(la => custodianCodes.Contains(la.CustodianCode)).ToList(); var missingCodes = custodianCodes.Where(code => !lasToRemove.Any(la => la.CustodianCode.Equals(code))).ToList(); @@ -310,13 +295,9 @@ private void AddConsortia(User? user, IReadOnlyCollection? consortiumCod var lasToRemove = dbOperation.GetLas(ownedCustodianCodesInConsortia); if (lasToRemove.Count > 0) - { dbOperation.AddConsortiaAndRemoveLasFromUser(user, consortiaToAdd, lasToRemove); - } else - { dbOperation.AddConsortiaToUser(user, consortiaToAdd); - } } catch (KeyNotFoundException keyNotFoundException) { @@ -357,7 +338,6 @@ public void CreateOrUpdateUserWithLas(string? userEmailAddress, IReadOnlyCollect var confirmation = ConfirmCustodianCodes(userEmailAddress, custodianCodes, user, false); if (confirmation) - { switch (userStatus) { case UserStatus.Active: @@ -367,7 +347,6 @@ public void CreateOrUpdateUserWithLas(string? userEmailAddress, IReadOnlyCollect TryCreateUser(userEmailAddress, custodianCodes, null); break; } - } } public void CreateOrUpdateUserWithConsortia(string? userEmailAddress, IReadOnlyCollection consortiumCodes) @@ -383,7 +362,6 @@ public void CreateOrUpdateUserWithConsortia(string? userEmailAddress, IReadOnlyC var confirmation = ConfirmConsortiumCodes(userEmailAddress, consortiumCodes, user, false); if (confirmation) - { switch (userStatus) { case UserStatus.Active: @@ -393,7 +371,6 @@ public void CreateOrUpdateUserWithConsortia(string? userEmailAddress, IReadOnlyC TryCreateUser(userEmailAddress, null, consortiumCodes); break; } - } } public void RemoveConsortia(User? user, IReadOnlyCollection consortiumCodes) @@ -411,10 +388,7 @@ public void RemoveConsortia(User? user, IReadOnlyCollection consortiumCo } var userConfirmation = ConfirmConsortiumCodes(user.EmailAddress, consortiumCodes, user, true); - if (!userConfirmation) - { - return; - } + if (!userConfirmation) return; var consortiaToRemove = user.Consortia.Where(consortium => consortiumCodes.Contains(consortium.ConsortiumCode)) .ToList(); diff --git a/HerPortal.ManagementShell/DatabaseOperation.cs b/HerPortal.ManagementShell/DatabaseOperation.cs index 602c04a..5595cca 100644 --- a/HerPortal.ManagementShell/DatabaseOperation.cs +++ b/HerPortal.ManagementShell/DatabaseOperation.cs @@ -27,11 +27,11 @@ public List GetUsersWithLocalAuthoritiesAndConsortia() public List GetLas(IReadOnlyCollection custodianCodes) { - var missingCustodianCode = custodianCodes.SingleOrDefault(code => !dbContext.LocalAuthorities.Any(la => la.CustodianCode == code), null); + var missingCustodianCode = + custodianCodes.SingleOrDefault(code => !dbContext.LocalAuthorities.Any(la => la.CustodianCode == code), + null); if (missingCustodianCode != null) - { throw new KeyNotFoundException($"Custodian Code {missingCustodianCode} not found."); - } return custodianCodes .Select(code => dbContext.LocalAuthorities @@ -41,11 +41,10 @@ public List GetLas(IReadOnlyCollection custodianCodes) public List GetConsortia(IReadOnlyCollection consortiumCodes) { - var missingConsortiumCode = consortiumCodes.SingleOrDefault(code => !dbContext.Consortia.Any(la => la.ConsortiumCode == code), null); + var missingConsortiumCode = + consortiumCodes.SingleOrDefault(code => !dbContext.Consortia.Any(la => la.ConsortiumCode == code), null); if (missingConsortiumCode != null) - { throw new KeyNotFoundException($"Consortium Code {missingConsortiumCode} not found."); - } return consortiumCodes .Select(code => dbContext.Consortia @@ -144,7 +143,7 @@ private void PerformTransaction(Action transaction) try { transaction(); - + dbContext.SaveChanges(); outputProvider.Output("Operation successful"); dbContextTransaction.Commit(); From 7366a123c5046cafd74bc29d9273fa146b76f8d4 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Thu, 30 May 2024 14:42:34 +0100 Subject: [PATCH 33/81] PC-1085: remove boolean logic split also add a wrapper for confirming database changes --- HerPortal.ManagementShell/AdminAction.cs | 78 ++++++++++++------------ 1 file changed, 40 insertions(+), 38 deletions(-) diff --git a/HerPortal.ManagementShell/AdminAction.cs b/HerPortal.ManagementShell/AdminAction.cs index d062a6f..9956eef 100644 --- a/HerPortal.ManagementShell/AdminAction.cs +++ b/HerPortal.ManagementShell/AdminAction.cs @@ -60,20 +60,11 @@ private void PrintCodes(IReadOnlyCollection codes, Func } } - private bool ConfirmCustodianCodes(string userEmailAddress, IReadOnlyCollection custodianCodes, User? user, - bool remove) + private bool ConfirmAddCustodianCodes(string userEmailAddress, IReadOnlyCollection custodianCodes, User? user) { - outputProvider.Output( - $"You are changing permissions for user {userEmailAddress} for the following Local Authorities:"); - - try + return ConfirmChangesToDatabase(userEmailAddress, () => { - if (remove) - { - outputProvider.Output("Remove the following Local Authorities:"); - PrintCodes(custodianCodes, code => custodianCodeToLaNameDict[code]); - } - else if (user != null) + if (user != null) { // flag the need to not add LAs that are in consortia the user owns var custodianCodeInOwnedConsortiumGrouping = custodianCodes.ToLookup(custodianCode => @@ -92,17 +83,16 @@ private bool ConfirmCustodianCodes(string userEmailAddress, IReadOnlyCollection< outputProvider.Output("Add the following Local Authorities:"); PrintCodes(custodianCodes, code => custodianCodeToLaNameDict[code]); } - } - catch (Exception e) - { - outputProvider.Output($"{e.Message} Process terminated"); - return false; - } - - var hasUserConfirmed = outputProvider.Confirm("Please confirm (y/n)"); - if (!hasUserConfirmed) outputProvider.Output("Process cancelled, no changes were made to the database"); + }); + } - return hasUserConfirmed; + private bool ConfirmRemoveCustodianCodes(string userEmailAddress, IReadOnlyCollection custodianCodes) + { + return ConfirmChangesToDatabase(userEmailAddress, () => + { + outputProvider.Output("Remove the following Local Authorities:"); + PrintCodes(custodianCodes, code => custodianCodeToLaNameDict[code]); + }); } private bool CustodianCodeIsInOwnedConsortium(User user, string custodianCode) @@ -113,20 +103,12 @@ private bool CustodianCodeIsInOwnedConsortium(User user, string custodianCode) return custodianCodesOfConsortia.Contains(custodianCode); } - private bool ConfirmConsortiumCodes(string? userEmailAddress, IReadOnlyCollection consortiumCodes, - User? user, bool remove) + private bool ConfirmAddConsortiumCodes(string? userEmailAddress, IReadOnlyCollection consortiumCodes, + User? user) { - outputProvider.Output( - $"You are changing permissions for user {userEmailAddress} for the following Consortia:"); - - try + return ConfirmChangesToDatabase(userEmailAddress, () => { - if (remove) - { - outputProvider.Output("Remove the following Consortia:"); - PrintCodes(consortiumCodes, code => consortiumCodeToConsortiumNameDict[code]); - } - else if (user != null) + if (user != null) { outputProvider.Output("Add the following Consortia:"); PrintCodes(consortiumCodes, code => consortiumCodeToConsortiumNameDict[code]); @@ -143,6 +125,26 @@ private bool ConfirmConsortiumCodes(string? userEmailAddress, IReadOnlyCollectio outputProvider.Output("Add the following Consortia:"); PrintCodes(consortiumCodes, code => consortiumCodeToConsortiumNameDict[code]); } + }); + } + + private bool ConfirmRemoveConsortiumCodes(string? userEmailAddress, IReadOnlyCollection consortiumCodes) + { + return ConfirmChangesToDatabase(userEmailAddress, () => + { + outputProvider.Output("Remove the following Consortia:"); + PrintCodes(consortiumCodes, code => consortiumCodeToConsortiumNameDict[code]); + }); + } + + private bool ConfirmChangesToDatabase(string? userEmailAddress, Action printAction) + { + outputProvider.Output( + $"You are changing permissions for user {userEmailAddress}:"); + + try + { + printAction(); } catch (Exception e) { @@ -258,7 +260,7 @@ public void RemoveLas(User? user, IReadOnlyCollection custodianCodes) return; } - var userConfirmation = ConfirmCustodianCodes(user.EmailAddress, custodianCodes, user, true); + var userConfirmation = ConfirmRemoveCustodianCodes(user.EmailAddress, custodianCodes); if (!userConfirmation) return; var lasToRemove = user.LocalAuthorities.Where(la => custodianCodes.Contains(la.CustodianCode)).ToList(); @@ -335,7 +337,7 @@ public void CreateOrUpdateUserWithLas(string? userEmailAddress, IReadOnlyCollect var (user, userStatus) = CheckUserStatus(userEmailAddress); - var confirmation = ConfirmCustodianCodes(userEmailAddress, custodianCodes, user, false); + var confirmation = ConfirmAddCustodianCodes(userEmailAddress, custodianCodes, user); if (confirmation) switch (userStatus) @@ -359,7 +361,7 @@ public void CreateOrUpdateUserWithConsortia(string? userEmailAddress, IReadOnlyC var (user, userStatus) = CheckUserStatus(userEmailAddress); - var confirmation = ConfirmConsortiumCodes(userEmailAddress, consortiumCodes, user, false); + var confirmation = ConfirmAddConsortiumCodes(userEmailAddress, consortiumCodes, user); if (confirmation) switch (userStatus) @@ -387,7 +389,7 @@ public void RemoveConsortia(User? user, IReadOnlyCollection consortiumCo return; } - var userConfirmation = ConfirmConsortiumCodes(user.EmailAddress, consortiumCodes, user, true); + var userConfirmation = ConfirmRemoveConsortiumCodes(user.EmailAddress, consortiumCodes); if (!userConfirmation) return; var consortiaToRemove = user.Consortia.Where(consortium => consortiumCodes.Contains(consortium.ConsortiumCode)) From cd22ceb32ed498691e0e31aa61d951cb23115684 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Thu, 30 May 2024 14:45:53 +0100 Subject: [PATCH 34/81] PC-1085: remove unneeded optional email type --- HerPortal.ManagementShell/AdminAction.cs | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/HerPortal.ManagementShell/AdminAction.cs b/HerPortal.ManagementShell/AdminAction.cs index 9956eef..100b5db 100644 --- a/HerPortal.ManagementShell/AdminAction.cs +++ b/HerPortal.ManagementShell/AdminAction.cs @@ -327,14 +327,8 @@ private void AddConsortia(User? user, IReadOnlyCollection? consortiumCod return (user, userStatus); } - public void CreateOrUpdateUserWithLas(string? userEmailAddress, IReadOnlyCollection custodianCodes) + public void CreateOrUpdateUserWithLas(string userEmailAddress, IReadOnlyCollection custodianCodes) { - if (userEmailAddress == null) - { - outputProvider.Output("Please specify user E-mail address to create or update"); - return; - } - var (user, userStatus) = CheckUserStatus(userEmailAddress); var confirmation = ConfirmAddCustodianCodes(userEmailAddress, custodianCodes, user); @@ -351,14 +345,8 @@ public void CreateOrUpdateUserWithLas(string? userEmailAddress, IReadOnlyCollect } } - public void CreateOrUpdateUserWithConsortia(string? userEmailAddress, IReadOnlyCollection consortiumCodes) + public void CreateOrUpdateUserWithConsortia(string userEmailAddress, IReadOnlyCollection consortiumCodes) { - if (userEmailAddress == null) - { - outputProvider.Output("Please specify user E-mail address to create or update"); - return; - } - var (user, userStatus) = CheckUserStatus(userEmailAddress); var confirmation = ConfirmAddConsortiumCodes(userEmailAddress, consortiumCodes, user); From cb2342f183503621cd796c77f287c782b0a5eb5d Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Thu, 30 May 2024 16:09:08 +0100 Subject: [PATCH 35/81] PC-1085: split AdminAction introduce a new CommandHandler class that does the printing and has a public method for each command AdminAction now interfaces with the objects and calls database methods --- HerPortal.ManagementShell/AdminAction.cs | 325 ++--------------- HerPortal.ManagementShell/CommandHandler.cs | 331 ++++++++++++++++++ HerPortal.ManagementShell/Program.cs | 17 +- ...nActionTests.cs => CommandHandlerTests.cs} | 27 +- 4 files changed, 381 insertions(+), 319 deletions(-) create mode 100644 HerPortal.ManagementShell/CommandHandler.cs rename HerPortal.UnitTests/ManagementShell/{AdminActionTests.cs => CommandHandlerTests.cs} (96%) diff --git a/HerPortal.ManagementShell/AdminAction.cs b/HerPortal.ManagementShell/AdminAction.cs index 100b5db..9bde35b 100644 --- a/HerPortal.ManagementShell/AdminAction.cs +++ b/HerPortal.ManagementShell/AdminAction.cs @@ -10,31 +10,17 @@ public enum UserStatus Active } - private readonly Dictionary consortiumCodeToConsortiumNameDict = - ConsortiumData.ConsortiumNamesByConsortiumCode; - private readonly Dictionary> consortiumCodeToCustodianCodesDict = ConsortiumData.ConsortiumCustodianCodesIdsByConsortiumCode; private readonly Dictionary custodianCodeToConsortiumCodeDict = LocalAuthorityData.LocalAuthorityConsortiumCodeByCustodianCode; - private readonly Dictionary custodianCodeToConsortiumNameDict = - LocalAuthorityData.LocalAuthorityConsortiumCodeByCustodianCode - .ToDictionary( - kvp => kvp.Key, - kvp => ConsortiumData.ConsortiumNamesByConsortiumCode[kvp.Value]); - - private readonly Dictionary custodianCodeToLaNameDict = - LocalAuthorityData.LocalAuthorityNamesByCustodianCode; - private readonly IDatabaseOperation dbOperation; - private readonly IOutputProvider outputProvider; - public AdminAction(IDatabaseOperation dbOperation, IOutputProvider outputProvider) + public AdminAction(IDatabaseOperation dbOperation) { this.dbOperation = dbOperation; - this.outputProvider = outputProvider; } public User? GetUser(string emailAddress) @@ -49,53 +35,7 @@ public AdminAction(IDatabaseOperation dbOperation, IOutputProvider outputProvide )); } - private void PrintCodes(IReadOnlyCollection codes, Func codeToName) - { - if (codes.Count < 1) outputProvider.Output("(None)"); - - foreach (var code in codes) - { - var name = codeToName(code); - outputProvider.Output($"{code}: {name}"); - } - } - - private bool ConfirmAddCustodianCodes(string userEmailAddress, IReadOnlyCollection custodianCodes, User? user) - { - return ConfirmChangesToDatabase(userEmailAddress, () => - { - if (user != null) - { - // flag the need to not add LAs that are in consortia the user owns - var custodianCodeInOwnedConsortiumGrouping = custodianCodes.ToLookup(custodianCode => - CustodianCodeIsInOwnedConsortium(user, custodianCode)); - var custodianCodesNotInOwnedConsortium = custodianCodeInOwnedConsortiumGrouping[false].ToList(); - var custodianCodesInOwnedConsortium = custodianCodeInOwnedConsortiumGrouping[true].ToList(); - - outputProvider.Output("Add the following Local Authorities:"); - PrintCodes(custodianCodesNotInOwnedConsortium, code => custodianCodeToLaNameDict[code]); - outputProvider.Output("Ignore the following Local Authorities already in owned Consortia:"); - PrintCodes(custodianCodesInOwnedConsortium, code => - $"{custodianCodeToLaNameDict[code]} ({custodianCodeToConsortiumNameDict[code]})"); - } - else - { - outputProvider.Output("Add the following Local Authorities:"); - PrintCodes(custodianCodes, code => custodianCodeToLaNameDict[code]); - } - }); - } - - private bool ConfirmRemoveCustodianCodes(string userEmailAddress, IReadOnlyCollection custodianCodes) - { - return ConfirmChangesToDatabase(userEmailAddress, () => - { - outputProvider.Output("Remove the following Local Authorities:"); - PrintCodes(custodianCodes, code => custodianCodeToLaNameDict[code]); - }); - } - - private bool CustodianCodeIsInOwnedConsortium(User user, string custodianCode) + public bool CustodianCodeIsInOwnedConsortium(User user, string custodianCode) { var custodianCodesOfConsortia = user.Consortia .SelectMany(consortium => @@ -103,62 +43,7 @@ private bool CustodianCodeIsInOwnedConsortium(User user, string custodianCode) return custodianCodesOfConsortia.Contains(custodianCode); } - private bool ConfirmAddConsortiumCodes(string? userEmailAddress, IReadOnlyCollection consortiumCodes, - User? user) - { - return ConfirmChangesToDatabase(userEmailAddress, () => - { - if (user != null) - { - outputProvider.Output("Add the following Consortia:"); - PrintCodes(consortiumCodes, code => consortiumCodeToConsortiumNameDict[code]); - - // flag the need to remove access for any LAs in the new consortia - var ownedCustodianCodesInConsortia = GetOwnedCustodianCodesInConsortia(user, consortiumCodes); - - outputProvider.Output("Remove the following Local Authorities in these Consortia:"); - PrintCodes(ownedCustodianCodesInConsortia, code => - $"{custodianCodeToLaNameDict[code]} ({custodianCodeToConsortiumNameDict[code]})"); - } - else - { - outputProvider.Output("Add the following Consortia:"); - PrintCodes(consortiumCodes, code => consortiumCodeToConsortiumNameDict[code]); - } - }); - } - - private bool ConfirmRemoveConsortiumCodes(string? userEmailAddress, IReadOnlyCollection consortiumCodes) - { - return ConfirmChangesToDatabase(userEmailAddress, () => - { - outputProvider.Output("Remove the following Consortia:"); - PrintCodes(consortiumCodes, code => consortiumCodeToConsortiumNameDict[code]); - }); - } - - private bool ConfirmChangesToDatabase(string? userEmailAddress, Action printAction) - { - outputProvider.Output( - $"You are changing permissions for user {userEmailAddress}:"); - - try - { - printAction(); - } - catch (Exception e) - { - outputProvider.Output($"{e.Message} Process terminated"); - return false; - } - - var hasUserConfirmed = outputProvider.Confirm("Please confirm (y/n)"); - if (!hasUserConfirmed) outputProvider.Output("Process cancelled, no changes were made to the database"); - - return hasUserConfirmed; - } - - private List GetOwnedCustodianCodesInConsortia(User user, IEnumerable consortiumCodes) + public List GetOwnedCustodianCodesInConsortia(User user, IEnumerable consortiumCodes) { return user.LocalAuthorities .Where(localAuthority => @@ -167,228 +52,72 @@ private List GetOwnedCustodianCodesInConsortia(User user, IEnumerable? custodianCodes, + public void CreateUser(string userEmailAddress, IReadOnlyCollection? custodianCodes, IReadOnlyCollection? consortiumCodes) { - try - { - var lasToAdd = dbOperation.GetLas(custodianCodes ?? Array.Empty()); - var consortiaToAdd = dbOperation.GetConsortia(consortiumCodes ?? Array.Empty()); + var lasToAdd = dbOperation.GetLas(custodianCodes ?? Array.Empty()); + var consortiaToAdd = dbOperation.GetConsortia(consortiumCodes ?? Array.Empty()); - dbOperation.CreateUserOrLogError(userEmailAddress, lasToAdd, consortiaToAdd); - } - catch (KeyNotFoundException keyNotFoundException) - { - outputProvider.Output($"Could not create user: {keyNotFoundException.Message}"); - } + dbOperation.CreateUserOrLogError(userEmailAddress, lasToAdd, consortiaToAdd); } - public void TryRemoveUser(User? user) + public void RemoveUser(User user) { - if (user == null) - { - outputProvider.Output("User not found"); - return; - } - - var deletionConfirmation = outputProvider.Confirm( - $"Attention! This will delete user {user.EmailAddress} and all associated rows from the database. Are you sure you want to commit this transaction? (y/n)"); - if (!deletionConfirmation) return; - dbOperation.RemoveUserOrLogError(user); } - private void AddLas(User? user, IReadOnlyCollection? custodianCodes) + public void AddLas(User user, IEnumerable custodianCodes) { - if (user == null) - { - outputProvider.Output("User not found"); - return; - } - - if (custodianCodes == null || custodianCodes.Count < 1) - { - outputProvider.Output("Please specify custodian codes to add to user"); - return; - } - - try - { - var filteredCustodianCodes = custodianCodes - .Where(custodianCode => !CustodianCodeIsInOwnedConsortium(user, custodianCode)) - .ToList(); + var filteredCustodianCodes = custodianCodes + .Where(custodianCode => !CustodianCodeIsInOwnedConsortium(user, custodianCode)) + .ToList(); - var lasToAdd = dbOperation.GetLas(filteredCustodianCodes); + var lasToAdd = dbOperation.GetLas(filteredCustodianCodes); - dbOperation.AddLasToUser(user, lasToAdd); - } - catch (KeyNotFoundException keyNotFoundException) - { - outputProvider.Output($"Could not add LAs to user: {keyNotFoundException.Message}"); - } + dbOperation.AddLasToUser(user, lasToAdd); } - public void RemoveLas(User? user, IReadOnlyCollection custodianCodes) + public void RemoveLas(User user, IReadOnlyCollection custodianCodes) { - if (user == null) - { - outputProvider.Output("User not found"); - return; - } - - if (custodianCodes.Count == 0) - { - outputProvider.Output("Please specify custodian codes to remove from user"); - return; - } - - var userConfirmation = ConfirmRemoveCustodianCodes(user.EmailAddress, custodianCodes); - if (!userConfirmation) return; - var lasToRemove = user.LocalAuthorities.Where(la => custodianCodes.Contains(la.CustodianCode)).ToList(); var missingCodes = custodianCodes.Where(code => !lasToRemove.Any(la => la.CustodianCode.Equals(code))).ToList(); if (missingCodes.Count > 0) { - outputProvider.Output( + throw new KeyNotFoundException( $"Could not find LAs attached to {user.EmailAddress} for the following codes: {string.Join(", ", missingCodes)}. Please check your inputs and try again."); - return; } dbOperation.RemoveLasFromUser(user, lasToRemove); } - private void AddConsortia(User? user, IReadOnlyCollection? consortiumCodes) - { - if (user == null) - { - outputProvider.Output("User not found"); - return; - } - - if (consortiumCodes == null || consortiumCodes.Count < 1) - { - outputProvider.Output("Please specify consortium codes to add to user"); - return; - } - - try - { - var consortiaToAdd = dbOperation.GetConsortia(consortiumCodes); - - var ownedCustodianCodesInConsortia = GetOwnedCustodianCodesInConsortia(user, consortiumCodes); - var lasToRemove = dbOperation.GetLas(ownedCustodianCodesInConsortia); - - if (lasToRemove.Count > 0) - dbOperation.AddConsortiaAndRemoveLasFromUser(user, consortiaToAdd, lasToRemove); - else - dbOperation.AddConsortiaToUser(user, consortiaToAdd); - } - catch (KeyNotFoundException keyNotFoundException) - { - outputProvider.Output($"Could not add Consortia to user: {keyNotFoundException.Message}"); - } - } - - private (User? user, UserStatus userStatus) CheckUserStatus(string userEmailAddress) - { - var user = GetUser(userEmailAddress); - var userStatus = GetUserStatus(user); - DisplayUserStatus(userStatus); - outputProvider.Output(""); - - outputProvider.Output("!!! ATTENTION! READ CAREFULLY OR RISK A DATA BREACH !!!"); - outputProvider.Output(""); - outputProvider.Output( - "You are about to grant a user permission to read PERSONALLY IDENTIFIABLE INFORMATION submitted to LAs."); - outputProvider.Output( - "Take a moment to double check the following list and only continue if you are certain this user should have access to these LAs."); - outputProvider.Output( - "NB: in particular, you should only do this for LAs that have signed their DSA contracts!"); - outputProvider.Output(""); - - return (user, userStatus); - } - - public void CreateOrUpdateUserWithLas(string userEmailAddress, IReadOnlyCollection custodianCodes) + public void AddConsortia(User user, IReadOnlyCollection consortiumCodes) { - var (user, userStatus) = CheckUserStatus(userEmailAddress); + var consortiaToAdd = dbOperation.GetConsortia(consortiumCodes); - var confirmation = ConfirmAddCustodianCodes(userEmailAddress, custodianCodes, user); + var ownedCustodianCodesInConsortia = GetOwnedCustodianCodesInConsortia(user, consortiumCodes); + var lasToRemove = dbOperation.GetLas(ownedCustodianCodesInConsortia); - if (confirmation) - switch (userStatus) - { - case UserStatus.Active: - AddLas(user, custodianCodes); - break; - case UserStatus.New: - TryCreateUser(userEmailAddress, custodianCodes, null); - break; - } + if (lasToRemove.Count > 0) + dbOperation.AddConsortiaAndRemoveLasFromUser(user, consortiaToAdd, lasToRemove); + else + dbOperation.AddConsortiaToUser(user, consortiaToAdd); } - public void CreateOrUpdateUserWithConsortia(string userEmailAddress, IReadOnlyCollection consortiumCodes) + public void RemoveConsortia(User user, IReadOnlyCollection consortiumCodes) { - var (user, userStatus) = CheckUserStatus(userEmailAddress); - - var confirmation = ConfirmAddConsortiumCodes(userEmailAddress, consortiumCodes, user); - - if (confirmation) - switch (userStatus) - { - case UserStatus.Active: - AddConsortia(user, consortiumCodes); - break; - case UserStatus.New: - TryCreateUser(userEmailAddress, null, consortiumCodes); - break; - } - } - - public void RemoveConsortia(User? user, IReadOnlyCollection consortiumCodes) - { - if (user == null) - { - outputProvider.Output("User not found"); - return; - } - - if (consortiumCodes.Count == 0) - { - outputProvider.Output("Please specify consortium codes to remove from user"); - return; - } - - var userConfirmation = ConfirmRemoveConsortiumCodes(user.EmailAddress, consortiumCodes); - if (!userConfirmation) return; - var consortiaToRemove = user.Consortia.Where(consortium => consortiumCodes.Contains(consortium.ConsortiumCode)) .ToList(); var missingCodes = consortiumCodes .Where(code => !consortiaToRemove.Any(consortium => consortium.ConsortiumCode.Equals(code))).ToList(); if (missingCodes.Count > 0) { - outputProvider.Output( + throw new KeyNotFoundException( $"Could not find Consortia attached to {user.EmailAddress} for the following codes: {string.Join(", ", missingCodes)}. Please check your inputs and try again."); - return; } dbOperation.RemoveConsortiaFromUser(user, consortiaToRemove); diff --git a/HerPortal.ManagementShell/CommandHandler.cs b/HerPortal.ManagementShell/CommandHandler.cs new file mode 100644 index 0000000..4a1efae --- /dev/null +++ b/HerPortal.ManagementShell/CommandHandler.cs @@ -0,0 +1,331 @@ +using HerPortal.BusinessLogic.Models; + +namespace HerPortal.ManagementShell; + +public class CommandHandler +{ + private readonly Dictionary consortiumCodeToConsortiumNameDict = + ConsortiumData.ConsortiumNamesByConsortiumCode; + + private readonly Dictionary custodianCodeToConsortiumNameDict = + LocalAuthorityData.LocalAuthorityConsortiumCodeByCustodianCode + .ToDictionary( + kvp => kvp.Key, + kvp => ConsortiumData.ConsortiumNamesByConsortiumCode[kvp.Value]); + + private readonly Dictionary custodianCodeToLaNameDict = + LocalAuthorityData.LocalAuthorityNamesByCustodianCode; + + private readonly AdminAction adminAction; + private readonly IOutputProvider outputProvider; + + public CommandHandler(AdminAction adminAction, IOutputProvider outputProvider) + { + this.adminAction = adminAction; + this.outputProvider = outputProvider; + } + + public User? GetUser(string emailAddress) + { + return adminAction.GetUser(emailAddress); + } + + public void TryRemoveUser(User? user) + { + if (user == null) + { + outputProvider.Output("User not found"); + return; + } + + var deletionConfirmation = outputProvider.Confirm( + $"Attention! This will delete user {user.EmailAddress} and all associated rows from the database. Are you sure you want to commit this transaction? (y/n)"); + if (!deletionConfirmation) return; + + adminAction.RemoveUser(user); + } + + public void CreateOrUpdateUserWithLas(string userEmailAddress, IReadOnlyCollection custodianCodes) + { + var (user, userStatus) = CheckUserStatus(userEmailAddress); + + var confirmation = ConfirmAddCustodianCodes(userEmailAddress, custodianCodes, user); + + if (confirmation) + switch (userStatus) + { + case AdminAction.UserStatus.Active: + TryAddLas(user, custodianCodes); + break; + case AdminAction.UserStatus.New: + TryCreateUser(userEmailAddress, custodianCodes, null); + break; + } + } + + public void TryRemoveLas(User? user, IReadOnlyCollection custodianCodes) + { + if (user == null) + { + outputProvider.Output("User not found"); + return; + } + + if (custodianCodes.Count == 0) + { + outputProvider.Output("Please specify custodian codes to remove from user"); + return; + } + var userConfirmation = ConfirmRemoveCustodianCodes(user.EmailAddress, custodianCodes); + if (!userConfirmation) return; + + try + { + adminAction.RemoveLas(user, custodianCodes); + } + catch (KeyNotFoundException keyNotFoundException) + { + outputProvider.Output($"Could not remove LAs from user: {keyNotFoundException.Message}"); + } + } + + public void CreateOrUpdateUserWithConsortia(string userEmailAddress, IReadOnlyCollection consortiumCodes) + { + var (user, userStatus) = CheckUserStatus(userEmailAddress); + + var confirmation = ConfirmAddConsortiumCodes(userEmailAddress, consortiumCodes, user); + + if (confirmation) + switch (userStatus) + { + case AdminAction.UserStatus.Active: + TryAddConsortia(user, consortiumCodes); + break; + case AdminAction.UserStatus.New: + TryCreateUser(userEmailAddress, null, consortiumCodes); + break; + } + } + + public void TryRemoveConsortia(User? user, IReadOnlyCollection consortiumCodes) + { + if (user == null) + { + outputProvider.Output("User not found"); + return; + } + + if (consortiumCodes.Count == 0) + { + outputProvider.Output("Please specify consortium codes to remove from user"); + return; + } + + var userConfirmation = ConfirmRemoveConsortiumCodes(user.EmailAddress, consortiumCodes); + if (!userConfirmation) return; + + try + { + adminAction.RemoveConsortia(user, consortiumCodes); + } + catch (KeyNotFoundException keyNotFoundException) + { + outputProvider.Output($"Could not remove Consortia from user: {keyNotFoundException.Message}"); + } + } + + private void PrintCodes(IReadOnlyCollection codes, Func codeToName) + { + if (codes.Count < 1) outputProvider.Output("(None)"); + + foreach (var code in codes) + { + var name = codeToName(code); + outputProvider.Output($"{code}: {name}"); + } + } + + private bool ConfirmAddCustodianCodes(string userEmailAddress, IReadOnlyCollection custodianCodes, User? user) + { + return ConfirmChangesToDatabase(userEmailAddress, () => + { + if (user != null) + { + // flag the need to not add LAs that are in consortia the user owns + var custodianCodeInOwnedConsortiumGrouping = custodianCodes.ToLookup(custodianCode => + adminAction.CustodianCodeIsInOwnedConsortium(user, custodianCode)); + var custodianCodesNotInOwnedConsortium = custodianCodeInOwnedConsortiumGrouping[false].ToList(); + var custodianCodesInOwnedConsortium = custodianCodeInOwnedConsortiumGrouping[true].ToList(); + + outputProvider.Output("Add the following Local Authorities:"); + PrintCodes(custodianCodesNotInOwnedConsortium, code => custodianCodeToLaNameDict[code]); + outputProvider.Output("Ignore the following Local Authorities already in owned Consortia:"); + PrintCodes(custodianCodesInOwnedConsortium, code => + $"{custodianCodeToLaNameDict[code]} ({custodianCodeToConsortiumNameDict[code]})"); + } + else + { + outputProvider.Output("Add the following Local Authorities:"); + PrintCodes(custodianCodes, code => custodianCodeToLaNameDict[code]); + } + }); + } + + private bool ConfirmRemoveCustodianCodes(string userEmailAddress, IReadOnlyCollection custodianCodes) + { + return ConfirmChangesToDatabase(userEmailAddress, () => + { + outputProvider.Output("Remove the following Local Authorities:"); + PrintCodes(custodianCodes, code => custodianCodeToLaNameDict[code]); + }); + } + + private bool ConfirmAddConsortiumCodes(string? userEmailAddress, IReadOnlyCollection consortiumCodes, + User? user) + { + return ConfirmChangesToDatabase(userEmailAddress, () => + { + if (user != null) + { + outputProvider.Output("Add the following Consortia:"); + PrintCodes(consortiumCodes, code => consortiumCodeToConsortiumNameDict[code]); + + // flag the need to remove access for any LAs in the new consortia + var ownedCustodianCodesInConsortia = adminAction.GetOwnedCustodianCodesInConsortia(user, consortiumCodes); + + outputProvider.Output("Remove the following Local Authorities in these Consortia:"); + PrintCodes(ownedCustodianCodesInConsortia, code => + $"{custodianCodeToLaNameDict[code]} ({custodianCodeToConsortiumNameDict[code]})"); + } + else + { + outputProvider.Output("Add the following Consortia:"); + PrintCodes(consortiumCodes, code => consortiumCodeToConsortiumNameDict[code]); + } + }); + } + + private bool ConfirmRemoveConsortiumCodes(string? userEmailAddress, IReadOnlyCollection consortiumCodes) + { + return ConfirmChangesToDatabase(userEmailAddress, () => + { + outputProvider.Output("Remove the following Consortia:"); + PrintCodes(consortiumCodes, code => consortiumCodeToConsortiumNameDict[code]); + }); + } + + private bool ConfirmChangesToDatabase(string? userEmailAddress, Action printAction) + { + outputProvider.Output( + $"You are changing permissions for user {userEmailAddress}:"); + + try + { + printAction(); + } + catch (Exception e) + { + outputProvider.Output($"{e.Message} Process terminated"); + return false; + } + + var hasUserConfirmed = outputProvider.Confirm("Please confirm (y/n)"); + if (!hasUserConfirmed) outputProvider.Output("Process cancelled, no changes were made to the database"); + + return hasUserConfirmed; + } + + private void DisplayUserStatus(Enum status) + { + switch (status) + { + case AdminAction.UserStatus.New: + outputProvider.Output("User not found in database. A new user will be created"); + break; + case AdminAction.UserStatus.Active: + outputProvider.Output("User found in database. LAs will be added to their account"); + break; + } + } + + private void TryCreateUser(string userEmailAddress, IReadOnlyCollection? custodianCodes, + IReadOnlyCollection? consortiumCodes) + { + try + { + adminAction.CreateUser(userEmailAddress, custodianCodes, consortiumCodes); + } + catch (KeyNotFoundException keyNotFoundException) + { + outputProvider.Output($"Could not create user: {keyNotFoundException.Message}"); + } + } + + private void TryAddLas(User? user, IReadOnlyCollection? custodianCodes) + { + if (user == null) + { + outputProvider.Output("User not found"); + return; + } + + if (custodianCodes == null || custodianCodes.Count < 1) + { + outputProvider.Output("Please specify custodian codes to add to user"); + return; + } + + try + { + adminAction.AddLas(user, custodianCodes); + } + catch (KeyNotFoundException keyNotFoundException) + { + outputProvider.Output($"Could not add LAs to user: {keyNotFoundException.Message}"); + } + } + + private void TryAddConsortia(User? user, IReadOnlyCollection? consortiumCodes) + { + if (user == null) + { + outputProvider.Output("User not found"); + return; + } + + if (consortiumCodes == null || consortiumCodes.Count < 1) + { + outputProvider.Output("Please specify consortium codes to add to user"); + return; + } + + try + { + adminAction.AddConsortia(user, consortiumCodes); + } + catch (KeyNotFoundException keyNotFoundException) + { + outputProvider.Output($"Could not add Consortia to user: {keyNotFoundException.Message}"); + } + } + + private (User? user, AdminAction.UserStatus userStatus) CheckUserStatus(string userEmailAddress) + { + var user = adminAction.GetUser(userEmailAddress); + var userStatus = adminAction.GetUserStatus(user); + DisplayUserStatus(userStatus); + outputProvider.Output(""); + + outputProvider.Output("!!! ATTENTION! READ CAREFULLY OR RISK A DATA BREACH !!!"); + outputProvider.Output(""); + outputProvider.Output( + "You are about to grant a user permission to read PERSONALLY IDENTIFIABLE INFORMATION submitted to LAs."); + outputProvider.Output( + "Take a moment to double check the following list and only continue if you are certain this user should have access to these LAs."); + outputProvider.Output( + "NB: in particular, you should only do this for LAs that have signed their DSA contracts!"); + outputProvider.Output(""); + + return (user, userStatus); + } +} \ No newline at end of file diff --git a/HerPortal.ManagementShell/Program.cs b/HerPortal.ManagementShell/Program.cs index 2dd2a8e..1bb91c0 100644 --- a/HerPortal.ManagementShell/Program.cs +++ b/HerPortal.ManagementShell/Program.cs @@ -26,7 +26,8 @@ public static void Main(string[] args) using var context = new HerDbContext(contextOptions); var outputProvider = new OutputProvider(); var dbOperation = new DatabaseOperation(context, outputProvider); - var adminAction = new AdminAction(dbOperation, outputProvider); + var adminAction = new AdminAction(dbOperation); + var commandHandler = new CommandHandler(adminAction, outputProvider); Subcommand command; var userEmailAddress = ""; @@ -49,19 +50,19 @@ public static void Main(string[] args) switch (command) { case Subcommand.RemoveUser: - adminAction.TryRemoveUser(adminAction.GetUser(userEmailAddress)); - return; - case Subcommand.RemoveLas: - adminAction.RemoveLas(adminAction.GetUser(userEmailAddress), codes); + commandHandler.TryRemoveUser(commandHandler.GetUser(userEmailAddress)); return; case Subcommand.AddLas: - adminAction.CreateOrUpdateUserWithLas(userEmailAddress, codes); + commandHandler.CreateOrUpdateUserWithLas(userEmailAddress, codes); return; case Subcommand.AddConsortia: - adminAction.CreateOrUpdateUserWithConsortia(userEmailAddress, codes); + commandHandler.CreateOrUpdateUserWithConsortia(userEmailAddress, codes); break; + case Subcommand.RemoveLas: + commandHandler.TryRemoveLas(commandHandler.GetUser(userEmailAddress), codes); + return; case Subcommand.RemoveConsortia: - adminAction.RemoveConsortia(adminAction.GetUser(userEmailAddress), codes); + commandHandler.TryRemoveConsortia(commandHandler.GetUser(userEmailAddress), codes); break; default: outputProvider.Output("Invalid terminal command entered. Please refer to the documentation"); diff --git a/HerPortal.UnitTests/ManagementShell/AdminActionTests.cs b/HerPortal.UnitTests/ManagementShell/CommandHandlerTests.cs similarity index 96% rename from HerPortal.UnitTests/ManagementShell/AdminActionTests.cs rename to HerPortal.UnitTests/ManagementShell/CommandHandlerTests.cs index 510b4f6..0db2d12 100644 --- a/HerPortal.UnitTests/ManagementShell/AdminActionTests.cs +++ b/HerPortal.UnitTests/ManagementShell/CommandHandlerTests.cs @@ -8,18 +8,19 @@ namespace Tests.ManagementShell; -public class AdminActionTests +public class CommandHandlerTests { private Mock mockDatabaseOperation; private Mock mockOutputProvider; - private AdminAction underTest; + private CommandHandler underTest; [SetUp] public void Setup() { mockOutputProvider = new Mock(); mockDatabaseOperation = new Mock(); - underTest = new AdminAction(mockDatabaseOperation.Object, mockOutputProvider.Object); + var adminAction = new AdminAction(mockDatabaseOperation.Object); + underTest = new CommandHandler(adminAction, mockOutputProvider.Object); } [Test] @@ -200,7 +201,7 @@ public void RemoveLas_RemovesLasFromExistingUser_IfUserFoundByDbOperation() mockOutputProvider.Setup(op => op.Confirm("Please confirm (y/n)")).Returns(true); // Act - underTest.RemoveLas(user, custodianCodes); + underTest.TryRemoveLas(user, custodianCodes); // Assert mockDatabaseOperation.Verify(mock => mock.RemoveLasFromUser(user, new List { laToRemove }), @@ -241,7 +242,7 @@ public void CreateOrUpdateUserWithLas_DisplaysErrorMessage_WhenNoLasSpecified_If underTest.CreateOrUpdateUserWithLas(userEmailAddress, Array.Empty()); // Assert - mockOutputProvider.Verify(mock => mock.Output(It.IsAny())); + mockOutputProvider.Verify(mock => mock.Output("Please specify custodian codes to add to user")); } [Test] @@ -270,10 +271,10 @@ public void RemoveLas_DisplaysErrorMessage_IfCustodianCodeToRemove_DoesNotMatchA mockOutputProvider.Setup(op => op.Confirm("Please confirm (y/n)")).Returns(true); // Act - underTest.RemoveLas(user, custodianCodes); + underTest.TryRemoveLas(user, custodianCodes); // Assert - mockOutputProvider.Verify(mock => mock.Output(It.IsAny())); + mockOutputProvider.Verify(mock => mock.Output("Could not remove LAs from user: Could not find LAs attached to existinguser@email.com for the following codes: 9052. Please check your inputs and try again.")); } [Test] @@ -318,7 +319,7 @@ public void RemoveLas_DisplaysErrorMessage_WhenRemovingLas_IfUserNotInDatabase() }; // Act - underTest.RemoveLas(null, custodianCodes); + underTest.TryRemoveLas(null, custodianCodes); // Assert mockOutputProvider.Verify(mock => mock.Output("User not found")); @@ -541,7 +542,7 @@ public void CreateOrUpdateUserWithConsortia_DisplaysErrorMessage_WhenNoLasSpecif underTest.CreateOrUpdateUserWithConsortia(userEmailAddress, Array.Empty()); // Assert - mockOutputProvider.Verify(mock => mock.Output(It.IsAny())); + mockOutputProvider.Verify(mock => mock.Output("Please specify consortium codes to add to user")); } @@ -769,7 +770,7 @@ public void RemoveConsortia_RemovesLasFromExistingUser_IfUserFoundByDbOperation( mockOutputProvider.Setup(op => op.Confirm("Please confirm (y/n)")).Returns(true); // Act - underTest.RemoveConsortia(user, custodianCodes); + underTest.TryRemoveConsortia(user, custodianCodes); // Assert mockDatabaseOperation.Verify(mock => mock.RemoveConsortiaFromUser(user, new List { consortiumToRemove }), @@ -802,12 +803,12 @@ public void RemoveConsortia_DisplaysErrorMessage_IfCustodianCodeToRemove_DoesNot mockOutputProvider.Setup(op => op.Confirm("Please confirm (y/n)")).Returns(true); // Act - underTest.RemoveConsortia(user, consortiumCodes); + underTest.TryRemoveConsortia(user, consortiumCodes); // Assert mockOutputProvider.Verify(mock => mock.Output( - "Could not find Consortia attached to existinguser@email.com for the following codes: C_0002. Please check your inputs and try again.")); + "Could not remove Consortia from user: Could not find Consortia attached to existinguser@email.com for the following codes: C_0002. Please check your inputs and try again.")); } [Test] @@ -824,7 +825,7 @@ public void RemoveConsortia_DisplaysErrorMessage_WhenRemovingLas_IfUserNotInData mockOutputProvider.Setup(op => op.Confirm("Please confirm (y/n)")).Returns(true); // Act - underTest.RemoveConsortia(null, consortiumCodes); + underTest.TryRemoveConsortia(null, consortiumCodes); // Assert mockOutputProvider.Verify(mock => mock.Output("User not found")); From 3514419c09c735ce28c68c116f0702557b311fea Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Thu, 30 May 2024 17:13:30 +0100 Subject: [PATCH 36/81] PC-1085: remove unnecessary null annotations --- HerPortal.ManagementShell/CommandHandler.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/HerPortal.ManagementShell/CommandHandler.cs b/HerPortal.ManagementShell/CommandHandler.cs index 4a1efae..7261d41 100644 --- a/HerPortal.ManagementShell/CommandHandler.cs +++ b/HerPortal.ManagementShell/CommandHandler.cs @@ -261,7 +261,7 @@ private void TryCreateUser(string userEmailAddress, IReadOnlyCollection? } } - private void TryAddLas(User? user, IReadOnlyCollection? custodianCodes) + private void TryAddLas(User? user, IReadOnlyCollection custodianCodes) { if (user == null) { @@ -269,7 +269,7 @@ private void TryAddLas(User? user, IReadOnlyCollection? custodianCodes) return; } - if (custodianCodes == null || custodianCodes.Count < 1) + if (custodianCodes.Count < 1) { outputProvider.Output("Please specify custodian codes to add to user"); return; @@ -285,7 +285,7 @@ private void TryAddLas(User? user, IReadOnlyCollection? custodianCodes) } } - private void TryAddConsortia(User? user, IReadOnlyCollection? consortiumCodes) + private void TryAddConsortia(User? user, IReadOnlyCollection consortiumCodes) { if (user == null) { @@ -293,7 +293,7 @@ private void TryAddConsortia(User? user, IReadOnlyCollection? consortium return; } - if (consortiumCodes == null || consortiumCodes.Count < 1) + if (consortiumCodes.Count < 1) { outputProvider.Output("Please specify consortium codes to add to user"); return; From ac1e68e082d45eeefd22742cfffd6d9576c15ee5 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Thu, 30 May 2024 17:24:42 +0100 Subject: [PATCH 37/81] PC-1085: format all touched files --- HerPortal.ManagementShell/AdminAction.cs | 4 - HerPortal.ManagementShell/CommandHandler.cs | 10 +- HerPortal.ManagementShell/Program.cs | 122 +++++++++--------- .../ManagementShell/CommandHandlerTests.cs | 12 +- 4 files changed, 76 insertions(+), 72 deletions(-) diff --git a/HerPortal.ManagementShell/AdminAction.cs b/HerPortal.ManagementShell/AdminAction.cs index 9bde35b..8f9cbf7 100644 --- a/HerPortal.ManagementShell/AdminAction.cs +++ b/HerPortal.ManagementShell/AdminAction.cs @@ -87,10 +87,8 @@ public void RemoveLas(User user, IReadOnlyCollection custodianCodes) var lasToRemove = user.LocalAuthorities.Where(la => custodianCodes.Contains(la.CustodianCode)).ToList(); var missingCodes = custodianCodes.Where(code => !lasToRemove.Any(la => la.CustodianCode.Equals(code))).ToList(); if (missingCodes.Count > 0) - { throw new KeyNotFoundException( $"Could not find LAs attached to {user.EmailAddress} for the following codes: {string.Join(", ", missingCodes)}. Please check your inputs and try again."); - } dbOperation.RemoveLasFromUser(user, lasToRemove); } @@ -115,10 +113,8 @@ public void RemoveConsortia(User user, IReadOnlyCollection consortiumCod var missingCodes = consortiumCodes .Where(code => !consortiaToRemove.Any(consortium => consortium.ConsortiumCode.Equals(code))).ToList(); if (missingCodes.Count > 0) - { throw new KeyNotFoundException( $"Could not find Consortia attached to {user.EmailAddress} for the following codes: {string.Join(", ", missingCodes)}. Please check your inputs and try again."); - } dbOperation.RemoveConsortiaFromUser(user, consortiaToRemove); } diff --git a/HerPortal.ManagementShell/CommandHandler.cs b/HerPortal.ManagementShell/CommandHandler.cs index 7261d41..30eaed0 100644 --- a/HerPortal.ManagementShell/CommandHandler.cs +++ b/HerPortal.ManagementShell/CommandHandler.cs @@ -4,6 +4,8 @@ namespace HerPortal.ManagementShell; public class CommandHandler { + private readonly AdminAction adminAction; + private readonly Dictionary consortiumCodeToConsortiumNameDict = ConsortiumData.ConsortiumNamesByConsortiumCode; @@ -16,7 +18,6 @@ public class CommandHandler private readonly Dictionary custodianCodeToLaNameDict = LocalAuthorityData.LocalAuthorityNamesByCustodianCode; - private readonly AdminAction adminAction; private readonly IOutputProvider outputProvider; public CommandHandler(AdminAction adminAction, IOutputProvider outputProvider) @@ -76,6 +77,7 @@ public void TryRemoveLas(User? user, IReadOnlyCollection custodianCodes) outputProvider.Output("Please specify custodian codes to remove from user"); return; } + var userConfirmation = ConfirmRemoveCustodianCodes(user.EmailAddress, custodianCodes); if (!userConfirmation) return; @@ -145,7 +147,8 @@ private void PrintCodes(IReadOnlyCollection codes, Func } } - private bool ConfirmAddCustodianCodes(string userEmailAddress, IReadOnlyCollection custodianCodes, User? user) + private bool ConfirmAddCustodianCodes(string userEmailAddress, IReadOnlyCollection custodianCodes, + User? user) { return ConfirmChangesToDatabase(userEmailAddress, () => { @@ -191,7 +194,8 @@ private bool ConfirmAddConsortiumCodes(string? userEmailAddress, IReadOnlyCollec PrintCodes(consortiumCodes, code => consortiumCodeToConsortiumNameDict[code]); // flag the need to remove access for any LAs in the new consortia - var ownedCustodianCodesInConsortia = adminAction.GetOwnedCustodianCodesInConsortia(user, consortiumCodes); + var ownedCustodianCodesInConsortia = + adminAction.GetOwnedCustodianCodesInConsortia(user, consortiumCodes); outputProvider.Output("Remove the following Local Authorities in these Consortia:"); PrintCodes(ownedCustodianCodesInConsortia, code => diff --git a/HerPortal.ManagementShell/Program.cs b/HerPortal.ManagementShell/Program.cs index 1bb91c0..b3a9735 100644 --- a/HerPortal.ManagementShell/Program.cs +++ b/HerPortal.ManagementShell/Program.cs @@ -1,73 +1,73 @@ -using HerPortal.Data; +using System.Diagnostics.CodeAnalysis; +using HerPortal.Data; using Microsoft.EntityFrameworkCore; -using System.Diagnostics.CodeAnalysis; -namespace HerPortal.ManagementShell +namespace HerPortal.ManagementShell; + +[ExcludeFromCodeCoverage] +public static class Program { - [ExcludeFromCodeCoverage] - public static class Program + public static void Main(string[] args) { - private enum Subcommand + var contextOptions = new DbContextOptionsBuilder() + .UseNpgsql( + Environment.GetEnvironmentVariable("ConnectionStrings__PostgreSQLConnection") ?? + @"UserId=postgres;Password=postgres;Server=localhost;Port=5432;Database=herportaldev;Integrated Security=true;Include Error Detail=true;Pooling=true") + .Options; + + using var context = new HerDbContext(contextOptions); + var outputProvider = new OutputProvider(); + var dbOperation = new DatabaseOperation(context, outputProvider); + var adminAction = new AdminAction(dbOperation); + var commandHandler = new CommandHandler(adminAction, outputProvider); + + Subcommand command; + var userEmailAddress = ""; + var codes = Array.Empty(); + + try { - AddLas, - RemoveLas, - RemoveUser, - AddConsortia, - RemoveConsortia + command = Enum.Parse(args[0], true); + userEmailAddress = args[1]; + codes = args.Skip(2).ToArray(); } - public static void Main(string[] args) + catch (Exception) { - var contextOptions = new DbContextOptionsBuilder() - .UseNpgsql( - Environment.GetEnvironmentVariable("ConnectionStrings__PostgreSQLConnection") ?? - @"UserId=postgres;Password=postgres;Server=localhost;Port=5432;Database=herportaldev;Integrated Security=true;Include Error Detail=true;Pooling=true") - .Options; - - using var context = new HerDbContext(contextOptions); - var outputProvider = new OutputProvider(); - var dbOperation = new DatabaseOperation(context, outputProvider); - var adminAction = new AdminAction(dbOperation); - var commandHandler = new CommandHandler(adminAction, outputProvider); - - Subcommand command; - var userEmailAddress = ""; - var codes = Array.Empty(); + var allSubcommands = string.Join(", ", Enum.GetValues()); + outputProvider.Output( + $"Please specify a valid subcommand - available options are: {allSubcommands}"); + return; + } - try - { - command = Enum.Parse(args[0], true); - userEmailAddress = args[1]; - codes = args.Skip(2).ToArray(); - } - catch (Exception) - { - var allSubcommands = string.Join(", ", Enum.GetValues()); - outputProvider.Output( - $"Please specify a valid subcommand - available options are: {allSubcommands}"); + switch (command) + { + case Subcommand.RemoveUser: + commandHandler.TryRemoveUser(commandHandler.GetUser(userEmailAddress)); + return; + case Subcommand.AddLas: + commandHandler.CreateOrUpdateUserWithLas(userEmailAddress, codes); + return; + case Subcommand.AddConsortia: + commandHandler.CreateOrUpdateUserWithConsortia(userEmailAddress, codes); + break; + case Subcommand.RemoveLas: + commandHandler.TryRemoveLas(commandHandler.GetUser(userEmailAddress), codes); + return; + case Subcommand.RemoveConsortia: + commandHandler.TryRemoveConsortia(commandHandler.GetUser(userEmailAddress), codes); + break; + default: + outputProvider.Output("Invalid terminal command entered. Please refer to the documentation"); return; - } - - switch (command) - { - case Subcommand.RemoveUser: - commandHandler.TryRemoveUser(commandHandler.GetUser(userEmailAddress)); - return; - case Subcommand.AddLas: - commandHandler.CreateOrUpdateUserWithLas(userEmailAddress, codes); - return; - case Subcommand.AddConsortia: - commandHandler.CreateOrUpdateUserWithConsortia(userEmailAddress, codes); - break; - case Subcommand.RemoveLas: - commandHandler.TryRemoveLas(commandHandler.GetUser(userEmailAddress), codes); - return; - case Subcommand.RemoveConsortia: - commandHandler.TryRemoveConsortia(commandHandler.GetUser(userEmailAddress), codes); - break; - default: - outputProvider.Output("Invalid terminal command entered. Please refer to the documentation"); - return; - } } } + + private enum Subcommand + { + AddLas, + RemoveLas, + RemoveUser, + AddConsortia, + RemoveConsortia + } } \ No newline at end of file diff --git a/HerPortal.UnitTests/ManagementShell/CommandHandlerTests.cs b/HerPortal.UnitTests/ManagementShell/CommandHandlerTests.cs index 0db2d12..45b0d92 100644 --- a/HerPortal.UnitTests/ManagementShell/CommandHandlerTests.cs +++ b/HerPortal.UnitTests/ManagementShell/CommandHandlerTests.cs @@ -274,7 +274,9 @@ public void RemoveLas_DisplaysErrorMessage_IfCustodianCodeToRemove_DoesNotMatchA underTest.TryRemoveLas(user, custodianCodes); // Assert - mockOutputProvider.Verify(mock => mock.Output("Could not remove LAs from user: Could not find LAs attached to existinguser@email.com for the following codes: 9052. Please check your inputs and try again.")); + mockOutputProvider.Verify(mock => + mock.Output( + "Could not remove LAs from user: Could not find LAs attached to existinguser@email.com for the following codes: 9052. Please check your inputs and try again.")); } [Test] @@ -741,9 +743,10 @@ public void CreateOrUpdateUserWithConsortia_WhenUserHasLasInConsortia_RemovesThe underTest.CreateOrUpdateUserWithConsortia(userEmailAddress, consortiumCodes); // Assert - mockDatabaseOperation.Verify(mock => mock.AddConsortiaAndRemoveLasFromUser(users[0], consortiaToAdd, currentLa)); + mockDatabaseOperation.Verify(mock => + mock.AddConsortiaAndRemoveLasFromUser(users[0], consortiaToAdd, currentLa)); } - + [Test] public void RemoveConsortia_RemovesLasFromExistingUser_IfUserFoundByDbOperation() { @@ -773,7 +776,8 @@ public void RemoveConsortia_RemovesLasFromExistingUser_IfUserFoundByDbOperation( underTest.TryRemoveConsortia(user, custodianCodes); // Assert - mockDatabaseOperation.Verify(mock => mock.RemoveConsortiaFromUser(user, new List { consortiumToRemove }), + mockDatabaseOperation.Verify( + mock => mock.RemoveConsortiaFromUser(user, new List { consortiumToRemove }), Times.Once()); } From a329b4669dc48735996c4b26bfe9743fb2af5cbd Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Fri, 31 May 2024 09:23:26 +0100 Subject: [PATCH 38/81] PC-1085: use a custom exception over inbuilt --- HerPortal.ManagementShell/AdminAction.cs | 4 ++-- HerPortal.ManagementShell/CommandException.cs | 8 ++++++++ HerPortal.ManagementShell/CommandHandler.cs | 20 +++++++++---------- .../DatabaseOperation.cs | 4 ++-- 4 files changed, 22 insertions(+), 14 deletions(-) create mode 100644 HerPortal.ManagementShell/CommandException.cs diff --git a/HerPortal.ManagementShell/AdminAction.cs b/HerPortal.ManagementShell/AdminAction.cs index 8f9cbf7..7a38a7b 100644 --- a/HerPortal.ManagementShell/AdminAction.cs +++ b/HerPortal.ManagementShell/AdminAction.cs @@ -87,7 +87,7 @@ public void RemoveLas(User user, IReadOnlyCollection custodianCodes) var lasToRemove = user.LocalAuthorities.Where(la => custodianCodes.Contains(la.CustodianCode)).ToList(); var missingCodes = custodianCodes.Where(code => !lasToRemove.Any(la => la.CustodianCode.Equals(code))).ToList(); if (missingCodes.Count > 0) - throw new KeyNotFoundException( + throw new CommandException( $"Could not find LAs attached to {user.EmailAddress} for the following codes: {string.Join(", ", missingCodes)}. Please check your inputs and try again."); dbOperation.RemoveLasFromUser(user, lasToRemove); @@ -113,7 +113,7 @@ public void RemoveConsortia(User user, IReadOnlyCollection consortiumCod var missingCodes = consortiumCodes .Where(code => !consortiaToRemove.Any(consortium => consortium.ConsortiumCode.Equals(code))).ToList(); if (missingCodes.Count > 0) - throw new KeyNotFoundException( + throw new CommandException( $"Could not find Consortia attached to {user.EmailAddress} for the following codes: {string.Join(", ", missingCodes)}. Please check your inputs and try again."); dbOperation.RemoveConsortiaFromUser(user, consortiaToRemove); diff --git a/HerPortal.ManagementShell/CommandException.cs b/HerPortal.ManagementShell/CommandException.cs new file mode 100644 index 0000000..4a32b60 --- /dev/null +++ b/HerPortal.ManagementShell/CommandException.cs @@ -0,0 +1,8 @@ +namespace HerPortal.ManagementShell; + +public class CommandException : Exception +{ + public CommandException(string message) : base(message) + { + } +} \ No newline at end of file diff --git a/HerPortal.ManagementShell/CommandHandler.cs b/HerPortal.ManagementShell/CommandHandler.cs index 30eaed0..b00d8d0 100644 --- a/HerPortal.ManagementShell/CommandHandler.cs +++ b/HerPortal.ManagementShell/CommandHandler.cs @@ -85,9 +85,9 @@ public void TryRemoveLas(User? user, IReadOnlyCollection custodianCodes) { adminAction.RemoveLas(user, custodianCodes); } - catch (KeyNotFoundException keyNotFoundException) + catch (CommandException commandException) { - outputProvider.Output($"Could not remove LAs from user: {keyNotFoundException.Message}"); + outputProvider.Output($"Could not remove LAs from user: {commandException.Message}"); } } @@ -130,9 +130,9 @@ public void TryRemoveConsortia(User? user, IReadOnlyCollection consortiu { adminAction.RemoveConsortia(user, consortiumCodes); } - catch (KeyNotFoundException keyNotFoundException) + catch (CommandException commandException) { - outputProvider.Output($"Could not remove Consortia from user: {keyNotFoundException.Message}"); + outputProvider.Output($"Could not remove Consortia from user: {commandException.Message}"); } } @@ -259,9 +259,9 @@ private void TryCreateUser(string userEmailAddress, IReadOnlyCollection? { adminAction.CreateUser(userEmailAddress, custodianCodes, consortiumCodes); } - catch (KeyNotFoundException keyNotFoundException) + catch (CommandException commandException) { - outputProvider.Output($"Could not create user: {keyNotFoundException.Message}"); + outputProvider.Output($"Could not create user: {commandException.Message}"); } } @@ -283,9 +283,9 @@ private void TryAddLas(User? user, IReadOnlyCollection custodianCodes) { adminAction.AddLas(user, custodianCodes); } - catch (KeyNotFoundException keyNotFoundException) + catch (CommandException commandException) { - outputProvider.Output($"Could not add LAs to user: {keyNotFoundException.Message}"); + outputProvider.Output($"Could not add LAs to user: {commandException.Message}"); } } @@ -307,9 +307,9 @@ private void TryAddConsortia(User? user, IReadOnlyCollection consortiumC { adminAction.AddConsortia(user, consortiumCodes); } - catch (KeyNotFoundException keyNotFoundException) + catch (CommandException commandException) { - outputProvider.Output($"Could not add Consortia to user: {keyNotFoundException.Message}"); + outputProvider.Output($"Could not add Consortia to user: {commandException.Message}"); } } diff --git a/HerPortal.ManagementShell/DatabaseOperation.cs b/HerPortal.ManagementShell/DatabaseOperation.cs index 5595cca..51cde6d 100644 --- a/HerPortal.ManagementShell/DatabaseOperation.cs +++ b/HerPortal.ManagementShell/DatabaseOperation.cs @@ -31,7 +31,7 @@ public List GetLas(IReadOnlyCollection custodianCodes) custodianCodes.SingleOrDefault(code => !dbContext.LocalAuthorities.Any(la => la.CustodianCode == code), null); if (missingCustodianCode != null) - throw new KeyNotFoundException($"Custodian Code {missingCustodianCode} not found."); + throw new CommandException($"Custodian Code {missingCustodianCode} not found."); return custodianCodes .Select(code => dbContext.LocalAuthorities @@ -44,7 +44,7 @@ public List GetConsortia(IReadOnlyCollection consortiumCodes var missingConsortiumCode = consortiumCodes.SingleOrDefault(code => !dbContext.Consortia.Any(la => la.ConsortiumCode == code), null); if (missingConsortiumCode != null) - throw new KeyNotFoundException($"Consortium Code {missingConsortiumCode} not found."); + throw new CommandException($"Consortium Code {missingConsortiumCode} not found."); return consortiumCodes .Select(code => dbContext.Consortia From e9c38017e0c418b523c9a6052cf74b35cdb216c9 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Fri, 31 May 2024 09:47:31 +0100 Subject: [PATCH 39/81] PC-1085: extract UserStatus to its own class rename it to UserAccountStatus as UserStatus is quite broad also specify what enum DisplayUserStatus expects --- HerPortal.ManagementShell/AdminAction.cs | 17 +++++++---------- HerPortal.ManagementShell/CommandHandler.cs | 18 +++++++++--------- HerPortal.ManagementShell/NewUserStatus.cs | 7 +++++++ 3 files changed, 23 insertions(+), 19 deletions(-) create mode 100644 HerPortal.ManagementShell/NewUserStatus.cs diff --git a/HerPortal.ManagementShell/AdminAction.cs b/HerPortal.ManagementShell/AdminAction.cs index 7a38a7b..c8fec21 100644 --- a/HerPortal.ManagementShell/AdminAction.cs +++ b/HerPortal.ManagementShell/AdminAction.cs @@ -2,14 +2,8 @@ namespace HerPortal.ManagementShell; -public class AdminAction +public partial class AdminAction { - public enum UserStatus - { - New, - Active - } - private readonly Dictionary> consortiumCodeToCustodianCodesDict = ConsortiumData.ConsortiumCustodianCodesIdsByConsortiumCode; @@ -52,9 +46,9 @@ public List GetOwnedCustodianCodesInConsortia(User user, IEnumerable? custodianCodes, @@ -85,7 +79,10 @@ public void AddLas(User user, IEnumerable custodianCodes) public void RemoveLas(User user, IReadOnlyCollection custodianCodes) { var lasToRemove = user.LocalAuthorities.Where(la => custodianCodes.Contains(la.CustodianCode)).ToList(); - var missingCodes = custodianCodes.Where(code => !lasToRemove.Any(la => la.CustodianCode.Equals(code))).ToList(); + var missingCodes = custodianCodes.Where(code => + !lasToRemove + .Any(la => la.CustodianCode.Equals(code))) + .ToList(); if (missingCodes.Count > 0) throw new CommandException( $"Could not find LAs attached to {user.EmailAddress} for the following codes: {string.Join(", ", missingCodes)}. Please check your inputs and try again."); diff --git a/HerPortal.ManagementShell/CommandHandler.cs b/HerPortal.ManagementShell/CommandHandler.cs index b00d8d0..babea8d 100644 --- a/HerPortal.ManagementShell/CommandHandler.cs +++ b/HerPortal.ManagementShell/CommandHandler.cs @@ -55,10 +55,10 @@ public void CreateOrUpdateUserWithLas(string userEmailAddress, IReadOnlyCollecti if (confirmation) switch (userStatus) { - case AdminAction.UserStatus.Active: + case UserAccountStatus.Active: TryAddLas(user, custodianCodes); break; - case AdminAction.UserStatus.New: + case UserAccountStatus.New: TryCreateUser(userEmailAddress, custodianCodes, null); break; } @@ -100,10 +100,10 @@ public void CreateOrUpdateUserWithConsortia(string userEmailAddress, IReadOnlyCo if (confirmation) switch (userStatus) { - case AdminAction.UserStatus.Active: + case UserAccountStatus.Active: TryAddConsortia(user, consortiumCodes); break; - case AdminAction.UserStatus.New: + case UserAccountStatus.New: TryCreateUser(userEmailAddress, null, consortiumCodes); break; } @@ -239,14 +239,14 @@ private bool ConfirmChangesToDatabase(string? userEmailAddress, Action printActi return hasUserConfirmed; } - private void DisplayUserStatus(Enum status) + private void DisplayUserStatus(UserAccountStatus userAccountStatus) { - switch (status) + switch (userAccountStatus) { - case AdminAction.UserStatus.New: + case UserAccountStatus.New: outputProvider.Output("User not found in database. A new user will be created"); break; - case AdminAction.UserStatus.Active: + case UserAccountStatus.Active: outputProvider.Output("User found in database. LAs will be added to their account"); break; } @@ -313,7 +313,7 @@ private void TryAddConsortia(User? user, IReadOnlyCollection consortiumC } } - private (User? user, AdminAction.UserStatus userStatus) CheckUserStatus(string userEmailAddress) + private (User? user, UserAccountStatus userStatus) CheckUserStatus(string userEmailAddress) { var user = adminAction.GetUser(userEmailAddress); var userStatus = adminAction.GetUserStatus(user); diff --git a/HerPortal.ManagementShell/NewUserStatus.cs b/HerPortal.ManagementShell/NewUserStatus.cs new file mode 100644 index 0000000..f30aefe --- /dev/null +++ b/HerPortal.ManagementShell/NewUserStatus.cs @@ -0,0 +1,7 @@ +namespace HerPortal.ManagementShell; + +public enum UserAccountStatus +{ + New, + Active +} \ No newline at end of file From 5fc935c3d28e0b94f98f73b4ada3c04964c020bd Mon Sep 17 00:00:00 2001 From: Glenn Clarke Date: Fri, 31 May 2024 10:20:23 +0100 Subject: [PATCH 40/81] PC-1084: Removed unnecessary ToLists. Removed unused import. Added new function to replace commonly used Consortium to list of string logic. Renamed variables to bring them in line with standard. --- HerPortal.BusinessLogic/Models/User.cs | 12 ++++++++---- .../Services/CsvFileService/CsvFileService.cs | 4 ++-- .../Website/Controllers/HomeControllerTests.cs | 1 - HerPortal/Models/HomepageViewModel.cs | 16 ++++++++-------- 4 files changed, 18 insertions(+), 15 deletions(-) diff --git a/HerPortal.BusinessLogic/Models/User.cs b/HerPortal.BusinessLogic/Models/User.cs index 2c95822..a3b5fd3 100644 --- a/HerPortal.BusinessLogic/Models/User.cs +++ b/HerPortal.BusinessLogic/Models/User.cs @@ -11,13 +11,17 @@ public class User public List GetAdministeredCustodianCodes() { - var consortiumCodes = Consortia.Select(consortium => consortium.ConsortiumCode).ToList(); - var custodianCodes = LocalAuthorities.Select(la => la.CustodianCode).ToList(); + var consortiumCodes = Consortia.Select(consortium => consortium.ConsortiumCode); + var custodianCodes = LocalAuthorities.Select(la => la.CustodianCode); - return consortiumCodes.SelectMany(consortiumCode => + return consortiumCodes.SelectMany(consortiumCode => ConsortiumData.ConsortiumCustodianCodesIdsByConsortiumCode[consortiumCode]) - .Distinct() .Union(custodianCodes) .ToList(); } + + public List GetAdministeredConsortiumCodes() + { + return Consortia.Select(c => c.ConsortiumCode).ToList(); + } } \ No newline at end of file diff --git a/HerPortal.BusinessLogic/Services/CsvFileService/CsvFileService.cs b/HerPortal.BusinessLogic/Services/CsvFileService/CsvFileService.cs index 882b1e7..20877dd 100644 --- a/HerPortal.BusinessLogic/Services/CsvFileService/CsvFileService.cs +++ b/HerPortal.BusinessLogic/Services/CsvFileService/CsvFileService.cs @@ -84,7 +84,7 @@ public async Task> GetFileDataForUserAsync(string userE // Make sure that we only return file data for files that the user currently has access to var user = await dataAccessProvider.GetUserByEmailAsync(userEmailAddress); var custodianCodes = user.GetAdministeredCustodianCodes(); - var consortiumCodes = user.Consortia.Select(c => c.ConsortiumCode).ToList(); + var consortiumCodes = user.GetAdministeredConsortiumCodes(); var localAuthoritiesFileData = await BuildCsvFileDataForLocalAuthorities(user, custodianCodes); var consortiaTransformedFileData = TransformFileDataForConsortia(consortiumCodes, localAuthoritiesFileData); @@ -204,7 +204,7 @@ public async Task GetConsortiumFileForDownloadAsync(string consortiumCod { // Important! First ensure the logged-in user is allowed to access this data var userData = await dataAccessProvider.GetUserByEmailAsync(userEmailAddress); - var consortiumCodes = userData.Consortia.Select(c => c.ConsortiumCode).ToList(); + var consortiumCodes = userData.GetAdministeredConsortiumCodes(); if (!consortiumCodes.Contains(consortiumCode)) { diff --git a/HerPortal.UnitTests/Website/Controllers/HomeControllerTests.cs b/HerPortal.UnitTests/Website/Controllers/HomeControllerTests.cs index b28eaed..e95862d 100644 --- a/HerPortal.UnitTests/Website/Controllers/HomeControllerTests.cs +++ b/HerPortal.UnitTests/Website/Controllers/HomeControllerTests.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; using HerPortal.BusinessLogic; using HerPortal.BusinessLogic.Models; diff --git a/HerPortal/Models/HomepageViewModel.cs b/HerPortal/Models/HomepageViewModel.cs index 99a9a7f..b67675c 100644 --- a/HerPortal/Models/HomepageViewModel.cs +++ b/HerPortal/Models/HomepageViewModel.cs @@ -61,15 +61,15 @@ public CsvFile(CsvFileData csvFileData, string downloadLink) public string[] PageUrls { get; } public HomepageViewModel( - User user, - PaginatedFileData paginatedFileData, + User user, + PaginatedFileData paginatedFileData, Func pageLinkGenerator, Func downloadLinkGenerator - ) + ) { - var administeredCustodianCodes = user.GetAdministeredCustodianCodes(); - var consortiumCodes = user.Consortia.Select(c => c.ConsortiumCode).ToList(); - var checkboxLabels = administeredCustodianCodes + var custodianCodes = user.GetAdministeredCustodianCodes(); + var consortiumCodes = user.GetAdministeredConsortiumCodes(); + var checkboxLabels = custodianCodes .Select(custodianCode => new KeyValuePair ( custodianCode, @@ -90,9 +90,9 @@ Func downloadLinkGenerator ))); ShouldShowBanner = !user.HasLoggedIn; - ShouldShowFilters = administeredCustodianCodes.Count >= 2; + ShouldShowFilters = custodianCodes.Count >= 2; Codes = new List(); - Codes.AddRange(administeredCustodianCodes); + Codes.AddRange(custodianCodes); Codes.AddRange(consortiumCodes); LocalAuthorityCheckboxLabels = new Dictionary(checkboxLabels .OrderBy(kvp => kvp.Value.Text) From 8d2ee6c1bbdcf0a85325f2ea6b69cceeb14377ca Mon Sep 17 00:00:00 2001 From: Glenn Clarke Date: Fri, 31 May 2024 16:45:41 +0100 Subject: [PATCH 41/81] PC-1084: Changed user methods to return enumerables. Changed TransformFileDataForConsortia to not mutate the passed list. Standardised consortium code security checking to work the same for LA and Consortia. --- HerPortal.BusinessLogic/Models/User.cs | 9 ++- .../Services/CsvFileService/CsvFileService.cs | 59 +++++++++---------- HerPortal/Models/HomepageViewModel.cs | 4 +- 3 files changed, 34 insertions(+), 38 deletions(-) diff --git a/HerPortal.BusinessLogic/Models/User.cs b/HerPortal.BusinessLogic/Models/User.cs index a3b5fd3..29e2436 100644 --- a/HerPortal.BusinessLogic/Models/User.cs +++ b/HerPortal.BusinessLogic/Models/User.cs @@ -9,19 +9,18 @@ public class User public List LocalAuthorities { get; set; } public List Consortia { get; set; } - public List GetAdministeredCustodianCodes() + public IEnumerable GetAdministeredCustodianCodes() { var consortiumCodes = Consortia.Select(consortium => consortium.ConsortiumCode); var custodianCodes = LocalAuthorities.Select(la => la.CustodianCode); return consortiumCodes.SelectMany(consortiumCode => ConsortiumData.ConsortiumCustodianCodesIdsByConsortiumCode[consortiumCode]) - .Union(custodianCodes) - .ToList(); + .Union(custodianCodes); } - public List GetAdministeredConsortiumCodes() + public IEnumerable GetAdministeredConsortiumCodes() { - return Consortia.Select(c => c.ConsortiumCode).ToList(); + return Consortia.Select(c => c.ConsortiumCode); } } \ No newline at end of file diff --git a/HerPortal.BusinessLogic/Services/CsvFileService/CsvFileService.cs b/HerPortal.BusinessLogic/Services/CsvFileService/CsvFileService.cs index 20877dd..e507627 100644 --- a/HerPortal.BusinessLogic/Services/CsvFileService/CsvFileService.cs +++ b/HerPortal.BusinessLogic/Services/CsvFileService/CsvFileService.cs @@ -88,45 +88,22 @@ public async Task> GetFileDataForUserAsync(string userE var localAuthoritiesFileData = await BuildCsvFileDataForLocalAuthorities(user, custodianCodes); var consortiaTransformedFileData = TransformFileDataForConsortia(consortiumCodes, localAuthoritiesFileData); + var combinedFileData = localAuthoritiesFileData.Concat(consortiaTransformedFileData); - return consortiaTransformedFileData + return combinedFileData .OrderByDescending(f => new DateOnly(f.Year, f.Month, 1)) .ThenByDescending(f => f is ConsortiumCsvFileData) .ThenBy(f => f.Name); } - private List TransformFileDataForConsortia(List consortiumCodes, List files) + private async Task> BuildCsvFileDataForLocalAuthorities(User user, IEnumerable currentCustodianCodes) { - files.AddRange( - files - .Where(file => LocalAuthorityData.LocalAuthorityConsortiumCodeByCustodianCode.ContainsKey(file.Code)) - .GroupBy(file => (LocalAuthorityData.LocalAuthorityConsortiumCodeByCustodianCode[file.Code], file.Month, - file.Year)) - .Where(grouping => consortiumCodes.Contains(grouping.Key.Item1)) - .Select(grouping => new ConsortiumCsvFileData( - grouping.Key.Item1, - grouping.Key.Month, - grouping.Key.Year, - grouping - .Select(fileData => fileData.LastUpdated) - .Max(), // there are csv updates as new as - grouping - .Select(fileData => fileData.LastDownloaded) - .Min() // there are csvs undownloaded for as long as - ) - ) - ); - return files; - } - - private async Task> BuildCsvFileDataForLocalAuthorities(User user, List currentCustodianCodes) - { - var files = new List(); + var laFileData = new List(); var downloads = await dataAccessProvider.GetCsvFileDownloadDataForUserAsync(user.Id); foreach (var custodianCode in currentCustodianCodes) { var s3Objects = await s3FileReader.GetS3ObjectsByCustodianCodeAsync(custodianCode); - files.AddRange(s3Objects.Select(s3O => + laFileData.AddRange(s3Objects.Select(s3O => { var data = keyService.GetDataFromS3Key(s3O.Key); var downloadData = downloads.SingleOrDefault(d => @@ -146,7 +123,28 @@ private async Task> BuildCsvFileDataForLocalAuthorities(User u )); } - return files; + return laFileData; + } + + private IEnumerable TransformFileDataForConsortia(IEnumerable consortiumCodes, IEnumerable laFileData) + { + return laFileData + .Where(fileRow => LocalAuthorityData.LocalAuthorityConsortiumCodeByCustodianCode.ContainsKey(fileRow.Code)) + .GroupBy(fileRow => (LocalAuthorityData.LocalAuthorityConsortiumCodeByCustodianCode[fileRow.Code], fileRow.Month, + fileRow.Year)) + .Where(grouping => consortiumCodes.Contains(grouping.Key.Item1)) + .Select(grouping => new ConsortiumCsvFileData( + grouping.Key.Item1, + grouping.Key.Month, + grouping.Key.Year, + grouping + .Select(fileData => fileData.LastUpdated) + .Max(), // there are csv updates as new as + grouping + .Select(fileData => fileData.LastDownloaded) + .Min() // there are csvs undownloaded for as long as + ) + ); } // Page number starts at 1 @@ -204,9 +202,8 @@ public async Task GetConsortiumFileForDownloadAsync(string consortiumCod { // Important! First ensure the logged-in user is allowed to access this data var userData = await dataAccessProvider.GetUserByEmailAsync(userEmailAddress); - var consortiumCodes = userData.GetAdministeredConsortiumCodes(); - if (!consortiumCodes.Contains(consortiumCode)) + if (!userData.GetAdministeredConsortiumCodes().Contains(consortiumCode)) { // We don't want to log the User's email address for GDPR reasons, but the ID is fine. throw new SecurityException( diff --git a/HerPortal/Models/HomepageViewModel.cs b/HerPortal/Models/HomepageViewModel.cs index b67675c..47e22b5 100644 --- a/HerPortal/Models/HomepageViewModel.cs +++ b/HerPortal/Models/HomepageViewModel.cs @@ -67,8 +67,8 @@ public HomepageViewModel( Func downloadLinkGenerator ) { - var custodianCodes = user.GetAdministeredCustodianCodes(); - var consortiumCodes = user.GetAdministeredConsortiumCodes(); + var custodianCodes = user.GetAdministeredCustodianCodes().ToList(); + var consortiumCodes = user.GetAdministeredConsortiumCodes().ToList(); var checkboxLabels = custodianCodes .Select(custodianCode => new KeyValuePair ( From be520d0ca54323e78499c850ad26767d4de371fa Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Fri, 31 May 2024 17:25:45 +0100 Subject: [PATCH 42/81] PC-1085: replace any with select -> contains --- HerPortal.ManagementShell/AdminAction.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/HerPortal.ManagementShell/AdminAction.cs b/HerPortal.ManagementShell/AdminAction.cs index c8fec21..9e2eae6 100644 --- a/HerPortal.ManagementShell/AdminAction.cs +++ b/HerPortal.ManagementShell/AdminAction.cs @@ -81,7 +81,8 @@ public void RemoveLas(User user, IReadOnlyCollection custodianCodes) var lasToRemove = user.LocalAuthorities.Where(la => custodianCodes.Contains(la.CustodianCode)).ToList(); var missingCodes = custodianCodes.Where(code => !lasToRemove - .Any(la => la.CustodianCode.Equals(code))) + .Select(la => la.CustodianCode) + .Contains(code)) .ToList(); if (missingCodes.Count > 0) throw new CommandException( @@ -108,7 +109,10 @@ public void RemoveConsortia(User user, IReadOnlyCollection consortiumCod var consortiaToRemove = user.Consortia.Where(consortium => consortiumCodes.Contains(consortium.ConsortiumCode)) .ToList(); var missingCodes = consortiumCodes - .Where(code => !consortiaToRemove.Any(consortium => consortium.ConsortiumCode.Equals(code))).ToList(); + .Where(code => !consortiaToRemove + .Select(consortium => consortium.ConsortiumCode) + .Contains(code) + ).ToList(); if (missingCodes.Count > 0) throw new CommandException( $"Could not find Consortia attached to {user.EmailAddress} for the following codes: {string.Join(", ", missingCodes)}. Please check your inputs and try again."); From ce1525ac6d25deece398478304c3bbbbd217e534 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Mon, 3 Jun 2024 10:32:39 +0100 Subject: [PATCH 43/81] PC-1085: move error message construction up the chain rename CommandException to CouldNotFindAuthorityException --- HerPortal.ManagementShell/AdminAction.cs | 6 ++-- HerPortal.ManagementShell/CommandException.cs | 8 ----- HerPortal.ManagementShell/CommandHandler.cs | 29 ++++++++++++------- .../CouldNotFindAuthorityException.cs | 11 +++++++ .../DatabaseOperation.cs | 4 +-- 5 files changed, 34 insertions(+), 24 deletions(-) delete mode 100644 HerPortal.ManagementShell/CommandException.cs create mode 100644 HerPortal.ManagementShell/CouldNotFindAuthorityException.cs diff --git a/HerPortal.ManagementShell/AdminAction.cs b/HerPortal.ManagementShell/AdminAction.cs index 9e2eae6..2caec27 100644 --- a/HerPortal.ManagementShell/AdminAction.cs +++ b/HerPortal.ManagementShell/AdminAction.cs @@ -85,8 +85,7 @@ public void RemoveLas(User user, IReadOnlyCollection custodianCodes) .Contains(code)) .ToList(); if (missingCodes.Count > 0) - throw new CommandException( - $"Could not find LAs attached to {user.EmailAddress} for the following codes: {string.Join(", ", missingCodes)}. Please check your inputs and try again."); + throw new CouldNotFindAuthorityException("Custodian Codes are not associated with this user.", missingCodes); dbOperation.RemoveLasFromUser(user, lasToRemove); } @@ -114,8 +113,7 @@ public void RemoveConsortia(User user, IReadOnlyCollection consortiumCod .Contains(code) ).ToList(); if (missingCodes.Count > 0) - throw new CommandException( - $"Could not find Consortia attached to {user.EmailAddress} for the following codes: {string.Join(", ", missingCodes)}. Please check your inputs and try again."); + throw new CouldNotFindAuthorityException("Consortium Codes are not associated with this user.", missingCodes); dbOperation.RemoveConsortiaFromUser(user, consortiaToRemove); } diff --git a/HerPortal.ManagementShell/CommandException.cs b/HerPortal.ManagementShell/CommandException.cs deleted file mode 100644 index 4a32b60..0000000 --- a/HerPortal.ManagementShell/CommandException.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace HerPortal.ManagementShell; - -public class CommandException : Exception -{ - public CommandException(string message) : base(message) - { - } -} \ No newline at end of file diff --git a/HerPortal.ManagementShell/CommandHandler.cs b/HerPortal.ManagementShell/CommandHandler.cs index babea8d..ee8e530 100644 --- a/HerPortal.ManagementShell/CommandHandler.cs +++ b/HerPortal.ManagementShell/CommandHandler.cs @@ -85,9 +85,9 @@ public void TryRemoveLas(User? user, IReadOnlyCollection custodianCodes) { adminAction.RemoveLas(user, custodianCodes); } - catch (CommandException commandException) + catch (CouldNotFindAuthorityException couldNotFindAuthorityException) { - outputProvider.Output($"Could not remove LAs from user: {commandException.Message}"); + OutputCouldNotFindAuthorityException($"Could not remove Custodian Codes from {user.EmailAddress}.", couldNotFindAuthorityException); } } @@ -130,12 +130,21 @@ public void TryRemoveConsortia(User? user, IReadOnlyCollection consortiu { adminAction.RemoveConsortia(user, consortiumCodes); } - catch (CommandException commandException) + catch (CouldNotFindAuthorityException couldNotFindAuthorityException) { - outputProvider.Output($"Could not remove Consortia from user: {commandException.Message}"); + OutputCouldNotFindAuthorityException($"Could not remove Consortium Codes from {user.EmailAddress}.", couldNotFindAuthorityException); } } + private void OutputCouldNotFindAuthorityException(string wrapperMessage, CouldNotFindAuthorityException couldNotFindAuthorityException) + { + outputProvider.Output("!!! Error occured during operation !!!"); + outputProvider.Output($"{wrapperMessage}"); + outputProvider.Output($"Invalid Codes: {string.Join(", ", couldNotFindAuthorityException.InvalidCodes)}"); + outputProvider.Output($"{couldNotFindAuthorityException.Message}"); + outputProvider.Output("No data has been changed."); + } + private void PrintCodes(IReadOnlyCollection codes, Func codeToName) { if (codes.Count < 1) outputProvider.Output("(None)"); @@ -259,9 +268,9 @@ private void TryCreateUser(string userEmailAddress, IReadOnlyCollection? { adminAction.CreateUser(userEmailAddress, custodianCodes, consortiumCodes); } - catch (CommandException commandException) + catch (CouldNotFindAuthorityException couldNotFindAuthorityException) { - outputProvider.Output($"Could not create user: {commandException.Message}"); + OutputCouldNotFindAuthorityException($"Could not create user {userEmailAddress}.", couldNotFindAuthorityException); } } @@ -283,9 +292,9 @@ private void TryAddLas(User? user, IReadOnlyCollection custodianCodes) { adminAction.AddLas(user, custodianCodes); } - catch (CommandException commandException) + catch (CouldNotFindAuthorityException couldNotFindAuthorityException) { - outputProvider.Output($"Could not add LAs to user: {commandException.Message}"); + OutputCouldNotFindAuthorityException($"Could not add Custodian Codes to {user.EmailAddress}.", couldNotFindAuthorityException); } } @@ -307,9 +316,9 @@ private void TryAddConsortia(User? user, IReadOnlyCollection consortiumC { adminAction.AddConsortia(user, consortiumCodes); } - catch (CommandException commandException) + catch (CouldNotFindAuthorityException couldNotFindAuthorityException) { - outputProvider.Output($"Could not add Consortia to user: {commandException.Message}"); + OutputCouldNotFindAuthorityException($"Could not add Consortium Codes to {user.EmailAddress}.", couldNotFindAuthorityException); } } diff --git a/HerPortal.ManagementShell/CouldNotFindAuthorityException.cs b/HerPortal.ManagementShell/CouldNotFindAuthorityException.cs new file mode 100644 index 0000000..245efd0 --- /dev/null +++ b/HerPortal.ManagementShell/CouldNotFindAuthorityException.cs @@ -0,0 +1,11 @@ +namespace HerPortal.ManagementShell; + +public class CouldNotFindAuthorityException : Exception +{ + public readonly List InvalidCodes; + + public CouldNotFindAuthorityException(string message, List invalidCodes) : base(message) + { + InvalidCodes = invalidCodes; + } +} \ No newline at end of file diff --git a/HerPortal.ManagementShell/DatabaseOperation.cs b/HerPortal.ManagementShell/DatabaseOperation.cs index 51cde6d..ad93df3 100644 --- a/HerPortal.ManagementShell/DatabaseOperation.cs +++ b/HerPortal.ManagementShell/DatabaseOperation.cs @@ -31,7 +31,7 @@ public List GetLas(IReadOnlyCollection custodianCodes) custodianCodes.SingleOrDefault(code => !dbContext.LocalAuthorities.Any(la => la.CustodianCode == code), null); if (missingCustodianCode != null) - throw new CommandException($"Custodian Code {missingCustodianCode} not found."); + throw new CouldNotFindAuthorityException("Could not find Custodian Code in database.", new List {missingCustodianCode}); return custodianCodes .Select(code => dbContext.LocalAuthorities @@ -44,7 +44,7 @@ public List GetConsortia(IReadOnlyCollection consortiumCodes var missingConsortiumCode = consortiumCodes.SingleOrDefault(code => !dbContext.Consortia.Any(la => la.ConsortiumCode == code), null); if (missingConsortiumCode != null) - throw new CommandException($"Consortium Code {missingConsortiumCode} not found."); + throw new CouldNotFindAuthorityException("Could not find Consortium Code in database.", new List {missingConsortiumCode}); return consortiumCodes .Select(code => dbContext.Consortia From e7172d7b8414a93bd2a3640f2fdf20137affcce0 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Mon, 3 Jun 2024 13:41:59 +0100 Subject: [PATCH 44/81] PC-1085: output string directly where possible --- HerPortal.ManagementShell/CommandHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/HerPortal.ManagementShell/CommandHandler.cs b/HerPortal.ManagementShell/CommandHandler.cs index ee8e530..b1bfb38 100644 --- a/HerPortal.ManagementShell/CommandHandler.cs +++ b/HerPortal.ManagementShell/CommandHandler.cs @@ -139,9 +139,9 @@ public void TryRemoveConsortia(User? user, IReadOnlyCollection consortiu private void OutputCouldNotFindAuthorityException(string wrapperMessage, CouldNotFindAuthorityException couldNotFindAuthorityException) { outputProvider.Output("!!! Error occured during operation !!!"); - outputProvider.Output($"{wrapperMessage}"); + outputProvider.Output(wrapperMessage); outputProvider.Output($"Invalid Codes: {string.Join(", ", couldNotFindAuthorityException.InvalidCodes)}"); - outputProvider.Output($"{couldNotFindAuthorityException.Message}"); + outputProvider.Output(couldNotFindAuthorityException.Message); outputProvider.Output("No data has been changed."); } From 002422f155b150c968b33e811b124e6d8f251faa Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Wed, 5 Jun 2024 11:40:39 +0100 Subject: [PATCH 45/81] PC-1047: fix test issues duplicate WithConsortia method added by both of us error test output not updated --- HerPortal.UnitTests/Builders/UserBuilder.cs | 6 ------ .../ManagementShell/CommandHandlerTests.cs | 10 ++++------ 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/HerPortal.UnitTests/Builders/UserBuilder.cs b/HerPortal.UnitTests/Builders/UserBuilder.cs index e976811..bce629c 100644 --- a/HerPortal.UnitTests/Builders/UserBuilder.cs +++ b/HerPortal.UnitTests/Builders/UserBuilder.cs @@ -36,12 +36,6 @@ public UserBuilder WithConsortia(List consortia) return this; } - public UserBuilder WithConsortia(List consortia) - { - user.Consortia = consortia; - return this; - } - public UserBuilder WithHasLoggedIn(bool hasLoggedIn) { user.HasLoggedIn = hasLoggedIn; diff --git a/HerPortal.UnitTests/ManagementShell/CommandHandlerTests.cs b/HerPortal.UnitTests/ManagementShell/CommandHandlerTests.cs index 45b0d92..ea30698 100644 --- a/HerPortal.UnitTests/ManagementShell/CommandHandlerTests.cs +++ b/HerPortal.UnitTests/ManagementShell/CommandHandlerTests.cs @@ -274,9 +274,8 @@ public void RemoveLas_DisplaysErrorMessage_IfCustodianCodeToRemove_DoesNotMatchA underTest.TryRemoveLas(user, custodianCodes); // Assert - mockOutputProvider.Verify(mock => - mock.Output( - "Could not remove LAs from user: Could not find LAs attached to existinguser@email.com for the following codes: 9052. Please check your inputs and try again.")); + mockOutputProvider.Verify(mock => mock.Output("Invalid Codes: 9052")); + mockOutputProvider.Verify(mock => mock.Output("Custodian Codes are not associated with this user.")); } [Test] @@ -810,9 +809,8 @@ public void RemoveConsortia_DisplaysErrorMessage_IfCustodianCodeToRemove_DoesNot underTest.TryRemoveConsortia(user, consortiumCodes); // Assert - mockOutputProvider.Verify(mock => - mock.Output( - "Could not remove Consortia from user: Could not find Consortia attached to existinguser@email.com for the following codes: C_0002. Please check your inputs and try again.")); + mockOutputProvider.Verify(mock => mock.Output("Invalid Codes: C_0002")); + mockOutputProvider.Verify(mock => mock.Output("Consortium Codes are not associated with this user.")); } [Test] From 9da504bbd47a3a80a6b38c182c9b5f9e1caf6157 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Wed, 5 Jun 2024 11:47:31 +0100 Subject: [PATCH 46/81] PC-1086: add skeleton for migration methods --- HerPortal.ManagementShell/AdminAction.cs | 5 +++++ HerPortal.ManagementShell/CommandHandler.cs | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/HerPortal.ManagementShell/AdminAction.cs b/HerPortal.ManagementShell/AdminAction.cs index 2caec27..b5aa101 100644 --- a/HerPortal.ManagementShell/AdminAction.cs +++ b/HerPortal.ManagementShell/AdminAction.cs @@ -117,4 +117,9 @@ public void RemoveConsortia(User user, IReadOnlyCollection consortiumCod dbOperation.RemoveConsortiaFromUser(user, consortiaToRemove); } + + public void MigrateAdmins() + { + // TODO: implement + } } \ No newline at end of file diff --git a/HerPortal.ManagementShell/CommandHandler.cs b/HerPortal.ManagementShell/CommandHandler.cs index b1bfb38..a960838 100644 --- a/HerPortal.ManagementShell/CommandHandler.cs +++ b/HerPortal.ManagementShell/CommandHandler.cs @@ -136,6 +136,11 @@ public void TryRemoveConsortia(User? user, IReadOnlyCollection consortiu } } + public void MigrateAdmins() + { + adminAction.MigrateAdmins(); + } + private void OutputCouldNotFindAuthorityException(string wrapperMessage, CouldNotFindAuthorityException couldNotFindAuthorityException) { outputProvider.Output("!!! Error occured during operation !!!"); From 8a805ab59f6cbd2082fcb96fdfcd6c4955ff6b2a Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Wed, 5 Jun 2024 12:04:45 +0100 Subject: [PATCH 47/81] PC-1047: auto format --- HerPortal.UnitTests/Builders/UserBuilder.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/HerPortal.UnitTests/Builders/UserBuilder.cs b/HerPortal.UnitTests/Builders/UserBuilder.cs index bce629c..ca2f6cc 100644 --- a/HerPortal.UnitTests/Builders/UserBuilder.cs +++ b/HerPortal.UnitTests/Builders/UserBuilder.cs @@ -5,7 +5,7 @@ namespace Tests.Builders; public class UserBuilder { - private User user; + private readonly User user; public UserBuilder(string emailAddress) { @@ -29,7 +29,7 @@ public UserBuilder WithLocalAuthorities(List localAuthorities) user.LocalAuthorities = localAuthorities; return this; } - + public UserBuilder WithConsortia(List consortia) { user.Consortia = consortia; @@ -47,5 +47,4 @@ public UserBuilder WithEmail(string email) user.EmailAddress = email; return this; } -} - +} \ No newline at end of file From 07c71304bf53494f10dd302510e25fac9b2b3d80 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Wed, 5 Jun 2024 14:21:20 +0100 Subject: [PATCH 48/81] PC-1086: add tests for new command function --- .../MigrateAdminsCommandTests.cs | 184 ++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 HerPortal.UnitTests/ManagementShell/MigrateAdminsCommandTests.cs diff --git a/HerPortal.UnitTests/ManagementShell/MigrateAdminsCommandTests.cs b/HerPortal.UnitTests/ManagementShell/MigrateAdminsCommandTests.cs new file mode 100644 index 0000000..0360c37 --- /dev/null +++ b/HerPortal.UnitTests/ManagementShell/MigrateAdminsCommandTests.cs @@ -0,0 +1,184 @@ +using System.Collections.Generic; +using System.Linq; +using HerPortal.BusinessLogic.Models; +using HerPortal.ManagementShell; +using Moq; +using NUnit.Framework; +using Tests.Builders; + +namespace Tests.ManagementShell; + +public class MigrateAdminsCommandTests +{ + private Mock mockDatabaseOperation; + private Mock mockOutputProvider; + private CommandHandler underTest; + + [SetUp] + public void Setup() + { + mockOutputProvider = new Mock(); + mockDatabaseOperation = new Mock(); + var adminAction = new AdminAction(mockDatabaseOperation.Object); + underTest = new CommandHandler(adminAction, mockOutputProvider.Object); + } + + // Cheshire East C_0008 contains only two LAs: + // - Cheshire East 660 + // - Cheshire West and Chester 665 + + [Test] + public void MigrateAdmins_IfOwnsAllLas_RemovesLasAndAddConsortia() + { + // Arrange + var (user, localAuthorities, expectedConsortia) = SetupUser( + new List { "660", "665" }, + new List(), + new List { "C_0008" }); + + // Act + underTest.MigrateAdmins(); + + // Assert + mockDatabaseOperation.Verify( + db => db.AddConsortiaAndRemoveLasFromUser(user, expectedConsortia, localAuthorities)); + } + + [Test] + public void MigrateAdmins_IfOwnsNotEnoughLas_DoesNothing() + { + // Arrange + var (user, _, _) = SetupUser( + new List { "660" }, + new List(), + new List { "C_0008" }); + + // Act + underTest.MigrateAdmins(); + + // Assert + mockDatabaseOperation.Verify( + db => db.AddConsortiaAndRemoveLasFromUser( + user, It.IsAny>(), It.IsAny>()), + Times.Never); + + mockDatabaseOperation.VerifyNoOtherCalls(); + } + + [Test] + public void MigrateAdmins_IfOwnsAllLasInTwoConsortia_AddsBoth() + { + // Arrange + var (user, localAuthorities, expectedConsortia) = SetupUser( + new List { "660", "665", "835", "840" }, + new List(), + new List { "C_0008", "C_0010" }); + + // Act + underTest.MigrateAdmins(); + + // Assert + mockDatabaseOperation.Verify( + db => db.AddConsortiaAndRemoveLasFromUser(user, expectedConsortia, localAuthorities)); + + mockDatabaseOperation.VerifyNoOtherCalls(); + } + + [Test] + public void MigrateAdmins_IfOwnsAllInAConsortiaButNotEnoughInAnother_AddsOneConsortia() + { + // Arrange + var (user, localAuthorities, expectedConsortia) = SetupUser( + new List { "660", "665", "835" }, + new List(), + new List { "C_0008" }); + + // Act + underTest.MigrateAdmins(); + + // Assert + mockDatabaseOperation.Verify( + db => db.AddConsortiaAndRemoveLasFromUser(user, expectedConsortia, localAuthorities)); + + mockDatabaseOperation.VerifyNoOtherCalls(); + } + + [Test] + public void MigrateAdmins_IfOwnsConsortia_DoesNothing() + { + // Arrange + var (user, _, _) = SetupUser( + new List(), + new List { "C_0008" }, + new List { "C_0008" }); + + // Act + underTest.MigrateAdmins(); + + // Assert + mockDatabaseOperation.Verify( + db => db.AddConsortiaAndRemoveLasFromUser( + user, It.IsAny>(), It.IsAny>()), + Times.Never); + + mockDatabaseOperation.VerifyNoOtherCalls(); + } + + [Test] + public void MigrateAdmins_IfOwnsAllLasAndAlreadyOwnsConsortia_RemovesLasButLeavesConsortia() + { + // Arrange + var (user, localAuthorities, _) = SetupUser( + new List { "835", "840" }, + new List { "C_0008" }, + new List { "C_0008" }); + + // Act + underTest.MigrateAdmins(); + + // Assert + mockDatabaseOperation.Verify( + db => db.RemoveLasFromUser(user, localAuthorities)); + + mockDatabaseOperation.VerifyNoOtherCalls(); + } + + private (User, List, List) SetupUser(IEnumerable custodianCodes, + IReadOnlyCollection consortiumCodes, IReadOnlyCollection expectedConsortiumCodes) + { + var localAuthorities = custodianCodes + .Select((custodianCode, i) => new LocalAuthority + { + Id = i, + CustodianCode = custodianCode + }) + .ToList(); + var consortia = consortiumCodes + .Select((consortiumCode, i) => new Consortium + { + Id = i, + ConsortiumCode = consortiumCode + }) + .ToList(); + var expectedConsortia = expectedConsortiumCodes + .Select((consortiumCode, i) => new Consortium + { + Id = i + consortiumCodes.Count, + ConsortiumCode = consortiumCode + }) + .ToList(); + + var user = new UserBuilder("user@example.com") + .WithLocalAuthorities(localAuthorities) + .WithConsortia(consortia) + .Build(); + mockDatabaseOperation + .Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()) + .Returns(new List { user }); + mockDatabaseOperation + .Setup(db => db.GetConsortia(expectedConsortiumCodes)) + .Returns(expectedConsortia); + + return (user, localAuthorities, expectedConsortia); + } +} \ No newline at end of file From 77c7063b011eb7c8b872fcde1864e10cc535bd7b Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Wed, 5 Jun 2024 15:48:26 +0100 Subject: [PATCH 49/81] PC-1047: make job run on any pull request --- .github/workflows/build-and-run-tests.yml | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/.github/workflows/build-and-run-tests.yml b/.github/workflows/build-and-run-tests.yml index a0390a3..19fa05f 100644 --- a/.github/workflows/build-and-run-tests.yml +++ b/.github/workflows/build-and-run-tests.yml @@ -1,16 +1,7 @@ name: Run automated tests on: - pull_request: - branches: - - develop - - staging - - main - push: - branches: - - develop - - staging - - main +- pull_request jobs: build-and-run-tests: From c44130f7304e01eb16b840b273cdd6ba094fcfa2 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Wed, 5 Jun 2024 15:49:27 +0100 Subject: [PATCH 50/81] PC-1047: run on push too --- .github/workflows/build-and-run-tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-and-run-tests.yml b/.github/workflows/build-and-run-tests.yml index 19fa05f..afc3d5e 100644 --- a/.github/workflows/build-and-run-tests.yml +++ b/.github/workflows/build-and-run-tests.yml @@ -2,6 +2,7 @@ name: Run automated tests on: - pull_request +- push jobs: build-and-run-tests: From da1daea9e25199968676d00341279225fdc8ff51 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Wed, 5 Jun 2024 16:56:12 +0100 Subject: [PATCH 51/81] PC-1086: add function to migrating admins changes AdminAction & CommandHandler to manage this finishes MigrateAdminsCommand to verify all calls and allows for setup to only remove some LAs --- HerPortal.ManagementShell/AdminAction.cs | 44 ++++++++++--- HerPortal.ManagementShell/CommandHandler.cs | 27 +++++++- .../MigrateAdminsCommandTests.cs | 62 ++++++++++--------- 3 files changed, 95 insertions(+), 38 deletions(-) diff --git a/HerPortal.ManagementShell/AdminAction.cs b/HerPortal.ManagementShell/AdminAction.cs index b5aa101..b86e58d 100644 --- a/HerPortal.ManagementShell/AdminAction.cs +++ b/HerPortal.ManagementShell/AdminAction.cs @@ -2,7 +2,7 @@ namespace HerPortal.ManagementShell; -public partial class AdminAction +public class AdminAction { private readonly Dictionary> consortiumCodeToCustodianCodesDict = ConsortiumData.ConsortiumCustodianCodesIdsByConsortiumCode; @@ -79,13 +79,14 @@ public void AddLas(User user, IEnumerable custodianCodes) public void RemoveLas(User user, IReadOnlyCollection custodianCodes) { var lasToRemove = user.LocalAuthorities.Where(la => custodianCodes.Contains(la.CustodianCode)).ToList(); - var missingCodes = custodianCodes.Where(code => - !lasToRemove - .Select(la => la.CustodianCode) - .Contains(code)) + var missingCodes = custodianCodes.Where(code => + !lasToRemove + .Select(la => la.CustodianCode) + .Contains(code)) .ToList(); if (missingCodes.Count > 0) - throw new CouldNotFindAuthorityException("Custodian Codes are not associated with this user.", missingCodes); + throw new CouldNotFindAuthorityException("Custodian Codes are not associated with this user.", + missingCodes); dbOperation.RemoveLasFromUser(user, lasToRemove); } @@ -113,13 +114,38 @@ public void RemoveConsortia(User user, IReadOnlyCollection consortiumCod .Contains(code) ).ToList(); if (missingCodes.Count > 0) - throw new CouldNotFindAuthorityException("Consortium Codes are not associated with this user.", missingCodes); + throw new CouldNotFindAuthorityException("Consortium Codes are not associated with this user.", + missingCodes); dbOperation.RemoveConsortiaFromUser(user, consortiaToRemove); } - public void MigrateAdmins() + public List GetUsers() { - // TODO: implement + return dbOperation.GetUsersWithLocalAuthoritiesAndConsortia(); + } + + public void MigrateAdmin(User user) + { + var consortiumCodesToAdd = GetConsortiumCodesUserShouldOwn(user).ToList(); + var custodianCodesToRemove = GetOwnedCustodianCodesInConsortia(user, consortiumCodesToAdd); + + var consortiaToAdd = dbOperation.GetConsortia(consortiumCodesToAdd); + var lasToRemove = dbOperation.GetLas(custodianCodesToRemove); + + dbOperation.AddConsortiaAndRemoveLasFromUser(user, consortiaToAdd, lasToRemove); + } + + public IEnumerable GetConsortiumCodesUserShouldOwn(User user) + { + var userCustodianCodes = user + .LocalAuthorities + .Select(la => la.CustodianCode) + .ToList(); + + return ConsortiumData.ConsortiumCustodianCodesIdsByConsortiumCode + .Where(kvp => + kvp.Value.All(custodianCode => userCustodianCodes.Contains(custodianCode))) + .Select(kvp => kvp.Key); } } \ No newline at end of file diff --git a/HerPortal.ManagementShell/CommandHandler.cs b/HerPortal.ManagementShell/CommandHandler.cs index a960838..463018d 100644 --- a/HerPortal.ManagementShell/CommandHandler.cs +++ b/HerPortal.ManagementShell/CommandHandler.cs @@ -138,7 +138,32 @@ public void TryRemoveConsortia(User? user, IReadOnlyCollection consortiu public void MigrateAdmins() { - adminAction.MigrateAdmins(); + outputProvider.Output("!!! User Migration Script !!!"); + outputProvider.Output("This script will ensure the validity of the LA / Consortium relationship for users."); + outputProvider.Output("If a user owns all LAs in a Consortium, they will be made a Consortium Admin."); + outputProvider.Output("If a user owns an LA in an owned Consortium, they will be removed."); + + var users = adminAction.GetUsers(); + + foreach (var user in users) + { + outputProvider.Output($"Processing user {user.EmailAddress}..."); + var consortiumCodesUserShouldOwn = adminAction.GetConsortiumCodesUserShouldOwn(user).ToList(); + + if (consortiumCodesUserShouldOwn.Count == 0) continue; + + outputProvider.Output("This user should own the following Consortia:"); + PrintCodes(consortiumCodesUserShouldOwn, consortiumCode => consortiumCodeToConsortiumNameDict[consortiumCode]); + + var custodianCodesToRemove = + adminAction.GetOwnedCustodianCodesInConsortia(user, consortiumCodesUserShouldOwn); + outputProvider.Output("To make this user a Consortium Admin, the following LAs will be removed:"); + PrintCodes(custodianCodesToRemove, custodianCode => custodianCodeToLaNameDict[custodianCode]); + + var confirmation = outputProvider.Confirm("Okay to proceed? (Y/N)"); + + if (confirmation) adminAction.MigrateAdmin(user); + } } private void OutputCouldNotFindAuthorityException(string wrapperMessage, CouldNotFindAuthorityException couldNotFindAuthorityException) diff --git a/HerPortal.UnitTests/ManagementShell/MigrateAdminsCommandTests.cs b/HerPortal.UnitTests/ManagementShell/MigrateAdminsCommandTests.cs index 0360c37..8f291b4 100644 --- a/HerPortal.UnitTests/ManagementShell/MigrateAdminsCommandTests.cs +++ b/HerPortal.UnitTests/ManagementShell/MigrateAdminsCommandTests.cs @@ -31,17 +31,23 @@ public void Setup() public void MigrateAdmins_IfOwnsAllLas_RemovesLasAndAddConsortia() { // Arrange - var (user, localAuthorities, expectedConsortia) = SetupUser( + var (user, expectedLasToRemove, expectedConsortia) = SetupUser( new List { "660", "665" }, new List(), + new List { "660", "665" }, new List { "C_0008" }); // Act underTest.MigrateAdmins(); // Assert + mockDatabaseOperation.Verify(db => db.GetUsersWithLocalAuthoritiesAndConsortia()); + mockDatabaseOperation.Verify(db => db.GetLas(new List { "660", "665" })); + mockDatabaseOperation.Verify(db => db.GetConsortia(new List { "C_0008" })); mockDatabaseOperation.Verify( - db => db.AddConsortiaAndRemoveLasFromUser(user, expectedConsortia, localAuthorities)); + db => db.AddConsortiaAndRemoveLasFromUser(user, expectedConsortia, expectedLasToRemove)); + + mockDatabaseOperation.VerifyNoOtherCalls(); } [Test] @@ -51,12 +57,14 @@ public void MigrateAdmins_IfOwnsNotEnoughLas_DoesNothing() var (user, _, _) = SetupUser( new List { "660" }, new List(), - new List { "C_0008" }); + new List(), + new List()); // Act underTest.MigrateAdmins(); // Assert + mockDatabaseOperation.Verify(db => db.GetUsersWithLocalAuthoritiesAndConsortia()); mockDatabaseOperation.Verify( db => db.AddConsortiaAndRemoveLasFromUser( user, It.IsAny>(), It.IsAny>()), @@ -69,17 +77,21 @@ public void MigrateAdmins_IfOwnsNotEnoughLas_DoesNothing() public void MigrateAdmins_IfOwnsAllLasInTwoConsortia_AddsBoth() { // Arrange - var (user, localAuthorities, expectedConsortia) = SetupUser( + var (user, expectedLasToRemove, expectedConsortia) = SetupUser( new List { "660", "665", "835", "840" }, new List(), + new List { "660", "665", "835", "840" }, new List { "C_0008", "C_0010" }); // Act underTest.MigrateAdmins(); // Assert + mockDatabaseOperation.Verify(db => db.GetUsersWithLocalAuthoritiesAndConsortia()); + mockDatabaseOperation.Verify(db => db.GetLas(new List { "660", "665", "835", "840" })); + mockDatabaseOperation.Verify(db => db.GetConsortia(new List { "C_0008", "C_0010" })); mockDatabaseOperation.Verify( - db => db.AddConsortiaAndRemoveLasFromUser(user, expectedConsortia, localAuthorities)); + db => db.AddConsortiaAndRemoveLasFromUser(user, expectedConsortia, expectedLasToRemove)); mockDatabaseOperation.VerifyNoOtherCalls(); } @@ -88,17 +100,21 @@ public void MigrateAdmins_IfOwnsAllLasInTwoConsortia_AddsBoth() public void MigrateAdmins_IfOwnsAllInAConsortiaButNotEnoughInAnother_AddsOneConsortia() { // Arrange - var (user, localAuthorities, expectedConsortia) = SetupUser( + var (user, expectedLasToRemove, expectedConsortia) = SetupUser( new List { "660", "665", "835" }, new List(), + new List { "660", "665" }, new List { "C_0008" }); // Act underTest.MigrateAdmins(); // Assert + mockDatabaseOperation.Verify(db => db.GetUsersWithLocalAuthoritiesAndConsortia()); + mockDatabaseOperation.Verify(db => db.GetLas(new List { "660", "665" })); + mockDatabaseOperation.Verify(db => db.GetConsortia(new List { "C_0008" })); mockDatabaseOperation.Verify( - db => db.AddConsortiaAndRemoveLasFromUser(user, expectedConsortia, localAuthorities)); + db => db.AddConsortiaAndRemoveLasFromUser(user, expectedConsortia, expectedLasToRemove)); mockDatabaseOperation.VerifyNoOtherCalls(); } @@ -110,12 +126,14 @@ public void MigrateAdmins_IfOwnsConsortia_DoesNothing() var (user, _, _) = SetupUser( new List(), new List { "C_0008" }, + new List(), new List { "C_0008" }); // Act underTest.MigrateAdmins(); // Assert + mockDatabaseOperation.Verify(db => db.GetUsersWithLocalAuthoritiesAndConsortia()); mockDatabaseOperation.Verify( db => db.AddConsortiaAndRemoveLasFromUser( user, It.IsAny>(), It.IsAny>()), @@ -124,27 +142,8 @@ public void MigrateAdmins_IfOwnsConsortia_DoesNothing() mockDatabaseOperation.VerifyNoOtherCalls(); } - [Test] - public void MigrateAdmins_IfOwnsAllLasAndAlreadyOwnsConsortia_RemovesLasButLeavesConsortia() - { - // Arrange - var (user, localAuthorities, _) = SetupUser( - new List { "835", "840" }, - new List { "C_0008" }, - new List { "C_0008" }); - - // Act - underTest.MigrateAdmins(); - - // Assert - mockDatabaseOperation.Verify( - db => db.RemoveLasFromUser(user, localAuthorities)); - - mockDatabaseOperation.VerifyNoOtherCalls(); - } - private (User, List, List) SetupUser(IEnumerable custodianCodes, - IReadOnlyCollection consortiumCodes, IReadOnlyCollection expectedConsortiumCodes) + IReadOnlyCollection consortiumCodes, IReadOnlyCollection expectedCustodianCodesToRemove, IReadOnlyCollection expectedConsortiumCodes) { var localAuthorities = custodianCodes .Select((custodianCode, i) => new LocalAuthority @@ -167,6 +166,10 @@ public void MigrateAdmins_IfOwnsAllLasAndAlreadyOwnsConsortia_RemovesLasButLeave ConsortiumCode = consortiumCode }) .ToList(); + var expectedLasToRemove = expectedCustodianCodesToRemove.Select(custodianCode => + localAuthorities.Single(la => la.CustodianCode == custodianCode)).ToList(); + + mockOutputProvider.Setup(op => op.Confirm("Okay to proceed? (Y/N)")).Returns(true); var user = new UserBuilder("user@example.com") .WithLocalAuthorities(localAuthorities) @@ -178,7 +181,10 @@ public void MigrateAdmins_IfOwnsAllLasAndAlreadyOwnsConsortia_RemovesLasButLeave mockDatabaseOperation .Setup(db => db.GetConsortia(expectedConsortiumCodes)) .Returns(expectedConsortia); + mockDatabaseOperation + .Setup(db => db.GetLas(expectedCustodianCodesToRemove)) + .Returns(expectedLasToRemove); - return (user, localAuthorities, expectedConsortia); + return (user, expectedLasToRemove, expectedConsortia); } } \ No newline at end of file From 5e06d53c4ceeb657a2d43ead5cd15fd397aa637a Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Wed, 5 Jun 2024 17:08:13 +0100 Subject: [PATCH 52/81] PC-1086: add entrypoint to migration system --- HerPortal.ManagementShell/Program.cs | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/HerPortal.ManagementShell/Program.cs b/HerPortal.ManagementShell/Program.cs index b3a9735..c0b3f94 100644 --- a/HerPortal.ManagementShell/Program.cs +++ b/HerPortal.ManagementShell/Program.cs @@ -28,8 +28,18 @@ public static void Main(string[] args) try { command = Enum.Parse(args[0], true); - userEmailAddress = args[1]; - codes = args.Skip(2).ToArray(); + if (new List + { + Subcommand.AddLas, + Subcommand.AddConsortia, + Subcommand.RemoveLas, + Subcommand.RemoveConsortia, + Subcommand.RemoveUser + }.Contains(command)) + { + userEmailAddress = args[1]; + codes = args.Skip(2).ToArray(); + } } catch (Exception) { @@ -56,6 +66,9 @@ public static void Main(string[] args) case Subcommand.RemoveConsortia: commandHandler.TryRemoveConsortia(commandHandler.GetUser(userEmailAddress), codes); break; + case Subcommand.MigrateUsers: + commandHandler.MigrateAdmins(); + break; default: outputProvider.Output("Invalid terminal command entered. Please refer to the documentation"); return; @@ -68,6 +81,7 @@ private enum Subcommand RemoveLas, RemoveUser, AddConsortia, - RemoveConsortia + RemoveConsortia, + MigrateUsers } } \ No newline at end of file From 94aaa4e5197f4246e1c19d87a3f32dbec98b3cbd Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Wed, 5 Jun 2024 17:12:34 +0100 Subject: [PATCH 53/81] PC-1047: add back filter for push runs --- .github/workflows/build-and-run-tests.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-and-run-tests.yml b/.github/workflows/build-and-run-tests.yml index afc3d5e..461b886 100644 --- a/.github/workflows/build-and-run-tests.yml +++ b/.github/workflows/build-and-run-tests.yml @@ -2,7 +2,11 @@ name: Run automated tests on: - pull_request -- push + push: + branches: + - develop + - staging + - main jobs: build-and-run-tests: From 5e0ec7645053ed881e173c2344050e7d288049f8 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Wed, 5 Jun 2024 17:21:56 +0100 Subject: [PATCH 54/81] PC-1047: remove unneeded variables to fix warnings --- HerPortal.UnitTests/ManagementShell/CommandHandlerTests.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/HerPortal.UnitTests/ManagementShell/CommandHandlerTests.cs b/HerPortal.UnitTests/ManagementShell/CommandHandlerTests.cs index ea30698..16a5574 100644 --- a/HerPortal.UnitTests/ManagementShell/CommandHandlerTests.cs +++ b/HerPortal.UnitTests/ManagementShell/CommandHandlerTests.cs @@ -302,7 +302,6 @@ public void CreateOrUpdateUserWithLas_DisplaysInnerExceptionMessage_IfCustodianC public void RemoveLas_DisplaysErrorMessage_WhenRemovingLas_IfUserNotInDatabase() { // Arrange - const string userEmailAddress = "usernotindb@email.com"; var users = new List { new UserBuilder("userindb@email.com").Build() @@ -817,7 +816,6 @@ public void RemoveConsortia_DisplaysErrorMessage_IfCustodianCodeToRemove_DoesNot public void RemoveConsortia_DisplaysErrorMessage_WhenRemovingLas_IfUserNotInDatabase() { // Arrange - const string userEmailAddress = "usernotindb@email.com"; var users = new List { new UserBuilder("userindb@email.com").Build() From 6e725bc692bb360be08751ca4fbf3a700a80d970 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Wed, 5 Jun 2024 17:25:19 +0100 Subject: [PATCH 55/81] PC-1047: specify branches in both cases --- .github/workflows/build-and-run-tests.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-and-run-tests.yml b/.github/workflows/build-and-run-tests.yml index 461b886..6844782 100644 --- a/.github/workflows/build-and-run-tests.yml +++ b/.github/workflows/build-and-run-tests.yml @@ -1,7 +1,9 @@ name: Run automated tests on: -- pull_request + pull_request: + branches: + - ** push: branches: - develop From 3eb2e52555dd5c8baca2782b889a6be8a263ee79 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Wed, 5 Jun 2024 17:27:03 +0100 Subject: [PATCH 56/81] PC-1047: quote wildcard --- .github/workflows/build-and-run-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-run-tests.yml b/.github/workflows/build-and-run-tests.yml index 6844782..a2a76ac 100644 --- a/.github/workflows/build-and-run-tests.yml +++ b/.github/workflows/build-and-run-tests.yml @@ -3,7 +3,7 @@ name: Run automated tests on: pull_request: branches: - - ** + - '**' push: branches: - develop From 5e681d2a2545078ad65993285c19c50a37d539df Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Wed, 5 Jun 2024 17:29:56 +0100 Subject: [PATCH 57/81] PC-1086: code format --- HerPortal.ManagementShell/AdminAction.cs | 2 +- HerPortal.ManagementShell/CommandHandler.cs | 27 ++++++++++++------- .../MigrateAdminsCommandTests.cs | 3 ++- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/HerPortal.ManagementShell/AdminAction.cs b/HerPortal.ManagementShell/AdminAction.cs index b86e58d..12cc271 100644 --- a/HerPortal.ManagementShell/AdminAction.cs +++ b/HerPortal.ManagementShell/AdminAction.cs @@ -132,7 +132,7 @@ public void MigrateAdmin(User user) var consortiaToAdd = dbOperation.GetConsortia(consortiumCodesToAdd); var lasToRemove = dbOperation.GetLas(custodianCodesToRemove); - + dbOperation.AddConsortiaAndRemoveLasFromUser(user, consortiaToAdd, lasToRemove); } diff --git a/HerPortal.ManagementShell/CommandHandler.cs b/HerPortal.ManagementShell/CommandHandler.cs index 463018d..30eba63 100644 --- a/HerPortal.ManagementShell/CommandHandler.cs +++ b/HerPortal.ManagementShell/CommandHandler.cs @@ -87,7 +87,8 @@ public void TryRemoveLas(User? user, IReadOnlyCollection custodianCodes) } catch (CouldNotFindAuthorityException couldNotFindAuthorityException) { - OutputCouldNotFindAuthorityException($"Could not remove Custodian Codes from {user.EmailAddress}.", couldNotFindAuthorityException); + OutputCouldNotFindAuthorityException($"Could not remove Custodian Codes from {user.EmailAddress}.", + couldNotFindAuthorityException); } } @@ -132,7 +133,8 @@ public void TryRemoveConsortia(User? user, IReadOnlyCollection consortiu } catch (CouldNotFindAuthorityException couldNotFindAuthorityException) { - OutputCouldNotFindAuthorityException($"Could not remove Consortium Codes from {user.EmailAddress}.", couldNotFindAuthorityException); + OutputCouldNotFindAuthorityException($"Could not remove Consortium Codes from {user.EmailAddress}.", + couldNotFindAuthorityException); } } @@ -144,16 +146,17 @@ public void MigrateAdmins() outputProvider.Output("If a user owns an LA in an owned Consortium, they will be removed."); var users = adminAction.GetUsers(); - + foreach (var user in users) { outputProvider.Output($"Processing user {user.EmailAddress}..."); var consortiumCodesUserShouldOwn = adminAction.GetConsortiumCodesUserShouldOwn(user).ToList(); if (consortiumCodesUserShouldOwn.Count == 0) continue; - + outputProvider.Output("This user should own the following Consortia:"); - PrintCodes(consortiumCodesUserShouldOwn, consortiumCode => consortiumCodeToConsortiumNameDict[consortiumCode]); + PrintCodes(consortiumCodesUserShouldOwn, + consortiumCode => consortiumCodeToConsortiumNameDict[consortiumCode]); var custodianCodesToRemove = adminAction.GetOwnedCustodianCodesInConsortia(user, consortiumCodesUserShouldOwn); @@ -161,12 +164,13 @@ public void MigrateAdmins() PrintCodes(custodianCodesToRemove, custodianCode => custodianCodeToLaNameDict[custodianCode]); var confirmation = outputProvider.Confirm("Okay to proceed? (Y/N)"); - + if (confirmation) adminAction.MigrateAdmin(user); } } - private void OutputCouldNotFindAuthorityException(string wrapperMessage, CouldNotFindAuthorityException couldNotFindAuthorityException) + private void OutputCouldNotFindAuthorityException(string wrapperMessage, + CouldNotFindAuthorityException couldNotFindAuthorityException) { outputProvider.Output("!!! Error occured during operation !!!"); outputProvider.Output(wrapperMessage); @@ -300,7 +304,8 @@ private void TryCreateUser(string userEmailAddress, IReadOnlyCollection? } catch (CouldNotFindAuthorityException couldNotFindAuthorityException) { - OutputCouldNotFindAuthorityException($"Could not create user {userEmailAddress}.", couldNotFindAuthorityException); + OutputCouldNotFindAuthorityException($"Could not create user {userEmailAddress}.", + couldNotFindAuthorityException); } } @@ -324,7 +329,8 @@ private void TryAddLas(User? user, IReadOnlyCollection custodianCodes) } catch (CouldNotFindAuthorityException couldNotFindAuthorityException) { - OutputCouldNotFindAuthorityException($"Could not add Custodian Codes to {user.EmailAddress}.", couldNotFindAuthorityException); + OutputCouldNotFindAuthorityException($"Could not add Custodian Codes to {user.EmailAddress}.", + couldNotFindAuthorityException); } } @@ -348,7 +354,8 @@ private void TryAddConsortia(User? user, IReadOnlyCollection consortiumC } catch (CouldNotFindAuthorityException couldNotFindAuthorityException) { - OutputCouldNotFindAuthorityException($"Could not add Consortium Codes to {user.EmailAddress}.", couldNotFindAuthorityException); + OutputCouldNotFindAuthorityException($"Could not add Consortium Codes to {user.EmailAddress}.", + couldNotFindAuthorityException); } } diff --git a/HerPortal.UnitTests/ManagementShell/MigrateAdminsCommandTests.cs b/HerPortal.UnitTests/ManagementShell/MigrateAdminsCommandTests.cs index 8f291b4..b5d0fb2 100644 --- a/HerPortal.UnitTests/ManagementShell/MigrateAdminsCommandTests.cs +++ b/HerPortal.UnitTests/ManagementShell/MigrateAdminsCommandTests.cs @@ -143,7 +143,8 @@ public void MigrateAdmins_IfOwnsConsortia_DoesNothing() } private (User, List, List) SetupUser(IEnumerable custodianCodes, - IReadOnlyCollection consortiumCodes, IReadOnlyCollection expectedCustodianCodesToRemove, IReadOnlyCollection expectedConsortiumCodes) + IReadOnlyCollection consortiumCodes, IReadOnlyCollection expectedCustodianCodesToRemove, + IReadOnlyCollection expectedConsortiumCodes) { var localAuthorities = custodianCodes .Select((custodianCode, i) => new LocalAuthority From db11e4d138475598ab66940762cd59b53bf78532 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Thu, 6 Jun 2024 09:36:49 +0100 Subject: [PATCH 58/81] PC-1086: use a struct to pass test setup info the parameters are getting a bit complex, makes the callsite a bit clearer --- .../MigrateAdminsCommandTests.cs | 91 +++++++++++-------- 1 file changed, 51 insertions(+), 40 deletions(-) diff --git a/HerPortal.UnitTests/ManagementShell/MigrateAdminsCommandTests.cs b/HerPortal.UnitTests/ManagementShell/MigrateAdminsCommandTests.cs index b5d0fb2..433fe3a 100644 --- a/HerPortal.UnitTests/ManagementShell/MigrateAdminsCommandTests.cs +++ b/HerPortal.UnitTests/ManagementShell/MigrateAdminsCommandTests.cs @@ -31,11 +31,12 @@ public void Setup() public void MigrateAdmins_IfOwnsAllLas_RemovesLasAndAddConsortia() { // Arrange - var (user, expectedLasToRemove, expectedConsortia) = SetupUser( - new List { "660", "665" }, - new List(), - new List { "660", "665" }, - new List { "C_0008" }); + var (user, expectedLasToRemove, expectedConsortiaToAdd) = SetupUser(new UserTestSetup + { + UserCustodianCodes = new List { "660", "665" }, + ExpectedCustodianCodesToRemove = new List { "660", "665" }, + ExpectedConsortiumCodesToAdd = new List { "C_0008" } + }); // Act underTest.MigrateAdmins(); @@ -45,7 +46,7 @@ public void MigrateAdmins_IfOwnsAllLas_RemovesLasAndAddConsortia() mockDatabaseOperation.Verify(db => db.GetLas(new List { "660", "665" })); mockDatabaseOperation.Verify(db => db.GetConsortia(new List { "C_0008" })); mockDatabaseOperation.Verify( - db => db.AddConsortiaAndRemoveLasFromUser(user, expectedConsortia, expectedLasToRemove)); + db => db.AddConsortiaAndRemoveLasFromUser(user, expectedConsortiaToAdd, expectedLasToRemove)); mockDatabaseOperation.VerifyNoOtherCalls(); } @@ -54,11 +55,10 @@ public void MigrateAdmins_IfOwnsAllLas_RemovesLasAndAddConsortia() public void MigrateAdmins_IfOwnsNotEnoughLas_DoesNothing() { // Arrange - var (user, _, _) = SetupUser( - new List { "660" }, - new List(), - new List(), - new List()); + var (user, _, _) = SetupUser(new UserTestSetup + { + UserCustodianCodes = new List { "660" } + }); // Act underTest.MigrateAdmins(); @@ -77,11 +77,12 @@ public void MigrateAdmins_IfOwnsNotEnoughLas_DoesNothing() public void MigrateAdmins_IfOwnsAllLasInTwoConsortia_AddsBoth() { // Arrange - var (user, expectedLasToRemove, expectedConsortia) = SetupUser( - new List { "660", "665", "835", "840" }, - new List(), - new List { "660", "665", "835", "840" }, - new List { "C_0008", "C_0010" }); + var (user, expectedLasToRemove, expectedConsortiaToAdd) = SetupUser(new UserTestSetup + { + UserCustodianCodes = new List { "660", "665", "835", "840" }, + ExpectedCustodianCodesToRemove = new List { "660", "665", "835", "840" }, + ExpectedConsortiumCodesToAdd = new List { "C_0008", "C_0010" } + }); // Act underTest.MigrateAdmins(); @@ -91,7 +92,7 @@ public void MigrateAdmins_IfOwnsAllLasInTwoConsortia_AddsBoth() mockDatabaseOperation.Verify(db => db.GetLas(new List { "660", "665", "835", "840" })); mockDatabaseOperation.Verify(db => db.GetConsortia(new List { "C_0008", "C_0010" })); mockDatabaseOperation.Verify( - db => db.AddConsortiaAndRemoveLasFromUser(user, expectedConsortia, expectedLasToRemove)); + db => db.AddConsortiaAndRemoveLasFromUser(user, expectedConsortiaToAdd, expectedLasToRemove)); mockDatabaseOperation.VerifyNoOtherCalls(); } @@ -100,11 +101,12 @@ public void MigrateAdmins_IfOwnsAllLasInTwoConsortia_AddsBoth() public void MigrateAdmins_IfOwnsAllInAConsortiaButNotEnoughInAnother_AddsOneConsortia() { // Arrange - var (user, expectedLasToRemove, expectedConsortia) = SetupUser( - new List { "660", "665", "835" }, - new List(), - new List { "660", "665" }, - new List { "C_0008" }); + var (user, expectedLasToRemove, expectedConsortiaToAdd) = SetupUser(new UserTestSetup + { + UserCustodianCodes = new List { "660", "665", "835" }, + ExpectedCustodianCodesToRemove = new List { "660", "665" }, + ExpectedConsortiumCodesToAdd = new List { "C_0008" } + }); // Act underTest.MigrateAdmins(); @@ -114,7 +116,7 @@ public void MigrateAdmins_IfOwnsAllInAConsortiaButNotEnoughInAnother_AddsOneCons mockDatabaseOperation.Verify(db => db.GetLas(new List { "660", "665" })); mockDatabaseOperation.Verify(db => db.GetConsortia(new List { "C_0008" })); mockDatabaseOperation.Verify( - db => db.AddConsortiaAndRemoveLasFromUser(user, expectedConsortia, expectedLasToRemove)); + db => db.AddConsortiaAndRemoveLasFromUser(user, expectedConsortiaToAdd, expectedLasToRemove)); mockDatabaseOperation.VerifyNoOtherCalls(); } @@ -123,11 +125,10 @@ public void MigrateAdmins_IfOwnsAllInAConsortiaButNotEnoughInAnother_AddsOneCons public void MigrateAdmins_IfOwnsConsortia_DoesNothing() { // Arrange - var (user, _, _) = SetupUser( - new List(), - new List { "C_0008" }, - new List(), - new List { "C_0008" }); + var (user, _, _) = SetupUser(new UserTestSetup + { + UserConsortiumCodes = new List { "C_0008" }, + }); // Act underTest.MigrateAdmins(); @@ -142,32 +143,30 @@ public void MigrateAdmins_IfOwnsConsortia_DoesNothing() mockDatabaseOperation.VerifyNoOtherCalls(); } - private (User, List, List) SetupUser(IEnumerable custodianCodes, - IReadOnlyCollection consortiumCodes, IReadOnlyCollection expectedCustodianCodesToRemove, - IReadOnlyCollection expectedConsortiumCodes) + private (User, List, List) SetupUser(UserTestSetup userTestSetup) { - var localAuthorities = custodianCodes + var localAuthorities = userTestSetup.UserCustodianCodes .Select((custodianCode, i) => new LocalAuthority { Id = i, CustodianCode = custodianCode }) .ToList(); - var consortia = consortiumCodes + var consortia = userTestSetup.UserConsortiumCodes .Select((consortiumCode, i) => new Consortium { Id = i, ConsortiumCode = consortiumCode }) .ToList(); - var expectedConsortia = expectedConsortiumCodes + var expectedConsortiaToAdd = userTestSetup.ExpectedConsortiumCodesToAdd .Select((consortiumCode, i) => new Consortium { - Id = i + consortiumCodes.Count, + Id = i + userTestSetup.UserConsortiumCodes.Count, ConsortiumCode = consortiumCode }) .ToList(); - var expectedLasToRemove = expectedCustodianCodesToRemove.Select(custodianCode => + var expectedLasToRemove = userTestSetup.ExpectedCustodianCodesToRemove.Select(custodianCode => localAuthorities.Single(la => la.CustodianCode == custodianCode)).ToList(); mockOutputProvider.Setup(op => op.Confirm("Okay to proceed? (Y/N)")).Returns(true); @@ -180,12 +179,24 @@ public void MigrateAdmins_IfOwnsConsortia_DoesNothing() .Setup(db => db.GetUsersWithLocalAuthoritiesAndConsortia()) .Returns(new List { user }); mockDatabaseOperation - .Setup(db => db.GetConsortia(expectedConsortiumCodes)) - .Returns(expectedConsortia); + .Setup(db => db.GetConsortia(userTestSetup.ExpectedConsortiumCodesToAdd)) + .Returns(expectedConsortiaToAdd); mockDatabaseOperation - .Setup(db => db.GetLas(expectedCustodianCodesToRemove)) + .Setup(db => db.GetLas(userTestSetup.ExpectedCustodianCodesToRemove)) .Returns(expectedLasToRemove); - return (user, expectedLasToRemove, expectedConsortia); + return (user, expectedLasToRemove, expectedConsortiaToAdd); + } + + private struct UserTestSetup + { + public IEnumerable UserCustodianCodes = new List(); + public IReadOnlyCollection UserConsortiumCodes = new List(); + public IReadOnlyCollection ExpectedCustodianCodesToRemove = new List(); + public IReadOnlyCollection ExpectedConsortiumCodesToAdd = new List(); + + public UserTestSetup() + { + } } } \ No newline at end of file From 45e368876612155310971f42da63a671f69cd913 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Thu, 6 Jun 2024 09:51:59 +0100 Subject: [PATCH 59/81] PC-1086: increase clarity on migration manifest --- HerPortal.ManagementShell/CommandHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/HerPortal.ManagementShell/CommandHandler.cs b/HerPortal.ManagementShell/CommandHandler.cs index 30eba63..8000b85 100644 --- a/HerPortal.ManagementShell/CommandHandler.cs +++ b/HerPortal.ManagementShell/CommandHandler.cs @@ -142,8 +142,8 @@ public void MigrateAdmins() { outputProvider.Output("!!! User Migration Script !!!"); outputProvider.Output("This script will ensure the validity of the LA / Consortium relationship for users."); - outputProvider.Output("If a user owns all LAs in a Consortium, they will be made a Consortium Admin."); - outputProvider.Output("If a user owns an LA in an owned Consortium, they will be removed."); + outputProvider.Output( + "If a user owns all LAs in a Consortium, they will be made a Consortium Admin and the LAs will be removed."); var users = adminAction.GetUsers(); From 04cf3bab8a2b1eece66dc3395bae8df439678b82 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Thu, 6 Jun 2024 10:07:25 +0100 Subject: [PATCH 60/81] PC-1086: improve result messages one for acknowledging a 'no' one for the migration finishing successfully --- HerPortal.ManagementShell/CommandHandler.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/HerPortal.ManagementShell/CommandHandler.cs b/HerPortal.ManagementShell/CommandHandler.cs index 8000b85..bb58978 100644 --- a/HerPortal.ManagementShell/CommandHandler.cs +++ b/HerPortal.ManagementShell/CommandHandler.cs @@ -165,8 +165,17 @@ public void MigrateAdmins() var confirmation = outputProvider.Confirm("Okay to proceed? (Y/N)"); - if (confirmation) adminAction.MigrateAdmin(user); + if (confirmation) + { + adminAction.MigrateAdmin(user); + } + else + { + outputProvider.Output("No changes made."); + } } + + outputProvider.Output("Migration complete."); } private void OutputCouldNotFindAuthorityException(string wrapperMessage, From 1d38f0bbeb1f7feae79042142551f4f3680e9b61 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Thu, 6 Jun 2024 10:10:17 +0100 Subject: [PATCH 61/81] PC-1086: code format --- HerPortal.ManagementShell/CommandHandler.cs | 6 +----- .../ManagementShell/MigrateAdminsCommandTests.cs | 4 ++-- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/HerPortal.ManagementShell/CommandHandler.cs b/HerPortal.ManagementShell/CommandHandler.cs index bb58978..52ecf59 100644 --- a/HerPortal.ManagementShell/CommandHandler.cs +++ b/HerPortal.ManagementShell/CommandHandler.cs @@ -166,15 +166,11 @@ public void MigrateAdmins() var confirmation = outputProvider.Confirm("Okay to proceed? (Y/N)"); if (confirmation) - { adminAction.MigrateAdmin(user); - } else - { outputProvider.Output("No changes made."); - } } - + outputProvider.Output("Migration complete."); } diff --git a/HerPortal.UnitTests/ManagementShell/MigrateAdminsCommandTests.cs b/HerPortal.UnitTests/ManagementShell/MigrateAdminsCommandTests.cs index 433fe3a..fabd724 100644 --- a/HerPortal.UnitTests/ManagementShell/MigrateAdminsCommandTests.cs +++ b/HerPortal.UnitTests/ManagementShell/MigrateAdminsCommandTests.cs @@ -127,7 +127,7 @@ public void MigrateAdmins_IfOwnsConsortia_DoesNothing() // Arrange var (user, _, _) = SetupUser(new UserTestSetup { - UserConsortiumCodes = new List { "C_0008" }, + UserConsortiumCodes = new List { "C_0008" } }); // Act @@ -187,7 +187,7 @@ public void MigrateAdmins_IfOwnsConsortia_DoesNothing() return (user, expectedLasToRemove, expectedConsortiaToAdd); } - + private struct UserTestSetup { public IEnumerable UserCustodianCodes = new List(); From 5043b824f583a8aa70aa1290df899bc13483835a Mon Sep 17 00:00:00 2001 From: Joe Gage Date: Fri, 7 Jun 2024 17:30:47 +0100 Subject: [PATCH 62/81] PC-NONE: Ensure tests run on all PRs --- .github/workflows/build-and-run-tests.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/build-and-run-tests.yml b/.github/workflows/build-and-run-tests.yml index a0390a3..a2a76ac 100644 --- a/.github/workflows/build-and-run-tests.yml +++ b/.github/workflows/build-and-run-tests.yml @@ -3,9 +3,7 @@ name: Run automated tests on: pull_request: branches: - - develop - - staging - - main + - '**' push: branches: - develop From f402146951817dce2f53612112291901843d55fe Mon Sep 17 00:00:00 2001 From: Joe Gage Date: Fri, 7 Jun 2024 17:31:02 +0100 Subject: [PATCH 63/81] PC-NONE: Ensure label check re-runs when appropriate --- .github/workflows/main-label-check.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main-label-check.yml b/.github/workflows/main-label-check.yml index 19539e4..1113582 100644 --- a/.github/workflows/main-label-check.yml +++ b/.github/workflows/main-label-check.yml @@ -2,7 +2,13 @@ name: main_label_check on: pull_request: - types: [opened, edited, labeled, unlabeled] + types: + - opened + - edited + - labeled + - reopened + - synchronize + - unlabeled jobs: label_check: From 6c4254cc0368e5626c8c47e5544a780f8b12a2f4 Mon Sep 17 00:00:00 2001 From: Joe Gage Date: Fri, 7 Jun 2024 17:31:19 +0100 Subject: [PATCH 64/81] PC-NONE: Auto-format yaml files --- .github/workflows/build-and-run-tests.yml | 88 +++++++++++------------ .github/workflows/main-label-check.yml | 3 +- 2 files changed, 45 insertions(+), 46 deletions(-) diff --git a/.github/workflows/build-and-run-tests.yml b/.github/workflows/build-and-run-tests.yml index a2a76ac..32f5398 100644 --- a/.github/workflows/build-and-run-tests.yml +++ b/.github/workflows/build-and-run-tests.yml @@ -14,52 +14,52 @@ jobs: build-and-run-tests: runs-on: ubuntu-latest steps: - - name: Checkout code onto job runner - uses: actions/checkout@v2 + - name: Checkout code onto job runner + uses: actions/checkout@v2 - - name: Install .Net (6.0) - uses: actions/setup-dotnet@v1 - with: - dotnet-version: 6.0.x + - name: Install .Net (6.0) + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 6.0.x - - name: Restore .Net dependencies - run: dotnet restore --configfile nuget.config + - name: Restore .Net dependencies + run: dotnet restore --configfile nuget.config - - name: Build .Net code - run: dotnet build --no-restore + - name: Build .Net code + run: dotnet build --no-restore - - name: Run tests - run: dotnet test --no-restore --logger trx --results-directory "TestResults" --collect:"XPlat Code Coverage" --settings HerPortal.UnitTests/coverlet.runsettings + - name: Run tests + run: dotnet test --no-restore --logger trx --results-directory "TestResults" --collect:"XPlat Code Coverage" --settings HerPortal.UnitTests/coverlet.runsettings - - name: Inline code coverage report and minimum coverage check - uses: irongut/CodeCoverageSummary@v1.3.0 - with: - filename: TestResults/*/coverage.cobertura.xml - badge: false - fail_below_min: true - format: text - hide_branch_rate: false - hide_complexity: false - indicators: true - output: console - # Threshold is low as this is a small project and a lot of the code is boilerplate or doesn't make sense to test - # Business logic coverage should be kept at/above 80% - thresholds: '50 80' - # Use always() to always run this step to publish coverage results when there are test failures - if: ${{ always() }} - - - name: Generate detailed code coverage report - uses: danielpalme/ReportGenerator-GitHub-Action@5.1.23 - with: - reports: TestResults/*/coverage.cobertura.xml - targetdir: TestResults/CoverageReport - # Use always() to always run this step to publish coverage report when there are test failures - if: ${{ always() }} - - - name: Upload dotnet test results - uses: actions/upload-artifact@v3 - with: - name: test-results - path: TestResults - # Use always() to always run this step to publish test results when there are test failures - if: ${{ always() }} + - name: Inline code coverage report and minimum coverage check + uses: irongut/CodeCoverageSummary@v1.3.0 + with: + filename: TestResults/*/coverage.cobertura.xml + badge: false + fail_below_min: true + format: text + hide_branch_rate: false + hide_complexity: false + indicators: true + output: console + # Threshold is low as this is a small project and a lot of the code is boilerplate or doesn't make sense to test + # Business logic coverage should be kept at/above 80% + thresholds: '50 80' + # Use always() to always run this step to publish coverage results when there are test failures + if: ${{ always() }} + + - name: Generate detailed code coverage report + uses: danielpalme/ReportGenerator-GitHub-Action@5.1.23 + with: + reports: TestResults/*/coverage.cobertura.xml + targetdir: TestResults/CoverageReport + # Use always() to always run this step to publish coverage report when there are test failures + if: ${{ always() }} + + - name: Upload dotnet test results + uses: actions/upload-artifact@v3 + with: + name: test-results + path: TestResults + # Use always() to always run this step to publish test results when there are test failures + if: ${{ always() }} diff --git a/.github/workflows/main-label-check.yml b/.github/workflows/main-label-check.yml index 1113582..b6f1f10 100644 --- a/.github/workflows/main-label-check.yml +++ b/.github/workflows/main-label-check.yml @@ -19,5 +19,4 @@ jobs: uses: actions/github-script@v3 with: script: | - core.setFailed("Label is missing: add a 'production release' label in order to merge to main") - + core.setFailed("Label is missing: add a 'production release' label in order to merge to main") From 82929b91dec2b3947eac509cb179dbb8cb1700b8 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Mon, 10 Jun 2024 10:18:34 +0100 Subject: [PATCH 65/81] PC-1086: update name of migrate command now describes a bit more what it does --- HerPortal.ManagementShell/AdminAction.cs | 2 +- HerPortal.ManagementShell/CommandHandler.cs | 4 ++-- HerPortal.ManagementShell/Program.cs | 6 +++--- .../ManagementShell/MigrateAdminsCommandTests.cs | 10 +++++----- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/HerPortal.ManagementShell/AdminAction.cs b/HerPortal.ManagementShell/AdminAction.cs index 12cc271..9e7543b 100644 --- a/HerPortal.ManagementShell/AdminAction.cs +++ b/HerPortal.ManagementShell/AdminAction.cs @@ -125,7 +125,7 @@ public List GetUsers() return dbOperation.GetUsersWithLocalAuthoritiesAndConsortia(); } - public void MigrateAdmin(User user) + public void FixUserOwnedConsortia(User user) { var consortiumCodesToAdd = GetConsortiumCodesUserShouldOwn(user).ToList(); var custodianCodesToRemove = GetOwnedCustodianCodesInConsortia(user, consortiumCodesToAdd); diff --git a/HerPortal.ManagementShell/CommandHandler.cs b/HerPortal.ManagementShell/CommandHandler.cs index 52ecf59..77c8c43 100644 --- a/HerPortal.ManagementShell/CommandHandler.cs +++ b/HerPortal.ManagementShell/CommandHandler.cs @@ -138,7 +138,7 @@ public void TryRemoveConsortia(User? user, IReadOnlyCollection consortiu } } - public void MigrateAdmins() + public void FixAllUserOwnedConsortia() { outputProvider.Output("!!! User Migration Script !!!"); outputProvider.Output("This script will ensure the validity of the LA / Consortium relationship for users."); @@ -166,7 +166,7 @@ public void MigrateAdmins() var confirmation = outputProvider.Confirm("Okay to proceed? (Y/N)"); if (confirmation) - adminAction.MigrateAdmin(user); + adminAction.FixUserOwnedConsortia(user); else outputProvider.Output("No changes made."); } diff --git a/HerPortal.ManagementShell/Program.cs b/HerPortal.ManagementShell/Program.cs index c0b3f94..a6af0c7 100644 --- a/HerPortal.ManagementShell/Program.cs +++ b/HerPortal.ManagementShell/Program.cs @@ -66,8 +66,8 @@ public static void Main(string[] args) case Subcommand.RemoveConsortia: commandHandler.TryRemoveConsortia(commandHandler.GetUser(userEmailAddress), codes); break; - case Subcommand.MigrateUsers: - commandHandler.MigrateAdmins(); + case Subcommand.FixAllUserOwnedConsortia: + commandHandler.FixAllUserOwnedConsortia(); break; default: outputProvider.Output("Invalid terminal command entered. Please refer to the documentation"); @@ -82,6 +82,6 @@ private enum Subcommand RemoveUser, AddConsortia, RemoveConsortia, - MigrateUsers + FixAllUserOwnedConsortia } } \ No newline at end of file diff --git a/HerPortal.UnitTests/ManagementShell/MigrateAdminsCommandTests.cs b/HerPortal.UnitTests/ManagementShell/MigrateAdminsCommandTests.cs index fabd724..aa75c3c 100644 --- a/HerPortal.UnitTests/ManagementShell/MigrateAdminsCommandTests.cs +++ b/HerPortal.UnitTests/ManagementShell/MigrateAdminsCommandTests.cs @@ -39,7 +39,7 @@ public void MigrateAdmins_IfOwnsAllLas_RemovesLasAndAddConsortia() }); // Act - underTest.MigrateAdmins(); + underTest.FixAllUserOwnedConsortia(); // Assert mockDatabaseOperation.Verify(db => db.GetUsersWithLocalAuthoritiesAndConsortia()); @@ -61,7 +61,7 @@ public void MigrateAdmins_IfOwnsNotEnoughLas_DoesNothing() }); // Act - underTest.MigrateAdmins(); + underTest.FixAllUserOwnedConsortia(); // Assert mockDatabaseOperation.Verify(db => db.GetUsersWithLocalAuthoritiesAndConsortia()); @@ -85,7 +85,7 @@ public void MigrateAdmins_IfOwnsAllLasInTwoConsortia_AddsBoth() }); // Act - underTest.MigrateAdmins(); + underTest.FixAllUserOwnedConsortia(); // Assert mockDatabaseOperation.Verify(db => db.GetUsersWithLocalAuthoritiesAndConsortia()); @@ -109,7 +109,7 @@ public void MigrateAdmins_IfOwnsAllInAConsortiaButNotEnoughInAnother_AddsOneCons }); // Act - underTest.MigrateAdmins(); + underTest.FixAllUserOwnedConsortia(); // Assert mockDatabaseOperation.Verify(db => db.GetUsersWithLocalAuthoritiesAndConsortia()); @@ -131,7 +131,7 @@ public void MigrateAdmins_IfOwnsConsortia_DoesNothing() }); // Act - underTest.MigrateAdmins(); + underTest.FixAllUserOwnedConsortia(); // Assert mockDatabaseOperation.Verify(db => db.GetUsersWithLocalAuthoritiesAndConsortia()); From fb48cc8d19d0c8e98bab3d2725e03fa9a925af91 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Mon, 10 Jun 2024 10:20:15 +0100 Subject: [PATCH 66/81] PC-1086: update test names too --- ...ts.cs => FixAllUserOwnedConsortiaCommandTests.cs} | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) rename HerPortal.UnitTests/ManagementShell/{MigrateAdminsCommandTests.cs => FixAllUserOwnedConsortiaCommandTests.cs} (94%) diff --git a/HerPortal.UnitTests/ManagementShell/MigrateAdminsCommandTests.cs b/HerPortal.UnitTests/ManagementShell/FixAllUserOwnedConsortiaCommandTests.cs similarity index 94% rename from HerPortal.UnitTests/ManagementShell/MigrateAdminsCommandTests.cs rename to HerPortal.UnitTests/ManagementShell/FixAllUserOwnedConsortiaCommandTests.cs index aa75c3c..02fa620 100644 --- a/HerPortal.UnitTests/ManagementShell/MigrateAdminsCommandTests.cs +++ b/HerPortal.UnitTests/ManagementShell/FixAllUserOwnedConsortiaCommandTests.cs @@ -8,7 +8,7 @@ namespace Tests.ManagementShell; -public class MigrateAdminsCommandTests +public class FixAllUserOwnedConsortiaCommandTests { private Mock mockDatabaseOperation; private Mock mockOutputProvider; @@ -28,7 +28,7 @@ public void Setup() // - Cheshire West and Chester 665 [Test] - public void MigrateAdmins_IfOwnsAllLas_RemovesLasAndAddConsortia() + public void FixAllUserOwnedConsortia_IfOwnsAllLas_RemovesLasAndAddConsortia() { // Arrange var (user, expectedLasToRemove, expectedConsortiaToAdd) = SetupUser(new UserTestSetup @@ -52,7 +52,7 @@ public void MigrateAdmins_IfOwnsAllLas_RemovesLasAndAddConsortia() } [Test] - public void MigrateAdmins_IfOwnsNotEnoughLas_DoesNothing() + public void FixAllUserOwnedConsortia_IfOwnsNotEnoughLas_DoesNothing() { // Arrange var (user, _, _) = SetupUser(new UserTestSetup @@ -74,7 +74,7 @@ public void MigrateAdmins_IfOwnsNotEnoughLas_DoesNothing() } [Test] - public void MigrateAdmins_IfOwnsAllLasInTwoConsortia_AddsBoth() + public void FixAllUserOwnedConsortia_IfOwnsAllLasInTwoConsortia_AddsBoth() { // Arrange var (user, expectedLasToRemove, expectedConsortiaToAdd) = SetupUser(new UserTestSetup @@ -98,7 +98,7 @@ public void MigrateAdmins_IfOwnsAllLasInTwoConsortia_AddsBoth() } [Test] - public void MigrateAdmins_IfOwnsAllInAConsortiaButNotEnoughInAnother_AddsOneConsortia() + public void FixAllUserOwnedConsortia_IfOwnsAllInAConsortiaButNotEnoughInAnother_AddsOneConsortia() { // Arrange var (user, expectedLasToRemove, expectedConsortiaToAdd) = SetupUser(new UserTestSetup @@ -122,7 +122,7 @@ public void MigrateAdmins_IfOwnsAllInAConsortiaButNotEnoughInAnother_AddsOneCons } [Test] - public void MigrateAdmins_IfOwnsConsortia_DoesNothing() + public void FixAllUserOwnedConsortia_IfOwnsConsortia_DoesNothing() { // Arrange var (user, _, _) = SetupUser(new UserTestSetup From 76c1994457f0038da137c28d93c400c0e3e6f643 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Mon, 10 Jun 2024 17:15:32 +0100 Subject: [PATCH 67/81] PC-1086: remove check to only remove LAs the user owns since, by needing to be consortium admin, they own all the LAs --- HerPortal.ManagementShell/AdminAction.cs | 7 ++++++- HerPortal.ManagementShell/CommandHandler.cs | 2 +- .../FixAllUserOwnedConsortiaCommandTests.cs | 4 ++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/HerPortal.ManagementShell/AdminAction.cs b/HerPortal.ManagementShell/AdminAction.cs index 9e7543b..3002219 100644 --- a/HerPortal.ManagementShell/AdminAction.cs +++ b/HerPortal.ManagementShell/AdminAction.cs @@ -46,6 +46,11 @@ public List GetOwnedCustodianCodesInConsortia(User user, IEnumerable GetCustodianCodesInConsortia(IEnumerable consortiumCodes) + { + return consortiumCodes.SelectMany(consortiumCode => consortiumCodeToCustodianCodesDict[consortiumCode]); + } + public UserAccountStatus GetUserStatus(User? userOrNull) { return userOrNull == null ? UserAccountStatus.New : UserAccountStatus.Active; @@ -128,7 +133,7 @@ public List GetUsers() public void FixUserOwnedConsortia(User user) { var consortiumCodesToAdd = GetConsortiumCodesUserShouldOwn(user).ToList(); - var custodianCodesToRemove = GetOwnedCustodianCodesInConsortia(user, consortiumCodesToAdd); + var custodianCodesToRemove = GetCustodianCodesInConsortia(consortiumCodesToAdd).ToList(); var consortiaToAdd = dbOperation.GetConsortia(consortiumCodesToAdd); var lasToRemove = dbOperation.GetLas(custodianCodesToRemove); diff --git a/HerPortal.ManagementShell/CommandHandler.cs b/HerPortal.ManagementShell/CommandHandler.cs index 77c8c43..2db900d 100644 --- a/HerPortal.ManagementShell/CommandHandler.cs +++ b/HerPortal.ManagementShell/CommandHandler.cs @@ -159,7 +159,7 @@ public void FixAllUserOwnedConsortia() consortiumCode => consortiumCodeToConsortiumNameDict[consortiumCode]); var custodianCodesToRemove = - adminAction.GetOwnedCustodianCodesInConsortia(user, consortiumCodesUserShouldOwn); + adminAction.GetCustodianCodesInConsortia(consortiumCodesUserShouldOwn).ToList(); outputProvider.Output("To make this user a Consortium Admin, the following LAs will be removed:"); PrintCodes(custodianCodesToRemove, custodianCode => custodianCodeToLaNameDict[custodianCode]); diff --git a/HerPortal.UnitTests/ManagementShell/FixAllUserOwnedConsortiaCommandTests.cs b/HerPortal.UnitTests/ManagementShell/FixAllUserOwnedConsortiaCommandTests.cs index 02fa620..20df0e1 100644 --- a/HerPortal.UnitTests/ManagementShell/FixAllUserOwnedConsortiaCommandTests.cs +++ b/HerPortal.UnitTests/ManagementShell/FixAllUserOwnedConsortiaCommandTests.cs @@ -80,7 +80,7 @@ public void FixAllUserOwnedConsortia_IfOwnsAllLasInTwoConsortia_AddsBoth() var (user, expectedLasToRemove, expectedConsortiaToAdd) = SetupUser(new UserTestSetup { UserCustodianCodes = new List { "660", "665", "835", "840" }, - ExpectedCustodianCodesToRemove = new List { "660", "665", "835", "840" }, + ExpectedCustodianCodesToRemove = new List { "660", "665", "840", "835" }, ExpectedConsortiumCodesToAdd = new List { "C_0008", "C_0010" } }); @@ -89,7 +89,7 @@ public void FixAllUserOwnedConsortia_IfOwnsAllLasInTwoConsortia_AddsBoth() // Assert mockDatabaseOperation.Verify(db => db.GetUsersWithLocalAuthoritiesAndConsortia()); - mockDatabaseOperation.Verify(db => db.GetLas(new List { "660", "665", "835", "840" })); + mockDatabaseOperation.Verify(db => db.GetLas(new List { "660", "665", "840", "835" })); mockDatabaseOperation.Verify(db => db.GetConsortia(new List { "C_0008", "C_0010" })); mockDatabaseOperation.Verify( db => db.AddConsortiaAndRemoveLasFromUser(user, expectedConsortiaToAdd, expectedLasToRemove)); From bca9a8eeeaa584e62cf59cd23538035241b2bd6c Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Mon, 10 Jun 2024 17:36:29 +0100 Subject: [PATCH 68/81] PC-1086: remove hardcoded custodian code values in tests --- .../FixAllUserOwnedConsortiaCommandTests.cs | 63 ++++++++++++------- 1 file changed, 42 insertions(+), 21 deletions(-) diff --git a/HerPortal.UnitTests/ManagementShell/FixAllUserOwnedConsortiaCommandTests.cs b/HerPortal.UnitTests/ManagementShell/FixAllUserOwnedConsortiaCommandTests.cs index 20df0e1..40dde36 100644 --- a/HerPortal.UnitTests/ManagementShell/FixAllUserOwnedConsortiaCommandTests.cs +++ b/HerPortal.UnitTests/ManagementShell/FixAllUserOwnedConsortiaCommandTests.cs @@ -23,19 +23,17 @@ public void Setup() underTest = new CommandHandler(adminAction, mockOutputProvider.Object); } - // Cheshire East C_0008 contains only two LAs: - // - Cheshire East 660 - // - Cheshire West and Chester 665 - [Test] public void FixAllUserOwnedConsortia_IfOwnsAllLas_RemovesLasAndAddConsortia() { // Arrange + var (consortiumCodes, custodianCodes) = GetExampleConsortiumCodesWithCustodianCodes().First(); + var (user, expectedLasToRemove, expectedConsortiaToAdd) = SetupUser(new UserTestSetup { - UserCustodianCodes = new List { "660", "665" }, - ExpectedCustodianCodesToRemove = new List { "660", "665" }, - ExpectedConsortiumCodesToAdd = new List { "C_0008" } + UserCustodianCodes = custodianCodes, + ExpectedCustodianCodesToRemove = custodianCodes, + ExpectedConsortiumCodesToAdd = new List { consortiumCodes } }); // Act @@ -43,8 +41,8 @@ public void FixAllUserOwnedConsortia_IfOwnsAllLas_RemovesLasAndAddConsortia() // Assert mockDatabaseOperation.Verify(db => db.GetUsersWithLocalAuthoritiesAndConsortia()); - mockDatabaseOperation.Verify(db => db.GetLas(new List { "660", "665" })); - mockDatabaseOperation.Verify(db => db.GetConsortia(new List { "C_0008" })); + mockDatabaseOperation.Verify(db => db.GetLas(custodianCodes)); + mockDatabaseOperation.Verify(db => db.GetConsortia(new List { consortiumCodes })); mockDatabaseOperation.Verify( db => db.AddConsortiaAndRemoveLasFromUser(user, expectedConsortiaToAdd, expectedLasToRemove)); @@ -55,9 +53,12 @@ public void FixAllUserOwnedConsortia_IfOwnsAllLas_RemovesLasAndAddConsortia() public void FixAllUserOwnedConsortia_IfOwnsNotEnoughLas_DoesNothing() { // Arrange + var (_, custodianCodes) = GetExampleConsortiumCodesWithCustodianCodes().First(); + var userCustodianCodes = custodianCodes.Take(1).ToList(); + var (user, _, _) = SetupUser(new UserTestSetup { - UserCustodianCodes = new List { "660" } + UserCustodianCodes = userCustodianCodes }); // Act @@ -77,11 +78,17 @@ public void FixAllUserOwnedConsortia_IfOwnsNotEnoughLas_DoesNothing() public void FixAllUserOwnedConsortia_IfOwnsAllLasInTwoConsortia_AddsBoth() { // Arrange + var consortiumAndLasInfo = GetExampleConsortiumCodesWithCustodianCodes().Take(2).ToList(); + var (consortiumCode1, custodianCodes1) = consortiumAndLasInfo[0]; + var (consortiumCode2, custodianCodes2) = consortiumAndLasInfo[1]; + var userCustodianCodes = custodianCodes1.Concat(custodianCodes2).ToList(); + var expectedConsortiumCodesToAdd = new List { consortiumCode1, consortiumCode2 }; + var (user, expectedLasToRemove, expectedConsortiaToAdd) = SetupUser(new UserTestSetup { - UserCustodianCodes = new List { "660", "665", "835", "840" }, - ExpectedCustodianCodesToRemove = new List { "660", "665", "840", "835" }, - ExpectedConsortiumCodesToAdd = new List { "C_0008", "C_0010" } + UserCustodianCodes = userCustodianCodes, + ExpectedCustodianCodesToRemove = custodianCodes1.Concat(custodianCodes2).ToList(), + ExpectedConsortiumCodesToAdd = expectedConsortiumCodesToAdd }); // Act @@ -89,8 +96,8 @@ public void FixAllUserOwnedConsortia_IfOwnsAllLasInTwoConsortia_AddsBoth() // Assert mockDatabaseOperation.Verify(db => db.GetUsersWithLocalAuthoritiesAndConsortia()); - mockDatabaseOperation.Verify(db => db.GetLas(new List { "660", "665", "840", "835" })); - mockDatabaseOperation.Verify(db => db.GetConsortia(new List { "C_0008", "C_0010" })); + mockDatabaseOperation.Verify(db => db.GetLas(userCustodianCodes)); + mockDatabaseOperation.Verify(db => db.GetConsortia(expectedConsortiumCodesToAdd)); mockDatabaseOperation.Verify( db => db.AddConsortiaAndRemoveLasFromUser(user, expectedConsortiaToAdd, expectedLasToRemove)); @@ -101,11 +108,17 @@ public void FixAllUserOwnedConsortia_IfOwnsAllLasInTwoConsortia_AddsBoth() public void FixAllUserOwnedConsortia_IfOwnsAllInAConsortiaButNotEnoughInAnother_AddsOneConsortia() { // Arrange + var consortiumAndLasInfo = GetExampleConsortiumCodesWithCustodianCodes().Take(2).ToList(); + var (consortiumCode1, custodianCodes1) = consortiumAndLasInfo[0]; + var (_, custodianCodes2) = consortiumAndLasInfo[1]; + var userCustodianCodes = custodianCodes1.Concat(custodianCodes2.Take(1)).ToList(); + var expectedConsortiumCodesToAdd = new List { consortiumCode1 }; + var (user, expectedLasToRemove, expectedConsortiaToAdd) = SetupUser(new UserTestSetup { - UserCustodianCodes = new List { "660", "665", "835" }, - ExpectedCustodianCodesToRemove = new List { "660", "665" }, - ExpectedConsortiumCodesToAdd = new List { "C_0008" } + UserCustodianCodes = userCustodianCodes, + ExpectedCustodianCodesToRemove = custodianCodes1, + ExpectedConsortiumCodesToAdd = expectedConsortiumCodesToAdd }); // Act @@ -113,8 +126,8 @@ public void FixAllUserOwnedConsortia_IfOwnsAllInAConsortiaButNotEnoughInAnother_ // Assert mockDatabaseOperation.Verify(db => db.GetUsersWithLocalAuthoritiesAndConsortia()); - mockDatabaseOperation.Verify(db => db.GetLas(new List { "660", "665" })); - mockDatabaseOperation.Verify(db => db.GetConsortia(new List { "C_0008" })); + mockDatabaseOperation.Verify(db => db.GetLas(custodianCodes1)); + mockDatabaseOperation.Verify(db => db.GetConsortia(expectedConsortiumCodesToAdd)); mockDatabaseOperation.Verify( db => db.AddConsortiaAndRemoveLasFromUser(user, expectedConsortiaToAdd, expectedLasToRemove)); @@ -125,9 +138,11 @@ public void FixAllUserOwnedConsortia_IfOwnsAllInAConsortiaButNotEnoughInAnother_ public void FixAllUserOwnedConsortia_IfOwnsConsortia_DoesNothing() { // Arrange + var (consortiumCode, _) = GetExampleConsortiumCodesWithCustodianCodes().First(); + var (user, _, _) = SetupUser(new UserTestSetup { - UserConsortiumCodes = new List { "C_0008" } + UserConsortiumCodes = new List { consortiumCode } }); // Act @@ -188,6 +203,12 @@ public void FixAllUserOwnedConsortia_IfOwnsConsortia_DoesNothing() return (user, expectedLasToRemove, expectedConsortiaToAdd); } + private IEnumerable<(string, List)> GetExampleConsortiumCodesWithCustodianCodes() + { + yield return ("C_0008", new List { "660", "665" }); + yield return ("C_0010", new List { "840", "835" }); + } + private struct UserTestSetup { public IEnumerable UserCustodianCodes = new List(); From ccd2d9bb91f7244940de9f1be8379c6ec9c92dc1 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Tue, 11 Jun 2024 09:09:05 +0100 Subject: [PATCH 69/81] PC-1086: clarify amount of LAs in test Consortia --- .../ManagementShell/FixAllUserOwnedConsortiaCommandTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/HerPortal.UnitTests/ManagementShell/FixAllUserOwnedConsortiaCommandTests.cs b/HerPortal.UnitTests/ManagementShell/FixAllUserOwnedConsortiaCommandTests.cs index 40dde36..536caa6 100644 --- a/HerPortal.UnitTests/ManagementShell/FixAllUserOwnedConsortiaCommandTests.cs +++ b/HerPortal.UnitTests/ManagementShell/FixAllUserOwnedConsortiaCommandTests.cs @@ -205,6 +205,7 @@ public void FixAllUserOwnedConsortia_IfOwnsConsortia_DoesNothing() private IEnumerable<(string, List)> GetExampleConsortiumCodesWithCustodianCodes() { + // both these Consortia have two LAs yield return ("C_0008", new List { "660", "665" }); yield return ("C_0010", new List { "840", "835" }); } From 32714a509b6b612e60bbc0f225cc6ba9abd73fe3 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Tue, 11 Jun 2024 09:12:29 +0100 Subject: [PATCH 70/81] PC-1086: output when a user won't be updated for clarity & comfort to the user --- HerPortal.ManagementShell/CommandHandler.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/HerPortal.ManagementShell/CommandHandler.cs b/HerPortal.ManagementShell/CommandHandler.cs index 2db900d..707d36f 100644 --- a/HerPortal.ManagementShell/CommandHandler.cs +++ b/HerPortal.ManagementShell/CommandHandler.cs @@ -152,7 +152,11 @@ public void FixAllUserOwnedConsortia() outputProvider.Output($"Processing user {user.EmailAddress}..."); var consortiumCodesUserShouldOwn = adminAction.GetConsortiumCodesUserShouldOwn(user).ToList(); - if (consortiumCodesUserShouldOwn.Count == 0) continue; + if (consortiumCodesUserShouldOwn.Count == 0) + { + outputProvider.Output("No changes needed."); + continue; + } outputProvider.Output("This user should own the following Consortia:"); PrintCodes(consortiumCodesUserShouldOwn, From 8556c565dc51237f63db480e1a31043924954ab4 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Tue, 11 Jun 2024 09:13:39 +0100 Subject: [PATCH 71/81] PC-1086: clarify what LAs will be removed from --- HerPortal.ManagementShell/CommandHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HerPortal.ManagementShell/CommandHandler.cs b/HerPortal.ManagementShell/CommandHandler.cs index 707d36f..10ddacd 100644 --- a/HerPortal.ManagementShell/CommandHandler.cs +++ b/HerPortal.ManagementShell/CommandHandler.cs @@ -164,7 +164,7 @@ public void FixAllUserOwnedConsortia() var custodianCodesToRemove = adminAction.GetCustodianCodesInConsortia(consortiumCodesUserShouldOwn).ToList(); - outputProvider.Output("To make this user a Consortium Admin, the following LAs will be removed:"); + outputProvider.Output("To make this user a Consortium Admin, the following LAs will be removed from the user:"); PrintCodes(custodianCodesToRemove, custodianCode => custodianCodeToLaNameDict[custodianCode]); var confirmation = outputProvider.Confirm("Okay to proceed? (Y/N)"); From 4e43a34b156eac74f0c7f297c0690602859beeca Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Wed, 12 Jun 2024 12:21:18 +0100 Subject: [PATCH 72/81] PC-1047: fix key error for LAs with no Consortium check if the dict contains the key before keying it --- HerPortal.ManagementShell/AdminAction.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/HerPortal.ManagementShell/AdminAction.cs b/HerPortal.ManagementShell/AdminAction.cs index 3002219..fc2ede6 100644 --- a/HerPortal.ManagementShell/AdminAction.cs +++ b/HerPortal.ManagementShell/AdminAction.cs @@ -40,8 +40,9 @@ public bool CustodianCodeIsInOwnedConsortium(User user, string custodianCode) public List GetOwnedCustodianCodesInConsortia(User user, IEnumerable consortiumCodes) { return user.LocalAuthorities - .Where(localAuthority => - consortiumCodes.Contains(custodianCodeToConsortiumCodeDict[localAuthority.CustodianCode])) + .Where(localAuthority => custodianCodeToConsortiumCodeDict.ContainsKey(localAuthority.CustodianCode) && + consortiumCodes.Contains( + custodianCodeToConsortiumCodeDict[localAuthority.CustodianCode])) .Select(localAuthority => localAuthority.CustodianCode) .ToList(); } From 225bc5a2ae16e2e0e427b5ca2c6bfef142fdf94b Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Wed, 12 Jun 2024 12:29:13 +0100 Subject: [PATCH 73/81] PC-1047: add a verifying test --- .../FixAllUserOwnedConsortiaCommandTests.cs | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/HerPortal.UnitTests/ManagementShell/FixAllUserOwnedConsortiaCommandTests.cs b/HerPortal.UnitTests/ManagementShell/FixAllUserOwnedConsortiaCommandTests.cs index 536caa6..fb17488 100644 --- a/HerPortal.UnitTests/ManagementShell/FixAllUserOwnedConsortiaCommandTests.cs +++ b/HerPortal.UnitTests/ManagementShell/FixAllUserOwnedConsortiaCommandTests.cs @@ -28,7 +28,7 @@ public void FixAllUserOwnedConsortia_IfOwnsAllLas_RemovesLasAndAddConsortia() { // Arrange var (consortiumCodes, custodianCodes) = GetExampleConsortiumCodesWithCustodianCodes().First(); - + var (user, expectedLasToRemove, expectedConsortiaToAdd) = SetupUser(new UserTestSetup { UserCustodianCodes = custodianCodes, @@ -55,7 +55,7 @@ public void FixAllUserOwnedConsortia_IfOwnsNotEnoughLas_DoesNothing() // Arrange var (_, custodianCodes) = GetExampleConsortiumCodesWithCustodianCodes().First(); var userCustodianCodes = custodianCodes.Take(1).ToList(); - + var (user, _, _) = SetupUser(new UserTestSetup { UserCustodianCodes = userCustodianCodes @@ -139,7 +139,7 @@ public void FixAllUserOwnedConsortia_IfOwnsConsortia_DoesNothing() { // Arrange var (consortiumCode, _) = GetExampleConsortiumCodesWithCustodianCodes().First(); - + var (user, _, _) = SetupUser(new UserTestSetup { UserConsortiumCodes = new List { consortiumCode } @@ -158,6 +158,26 @@ public void FixAllUserOwnedConsortia_IfOwnsConsortia_DoesNothing() mockDatabaseOperation.VerifyNoOtherCalls(); } + [Test] + public void FixAllUserOwnedConsortia_IfOwnsAllLaNotInConsortia_DoesNothing() + { + // Arrange + var custodianCodes = new List { GetExampleCustodianCodesNotInConsortium().First() }; + + var (user, expectedLasToRemove, expectedConsortiaToAdd) = SetupUser(new UserTestSetup + { + UserCustodianCodes = custodianCodes + }); + + // Act + underTest.FixAllUserOwnedConsortia(); + + // Assert + mockDatabaseOperation.Verify(db => db.GetUsersWithLocalAuthoritiesAndConsortia()); + + mockDatabaseOperation.VerifyNoOtherCalls(); + } + private (User, List, List) SetupUser(UserTestSetup userTestSetup) { var localAuthorities = userTestSetup.UserCustodianCodes @@ -210,6 +230,12 @@ public void FixAllUserOwnedConsortia_IfOwnsConsortia_DoesNothing() yield return ("C_0010", new List { "840", "835" }); } + private IEnumerable GetExampleCustodianCodesNotInConsortium() + { + // both these Consortia have two LAs + yield return "2004"; + } + private struct UserTestSetup { public IEnumerable UserCustodianCodes = new List(); From beab04dd7eab09b90cdc078e134f70bfda86d80a Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Wed, 12 Jun 2024 13:47:58 +0100 Subject: [PATCH 74/81] PC-1047: remove unused variables --- .../ManagementShell/FixAllUserOwnedConsortiaCommandTests.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/HerPortal.UnitTests/ManagementShell/FixAllUserOwnedConsortiaCommandTests.cs b/HerPortal.UnitTests/ManagementShell/FixAllUserOwnedConsortiaCommandTests.cs index fb17488..86ca95f 100644 --- a/HerPortal.UnitTests/ManagementShell/FixAllUserOwnedConsortiaCommandTests.cs +++ b/HerPortal.UnitTests/ManagementShell/FixAllUserOwnedConsortiaCommandTests.cs @@ -164,7 +164,7 @@ public void FixAllUserOwnedConsortia_IfOwnsAllLaNotInConsortia_DoesNothing() // Arrange var custodianCodes = new List { GetExampleCustodianCodesNotInConsortium().First() }; - var (user, expectedLasToRemove, expectedConsortiaToAdd) = SetupUser(new UserTestSetup + var (user, _, _) = SetupUser(new UserTestSetup { UserCustodianCodes = custodianCodes }); @@ -174,6 +174,10 @@ public void FixAllUserOwnedConsortia_IfOwnsAllLaNotInConsortia_DoesNothing() // Assert mockDatabaseOperation.Verify(db => db.GetUsersWithLocalAuthoritiesAndConsortia()); + mockDatabaseOperation.Verify( + db => db.AddConsortiaAndRemoveLasFromUser( + user, It.IsAny>(), It.IsAny>()), + Times.Never); mockDatabaseOperation.VerifyNoOtherCalls(); } From 0e0e1189488957b3690f6276a1cfc3b11a8475f2 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Wed, 12 Jun 2024 16:45:59 +0100 Subject: [PATCH 75/81] PC-1048: remove incorrect comment --- .../ManagementShell/FixAllUserOwnedConsortiaCommandTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/HerPortal.UnitTests/ManagementShell/FixAllUserOwnedConsortiaCommandTests.cs b/HerPortal.UnitTests/ManagementShell/FixAllUserOwnedConsortiaCommandTests.cs index 86ca95f..fd66329 100644 --- a/HerPortal.UnitTests/ManagementShell/FixAllUserOwnedConsortiaCommandTests.cs +++ b/HerPortal.UnitTests/ManagementShell/FixAllUserOwnedConsortiaCommandTests.cs @@ -236,7 +236,6 @@ public void FixAllUserOwnedConsortia_IfOwnsAllLaNotInConsortia_DoesNothing() private IEnumerable GetExampleCustodianCodesNotInConsortium() { - // both these Consortia have two LAs yield return "2004"; } From 6e85123dada85ad4f56f29817fd934778623acbf Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Thu, 13 Jun 2024 15:57:46 +0100 Subject: [PATCH 76/81] PC-1047: add consortium codes script --- SQL/populate_consortium_codes.sql | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 SQL/populate_consortium_codes.sql diff --git a/SQL/populate_consortium_codes.sql b/SQL/populate_consortium_codes.sql new file mode 100644 index 0000000..6531cfb --- /dev/null +++ b/SQL/populate_consortium_codes.sql @@ -0,0 +1,27 @@ +-- Create LA records +INSERT INTO "Consortia" ("ConsortiumCode") VALUES + ('C_0002'), -- Blackpool + ('C_0003'), -- Bristol + ('C_0004'), -- Broadland + ('C_0006'), -- Cambridge + ('C_0007'), -- Cambridgeshire & Peterborough Combined Authority + ('C_0008'), -- Cheshire East + ('C_0010'), -- Cornwall + ('C_0012'), -- Darlington Borough Council + ('C_0013'), -- Dartford + ('C_0014'), -- Devon + ('C_0015'), -- Dorset + ('C_0016'), -- Eden District Council + ('C_0017'), -- Greater London Authority + ('C_0021'), -- Lewes + ('C_0022'), -- Liverpool City Region + ('C_0024'), -- Midlands Net Zero Hub + ('C_0027'), -- North Yorkshire County Council + ('C_0029'), -- Oxfordshire County Council + ('C_0031'), -- Portsmouth + ('C_0033'), -- Sedgemoor + ('C_0037'), -- Stroud + ('C_0038'), -- Suffolk County Council + ('C_0039'), -- Surrey County Council + ('C_0044') -- West Devon +ON CONFLICT DO NOTHING; From ae4b5aafeec155408543f3690e79f04188bb9802 Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Thu, 13 Jun 2024 16:11:14 +0100 Subject: [PATCH 77/81] PC-1047: fix add consortia messaging references to LAs when adding a consortia will be replaced with Consortia --- HerPortal.ManagementShell/CommandHandler.cs | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/HerPortal.ManagementShell/CommandHandler.cs b/HerPortal.ManagementShell/CommandHandler.cs index 10ddacd..184ba2e 100644 --- a/HerPortal.ManagementShell/CommandHandler.cs +++ b/HerPortal.ManagementShell/CommandHandler.cs @@ -48,7 +48,7 @@ public void TryRemoveUser(User? user) public void CreateOrUpdateUserWithLas(string userEmailAddress, IReadOnlyCollection custodianCodes) { - var (user, userStatus) = CheckUserStatus(userEmailAddress); + var (user, userStatus) = CheckUserStatus(userEmailAddress, "LAs"); var confirmation = ConfirmAddCustodianCodes(userEmailAddress, custodianCodes, user); @@ -94,7 +94,7 @@ public void TryRemoveLas(User? user, IReadOnlyCollection custodianCodes) public void CreateOrUpdateUserWithConsortia(string userEmailAddress, IReadOnlyCollection consortiumCodes) { - var (user, userStatus) = CheckUserStatus(userEmailAddress); + var (user, userStatus) = CheckUserStatus(userEmailAddress, "Consortia"); var confirmation = ConfirmAddConsortiumCodes(userEmailAddress, consortiumCodes, user); @@ -164,7 +164,8 @@ public void FixAllUserOwnedConsortia() var custodianCodesToRemove = adminAction.GetCustodianCodesInConsortia(consortiumCodesUserShouldOwn).ToList(); - outputProvider.Output("To make this user a Consortium Admin, the following LAs will be removed from the user:"); + outputProvider.Output( + "To make this user a Consortium Admin, the following LAs will be removed from the user:"); PrintCodes(custodianCodesToRemove, custodianCode => custodianCodeToLaNameDict[custodianCode]); var confirmation = outputProvider.Confirm("Okay to proceed? (Y/N)"); @@ -291,7 +292,7 @@ private bool ConfirmChangesToDatabase(string? userEmailAddress, Action printActi return hasUserConfirmed; } - private void DisplayUserStatus(UserAccountStatus userAccountStatus) + private void DisplayUserStatus(UserAccountStatus userAccountStatus, string authorityType) { switch (userAccountStatus) { @@ -299,7 +300,7 @@ private void DisplayUserStatus(UserAccountStatus userAccountStatus) outputProvider.Output("User not found in database. A new user will be created"); break; case UserAccountStatus.Active: - outputProvider.Output("User found in database. LAs will be added to their account"); + outputProvider.Output($"User found in database. {authorityType} will be added to their account"); break; } } @@ -368,21 +369,21 @@ private void TryAddConsortia(User? user, IReadOnlyCollection consortiumC } } - private (User? user, UserAccountStatus userStatus) CheckUserStatus(string userEmailAddress) + private (User? user, UserAccountStatus userStatus) CheckUserStatus(string userEmailAddress, string authorityType) { var user = adminAction.GetUser(userEmailAddress); var userStatus = adminAction.GetUserStatus(user); - DisplayUserStatus(userStatus); + DisplayUserStatus(userStatus, authorityType); outputProvider.Output(""); outputProvider.Output("!!! ATTENTION! READ CAREFULLY OR RISK A DATA BREACH !!!"); outputProvider.Output(""); outputProvider.Output( - "You are about to grant a user permission to read PERSONALLY IDENTIFIABLE INFORMATION submitted to LAs."); + $"You are about to grant a user permission to read PERSONALLY IDENTIFIABLE INFORMATION submitted to {authorityType}."); outputProvider.Output( - "Take a moment to double check the following list and only continue if you are certain this user should have access to these LAs."); + $"Take a moment to double check the following list and only continue if you are certain this user should have access to these {authorityType}."); outputProvider.Output( - "NB: in particular, you should only do this for LAs that have signed their DSA contracts!"); + $"NB: in particular, you should only do this for {authorityType} that have signed their DSA contracts!"); outputProvider.Output(""); return (user, userStatus); From 8dd45a669d5dbf56649d53b1572dbb1ae279603b Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Thu, 13 Jun 2024 16:13:57 +0100 Subject: [PATCH 78/81] PC-1047: update tests to new messaging --- HerPortal.UnitTests/ManagementShell/CommandHandlerTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/HerPortal.UnitTests/ManagementShell/CommandHandlerTests.cs b/HerPortal.UnitTests/ManagementShell/CommandHandlerTests.cs index 16a5574..2cc0b9d 100644 --- a/HerPortal.UnitTests/ManagementShell/CommandHandlerTests.cs +++ b/HerPortal.UnitTests/ManagementShell/CommandHandlerTests.cs @@ -633,7 +633,8 @@ public void CreateOrUpdateUserWithConsortia_DisplaysCorrectUserStatus_WhenUserAc underTest.CreateOrUpdateUserWithConsortia(userEmailAddress, consortiumCodes); // Assert - mockOutputProvider.Verify(mock => mock.Output("User found in database. LAs will be added to their account"), + mockOutputProvider.Verify( + mock => mock.Output("User found in database. Consortia will be added to their account"), Times.Once()); } From 750f4b43bc2997242fd17441cf0884756de0368d Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Thu, 13 Jun 2024 16:18:10 +0100 Subject: [PATCH 79/81] PC-1047: fix sql file comment --- SQL/populate_consortium_codes.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SQL/populate_consortium_codes.sql b/SQL/populate_consortium_codes.sql index 6531cfb..16ff5b5 100644 --- a/SQL/populate_consortium_codes.sql +++ b/SQL/populate_consortium_codes.sql @@ -1,4 +1,4 @@ --- Create LA records +-- Create Consortium records INSERT INTO "Consortia" ("ConsortiumCode") VALUES ('C_0002'), -- Blackpool ('C_0003'), -- Bristol From 884fa637875d78bcc73b0210e9b0fd6eb294e85b Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Thu, 13 Jun 2024 16:23:04 +0100 Subject: [PATCH 80/81] PC-1047: revert authorityType changes to data breach warning --- HerPortal.ManagementShell/CommandHandler.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/HerPortal.ManagementShell/CommandHandler.cs b/HerPortal.ManagementShell/CommandHandler.cs index 184ba2e..6fc4491 100644 --- a/HerPortal.ManagementShell/CommandHandler.cs +++ b/HerPortal.ManagementShell/CommandHandler.cs @@ -379,11 +379,11 @@ private void TryAddConsortia(User? user, IReadOnlyCollection consortiumC outputProvider.Output("!!! ATTENTION! READ CAREFULLY OR RISK A DATA BREACH !!!"); outputProvider.Output(""); outputProvider.Output( - $"You are about to grant a user permission to read PERSONALLY IDENTIFIABLE INFORMATION submitted to {authorityType}."); + "You are about to grant a user permission to read PERSONALLY IDENTIFIABLE INFORMATION submitted to LAs."); outputProvider.Output( - $"Take a moment to double check the following list and only continue if you are certain this user should have access to these {authorityType}."); + "Take a moment to double check the following list and only continue if you are certain this user should have access to these LAs."); outputProvider.Output( - $"NB: in particular, you should only do this for {authorityType} that have signed their DSA contracts!"); + "NB: in particular, you should only do this for LAs that have signed their DSA contracts!"); outputProvider.Output(""); return (user, userStatus); From 56d6a691e214a7d04b781e04c314dbfee74f520a Mon Sep 17 00:00:00 2001 From: Joe Gage Date: Fri, 14 Jun 2024 17:07:52 +0100 Subject: [PATCH 81/81] PC-NONE: Add note about database migrations --- HerPortal/Program.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/HerPortal/Program.cs b/HerPortal/Program.cs index f2d12ba..db4fb31 100644 --- a/HerPortal/Program.cs +++ b/HerPortal/Program.cs @@ -26,7 +26,12 @@ public static void Main(string[] args) startup.Configure(app, app.Environment); - // Migrate the database if it's out of date + // Migrate the database if it's out of date. Ideally we wouldn't do this on app startup for our deployed + // environments, because we're risking multiple containers attempting to run the migrations concurrently and + // getting into a mess. However, we very rarely add migrations at this point, so in practice it's easier to + // risk it and keep an eye on the deployment: we should be doing rolling deployments anyway which makes it + // very unlikely we run into concurrency issues. If that changes though we should look at moving migrations + // to a deployment pipeline step, and only doing the following locally (PC-1151). using var scope = app.Services.CreateScope(); var dbContext = scope.ServiceProvider.GetRequiredService(); dbContext.Database.Migrate();