From f920a6d0a54d6d971a1b08700363b0c598e04645 Mon Sep 17 00:00:00 2001 From: James Wampler Date: Sun, 5 Jul 2026 22:30:10 -0700 Subject: [PATCH] Extract IAuditService, drop virtual mock seam from AuditService Consumers now depend on IAuditService instead of the concrete class, so tests mock the interface (Mock()) rather than subclassing AuditService via virtual methods. --- src/api/MicCheck.Api/Audit/AuditService.cs | 30 +++++++++++++++++-- .../Environments/EnvironmentService.cs | 2 +- .../MicCheck.Api/Features/FeatureService.cs | 2 +- .../Features/FeatureStateService.cs | 2 +- .../Organizations/OrganizationService.cs | 2 +- src/api/MicCheck.Api/Program.cs | 2 +- .../MicCheck.Api/Projects/ProjectService.cs | 2 +- .../MicCheck.Api/Segments/SegmentService.cs | 2 +- .../Admin/AdminApiIntegrationTests.cs | 4 +-- .../Audit/AuditLogsControllerTests.cs | 2 +- .../Environments/EnvironmentServiceTests.cs | 2 +- .../EnvironmentsControllerTests.cs | 2 +- .../Features/FeatureServiceTests.cs | 2 +- .../Features/FeatureStateServiceTests.cs | 4 +-- .../Features/FeaturesControllerTests.cs | 2 +- .../Organizations/OrganizationServiceTests.cs | 2 +- .../OrganizationsControllerTests.cs | 2 +- .../Projects/ProjectServiceTests.cs | 2 +- .../Projects/ProjectsControllerTests.cs | 2 +- .../Segments/SegmentServiceTests.cs | 2 +- .../Segments/SegmentsControllerTests.cs | 2 +- 21 files changed, 49 insertions(+), 25 deletions(-) diff --git a/src/api/MicCheck.Api/Audit/AuditService.cs b/src/api/MicCheck.Api/Audit/AuditService.cs index e54e285..721321e 100755 --- a/src/api/MicCheck.Api/Audit/AuditService.cs +++ b/src/api/MicCheck.Api/Audit/AuditService.cs @@ -4,7 +4,7 @@ using MicCheck.Api.Webhooks; namespace MicCheck.Api.Audit; -public class AuditService(IMicCheckDbContext db, IHttpContextAccessor httpContextAccessor, WebhookQueue webhookQueue) +public class AuditService(IMicCheckDbContext db, IHttpContextAccessor httpContextAccessor, WebhookQueue webhookQueue) : IAuditService { private static readonly JsonSerializerOptions JsonOptions = new() { @@ -12,7 +12,7 @@ public class AuditService(IMicCheckDbContext db, IHttpContextAccessor httpContex PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; - public virtual async Task RecordAsync( + public async Task RecordAsync( string resourceType, string resourceId, string action, @@ -68,7 +68,7 @@ public class AuditService(IMicCheckDbContext db, IHttpContextAccessor httpContex } // Backward-compatible overload used by existing callers - public virtual async Task LogAsync( + public async Task LogAsync( string resourceType, string resourceId, string action, @@ -118,3 +118,27 @@ public class AuditService(IMicCheckDbContext db, IHttpContextAccessor httpContex return int.TryParse(claim, out var id) ? id : null; } } + +public interface IAuditService +{ + Task RecordAsync( + string resourceType, + string resourceId, + string action, + int organizationId, + int? projectId = null, + int? environmentId = null, + object? before = null, + object? after = null, + CancellationToken ct = default); + + Task LogAsync( + string resourceType, + string resourceId, + string action, + int organizationId, + int? projectId = null, + int? environmentId = null, + string? changes = null, + CancellationToken ct = default); +} diff --git a/src/api/MicCheck.Api/Environments/EnvironmentService.cs b/src/api/MicCheck.Api/Environments/EnvironmentService.cs index 991e2eb..dcba660 100755 --- a/src/api/MicCheck.Api/Environments/EnvironmentService.cs +++ b/src/api/MicCheck.Api/Environments/EnvironmentService.cs @@ -7,7 +7,7 @@ using AppEnvironment = MicCheck.Api.Environments.Environment; namespace MicCheck.Api.Environments; -public class EnvironmentService(IMicCheckDbContext db, AuditService auditService) +public class EnvironmentService(IMicCheckDbContext db, IAuditService auditService) { public async Task> ListByProjectAsync(int projectId, CancellationToken ct = default) { diff --git a/src/api/MicCheck.Api/Features/FeatureService.cs b/src/api/MicCheck.Api/Features/FeatureService.cs index 7809d0e..bbfd99f 100755 --- a/src/api/MicCheck.Api/Features/FeatureService.cs +++ b/src/api/MicCheck.Api/Features/FeatureService.cs @@ -6,7 +6,7 @@ using Microsoft.EntityFrameworkCore; namespace MicCheck.Api.Features; -public class FeatureService(IMicCheckDbContext db, AuditService auditService, WebhookQueue webhookQueue) +public class FeatureService(IMicCheckDbContext db, IAuditService auditService, WebhookQueue webhookQueue) { private const int MaxFeaturesPerProject = 400; diff --git a/src/api/MicCheck.Api/Features/FeatureStateService.cs b/src/api/MicCheck.Api/Features/FeatureStateService.cs index 4325be4..a509023 100755 --- a/src/api/MicCheck.Api/Features/FeatureStateService.cs +++ b/src/api/MicCheck.Api/Features/FeatureStateService.cs @@ -5,7 +5,7 @@ using Microsoft.EntityFrameworkCore; namespace MicCheck.Api.Features; -public class FeatureStateService(IMicCheckDbContext db, WebhookQueue webhookQueue, AuditService auditService) +public class FeatureStateService(IMicCheckDbContext db, WebhookQueue webhookQueue, IAuditService auditService) { public async Task> ListByEnvironmentAsync(int environmentId, CancellationToken ct = default) { diff --git a/src/api/MicCheck.Api/Organizations/OrganizationService.cs b/src/api/MicCheck.Api/Organizations/OrganizationService.cs index f0c04db..962d2fd 100755 --- a/src/api/MicCheck.Api/Organizations/OrganizationService.cs +++ b/src/api/MicCheck.Api/Organizations/OrganizationService.cs @@ -5,7 +5,7 @@ using Microsoft.EntityFrameworkCore; namespace MicCheck.Api.Organizations; -public class OrganizationService(IMicCheckDbContext db, AuditService auditService) +public class OrganizationService(IMicCheckDbContext db, IAuditService auditService) { public async Task> ListForUserAsync(int userId, CancellationToken ct = default) { diff --git a/src/api/MicCheck.Api/Program.cs b/src/api/MicCheck.Api/Program.cs index cec1604..bb5f1b8 100755 --- a/src/api/MicCheck.Api/Program.cs +++ b/src/api/MicCheck.Api/Program.cs @@ -113,7 +113,7 @@ try builder.Services.AddScoped(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); - builder.Services.AddScoped(); + builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); diff --git a/src/api/MicCheck.Api/Projects/ProjectService.cs b/src/api/MicCheck.Api/Projects/ProjectService.cs index de54cb7..9e2e4b4 100755 --- a/src/api/MicCheck.Api/Projects/ProjectService.cs +++ b/src/api/MicCheck.Api/Projects/ProjectService.cs @@ -5,7 +5,7 @@ using Microsoft.EntityFrameworkCore; namespace MicCheck.Api.Projects; -public class ProjectService(IMicCheckDbContext db, AuditService auditService) +public class ProjectService(IMicCheckDbContext db, IAuditService auditService) { public async Task> ListByOrganizationAsync(int organizationId, CancellationToken ct = default) { diff --git a/src/api/MicCheck.Api/Segments/SegmentService.cs b/src/api/MicCheck.Api/Segments/SegmentService.cs index e7fb359..545a59b 100755 --- a/src/api/MicCheck.Api/Segments/SegmentService.cs +++ b/src/api/MicCheck.Api/Segments/SegmentService.cs @@ -9,7 +9,7 @@ public record SegmentConditionDefinition(string Property, SegmentConditionOperat public record SegmentRuleDefinition(SegmentRuleType Type, IReadOnlyList Conditions, IReadOnlyList? ChildRules = null); -public class SegmentService(IMicCheckDbContext db, AuditService auditService) +public class SegmentService(IMicCheckDbContext db, IAuditService auditService) { private const int MaxSegmentsPerProject = 100; private const int MaxConditionsPerSegment = 100; diff --git a/tests/api/MicCheck.Api.Tests.Unit/Admin/AdminApiIntegrationTests.cs b/tests/api/MicCheck.Api.Tests.Unit/Admin/AdminApiIntegrationTests.cs index 37b3b1e..af19876 100755 --- a/tests/api/MicCheck.Api.Tests.Unit/Admin/AdminApiIntegrationTests.cs +++ b/tests/api/MicCheck.Api.Tests.Unit/Admin/AdminApiIntegrationTests.cs @@ -23,7 +23,7 @@ public class AdminApiIntegrationTests private List _featureStates = null!; private List _environments = null!; private List _segments = null!; - private Mock _auditService = null!; + private Mock _auditService = null!; private int _organizationId; [SetUp] @@ -50,7 +50,7 @@ public class AdminApiIntegrationTests _db.SetupDbSetWithGeneratedIds(c => c.SegmentConditions, []); var webhookQueue = new WebhookQueue(); - _auditService = new Mock(_db.Object, null!, webhookQueue); + _auditService = new Mock(); _auditService.Setup(a => a.LogAsync( It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), diff --git a/tests/api/MicCheck.Api.Tests.Unit/Audit/AuditLogsControllerTests.cs b/tests/api/MicCheck.Api.Tests.Unit/Audit/AuditLogsControllerTests.cs index 0ae3d3f..38848c6 100644 --- a/tests/api/MicCheck.Api.Tests.Unit/Audit/AuditLogsControllerTests.cs +++ b/tests/api/MicCheck.Api.Tests.Unit/Audit/AuditLogsControllerTests.cs @@ -46,7 +46,7 @@ public class AuditLogsControllerTests _db.SetupDbSet(c => c.AuditLogs, _auditLogs); _db.SetupDbSet(c => c.Users, []); - var auditServiceMock = new Mock(_db.Object, null!, new MicCheck.Api.Webhooks.WebhookQueue()); + var auditServiceMock = new Mock(); var auditService = auditServiceMock.Object; _controller = new AuditLogsController( diff --git a/tests/api/MicCheck.Api.Tests.Unit/Environments/EnvironmentServiceTests.cs b/tests/api/MicCheck.Api.Tests.Unit/Environments/EnvironmentServiceTests.cs index d7591f1..1da20dd 100755 --- a/tests/api/MicCheck.Api.Tests.Unit/Environments/EnvironmentServiceTests.cs +++ b/tests/api/MicCheck.Api.Tests.Unit/Environments/EnvironmentServiceTests.cs @@ -45,7 +45,7 @@ public class EnvironmentServiceTests _db.SetupDbSet(c => c.Identities, _identities); var webhookQueue = new MicCheck.Api.Webhooks.WebhookQueue(); - var auditService = new Mock(_db.Object, null!, webhookQueue); + var auditService = new Mock(); auditService.Setup(a => a.LogAsync( It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), diff --git a/tests/api/MicCheck.Api.Tests.Unit/Environments/EnvironmentsControllerTests.cs b/tests/api/MicCheck.Api.Tests.Unit/Environments/EnvironmentsControllerTests.cs index 6fb5401..59a407c 100644 --- a/tests/api/MicCheck.Api.Tests.Unit/Environments/EnvironmentsControllerTests.cs +++ b/tests/api/MicCheck.Api.Tests.Unit/Environments/EnvironmentsControllerTests.cs @@ -113,7 +113,7 @@ public class EnvironmentsControllerTests _db.SetupDbSet(c => c.Users, []); var webhookQueue = new WebhookQueue(); - var auditServiceMock = new Mock(_db.Object, null!, webhookQueue); + var auditServiceMock = new Mock(); auditServiceMock.Setup(a => a.LogAsync( It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), diff --git a/tests/api/MicCheck.Api.Tests.Unit/Features/FeatureServiceTests.cs b/tests/api/MicCheck.Api.Tests.Unit/Features/FeatureServiceTests.cs index 45ec63c..0c0b530 100755 --- a/tests/api/MicCheck.Api.Tests.Unit/Features/FeatureServiceTests.cs +++ b/tests/api/MicCheck.Api.Tests.Unit/Features/FeatureServiceTests.cs @@ -45,7 +45,7 @@ public class FeatureServiceTests _db.SetupDbSetWithGeneratedIds(c => c.Tags, _tags); var webhookQueue = new MicCheck.Api.Webhooks.WebhookQueue(); - var auditService = new Mock(_db.Object, null!, webhookQueue); + var auditService = new Mock(); auditService.Setup(a => a.RecordAsync( It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), diff --git a/tests/api/MicCheck.Api.Tests.Unit/Features/FeatureStateServiceTests.cs b/tests/api/MicCheck.Api.Tests.Unit/Features/FeatureStateServiceTests.cs index 834991d..92a639d 100644 --- a/tests/api/MicCheck.Api.Tests.Unit/Features/FeatureStateServiceTests.cs +++ b/tests/api/MicCheck.Api.Tests.Unit/Features/FeatureStateServiceTests.cs @@ -43,7 +43,7 @@ public class FeatureStateServiceTests _db.SetupDbSetWithGeneratedIds(c => c.FeatureStates, _featureStates); var webhookQueue = new WebhookQueue(); - var auditService = new Mock(_db.Object, null!, webhookQueue); + var auditService = new Mock(); auditService.Setup(a => a.RecordAsync( It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), @@ -154,7 +154,7 @@ public class FeatureStateServiceTests var state = AddState(); var queue = new WebhookQueue(); - var auditService = new Mock(_db.Object, null!, queue); + var auditService = new Mock(); auditService.Setup(a => a.RecordAsync( It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), diff --git a/tests/api/MicCheck.Api.Tests.Unit/Features/FeaturesControllerTests.cs b/tests/api/MicCheck.Api.Tests.Unit/Features/FeaturesControllerTests.cs index 36456da..43c4f54 100644 --- a/tests/api/MicCheck.Api.Tests.Unit/Features/FeaturesControllerTests.cs +++ b/tests/api/MicCheck.Api.Tests.Unit/Features/FeaturesControllerTests.cs @@ -41,7 +41,7 @@ public class FeaturesControllerTests _db.SetupDbSetWithGeneratedIds(c => c.Tags, _tags); var webhookQueue = new MicCheck.Api.Webhooks.WebhookQueue(); - var auditService = new Mock(_db.Object, null!, webhookQueue); + var auditService = new Mock(); auditService.Setup(a => a.RecordAsync( It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), diff --git a/tests/api/MicCheck.Api.Tests.Unit/Organizations/OrganizationServiceTests.cs b/tests/api/MicCheck.Api.Tests.Unit/Organizations/OrganizationServiceTests.cs index 023fe5d..ca9980a 100644 --- a/tests/api/MicCheck.Api.Tests.Unit/Organizations/OrganizationServiceTests.cs +++ b/tests/api/MicCheck.Api.Tests.Unit/Organizations/OrganizationServiceTests.cs @@ -41,7 +41,7 @@ public class OrganizationServiceTests _db.SetupDbSet(c => c.Users, _users); var webhookQueue = new MicCheck.Api.Webhooks.WebhookQueue(); - var auditService = new Mock(_db.Object, null!, webhookQueue); + var auditService = new Mock(); auditService.Setup(a => a.LogAsync( It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), diff --git a/tests/api/MicCheck.Api.Tests.Unit/Organizations/OrganizationsControllerTests.cs b/tests/api/MicCheck.Api.Tests.Unit/Organizations/OrganizationsControllerTests.cs index 6c7956a..585bf1b 100644 --- a/tests/api/MicCheck.Api.Tests.Unit/Organizations/OrganizationsControllerTests.cs +++ b/tests/api/MicCheck.Api.Tests.Unit/Organizations/OrganizationsControllerTests.cs @@ -49,7 +49,7 @@ public class OrganizationsControllerTests _db.SetupDbSetWithGeneratedIds(c => c.Webhooks, _webhooks); var webhookQueue = new WebhookQueue(); - var auditService = new Mock(_db.Object, null!, webhookQueue); + var auditService = new Mock(); auditService.Setup(a => a.LogAsync( It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), diff --git a/tests/api/MicCheck.Api.Tests.Unit/Projects/ProjectServiceTests.cs b/tests/api/MicCheck.Api.Tests.Unit/Projects/ProjectServiceTests.cs index 1c04bfc..707a89c 100644 --- a/tests/api/MicCheck.Api.Tests.Unit/Projects/ProjectServiceTests.cs +++ b/tests/api/MicCheck.Api.Tests.Unit/Projects/ProjectServiceTests.cs @@ -32,7 +32,7 @@ public class ProjectServiceTests _db.SetupDbSet(c => c.UserProjectPermissions, _permissions); var webhookQueue = new MicCheck.Api.Webhooks.WebhookQueue(); - var auditService = new Mock(_db.Object, null!, webhookQueue); + var auditService = new Mock(); auditService.Setup(a => a.LogAsync( It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), diff --git a/tests/api/MicCheck.Api.Tests.Unit/Projects/ProjectsControllerTests.cs b/tests/api/MicCheck.Api.Tests.Unit/Projects/ProjectsControllerTests.cs index 33c67e4..524f8c2 100644 --- a/tests/api/MicCheck.Api.Tests.Unit/Projects/ProjectsControllerTests.cs +++ b/tests/api/MicCheck.Api.Tests.Unit/Projects/ProjectsControllerTests.cs @@ -34,7 +34,7 @@ public class ProjectsControllerTests _db.SetupDbSet(c => c.UserProjectPermissions, _permissions); var webhookQueue = new MicCheck.Api.Webhooks.WebhookQueue(); - var auditService = new Mock(_db.Object, null!, webhookQueue); + var auditService = new Mock(); auditService.Setup(a => a.LogAsync( It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), diff --git a/tests/api/MicCheck.Api.Tests.Unit/Segments/SegmentServiceTests.cs b/tests/api/MicCheck.Api.Tests.Unit/Segments/SegmentServiceTests.cs index 51bb9f1..232b6ab 100755 --- a/tests/api/MicCheck.Api.Tests.Unit/Segments/SegmentServiceTests.cs +++ b/tests/api/MicCheck.Api.Tests.Unit/Segments/SegmentServiceTests.cs @@ -40,7 +40,7 @@ public class SegmentServiceTests _db.SetupDbSetWithGeneratedIds(c => c.SegmentConditions, _segmentConditions); var webhookQueue = new MicCheck.Api.Webhooks.WebhookQueue(); - var auditService = new Mock(_db.Object, null!, webhookQueue); + var auditService = new Mock(); auditService.Setup(a => a.LogAsync( It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), diff --git a/tests/api/MicCheck.Api.Tests.Unit/Segments/SegmentsControllerTests.cs b/tests/api/MicCheck.Api.Tests.Unit/Segments/SegmentsControllerTests.cs index b85c22e..b143d13 100644 --- a/tests/api/MicCheck.Api.Tests.Unit/Segments/SegmentsControllerTests.cs +++ b/tests/api/MicCheck.Api.Tests.Unit/Segments/SegmentsControllerTests.cs @@ -41,7 +41,7 @@ public class SegmentsControllerTests _db.SetupDbSetWithGeneratedIds(c => c.SegmentConditions, _segmentConditions); var webhookQueue = new MicCheck.Api.Webhooks.WebhookQueue(); - var auditService = new Mock(_db.Object, null!, webhookQueue); + var auditService = new Mock(); auditService.Setup(a => a.LogAsync( It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(),