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(); } }