using Microsoft.Extensions.DependencyInjection; using System.Net.Http.Json; using System.Text.Json; using DramaLing.Api.Data; using DramaLing.Api.Tests.Integration.Fixtures; namespace DramaLing.Api.Tests.Integration; /// /// 整合測試基底類別 /// 提供所有整合測試的共用功能和設定 /// public abstract class IntegrationTestBase : IClassFixture, IDisposable { protected readonly DramaLingWebApplicationFactory Factory; protected readonly HttpClient HttpClient; protected readonly JsonSerializerOptions JsonOptions; protected IntegrationTestBase(DramaLingWebApplicationFactory factory) { Factory = factory; HttpClient = factory.CreateClient(); // 設定 JSON 序列化選項 JsonOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, WriteIndented = true }; // 每個測試開始前重置資料庫 ResetDatabase(); } /// /// 重置測試資料庫 /// protected void ResetDatabase() { Factory.ResetDatabase(); } /// /// 取得測試資料庫上下文 /// protected DramaLingDbContext GetDbContext() { return Factory.GetDbContext(); } /// /// 建立帶有認證的 HttpClient /// protected HttpClient CreateAuthenticatedClient(Guid userId) { var token = JwtTestHelper.GenerateJwtToken(userId); return Factory.CreateClientWithAuth(token); } /// /// 建立 TestUser1 的認證 HttpClient /// protected HttpClient CreateTestUser1Client() { var token = JwtTestHelper.GenerateTestUser1Token(); return Factory.CreateClientWithAuth(token); } /// /// 建立 TestUser2 的認證 HttpClient /// protected HttpClient CreateTestUser2Client() { var token = JwtTestHelper.GenerateTestUser2Token(); return Factory.CreateClientWithAuth(token); } /// /// 發送 GET 請求並反序列化回應 /// protected async Task GetAsync(string endpoint, HttpClient? client = null) { client ??= HttpClient; var response = await client.GetAsync(endpoint); var content = await response.Content.ReadAsStringAsync(); if (!response.IsSuccessStatusCode) { throw new HttpRequestException( $"GET {endpoint} failed with status {response.StatusCode}: {content}"); } return JsonSerializer.Deserialize(content, JsonOptions); } /// /// 發送 POST 請求並反序列化回應 /// protected async Task PostAsync(string endpoint, object? data = null, HttpClient? client = null) { client ??= HttpClient; var response = await client.PostAsJsonAsync(endpoint, data, JsonOptions); var content = await response.Content.ReadAsStringAsync(); if (!response.IsSuccessStatusCode) { throw new HttpRequestException( $"POST {endpoint} failed with status {response.StatusCode}: {content}"); } return JsonSerializer.Deserialize(content, JsonOptions); } /// /// 發送 PUT 請求並反序列化回應 /// protected async Task PutAsync(string endpoint, object data, HttpClient? client = null) { client ??= HttpClient; var response = await client.PutAsJsonAsync(endpoint, data, JsonOptions); var content = await response.Content.ReadAsStringAsync(); if (!response.IsSuccessStatusCode) { throw new HttpRequestException( $"PUT {endpoint} failed with status {response.StatusCode}: {content}"); } return JsonSerializer.Deserialize(content, JsonOptions); } /// /// 發送 DELETE 請求 /// protected async Task DeleteAsync(string endpoint, HttpClient? client = null) { client ??= HttpClient; var response = await client.DeleteAsync(endpoint); if (!response.IsSuccessStatusCode) { var content = await response.Content.ReadAsStringAsync(); throw new HttpRequestException( $"DELETE {endpoint} failed with status {response.StatusCode}: {content}"); } } /// /// 發送不期望成功的請求,並返回 HttpResponseMessage /// protected async Task SendRequestExpectingError( HttpMethod method, string endpoint, object? data = null, HttpClient? client = null) { client ??= HttpClient; var request = new HttpRequestMessage(method, endpoint); if (data != null) { request.Content = JsonContent.Create(data, options: JsonOptions); } return await client.SendAsync(request); } /// /// 等待異步操作完成 (用於測試背景任務) /// protected async Task WaitForAsync(Func> condition, TimeSpan timeout = default) { if (timeout == default) timeout = TimeSpan.FromSeconds(30); var start = DateTime.UtcNow; while (DateTime.UtcNow - start < timeout) { if (await condition()) return; await Task.Delay(100); } throw new TimeoutException($"Condition was not met within {timeout}"); } /// /// 驗證 API 回應格式 /// protected void AssertApiResponse(object response, bool expectedSuccess = true) { response.Should().NotBeNull(); // 可以根據你的 ApiResponse 格式調整 var responseType = response.GetType(); if (responseType.GetProperty("Success") != null) { var success = (bool)responseType.GetProperty("Success")!.GetValue(response)!; success.Should().Be(expectedSuccess); } if (expectedSuccess && responseType.GetProperty("Data") != null) { var data = responseType.GetProperty("Data")!.GetValue(response); data.Should().NotBeNull(); } } public virtual void Dispose() { HttpClient?.Dispose(); GC.SuppressFinalize(this); } }