profileShare

rasmusjy / splitapp-backend-clean-onion

Read-only snapshot

No repository description.

main default branch 429 files Expires Sep 13, 2026, 9:06 AM
WebApiTestFactory.cs 3,856 bytes
1 using App.DAL.EF;
2 using Microsoft.AspNetCore.Hosting;
3 using Microsoft.AspNetCore.Mvc.Testing;
4 using Microsoft.Data.Sqlite;
5 using Microsoft.EntityFrameworkCore;
6 using Microsoft.Extensions.Configuration;
7 using Microsoft.AspNetCore.Identity;
8 using Microsoft.Extensions.DependencyInjection;
9 using Microsoft.Extensions.Hosting;
10
11 namespace App.Tests.Integration;
12
13 /// <summary>
14 /// Real ASP.NET pipeline (controllers + auth + EF) but Postgres → SQLite in-memory.
15 /// One factory per test class via IClassFixture — DB lives for the class lifetime.
16 /// </summary>
17 public class WebApiTestFactory : WebApplicationFactory<Program>
18 {
19 private readonly SqliteConnection _connection;
20
21 public WebApiTestFactory()
22 {
23 _connection = new SqliteConnection("Data Source=:memory:");
24 _connection.Open();
25 }
26
27 protected override void ConfigureWebHost(IWebHostBuilder builder)
28 {
29 builder.UseEnvironment("Testing");
30
31 builder.ConfigureAppConfiguration((_, configBuilder) =>
32 {
33 configBuilder.AddInMemoryCollection(new Dictionary<string, string?>
34 {
35 // Skip data initialization (handled by Program.cs env check)
36 ["DataInitialization:DropDatabase"] = "false",
37 ["DataInitialization:MigrateDatabase"] = "false",
38 ["DataInitialization:SeedIdentity"] = "false",
39 ["DataInitialization:SeedData"] = "false",
40
41 // JWT (HS256 needs ≥32-byte key)
42 ["JWT:Key"] = "test-jwt-key-for-integration-tests-only-1234567890",
43 ["JWT:Issuer"] = "test-issuer",
44 ["JWT:Audience"] = "test-audience",
45
46 ["SupportedCultures:0"] = "en",
47 ["SupportedCultures:1"] = "et",
48
49 ["ConnectionStrings:DefaultConnection"] = "Host=replaced;Database=replaced",
50 });
51 });
52
53 builder.ConfigureServices(services =>
54 {
55 // Nuke EVERY EF-related descriptor that the real app registered (AddDbContext
56 // with UseNpgsql leaves behind ~15 DbContextOptions / EF service entries).
57 // Without this, both Npgsql AND Sqlite providers end up configured on the
58 // same DbContextOptions and EF throws "multiple providers" errors.
59 var efDescriptors = services.Where(d =>
60 d.ServiceType.FullName != null &&
61 (d.ServiceType.FullName.Contains("EntityFrameworkCore") ||
62 d.ServiceType == typeof(AppDbContext))
63 ).ToList();
64 foreach (var d in efDescriptors) services.Remove(d);
65
66 // Re-add fresh with SQLite only
67 services.AddDbContext<AppDbContext>(opts => opts.UseSqlite(_connection));
68 });
69 }
70
71 /// <summary>
72 /// EnsureCreated runs against the fully-built service provider (host.Services),
73 /// not the half-built collection inside ConfigureServices. Also seeds the "user"
74 /// role since IdentityService.RegisterAsync calls AddToRoleAsync("user").
75 /// </summary>
76 protected override IHost CreateHost(IHostBuilder builder)
77 {
78 var host = base.CreateHost(builder);
79 using var scope = host.Services.CreateScope();
80 var sp = scope.ServiceProvider;
81
82 var db = sp.GetRequiredService<AppDbContext>();
83 db.Database.EnsureCreated();
84
85 var roleManager = sp.GetRequiredService<RoleManager<App.Domain.Identity.AppRole>>();
86 if (!roleManager.RoleExistsAsync("user").GetAwaiter().GetResult())
87 {
88 roleManager.CreateAsync(new App.Domain.Identity.AppRole { Name = "user" })
89 .GetAwaiter().GetResult();
90 }
91
92 return host;
93 }
94
95 protected override void Dispose(bool disposing)
96 {
97 base.Dispose(disposing);
98 if (disposing) _connection.Dispose();
99 }
100 }
101