Skip to content
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
143 changes: 143 additions & 0 deletions apps/gateway/Gateway.API.Tests/Data/MigrationGateMiddlewareTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// =============================================================================
// <copyright file="MigrationGateMiddlewareTests.cs" company="Levelup Software">
// Copyright (c) Levelup Software. All rights reserved.
// </copyright>
// =============================================================================

namespace Gateway.API.Tests.Data;

using Gateway.API.Data;
using Microsoft.AspNetCore.Http;

/// <summary>
/// Tests for <see cref="MigrationGateMiddleware"/>.
/// </summary>
[NotInParallel("MigrationHealthCheck")]
public class MigrationGateMiddlewareTests
{
[Before(Test)]
public void Setup()
{
MigrationHealthCheck.Reset();
}

[After(Test)]
public void Cleanup()
{
MigrationHealthCheck.Reset();
}

[Test]
public async Task InvokeAsync_NotReady_Returns503()
{
// Arrange — register but don't complete
MigrationHealthCheck.RegisterExpected("TestContext");
var nextCalled = false;
var middleware = new MigrationGateMiddleware(_ =>
{
nextCalled = true;
return Task.CompletedTask;
});
var context = CreateHttpContext("/api/graphql");

// Act
await middleware.InvokeAsync(context);

// Assert
await Assert.That(context.Response.StatusCode).IsEqualTo(503);
await Assert.That(nextCalled).IsFalse();
}

[Test]
public async Task InvokeAsync_NotReady_SetsRetryAfterHeader()
{
MigrationHealthCheck.RegisterExpected("TestContext");
var middleware = new MigrationGateMiddleware(_ => Task.CompletedTask);
var context = CreateHttpContext("/api/graphql");

await middleware.InvokeAsync(context);

await Assert.That(context.Response.Headers["Retry-After"].ToString()).IsEqualTo("5");
}

[Test]
public async Task InvokeAsync_Ready_CallsNext()
{
// Arrange — complete migration
MigrationHealthCheck.RegisterExpected("TestContext");
MigrationHealthCheck.MarkComplete("TestContext");
var nextCalled = false;
var middleware = new MigrationGateMiddleware(_ =>
{
nextCalled = true;
return Task.CompletedTask;
});
var context = CreateHttpContext("/api/graphql");

// Act
await middleware.InvokeAsync(context);

// Assert
await Assert.That(nextCalled).IsTrue();
}

[Test]
public async Task InvokeAsync_NotReady_HealthEndpoint_CallsNext()
{
MigrationHealthCheck.RegisterExpected("TestContext");
var nextCalled = false;
var middleware = new MigrationGateMiddleware(_ =>
{
nextCalled = true;
return Task.CompletedTask;
});
var context = CreateHttpContext("/health");

await middleware.InvokeAsync(context);

await Assert.That(nextCalled).IsTrue();
}

[Test]
public async Task InvokeAsync_NotReady_AliveEndpoint_CallsNext()
{
MigrationHealthCheck.RegisterExpected("TestContext");
var nextCalled = false;
var middleware = new MigrationGateMiddleware(_ =>
{
nextCalled = true;
return Task.CompletedTask;
});
var context = CreateHttpContext("/alive");

await middleware.InvokeAsync(context);

await Assert.That(nextCalled).IsTrue();
}

[Test]
public async Task InvokeAsync_NoRegistrations_Returns503()
{
// No registrations means IsReady is false (Count == 0)
var nextCalled = false;
var middleware = new MigrationGateMiddleware(_ =>
{
nextCalled = true;
return Task.CompletedTask;
});
var context = CreateHttpContext("/api/data");

await middleware.InvokeAsync(context);

await Assert.That(context.Response.StatusCode).IsEqualTo(503);
await Assert.That(nextCalled).IsFalse();
}

private static DefaultHttpContext CreateHttpContext(string path)
{
var context = new DefaultHttpContext();
context.Request.Path = path;
context.Response.Body = new MemoryStream();
return context;
}
}
109 changes: 109 additions & 0 deletions apps/gateway/Gateway.API.Tests/Data/MigrationHealthCheckTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// =============================================================================
// <copyright file="MigrationHealthCheckTests.cs" company="Levelup Software">
// Copyright (c) Levelup Software. All rights reserved.
// </copyright>
// =============================================================================

