Skip to content

Commit 22b3654

Browse files
committed
fix: use env vars in PortalWebApplicationFactory for CI compatibility
1 parent 752229a commit 22b3654

1 file changed

Lines changed: 38 additions & 48 deletions

File tree

Lines changed: 38 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,66 @@
11
using Microsoft.AspNetCore.Hosting;
22
using Microsoft.AspNetCore.Mvc.Testing;
3-
using Microsoft.EntityFrameworkCore;
4-
using Microsoft.Extensions.Configuration;
53
using Microsoft.Extensions.DependencyInjection;
64
using NSubstitute;
75
using SEBT.Portal.Core.Services;
8-
using SEBT.Portal.Infrastructure.Data;
96
using SEBT.Portal.Infrastructure.Services;
10-
using SEBT.Portal.StatesPlugins.Interfaces;
117

128
namespace SEBT.Portal.Tests.Integration;
139

1410
/// <summary>
1511
/// Shared test factory for integration tests that spin up the real HTTP pipeline.
1612
/// Handles common concerns so individual test classes can focus on endpoint behavior:
1713
/// <list type="bullet">
18-
/// <item>Configures plugin assembly paths to a non-existent directory so no plugins load</item>
19-
/// <item>Replaces SQL Server with InMemory EF provider</item>
20-
/// <item>Replaces database migration/seeding with no-op mocks</item>
21-
/// <item>Mocks plugin service interfaces so tests don't depend on state plugins</item>
14+
/// <item>Redirects plugin assembly paths to prevent loading DLLs with missing transitive dependencies</item>
15+
/// <item>Replaces database services with no-op mocks (no SQL Server required)</item>
2216
/// </list>
2317
/// </summary>
2418
public class PortalWebApplicationFactory : WebApplicationFactory<Program>
2519
{
2620
protected override void ConfigureWebHost(IWebHostBuilder builder)
2721
{
28-
builder.UseEnvironment("Development");
22+
// Override plugin assembly paths via environment variables BEFORE the server starts.
23+
// WebApplicationFactory lazily starts the server, so env vars set here are visible
24+
// when Program.cs reads builder.Configuration during startup.
25+
// This prevents loading plugin DLLs (copied to test output by the API csproj)
26+
// that have unresolvable transitive dependencies in the test environment.
27+
Environment.SetEnvironmentVariable("PluginAssemblyPaths__0", "plugins-none");
28+
Environment.SetEnvironmentVariable("PluginAssemblyPaths__1", "plugins-none");
2929

30-
builder.ConfigureAppConfiguration((_, config) =>
31-
config.AddInMemoryCollection(new Dictionary<string, string?>
32-
{
33-
["PluginAssemblyPaths:0"] = "plugins-test",
34-
["JwtSettings:SecretKey"] =
35-
"integration-test-key-must-be-at-least-32-bytes-long",
36-
}));
30+
// Provide a dummy JWT secret so the JwtBearer handler can initialize.
31+
// The auth middleware runs on every request (including /health), and
32+
// PostConfigure reads JwtSettings:SecretKey to create a SymmetricSecurityKey.
33+
Environment.SetEnvironmentVariable("JwtSettings__SecretKey",
34+
"integration-test-secret-key-at-least-32-chars!");
3735

3836
builder.ConfigureServices(services =>
3937
{
40-
// Remove the real SQL Server DbContext registration
41-
var dbContextDescriptor = services.SingleOrDefault(
42-
d => d.ServiceType == typeof(DbContextOptions<PortalDbContext>));
43-
if (dbContextDescriptor != null)
44-
{
45-
services.Remove(dbContextDescriptor);
46-
}
47-
48-
// Add InMemory EF provider instead
49-
services.AddDbContext<PortalDbContext>(options =>
50-
options.UseInMemoryDatabase("IntegrationTests"));
38+
// Replace database services with no-op mocks so startup
39+
// doesn't require a real SQL Server instance.
40+
ReplaceWithMock<IDatabaseMigrator>(services);
41+
ReplaceWithMock<IDatabaseSeeder>(services);
42+
});
43+
}
5144

52-
// Replace database migrator and seeder with no-ops
53-
var migratorDescriptor = services.SingleOrDefault(
54-
d => d.ServiceType == typeof(IDatabaseMigrator));
55-
if (migratorDescriptor != null)
56-
{
57-
services.Remove(migratorDescriptor);
58-
}
59-
services.AddScoped(_ => Substitute.For<IDatabaseMigrator>());
45+
/// <summary>
46+
/// Replaces an existing service registration with a no-op NSubstitute mock.
47+
/// </summary>
48+
private static void ReplaceWithMock<TService>(IServiceCollection services) where TService : class
49+
{
50+
var descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(TService));
51+
if (descriptor != null)
52+
{
53+
services.Remove(descriptor);
54+
}
6055

61-
var seederDescriptor = services.SingleOrDefault(
62-
d => d.ServiceType == typeof(IDatabaseSeeder));
63-
if (seederDescriptor != null)
64-
{
65-
services.Remove(seederDescriptor);
66-
}
67-
services.AddScoped(_ => Substitute.For<IDatabaseSeeder>());
56+
services.AddScoped(_ => Substitute.For<TService>());
57+
}
6858

69-
// Override plugin service registrations with mocks.
70-
// These AddSingleton calls come after AddPlugins' TryAddSingleton defaults
71-
// and any MEF-loaded plugins, so they win — last registration wins in DI.
72-
services.AddSingleton(Substitute.For<ISummerEbtCaseService>());
73-
services.AddSingleton(Substitute.For<IEnrollmentCheckService>());
74-
});
59+
protected override void Dispose(bool disposing)
60+
{
61+
Environment.SetEnvironmentVariable("PluginAssemblyPaths__0", null);
62+
Environment.SetEnvironmentVariable("PluginAssemblyPaths__1", null);
63+
Environment.SetEnvironmentVariable("JwtSettings__SecretKey", null);
64+
base.Dispose(disposing);
7565
}
7666
}

0 commit comments

Comments
 (0)