dramaling-vocab-learning/backend/DramaLing.Api.Tests/Integration/IntegrationTestBase.cs

213 lines
6.6 KiB
C#

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;
/// <summary>
/// 整合測試基底類別
/// 提供所有整合測試的共用功能和設定
/// </summary>
public abstract class IntegrationTestBase : IClassFixture<DramaLingWebApplicationFactory>, 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();
}
/// <summary>
/// 重置測試資料庫
/// </summary>
protected void ResetDatabase()
{
Factory.ResetDatabase();
}
/// <summary>
/// 取得測試資料庫上下文
/// </summary>
protected DramaLingDbContext GetDbContext()
{
return Factory.GetDbContext();
}
/// <summary>
/// 建立帶有認證的 HttpClient
/// </summary>
protected HttpClient CreateAuthenticatedClient(Guid userId)
{
var token = JwtTestHelper.GenerateJwtToken(userId);
return Factory.CreateClientWithAuth(token);
}
/// <summary>
/// 建立 TestUser1 的認證 HttpClient
/// </summary>
protected HttpClient CreateTestUser1Client()
{
var token = JwtTestHelper.GenerateTestUser1Token();
return Factory.CreateClientWithAuth(token);
}
/// <summary>
/// 建立 TestUser2 的認證 HttpClient
/// </summary>
protected HttpClient CreateTestUser2Client()
{
var token = JwtTestHelper.GenerateTestUser2Token();
return Factory.CreateClientWithAuth(token);
}
/// <summary>
/// 發送 GET 請求並反序列化回應
/// </summary>
protected async Task<T?> GetAsync<T>(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<T>(content, JsonOptions);
}
/// <summary>
/// 發送 POST 請求並反序列化回應
/// </summary>
protected async Task<T?> PostAsync<T>(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<T>(content, JsonOptions);
}
/// <summary>
/// 發送 PUT 請求並反序列化回應
/// </summary>
protected async Task<T?> PutAsync<T>(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<T>(content, JsonOptions);
}
/// <summary>
/// 發送 DELETE 請求
/// </summary>
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}");
}
}
/// <summary>
/// 發送不期望成功的請求,並返回 HttpResponseMessage
/// </summary>
protected async Task<HttpResponseMessage> 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);
}
/// <summary>
/// 等待異步操作完成 (用於測試背景任務)
/// </summary>
protected async Task WaitForAsync(Func<Task<bool>> 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}");
}
/// <summary>
/// 驗證 API 回應格式
/// </summary>
protected void AssertApiResponse<T>(object response, bool expectedSuccess = true)
{
response.Should().NotBeNull();
// 可以根據你的 ApiResponse<T> 格式調整
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);
}
}