Remove old implementation for easier maintainability
This commit is contained in:
parent
1de9b57c5c
commit
8ae97efd41
|
@ -1,99 +0,0 @@
|
||||||
using AccessQueueService.Models;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
|
|
||||||
namespace AccessQueueService.Data
|
|
||||||
{
|
|
||||||
public class DictionaryAccessQueueRepo : IAccessQueueRepo
|
|
||||||
{
|
|
||||||
private readonly Dictionary<Guid, AccessTicket> _accessTickets = new();
|
|
||||||
private readonly Queue<AccessTicket> _accessQueue = new();
|
|
||||||
|
|
||||||
public int GetUnexpiredTicketsCount() => _accessTickets.Count(t => t.Value.ExpiresOn > DateTime.UtcNow);
|
|
||||||
public int GetActiveTicketsCount(DateTime activeCutoff) => _accessTickets
|
|
||||||
.Count(t => t.Value.ExpiresOn > DateTime.UtcNow && t.Value.LastActive >activeCutoff);
|
|
||||||
public int GetQueueCount() => _accessQueue.Count;
|
|
||||||
public int GetRequestsAhead(Guid userId)
|
|
||||||
{
|
|
||||||
var index = 0;
|
|
||||||
foreach (var ticket in _accessQueue)
|
|
||||||
{
|
|
||||||
if (ticket.UserId == userId)
|
|
||||||
{
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Enqueue(AccessTicket ticket)
|
|
||||||
{
|
|
||||||
_accessQueue.Enqueue(ticket);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int DeleteExpiredTickets()
|
|
||||||
{
|
|
||||||
var cutoff = DateTime.UtcNow;
|
|
||||||
var expiredTickets = _accessTickets.Where(t => t.Value.ExpiresOn < cutoff);
|
|
||||||
int count = 0;
|
|
||||||
foreach (var ticket in expiredTickets)
|
|
||||||
{
|
|
||||||
count++;
|
|
||||||
_accessTickets.Remove(ticket.Key);
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RemoveUser(Guid userId)
|
|
||||||
{
|
|
||||||
_accessTickets.Remove(userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool DidDequeueUntilFull(int activeSeconds, int expirationSeconds, int capacityLimit)
|
|
||||||
{
|
|
||||||
var now = DateTime.UtcNow;
|
|
||||||
var activeCutoff = now.AddSeconds(-activeSeconds);
|
|
||||||
var numberOfActiveUsers = _accessTickets.Count(t => t.Value.ExpiresOn > now && t.Value.LastActive > activeCutoff);
|
|
||||||
var openSpots = capacityLimit - numberOfActiveUsers;
|
|
||||||
int filledSpots = 0;
|
|
||||||
while (filledSpots < openSpots)
|
|
||||||
{
|
|
||||||
if (_accessQueue.TryDequeue(out var nextUser))
|
|
||||||
{
|
|
||||||
if (nextUser.LastActive < activeCutoff)
|
|
||||||
{
|
|
||||||
// User is inactive, throw away their ticket
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
_accessTickets[nextUser.UserId] = new AccessTicket
|
|
||||||
{
|
|
||||||
UserId = nextUser.UserId,
|
|
||||||
ExpiresOn = now.AddSeconds(expirationSeconds),
|
|
||||||
LastActive = now
|
|
||||||
};
|
|
||||||
filledSpots++;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return filledSpots == openSpots;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AccessTicket? GetTicket(Guid userId)
|
|
||||||
{
|
|
||||||
return _accessTickets.TryGetValue(userId, out var ticket) ? ticket : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UpsertTicket(AccessTicket ticket)
|
|
||||||
{
|
|
||||||
_accessTickets[ticket.UserId] = ticket;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IAccessQueueRepo.RemoveUser(Guid userId)
|
|
||||||
{
|
|
||||||
return _accessTickets.Remove(userId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -10,7 +10,7 @@ builder.Services.AddControllers();
|
||||||
builder.Services.AddEndpointsApiExplorer();
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
builder.Services.AddSwaggerGen();
|
builder.Services.AddSwaggerGen();
|
||||||
builder.Services.AddSingleton<IAccessService, AccessService>();
|
builder.Services.AddSingleton<IAccessService, AccessService>();
|
||||||
builder.Services.AddSingleton<IAccessQueueRepo, DictionaryAccessQueueRepo>();
|
builder.Services.AddSingleton<IAccessQueueRepo, TakeANumberAccessQueueRepo>();
|
||||||
|
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
|
@ -18,14 +18,9 @@ namespace AccessQueueServiceTests
|
||||||
const int ACT_MILLIS = 1000 * ACT_SECONDS;
|
const int ACT_MILLIS = 1000 * ACT_SECONDS;
|
||||||
const int CAP_LIMIT = 5;
|
const int CAP_LIMIT = 5;
|
||||||
const int BULK_COUNT = 50000;
|
const int BULK_COUNT = 50000;
|
||||||
private AccessService _accessService;
|
private readonly AccessService _accessService;
|
||||||
public static IEnumerable<object[]> RepoImplementations()
|
|
||||||
{
|
|
||||||
yield return new object[] { new DictionaryAccessQueueRepo() };
|
|
||||||
yield return new object[] { new TakeANumberAccessQueueRepo() };
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CreateService(IAccessQueueRepo repo)
|
public AccessServiceTests()
|
||||||
{
|
{
|
||||||
var inMemorySettings = new Dictionary<string, string?>
|
var inMemorySettings = new Dictionary<string, string?>
|
||||||
{
|
{
|
||||||
|
@ -38,16 +33,14 @@ namespace AccessQueueServiceTests
|
||||||
var configuration = new ConfigurationBuilder()
|
var configuration = new ConfigurationBuilder()
|
||||||
.AddInMemoryCollection(inMemorySettings)
|
.AddInMemoryCollection(inMemorySettings)
|
||||||
.Build();
|
.Build();
|
||||||
|
var accessQueueRepo = new TakeANumberAccessQueueRepo();
|
||||||
|
|
||||||
|
_accessService = new AccessService(configuration, accessQueueRepo);
|
||||||
_accessService = new AccessService(configuration, repo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Fact]
|
||||||
[MemberData(nameof(RepoImplementations))]
|
public async Task RequestAccess_ShouldGrantAccess_WhenCapacityIsAvailable()
|
||||||
public async Task RequestAccess_ShouldGrantAccess_WhenCapacityIsAvailable(IAccessQueueRepo repo)
|
|
||||||
{
|
{
|
||||||
CreateService(repo);
|
|
||||||
var userId = Guid.NewGuid();
|
var userId = Guid.NewGuid();
|
||||||
|
|
||||||
var response = await _accessService.RequestAccess(userId);
|
var response = await _accessService.RequestAccess(userId);
|
||||||
|
@ -60,11 +53,9 @@ namespace AccessQueueServiceTests
|
||||||
Assert.Equal(0, _accessService.QueueCount);
|
Assert.Equal(0, _accessService.QueueCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Fact]
|
||||||
[MemberData(nameof(RepoImplementations))]
|
public async Task RequestAccess_ShouldReturnAccessResponse_WhenUserAlreadyHasTicket()
|
||||||
public async Task RequestAccess_ShouldReturnAccessResponse_WhenUserAlreadyHasTicket(IAccessQueueRepo repo)
|
|
||||||
{
|
{
|
||||||
CreateService(repo);
|
|
||||||
var userId = Guid.NewGuid();
|
var userId = Guid.NewGuid();
|
||||||
await _accessService.RequestAccess(userId);
|
await _accessService.RequestAccess(userId);
|
||||||
|
|
||||||
|
@ -78,11 +69,9 @@ namespace AccessQueueServiceTests
|
||||||
Assert.Equal(0, _accessService.QueueCount);
|
Assert.Equal(0, _accessService.QueueCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Fact]
|
||||||
[MemberData(nameof(RepoImplementations))]
|
public async Task RequestAccess_ShouldQueueUser_WhenCapacityIsFull()
|
||||||
public async Task RequestAccess_ShouldQueueUser_WhenCapacityIsFull(IAccessQueueRepo repo)
|
|
||||||
{
|
{
|
||||||
CreateService(repo);
|
|
||||||
for (int i = 0; i < CAP_LIMIT * 2; i++) // Fill double capacity
|
for (int i = 0; i < CAP_LIMIT * 2; i++) // Fill double capacity
|
||||||
{
|
{
|
||||||
await _accessService.RequestAccess(Guid.NewGuid());
|
await _accessService.RequestAccess(Guid.NewGuid());
|
||||||
|
@ -100,11 +89,9 @@ namespace AccessQueueServiceTests
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[Theory]
|
[Fact]
|
||||||
[MemberData(nameof(RepoImplementations))]
|
public async Task RevokeAccess_ShouldReturnTrue_WhenUserHasAccess()
|
||||||
public async Task RevokeAccess_ShouldReturnTrue_WhenUserHasAccess(IAccessQueueRepo repo)
|
|
||||||
{
|
{
|
||||||
CreateService(repo);
|
|
||||||
var userId = Guid.NewGuid();
|
var userId = Guid.NewGuid();
|
||||||
await _accessService.RequestAccess(userId);
|
await _accessService.RequestAccess(userId);
|
||||||
|
|
||||||
|
@ -113,11 +100,9 @@ namespace AccessQueueServiceTests
|
||||||
Assert.True(result);
|
Assert.True(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Fact]
|
||||||
[MemberData(nameof(RepoImplementations))]
|
public async Task RevokeAccess_ShouldReturnFalse_WhenUserDoesNotHaveAccess()
|
||||||
public async Task RevokeAccess_ShouldReturnFalse_WhenUserDoesNotHaveAccess(IAccessQueueRepo repo)
|
|
||||||
{
|
{
|
||||||
CreateService(repo);
|
|
||||||
var userId = Guid.NewGuid();
|
var userId = Guid.NewGuid();
|
||||||
|
|
||||||
var result = await _accessService.RevokeAccess(userId);
|
var result = await _accessService.RevokeAccess(userId);
|
||||||
|
@ -125,11 +110,9 @@ namespace AccessQueueServiceTests
|
||||||
Assert.False(result);
|
Assert.False(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Fact]
|
||||||
[MemberData(nameof(RepoImplementations))]
|
public async Task RequestAccess_ShouldQueueUser_AfterAccessRevoked()
|
||||||
public async Task RequestAccess_ShouldQueueUser_AfterAccessRevoked(IAccessQueueRepo repo)
|
|
||||||
{
|
{
|
||||||
CreateService(repo);
|
|
||||||
var userId = Guid.NewGuid();
|
var userId = Guid.NewGuid();
|
||||||
await _accessService.RequestAccess(userId);
|
await _accessService.RequestAccess(userId);
|
||||||
|
|
||||||
|
@ -148,11 +131,9 @@ namespace AccessQueueServiceTests
|
||||||
Assert.False(responseAfterRevoke.HasAccess);
|
Assert.False(responseAfterRevoke.HasAccess);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Fact]
|
||||||
[MemberData(nameof(RepoImplementations))]
|
public async Task RequestAccess_ShouldNotQueueUser_WhenMultipleRequestsForOtherUsersMade()
|
||||||
public async Task RequestAccess_ShouldNotQueueUser_WhenMultipleRequestsForOtherUsersMade(IAccessQueueRepo repo)
|
|
||||||
{
|
{
|
||||||
CreateService(repo);
|
|
||||||
for (int i = 0; i < CAP_LIMIT; i++) // Fill slots without awaiting
|
for (int i = 0; i < CAP_LIMIT; i++) // Fill slots without awaiting
|
||||||
{
|
{
|
||||||
_ = _accessService.RequestAccess(Guid.NewGuid());
|
_ = _accessService.RequestAccess(Guid.NewGuid());
|
||||||
|
@ -162,11 +143,9 @@ namespace AccessQueueServiceTests
|
||||||
Assert.False(response.HasAccess);
|
Assert.False(response.HasAccess);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Fact]
|
||||||
[MemberData(nameof(RepoImplementations))]
|
public async Task RequestAccess_ShouldUpdateExpirationTime_WhenRollingExpirationTrue()
|
||||||
public async Task RequestAccess_ShouldUpdateExpirationTime_WhenRollingExpirationTrue(IAccessQueueRepo repo)
|
|
||||||
{
|
{
|
||||||
CreateService(repo);
|
|
||||||
var userId = Guid.NewGuid();
|
var userId = Guid.NewGuid();
|
||||||
var initialResponse = await _accessService.RequestAccess(userId);
|
var initialResponse = await _accessService.RequestAccess(userId);
|
||||||
await Task.Delay(ACT_MILLIS);
|
await Task.Delay(ACT_MILLIS);
|
||||||
|
@ -174,11 +153,9 @@ namespace AccessQueueServiceTests
|
||||||
Assert.True(updatedResponse.ExpiresOn > initialResponse.ExpiresOn);
|
Assert.True(updatedResponse.ExpiresOn > initialResponse.ExpiresOn);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Fact]
|
||||||
[MemberData(nameof(RepoImplementations))]
|
public async Task RequestAccess_ShouldGrantAccess_WhenUsersWithAccessInactive()
|
||||||
public async Task RequestAccess_ShouldGrantAccess_WhenUsersWithAccessInactive(IAccessQueueRepo repo)
|
|
||||||
{
|
{
|
||||||
CreateService(repo);
|
|
||||||
for (int i = 0; i < CAP_LIMIT; i++)
|
for (int i = 0; i < CAP_LIMIT; i++)
|
||||||
{
|
{
|
||||||
await _accessService.RequestAccess(Guid.NewGuid());
|
await _accessService.RequestAccess(Guid.NewGuid());
|
||||||
|
@ -191,11 +168,9 @@ namespace AccessQueueServiceTests
|
||||||
Assert.True(response.HasAccess);
|
Assert.True(response.HasAccess);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Fact]
|
||||||
[MemberData(nameof(RepoImplementations))]
|
public async Task RequestAccess_ShouldRevokeAccess_WhenExpired()
|
||||||
public async Task RequestAccess_ShouldRevokeAccess_WhenExpired(IAccessQueueRepo repo)
|
|
||||||
{
|
{
|
||||||
CreateService(repo);
|
|
||||||
var userId = Guid.NewGuid();
|
var userId = Guid.NewGuid();
|
||||||
var response = await _accessService.RequestAccess(userId);
|
var response = await _accessService.RequestAccess(userId);
|
||||||
Assert.True(response.HasAccess);
|
Assert.True(response.HasAccess);
|
||||||
|
@ -208,11 +183,9 @@ namespace AccessQueueServiceTests
|
||||||
Assert.False(response.HasAccess);
|
Assert.False(response.HasAccess);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Fact]
|
||||||
[MemberData(nameof(RepoImplementations))]
|
public async Task RequestAccess_ShouldRetailAccess_WhenNotExpired()
|
||||||
public async Task RequestAccess_ShouldRetailAccess_WhenNotExpired(IAccessQueueRepo repo)
|
|
||||||
{
|
{
|
||||||
CreateService(repo);
|
|
||||||
var userId = Guid.NewGuid();
|
var userId = Guid.NewGuid();
|
||||||
var response = await _accessService.RequestAccess(userId);
|
var response = await _accessService.RequestAccess(userId);
|
||||||
Assert.True(response.HasAccess);
|
Assert.True(response.HasAccess);
|
||||||
|
@ -226,11 +199,9 @@ namespace AccessQueueServiceTests
|
||||||
Assert.True(response.HasAccess);
|
Assert.True(response.HasAccess);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Fact]
|
||||||
[MemberData(nameof(RepoImplementations))]
|
public async Task RequestAccess_ShouldProcessBulkRequests()
|
||||||
public async Task RequestAccess_ShouldProcessBulkRequests(IAccessQueueRepo repo)
|
|
||||||
{
|
{
|
||||||
CreateService(repo);
|
|
||||||
var userId = Guid.NewGuid();
|
var userId = Guid.NewGuid();
|
||||||
await _accessService.RequestAccess(userId);
|
await _accessService.RequestAccess(userId);
|
||||||
for (int i = 0; i < BULK_COUNT; i++)
|
for (int i = 0; i < BULK_COUNT; i++)
|
||||||
|
@ -242,11 +213,9 @@ namespace AccessQueueServiceTests
|
||||||
Assert.True(response.HasAccess);
|
Assert.True(response.HasAccess);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Fact]
|
||||||
[MemberData(nameof(RepoImplementations))]
|
public async Task RequestAccess_ShouldReportLessInQueue_AsTicketsInactivate()
|
||||||
public async Task RequestAccess_ShouldReportLessInQueue_AsTicketsInactivate(IAccessQueueRepo repo)
|
|
||||||
{
|
{
|
||||||
CreateService(repo);
|
|
||||||
var start = DateTime.UtcNow;
|
var start = DateTime.UtcNow;
|
||||||
for (int i = 0; i < CAP_LIMIT; i++)
|
for (int i = 0; i < CAP_LIMIT; i++)
|
||||||
{
|
{
|
||||||
|
@ -276,11 +245,9 @@ namespace AccessQueueServiceTests
|
||||||
Assert.Equal(0, response.RequestsAhead);
|
Assert.Equal(0, response.RequestsAhead);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Fact]
|
||||||
[MemberData(nameof(RepoImplementations))]
|
public async Task RequestAccess_ShouldShowCorrectRequestsAhead_WhenAccessRerequested()
|
||||||
public async Task RequestAccess_ShouldShowCorrectRequestsAhead_WhenAccessRerequested(IAccessQueueRepo repo)
|
|
||||||
{
|
{
|
||||||
CreateService(repo);
|
|
||||||
for (int i = 0; i < CAP_LIMIT; i++)
|
for (int i = 0; i < CAP_LIMIT; i++)
|
||||||
{
|
{
|
||||||
await _accessService.RequestAccess(Guid.NewGuid());
|
await _accessService.RequestAccess(Guid.NewGuid());
|
||||||
|
@ -306,6 +273,7 @@ namespace AccessQueueServiceTests
|
||||||
Assert.Equal(1, response2.RequestsAhead);
|
Assert.Equal(1, response2.RequestsAhead);
|
||||||
Assert.Equal(2, response3.RequestsAhead);
|
Assert.Equal(2, response3.RequestsAhead);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue