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

240 lines
10 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using DramaLing.Api.Tests.Integration.Fixtures;
using System.Net;
using System.Net.Http.Json;
using System.Text.Json;
namespace DramaLing.Api.Tests.Integration.EndToEnd;
/// <summary>
/// AI 詞彙生成到儲存完整流程測試
/// 驗證從 AI 分析句子、生成詞彙、同義詞到儲存的完整業務流程
/// </summary>
public class AIVocabularyWorkflowTests : IntegrationTestBase
{
public AIVocabularyWorkflowTests(DramaLingWebApplicationFactory factory) : base(factory)
{
}
[Fact]
public async Task CompleteAIVocabularyWorkflow_ShouldGenerateAndStoreFlashcard()
{
// Arrange
var client = CreateTestUser1Client();
// Step 1: AI 分析句子生成詞彙
var analysisRequest = new
{
text = "The magnificent sunset painted the sky with brilliant colors.",
targetLevel = "B2",
includeGrammar = true,
includeVocabulary = true
};
var analysisResponse = await client.PostAsJsonAsync("/api/ai/analyze-sentence", analysisRequest);
analysisResponse.StatusCode.Should().Be(HttpStatusCode.OK);
var analysisContent = await analysisResponse.Content.ReadAsStringAsync();
var analysisJson = JsonSerializer.Deserialize<JsonElement>(analysisContent);
// 驗證 AI 分析結果包含詞彙資訊
analysisJson.GetProperty("success").GetBoolean().Should().BeTrue();
// Step 2: 模擬從 AI 分析結果中選擇詞彙並建立詞卡
// 假設 AI 分析返回了 "magnificent" 這個詞
var newFlashcard = new
{
word = "magnificent",
translation = "宏偉的,壯麗的",
definition = "Very beautiful and impressive",
partOfSpeech = "adjective",
pronunciation = "/mæɡˈnɪf.ɪ.sənt/",
example = "The magnificent sunset painted the sky.",
exampleTranslation = "壯麗的夕陽將天空染色。",
difficultyLevelNumeric = 4, // B2
synonyms = "[\"splendid\", \"impressive\", \"gorgeous\"]" // AI 生成的同義詞
};
var createResponse = await client.PostAsJsonAsync("/api/flashcards", newFlashcard);
createResponse.StatusCode.Should().Be(HttpStatusCode.OK);
var createContent = await createResponse.Content.ReadAsStringAsync();
var createJson = JsonSerializer.Deserialize<JsonElement>(createContent);
// Step 3: 驗證詞卡已正確儲存
var createdFlashcardId = createJson.GetProperty("data").GetProperty("id").GetString();
createdFlashcardId.Should().NotBeNullOrEmpty();
// Step 4: 取得儲存的詞卡並驗證同義詞
var getResponse = await client.GetAsync($"/api/flashcards/{createdFlashcardId}");
getResponse.StatusCode.Should().Be(HttpStatusCode.OK);
var getContent = await getResponse.Content.ReadAsStringAsync();
var getJson = JsonSerializer.Deserialize<JsonElement>(getContent);
var flashcard = getJson.GetProperty("data");
flashcard.GetProperty("word").GetString().Should().Be("magnificent");
flashcard.GetProperty("synonyms").EnumerateArray().Should().HaveCountGreaterThan(0, "應該有同義詞");
}
[Fact]
public async Task SynonymsGeneration_ShouldBeStoredAndDisplayedCorrectly()
{
// Arrange
var client = CreateTestUser1Client();
// Step 1: 建立包含同義詞的詞卡
var flashcardWithSynonyms = new
{
word = "brilliant",
translation = "聰明的,傑出的",
definition = "Exceptionally clever or talented",
partOfSpeech = "adjective",
pronunciation = "/ˈbrɪl.jənt/",
example = "She has a brilliant mind.",
exampleTranslation = "她有聰明的頭腦。",
difficultyLevelNumeric = 3, // B1
synonyms = "[\"intelligent\", \"smart\", \"clever\", \"outstanding\"]" // JSON 格式的同義詞
};
var createResponse = await client.PostAsJsonAsync("/api/flashcards", flashcardWithSynonyms);
createResponse.StatusCode.Should().Be(HttpStatusCode.OK);
var createContent = await createResponse.Content.ReadAsStringAsync();
var createJson = JsonSerializer.Deserialize<JsonElement>(createContent);
var flashcardId = createJson.GetProperty("data").GetProperty("id").GetString();
// Step 2: 取得詞卡並驗證同義詞正確解析
var getResponse = await client.GetAsync($"/api/flashcards/{flashcardId}");
getResponse.StatusCode.Should().Be(HttpStatusCode.OK);
var getContent = await getResponse.Content.ReadAsStringAsync();
var getJson = JsonSerializer.Deserialize<JsonElement>(getContent);
var retrievedFlashcard = getJson.GetProperty("data");
// Step 3: 驗證同義詞格式和內容
var synonymsArray = retrievedFlashcard.GetProperty("synonyms");
var synonymsList = synonymsArray.EnumerateArray().Select(s => s.GetString()).ToList();
synonymsList.Should().Contain("intelligent");
synonymsList.Should().Contain("smart");
synonymsList.Should().Contain("clever");
synonymsList.Should().Contain("outstanding");
synonymsList.Should().HaveCount(4, "應該有4個同義詞");
// Step 4: 驗證同義詞在複習時正確顯示
var dueResponse = await client.GetAsync("/api/flashcards/due");
var dueContent = await dueResponse.Content.ReadAsStringAsync();
var dueJson = JsonSerializer.Deserialize<JsonElement>(dueContent);
var flashcards = dueJson.GetProperty("data").GetProperty("flashcards");
var targetFlashcard = flashcards.EnumerateArray()
.FirstOrDefault(f => f.GetProperty("id").GetString() == flashcardId);
if (!targetFlashcard.Equals(default(JsonElement)))
{
var synonymsInDue = targetFlashcard.GetProperty("synonyms");
synonymsInDue.GetArrayLength().Should().BeGreaterThan(0, "複習時應該顯示同義詞");
}
}
[Fact]
public async Task OptionsGeneration_ShouldProvideValidDistractors()
{
// Arrange
var client = CreateTestUser1Client();
// Step 1: 建立詞卡
var flashcard = new
{
word = "extraordinary",
translation = "非凡的",
definition = "Very unusual or remarkable",
partOfSpeech = "adjective",
difficultyLevelNumeric = 4 // B2
};
var createResponse = await client.PostAsJsonAsync("/api/flashcards", flashcard);
var createContent = await createResponse.Content.ReadAsStringAsync();
var createJson = JsonSerializer.Deserialize<JsonElement>(createContent);
var flashcardId = createJson.GetProperty("data").GetProperty("id").GetString();
// Step 2: 取得詞卡的待複習狀態 (應該包含 AI 生成的選項)
var dueResponse = await client.GetAsync("/api/flashcards/due");
dueResponse.StatusCode.Should().Be(HttpStatusCode.OK);
var dueContent = await dueResponse.Content.ReadAsStringAsync();
var dueJson = JsonSerializer.Deserialize<JsonElement>(dueContent);
var flashcards = dueJson.GetProperty("data").GetProperty("flashcards");
var targetFlashcard = flashcards.EnumerateArray()
.FirstOrDefault(f => f.GetProperty("id").GetString() == flashcardId);
if (!targetFlashcard.Equals(default(JsonElement)))
{
// Step 3: 驗證 AI 生成的測驗選項
var quizOptions = targetFlashcard.GetProperty("quizOptions");
quizOptions.GetArrayLength().Should().BeGreaterThan(0, "應該有 AI 生成的測驗選項");
// 驗證選項不包含正確答案 (混淆選項)
var optionsList = quizOptions.EnumerateArray().Select(o => o.GetString()).ToList();
optionsList.Should().NotContain("非凡的", "混淆選項不應該包含正確翻譯");
}
}
[Fact]
public async Task VocabularyGenerationToReview_EndToEndFlow_ShouldWork()
{
// Arrange
var client = CreateTestUser1Client();
// Step 1: 從AI分析開始 → Step 2: 生成詞卡 → Step 3: 複習詞卡
var analysisRequest = new
{
text = "The sophisticated algorithm processes complex data efficiently.",
targetLevel = "C1"
};
await client.PostAsJsonAsync("/api/ai/analyze-sentence", analysisRequest);
// Step 2: 建立從分析中得出的詞彙 (模擬用戶選擇 "algorithm")
var newFlashcard = new
{
word = "algorithm",
translation = "演算法",
definition = "A process or set of rules for calculations",
partOfSpeech = "noun",
difficultyLevelNumeric = 5, // C1
synonyms = "[\"procedure\", \"method\", \"process\"]"
};
var createResponse = await client.PostAsJsonAsync("/api/flashcards", newFlashcard);
var createContent = await createResponse.Content.ReadAsStringAsync();
var createJson = JsonSerializer.Deserialize<JsonElement>(createContent);
var newFlashcardId = createJson.GetProperty("data").GetProperty("id").GetString();
// Step 3: 立即複習新詞卡
var reviewRequest = new
{
confidence = 1, // 中等信心度
wasSkipped = false,
responseTime = 4000
};
var reviewResponse = await client.PostAsJsonAsync($"/api/flashcards/{newFlashcardId}/review", reviewRequest);
// Assert: 驗證完整流程
reviewResponse.StatusCode.Should().Be(HttpStatusCode.OK);
var reviewContent = await reviewResponse.Content.ReadAsStringAsync();
var reviewJson = JsonSerializer.Deserialize<JsonElement>(reviewContent);
var reviewResult = reviewJson.GetProperty("data");
reviewResult.GetProperty("newSuccessCount").GetInt32().Should().Be(1, "新詞卡第一次答對應該成功次數為1");
// 驗證下次複習間隔
var nextReviewDate = DateTime.Parse(reviewResult.GetProperty("nextReviewDate").GetString()!);
var intervalHours = (nextReviewDate - DateTime.UtcNow).TotalHours;
intervalHours.Should().BeInRange(40, 56, "第一次答對應該約2天後再複習 (2^1 = 2天)");
}
}