Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

updates to unit tests #26

Merged
merged 1 commit into from
Jul 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using MediatR;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using SecretsSharingTool.Core.Interfaces;
using SecretsSharingTool.Core.Tests.Database;
using SecretsSharingTool.Data;

Expand All @@ -26,13 +28,15 @@ protected IMediator Mediator

protected readonly IServiceCollection ServiceCollection;
protected readonly ServiceProvider ServiceProvider;
protected readonly Mock<IDateTimeProvider> MockDateTimeProvider = new ();

protected BaseHandlerTest(DatabaseFixture fixture)
{
ServiceCollection = new ServiceCollection();
ServiceCollection.AddLogging();
ServiceCollection.AddDatabase(fixture.Configuration.BuildConnectionString(true));
ServiceCollection.AddCoreServices();
ServiceCollection.AddSingleton(MockDateTimeProvider.Object);

ConfigureAdditionalServices();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@ namespace SecretsSharingTool.Core.Tests.Handlers.Secret.Cleanse;

public class CleanseSecretsCommandHandlerTests : BaseHandlerTest
{
public CleanseSecretsCommandHandlerTests(DatabaseFixture fixture) : base(fixture) { }
public CleanseSecretsCommandHandlerTests(DatabaseFixture fixture) : base(fixture)
{
MockDateTimeProvider.Setup(p => p.GetCurrentDateTimeOffset())
.Returns(_testTime);
}

private readonly DateTimeOffset _testTime = DateTimeOffset.UtcNow;

[Fact]
public async Task Handle_WhenSecretsAreExpired_DeactivatesSecrets()
Expand All @@ -17,14 +23,14 @@ public async Task Handle_WhenSecretsAreExpired_DeactivatesSecrets()
{
EncryptedMessage = new [] { (byte) 0 },
IsActive = true,
CreatedOn = DateTimeOffset.UtcNow,
CreatedOn = _testTime,
ExpiryMinutes = 10
},
new ()
{
EncryptedMessage = new [] { (byte) 1 },
IsActive = true,
CreatedOn = DateTimeOffset.UtcNow.AddMinutes(-9),
CreatedOn = _testTime.AddMinutes(-9).AddSeconds(-59),
ExpiryMinutes = 10
},
};
Expand All @@ -35,14 +41,14 @@ public async Task Handle_WhenSecretsAreExpired_DeactivatesSecrets()
{
EncryptedMessage = new [] { (byte) 2 },
IsActive = false,
CreatedOn = DateTimeOffset.UtcNow,
CreatedOn = _testTime,
ExpiryMinutes = 10
},
new ()
{
EncryptedMessage = new [] { (byte) 3 },
IsActive = true,
CreatedOn = DateTimeOffset.UtcNow.AddMinutes(-11),
CreatedOn = _testTime.AddMinutes(-11),
ExpiryMinutes = 10
},
};
Expand Down Expand Up @@ -70,14 +76,14 @@ public async Task Handle_WhenNoSecretsExpired_AllSecretsAreActive()
{
EncryptedMessage = new [] { (byte) 0 },
IsActive = true,
CreatedOn = DateTimeOffset.UtcNow.AddMinutes(-5),
CreatedOn = _testTime.AddMinutes(-5),
ExpiryMinutes = 10
},
new ()
{
EncryptedMessage = new [] { (byte) 1 },
IsActive = true,
CreatedOn = DateTimeOffset.UtcNow.AddMinutes(-9),
CreatedOn = _testTime.AddMinutes(-9),
ExpiryMinutes = 10
},
};
Expand Down Expand Up @@ -106,7 +112,7 @@ public async Task Handle_WithLargeNumberOfSecretsToExpire_ExpiresAllSecrets()
{
EncryptedMessage = new [] { (byte) 0 },
IsActive = false,
CreatedOn = DateTimeOffset.UtcNow,
CreatedOn = _testTime,
ExpiryMinutes = 10
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ private static HttpContext CreateFakeHttpContext()
};
}

