using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Configuration;
using DramaLing.Api.Data;
using DramaLing.Api.Tests.Integration.Fixtures;
using DramaLing.Api.Tests.Integration.Mocks;
using DramaLing.Api.Services.AI.Gemini;
using DramaLing.Api.Models.Configuration;
namespace DramaLing.Api.Tests.Integration;
///
/// API 整合測試的 WebApplicationFactory
/// 提供完整的測試環境設定,包含 InMemory 資料庫和測試配置
///
public class DramaLingWebApplicationFactory : WebApplicationFactory
{
private readonly string _databaseName;
public DramaLingWebApplicationFactory()
{
_databaseName = $"TestDb_{Guid.NewGuid()}";
}
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
// 移除原有的資料庫配置
var descriptor = services.SingleOrDefault(
d => d.ServiceType == typeof(DbContextOptions));
if (descriptor != null)
{
services.Remove(descriptor);
}
// 使用 InMemory 資料庫
services.AddDbContext(options =>
{
options.UseInMemoryDatabase(_databaseName);
options.EnableSensitiveDataLogging();
});
// 替換 Gemini Client 為 Mock
var geminiDescriptor = services.SingleOrDefault(
d => d.ServiceType == typeof(IGeminiClient));
if (geminiDescriptor != null)
{
services.Remove(geminiDescriptor);
}
services.AddScoped();
// 設定測試用的 Gemini 配置
services.Configure(options =>
{
options.ApiKey = "AIza-test-key-for-integration-testing-purposes-only";
options.BaseUrl = "https://test.googleapis.com";
options.TimeoutSeconds = 10;
options.MaxRetries = 1;
options.Temperature = 0.5;
});
// 建立資料庫並種子資料
var serviceProvider = services.BuildServiceProvider();
using var scope = serviceProvider.CreateScope();
var context = scope.ServiceProvider.GetRequiredService();
context.Database.EnsureCreated();
TestDataSeeder.SeedTestData(context);
});
builder.UseEnvironment("Testing");
// 設定測試用環境變數
Environment.SetEnvironmentVariable("USE_INMEMORY_DB", "true");
Environment.SetEnvironmentVariable("DRAMALING_SUPABASE_JWT_SECRET", "test-secret-minimum-32-characters-long-for-jwt-signing-in-test-mode-only");
Environment.SetEnvironmentVariable("DRAMALING_SUPABASE_URL", "https://test.supabase.co");
Environment.SetEnvironmentVariable("DRAMALING_GEMINI_API_KEY", "AIza-test-key-for-integration-testing-purposes-only");
// 設定測試專用的配置
builder.ConfigureAppConfiguration((context, config) =>
{
// 添加測試用的記憶體配置
var testConfig = new Dictionary
{
["Supabase:JwtSecret"] = "test-secret-minimum-32-characters-long-for-jwt-signing-in-test-mode-only",
["Supabase:Url"] = "https://test.supabase.co",
["Gemini:ApiKey"] = "AIza-test-key-for-integration-testing-purposes-only"
};
config.AddInMemoryCollection(testConfig);
});
// 設定 Logging 層級
builder.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.AddConsole();
logging.SetMinimumLevel(LogLevel.Warning);
});
}
///
/// 取得測試用的 HttpClient,並設定預設的 JWT Token
///
public HttpClient CreateClientWithAuth(string? token = null)
{
var client = CreateClient();
if (!string.IsNullOrEmpty(token))
{
client.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
}
return client;
}
///
/// 重置資料庫資料 - 用於測試間的隔離
///
public void ResetDatabase()
{
using var scope = Services.CreateScope();
var context = scope.ServiceProvider.GetRequiredService();
// 清除所有資料
context.FlashcardReviews.RemoveRange(context.FlashcardReviews);
context.Flashcards.RemoveRange(context.Flashcards);
context.Users.RemoveRange(context.Users);
context.OptionsVocabularies.RemoveRange(context.OptionsVocabularies);
context.SaveChanges();
// 重新種子測試資料
TestDataSeeder.SeedTestData(context);
}
///
/// 取得測試資料庫上下文
///
public DramaLingDbContext GetDbContext()
{
var scope = Services.CreateScope();
return scope.ServiceProvider.GetRequiredService();
}
}