213 lines
6.6 KiB
C#
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);
|
|
}
|
|
} |