namespace Gateway.API.Tests.Data;

using Gateway.API.Data;
using Microsoft.Extensions.Diagnostics.HealthChecks;

/// <summary>
/// Tests for <see cref="MigrationHealthCheck"/>.
/// </summary>
[NotInParallel("MigrationHealthCheck")]
public class MigrationHealthCheckTests
{
[Before(Test)]
public void Setup()
{
MigrationHealthCheck.Reset();
}

[After(Test)]
public void Cleanup()
{
MigrationHealthCheck.Reset();
}

[Test]
public async Task IsReady_NoRegistrations_ReturnsFalse()
{
await Assert.That(MigrationHealthCheck.IsReady).IsFalse();
}

[Test]
public async Task IsReady_RegisteredButNotComplete_ReturnsFalse()
{
MigrationHealthCheck.RegisterExpected("TestContext");

await Assert.That(MigrationHealthCheck.IsReady).IsFalse();
}

[Test]
public async Task IsReady_AllComplete_ReturnsTrue()
{
MigrationHealthCheck.RegisterExpected("TestContext");
MigrationHealthCheck.MarkComplete("TestContext");

await Assert.That(MigrationHealthCheck.IsReady).IsTrue();
}

[Test]
public async Task IsReady_MultipleContexts_OneIncomplete_ReturnsFalse()
{
MigrationHealthCheck.RegisterExpected("Context1");
MigrationHealthCheck.RegisterExpected("Context2");
MigrationHealthCheck.MarkComplete("Context1");

await Assert.That(MigrationHealthCheck.IsReady).IsFalse();
}

[Test]
public async Task IsReady_MultipleContexts_AllComplete_ReturnsTrue()
{
MigrationHealthCheck.RegisterExpected("Context1");
MigrationHealthCheck.RegisterExpected("Context2");
MigrationHealthCheck.MarkComplete("Context1");
MigrationHealthCheck.MarkComplete("Context2");

await Assert.That(MigrationHealthCheck.IsReady).IsTrue();
}

[Test]
public async Task CheckHealthAsync_Pending_ReturnsUnhealthy()
{
MigrationHealthCheck.RegisterExpected("TestContext");
var check = new MigrationHealthCheck();

var result = await check.CheckHealthAsync(new HealthCheckContext());

await Assert.That(result.Status).IsEqualTo(HealthStatus.Unhealthy);
}

[Test]
public async Task CheckHealthAsync_AllComplete_ReturnsHealthy()
{
MigrationHealthCheck.RegisterExpected("TestContext");
MigrationHealthCheck.MarkComplete("TestContext");
var check = new MigrationHealthCheck();

var result = await check.CheckHealthAsync(new HealthCheckContext());

await Assert.That(result.Status).IsEqualTo(HealthStatus.Healthy);
}

[Test]
public async Task Reset_ClearsAllState()
{
MigrationHealthCheck.RegisterExpected("TestContext");
MigrationHealthCheck.MarkComplete("TestContext");

await Assert.That(MigrationHealthCheck.IsReady).IsTrue();

MigrationHealthCheck.Reset();

await Assert.That(MigrationHealthCheck.IsReady).IsFalse();
}
}
128 changes: 128 additions & 0 deletions apps/gateway/Gateway.API.Tests/Data/PARequestDataSeederTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// =============================================================================
// <copyright file="PARequestDataSeederTests.cs" company="Levelup Software">
// Copyright (c) Levelup Software. All rights reserved.
// </copyright>
// =============================================================================