private void TestAuditRecordUserAgentAndIpAddress(SecretAccessAudit audit)
private void TestAuditRecordUserAgentAndIpAddress(Models.SecretAccessAudit audit)
{
audit.ClientUserAgent.Should().Be("Test Browser");
audit.ClientIpAddress.Should().Be("109.142.41.4");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
using Microsoft.EntityFrameworkCore;
using SecretsSharingTool.Core.Handlers.SecretAccessAudit.Cleanse;
using SecretsSharingTool.Core.Tests.Database;

namespace SecretsSharingTool.Core.Tests.Handlers.SecretAccessAudit.Cleanse;

public class CleanseSecretAccessAuditsCommandHandlerTests : BaseHandlerTest
{
public CleanseSecretAccessAuditsCommandHandlerTests(DatabaseFixture fixture) : base(fixture)
{
MockDateTimeProvider.Setup(p => p.GetCurrentDateTimeOffset())
.Returns(_testTime);
}

private readonly DateTimeOffset _testTime = DateTimeOffset.UtcNow;

[Fact]
public async Task Handle_WhenSecretAccessAuditsAreExpired_DeactivatesSecretAccessAudits()
{
var activeAudits = new List<Models.SecretAccessAudit>
{
NewSecretAccessAudit(_testTime),
NewSecretAccessAudit(_testTime.AddDays(-180)),
};

var expiredAudits = new List<Models.SecretAccessAudit>
{
NewSecretAccessAudit(_testTime.AddDays(-180).AddMilliseconds(-1)),
NewSecretAccessAudit(_testTime.AddDays(-181)),
};

await Database.AuditRecords.AddRangeAsync(activeAudits);
await Database.AuditRecords.AddRangeAsync(expiredAudits);
await Database.SaveChangesAsync();

await Mediator.Send(new CleanseSecretAccessAuditsCommand());

var emptySecrets = await Database.AuditRecords.ToListAsync();

expiredAudits.ForEach(p =>
emptySecrets.FirstOrDefault(x => x.Id == p.Id)
.Should()
.BeNull());
}

[Fact]
public async Task Handle_WhenNoSecretAuditRecordsExpired_AllAuditRecordsArePresent()
{
var activeAudits = new List<Models.SecretAccessAudit>
{
NewSecretAccessAudit(_testTime),
NewSecretAccessAudit(_testTime.AddDays(-180)),
};

await Database.AuditRecords.AddRangeAsync(activeAudits);
await Database.SaveChangesAsync();

await Mediator.Send(new CleanseSecretAccessAuditsCommand());

var dbSecrets = await Database.AuditRecords.ToListAsync();
activeAudits.ForEach(p =>
dbSecrets.FirstOrDefault(x => x.Id == p.Id)
.Should()
.NotBeNull());
}

[Fact]
public async Task Handle_WithLargeNumberOfSecretAuditRecordsToExpire_DeletesAllAuditRecords()
{
var auditRecordsList = new List<Models.SecretAccessAudit>();

for (var i = 0; i < 105; i++)
{
auditRecordsList.Add(NewSecretAccessAudit(_testTime.AddDays(-181)));
}

await Database.AuditRecords.AddRangeAsync(auditRecordsList);
await Database.SaveChangesAsync();

await Mediator.Send(new CleanseSecretAccessAuditsCommand());

var auditRecords = await Database.AuditRecords.ToListAsync();

auditRecordsList.Count.Should().Be(105);

auditRecordsList.ForEach(p => auditRecords.FirstOrDefault(q => q.Id == p.Id).Should().BeNull());
}

private Models.SecretAccessAudit NewSecretAccessAudit(DateTimeOffset createdOn) => new()
{ ClientIpAddress = "", ClientUserAgent = "", CreatedOn = createdOn };

}
7 changes: 7 additions & 0 deletions src/api/SecretsSharingTool.Core/DependencyInjection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Microsoft.Extensions.DependencyInjection;
using SecretsSharingTool.Core.Interfaces;
using SecretsSharingTool.Core.Pipelines;
using SecretsSharingTool.Core.Providers;

namespace SecretsSharingTool.Core;

Expand All @@ -19,4 +20,10 @@ public static IServiceCollection AddCoreServices(this IServiceCollection service
services.AddScoped(typeof(IPipelineBehavior<,>), typeof(ValidationPipelineBehavior<,>));
return services;
}

public static IServiceCollection AddDateTimeProvider(this IServiceCollection services)
{
services.AddSingleton<IDateTimeProvider, DateTimeProvider>();
return services;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@

namespace SecretsSharingTool.Core.Handlers.Secret.Cleanse;

public class CleanseSecretsCommand : IRequest
public sealed class CleanseSecretsCommand : IRequest
{
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using SecretsSharingTool.Core.Interfaces;
using SecretsSharingTool.Data;

namespace SecretsSharingTool.Core.Handlers.Secret.Cleanse;

public class CleanseSecretsCommandHandler : BaseRequestHandler<CleanseSecretsCommand>
{
public CleanseSecretsCommandHandler(AppUnitOfWork appUnitOfWork, ILogger<CleanseSecretsCommandHandler> logger) : base(appUnitOfWork, logger)
public CleanseSecretsCommandHandler(AppUnitOfWork appUnitOfWork, ILogger<CleanseSecretsCommandHandler> logger, IDateTimeProvider dateTimeProvider) : base(appUnitOfWork, logger)
{
_dateTimeProvider = dateTimeProvider;
}

private readonly IDateTimeProvider _dateTimeProvider;
private const int BatchSize = 50;

public override async Task<Unit> Handle(CleanseSecretsCommand request, CancellationToken cancellationToken)
{
var expiredSecrets = AppUnitOfWork.Secrets.Where(p => p.EncryptedMessage != null
&& (!p.IsActive ||
p.CreatedOn.AddMinutes(p.ExpiryMinutes) <
DateTimeOffset.UtcNow));
_dateTimeProvider.GetCurrentDateTimeOffset()));

var count = await expiredSecrets.CountAsync(cancellationToken);
var batches = (count / BatchSize) + 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ private async Task<bool> ValidateSecretState(RetrieveSecretCommand request, Mode
private async Task CreateAuditRecordForState(Guid secretId, FailureReason? failureReason = null)
{
var httpContext = _httpContextAccessor.HttpContext;
await Database.AuditRecords.AddAsync(new SecretAccessAudit()
await Database.AuditRecords.AddAsync(new Models.SecretAccessAudit()
{
SecretId = secretId,
FailureReason = failureReason,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
using MediatR;

namespace SecretsSharingTool.Core.Handlers.SecretAccessAudit.Cleanse;

public sealed class CleanseSecretAccessAuditsCommand : IRequest
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using SecretsSharingTool.Core.Interfaces;
using SecretsSharingTool.Data;

namespace SecretsSharingTool.Core.Handlers.SecretAccessAudit.Cleanse;

public sealed class CleanseSecretAccessAuditsCommandHandler : BaseRequestHandler<CleanseSecretAccessAuditsCommand>
{
public CleanseSecretAccessAuditsCommandHandler(AppUnitOfWork db, ILogger<CleanseSecretAccessAuditsCommandHandler> logger, IDateTimeProvider dateTimeProvider) : base(db, logger)
{
_dateTimeProvider = dateTimeProvider;
}

private readonly IDateTimeProvider _dateTimeProvider;
private const int BatchSize = 50;

public override async Task<Unit> Handle(CleanseSecretAccessAuditsCommand command, CancellationToken cancellationToken)
{
var expiredAudits = AppUnitOfWork.AuditRecords.Where(p => p.CreatedOn.AddDays(180) <
_dateTimeProvider.GetCurrentDateTimeOffset());

var count = await expiredAudits.CountAsync(cancellationToken);
var batches = (count / BatchSize) + 1;

for (var i = 0; i < batches; i++)
{
var batch = await expiredAudits.Take(BatchSize).ToListAsync(cancellationToken);
AppUnitOfWork.AuditRecords.RemoveRange(batch);
await AppUnitOfWork.SaveChangesAsync(cancellationToken);
}

return Unit.Value;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace SecretsSharingTool.Core.Interfaces;

public interface IDateTimeProvider
{
public DateTimeOffset GetCurrentDateTimeOffset();
public DateTime GetCurrentDateTime();
}
10 changes: 10 additions & 0 deletions src/api/SecretsSharingTool.Core/Providers/DateTimeProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using SecretsSharingTool.Core.Interfaces;

namespace SecretsSharingTool.Core.Providers;

public class DateTimeProvider : IDateTimeProvider
{
public DateTimeOffset GetCurrentDateTimeOffset() => DateTimeOffset.UtcNow;

public DateTime GetCurrentDateTime() => DateTime.UtcNow;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using MediatR;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using SecretsSharingTool.Web.HostedServices;

namespace SecretsSharingTool.Web.Tests.HostedServices;

public abstract class BaseHostedServiceTests<TService> where TService : BaseHostedService<TService>
{
protected readonly Mock<IMediator> MockMediator = new ();
private readonly IServiceProvider _serviceProvider;

protected BaseHostedServiceTests()
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddLogging();
serviceCollection.AddSingleton(MockMediator.Object);
serviceCollection.AddTransient<TService>();

_serviceProvider = serviceCollection.BuildServiceProvider();
}

protected TService GetService() => _serviceProvider.GetService<TService>()!;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Moq;
using SecretsSharingTool.Core.Handlers.SecretAccessAudit.Cleanse;
using SecretsSharingTool.Web.HostedServices;

namespace SecretsSharingTool.Web.Tests.HostedServices;

public class CleanseSecretAccessAuditsHostedServiceTests : BaseHostedServiceTests<CleanseSecretAccessAuditsHostedService>
{
[Fact]
public async Task StartAsync_WhenCalled_InvokesMediatrCall()
{
using var service = GetService();
await service.StartAsync(new CancellationToken());
MockMediator.Verify(p => p.Send(It.IsAny<CleanseSecretAccessAuditsCommand>(), It.IsAny<CancellationToken>()), Times.Once);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Moq;
using SecretsSharingTool.Core.Handlers.Secret.Cleanse;
using SecretsSharingTool.Web.HostedServices;

namespace SecretsSharingTool.Web.Tests.HostedServices;

public class CleanseSecretsHostedServiceTests : BaseHostedServiceTests<CleanseSecretsHostedService>
{
[Fact]
public async Task StartAsync_WhenCalled_InvokesMediatrCall()
{
using var service = GetService();
await service.StartAsync(new CancellationToken());
MockMediator.Verify(p => p.Send(It.IsAny<CleanseSecretsCommand>(), It.IsAny<CancellationToken>()), Times.Once);
}
}
Loading
Loading