240 lines
10 KiB
C#
240 lines
10 KiB
C#
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天)");
|
||
}
|
||
} |