namespace Gateway.API.Tests.Data;

using Gateway.API.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using NSubstitute;

/// <summary>
/// Tests for <see cref="PARequestDataSeeder"/>.
/// </summary>
public class PARequestDataSeederTests
{
private GatewayDbContext CreateDbContext()
{
var options = new DbContextOptionsBuilder<GatewayDbContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.Options;
return new GatewayDbContext(options);
}

[Test]
public async Task SeedAsync_EmptyDatabase_SeedsData()
{
// Arrange
using var context = CreateDbContext();
var logger = Substitute.For<ILogger<PARequestDataSeeder>>();
var seeder = new PARequestDataSeeder(logger);

// Act
await seeder.SeedAsync(context, CancellationToken.None);

// Assert
var count = await context.PriorAuthRequests.CountAsync();
await Assert.That(count).IsEqualTo(4);
}

[Test]
public async Task SeedAsync_ExistingData_SkipsSeeding()
{
// Arrange
using var context = CreateDbContext();
var logger = Substitute.For<ILogger<PARequestDataSeeder>>();
var seeder = new PARequestDataSeeder(logger);

// Seed once
await seeder.SeedAsync(context, CancellationToken.None);

// Act — seed again
await seeder.SeedAsync(context, CancellationToken.None);

// Assert — still 4, not 8
var count = await context.PriorAuthRequests.CountAsync();
await Assert.That(count).IsEqualTo(4);
}

[Test]
public async Task SeedAsync_CreatesVariousStatuses()
{
// Arrange
using var context = CreateDbContext();
var logger = Substitute.For<ILogger<PARequestDataSeeder>>();
var seeder = new PARequestDataSeeder(logger);

// Act
await seeder.SeedAsync(context, CancellationToken.None);

// Assert — should have draft, ready, and submitted statuses
var statuses = await context.PriorAuthRequests
.Select(e => e.Status)
.Distinct()
.ToListAsync();

await Assert.That(statuses).Contains("draft");
await Assert.That(statuses).Contains("ready");
await Assert.That(statuses).Contains("submitted");
}

[Test]
public async Task SeedAsync_SetsRequiredFields()
{
// Arrange
using var context = CreateDbContext();
var logger = Substitute.For<ILogger<PARequestDataSeeder>>();
var seeder = new PARequestDataSeeder(logger);

// Act
await seeder.SeedAsync(context, CancellationToken.None);

// Assert — all entities have required fields
var entities = await context.PriorAuthRequests.ToListAsync();
foreach (var entity in entities)
{
await Assert.That(entity.Id).IsNotNull().And.IsNotEqualTo(string.Empty);
await Assert.That(entity.PatientId).IsNotNull().And.IsNotEqualTo(string.Empty);
await Assert.That(entity.PatientName).IsNotNull().And.IsNotEqualTo(string.Empty);
await Assert.That(entity.ProcedureCode).IsNotNull().And.IsNotEqualTo(string.Empty);
await Assert.That(entity.ProcedureName).IsNotNull().And.IsNotEqualTo(string.Empty);
}
}

[Test]
public async Task CreateDemoEntities_ReturnsExpectedCount()
{
var entities = PARequestDataSeeder.CreateDemoEntities(DateTimeOffset.UtcNow);

await Assert.That(entities.Length).IsEqualTo(4);
}

[Test]
public async Task CreateDemoEntities_ReadyEntities_HaveCriteria()
{
var entities = PARequestDataSeeder.CreateDemoEntities(DateTimeOffset.UtcNow);
var readyEntities = entities.Where(e => e.Status == "ready").ToList();

foreach (var entity in readyEntities)
{
await Assert.That(entity.CriteriaJson).IsNotNull().And.IsNotEqualTo(string.Empty);
await Assert.That(entity.Confidence).IsGreaterThan(0);
await Assert.That(entity.ReadyAt).IsNotNull();
}
}
}
Loading
Loading