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

182 lines
7.7 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>
/// 使用者資料隔離測試
/// 驗證多用戶環境下的資料安全和隔離機制
/// </summary>
public class DataIsolationTests : IntegrationTestBase
{
public DataIsolationTests(DramaLingWebApplicationFactory factory) : base(factory)
{
}
[Fact]
public async Task UserFlashcards_ShouldBeCompletelyIsolated()
{
// Arrange
var user1Client = CreateTestUser1Client();
var user2Client = CreateTestUser2Client();
// Act: 兩個用戶分別取得詞卡列表
var user1Response = await user1Client.GetAsync("/api/flashcards");
var user2Response = await user2Client.GetAsync("/api/flashcards");
// Assert
user1Response.StatusCode.Should().Be(HttpStatusCode.OK);
user2Response.StatusCode.Should().Be(HttpStatusCode.OK);
var user1Content = await user1Response.Content.ReadAsStringAsync();
var user2Content = await user2Response.Content.ReadAsStringAsync();
var user1Json = JsonSerializer.Deserialize<JsonElement>(user1Content);
var user2Json = JsonSerializer.Deserialize<JsonElement>(user2Content);
var user1Flashcards = user1Json.GetProperty("data").EnumerateArray().ToList();
var user2Flashcards = user2Json.GetProperty("data").EnumerateArray().ToList();
// 驗證 User1 只能看到自己的詞卡 (hello, beautiful)
user1Flashcards.Should().HaveCount(2);
user1Flashcards.Should().Contain(f => f.GetProperty("word").GetString() == "hello");
user1Flashcards.Should().Contain(f => f.GetProperty("word").GetString() == "beautiful");
// 驗證 User2 只能看到自己的詞卡 (sophisticated)
user2Flashcards.Should().HaveCount(1);
user2Flashcards.Should().Contain(f => f.GetProperty("word").GetString() == "sophisticated");
// 交叉驗證:確保絕對隔離
user1Flashcards.Should().NotContain(f => f.GetProperty("word").GetString() == "sophisticated");
user2Flashcards.Should().NotContain(f => f.GetProperty("word").GetString() == "hello");
user2Flashcards.Should().NotContain(f => f.GetProperty("word").GetString() == "beautiful");
}
[Fact]
public async Task ReviewData_ShouldBeIsolatedBetweenUsers()
{
// Arrange
var user1Client = CreateTestUser1Client();
var user2Client = CreateTestUser2Client();
// Step 1: 用戶1進行複習
var user1FlashcardId = TestDataSeeder.TestFlashcard1Id;
await user1Client.PostAsJsonAsync($"/api/flashcards/{user1FlashcardId}/review", new
{
confidence = 2,
wasSkipped = false,
responseTime = 2000
});
// Step 2: 檢查複習記錄隔離
using var context = GetDbContext();
var user1Reviews = context.FlashcardReviews
.Where(r => r.UserId == TestDataSeeder.TestUser1Id)
.ToList();
var user2Reviews = context.FlashcardReviews
.Where(r => r.UserId == TestDataSeeder.TestUser2Id)
.ToList();
// Assert: 驗證複習記錄隔離
user1Reviews.Should().HaveCountGreaterThan(0, "用戶1應該有複習記錄");
user2Reviews.Should().HaveCount(0, "用戶2不應該有複習記錄在測試資料中");
// 驗證 User1 的複習不會影響 User2 的資料
user1Reviews.Should().OnlyContain(r => r.UserId == TestDataSeeder.TestUser1Id);
}
[Fact]
public async Task CreateFlashcard_ShouldOnlyBeAccessibleByOwner()
{
// Arrange
var user1Client = CreateTestUser1Client();
var user2Client = CreateTestUser2Client();
// Step 1: User1 建立新詞卡
var newFlashcard = new
{
word = "isolation-test",
translation = "隔離測試",
definition = "A test for data isolation",
partOfSpeech = "noun"
};
var createResponse = await user1Client.PostAsJsonAsync("/api/flashcards", newFlashcard);
createResponse.StatusCode.Should().Be(HttpStatusCode.OK);
var createContent = await createResponse.Content.ReadAsStringAsync();
var createJson = JsonSerializer.Deserialize<JsonElement>(createContent);
var newFlashcardId = createJson.GetProperty("data").GetProperty("id").GetString();
// Step 2: User2 嘗試存取 User1 的詞卡
var accessResponse = await user2Client.GetAsync($"/api/flashcards/{newFlashcardId}");
// Assert: User2 應該無法存取 User1 的詞卡
accessResponse.StatusCode.Should().Be(HttpStatusCode.NotFound, "用戶不應該能存取其他用戶的詞卡");
// Step 3: User1 應該能正常存取自己的詞卡
var ownerAccessResponse = await user1Client.GetAsync($"/api/flashcards/{newFlashcardId}");
ownerAccessResponse.StatusCode.Should().Be(HttpStatusCode.OK, "用戶應該能存取自己的詞卡");
}
[Fact]
public async Task ReviewStats_ShouldBeUserSpecific()
{
// Arrange
var user1Client = CreateTestUser1Client();
var user2Client = CreateTestUser2Client();
// Act: 獲取各用戶的複習統計
var user1StatsResponse = await user1Client.GetAsync("/api/flashcards/review-stats");
var user2StatsResponse = await user2Client.GetAsync("/api/flashcards/review-stats");
// Assert
user1StatsResponse.StatusCode.Should().Be(HttpStatusCode.OK);
user2StatsResponse.StatusCode.Should().Be(HttpStatusCode.OK);
var user1StatsContent = await user1StatsResponse.Content.ReadAsStringAsync();
var user2StatsContent = await user2StatsResponse.Content.ReadAsStringAsync();
// 統計資料應該不同 (因為用戶有不同的詞卡和複習歷史)
user1StatsContent.Should().NotBe(user2StatsContent, "不同用戶的統計資料應該不同");
// 解析並驗證統計資料結構
var user1Stats = JsonSerializer.Deserialize<JsonElement>(user1StatsContent);
var user2Stats = JsonSerializer.Deserialize<JsonElement>(user2StatsContent);
user1Stats.GetProperty("success").GetBoolean().Should().BeTrue();
user2Stats.GetProperty("success").GetBoolean().Should().BeTrue();
}
[Fact]
public async Task MasteredFlashcards_ShouldOnlyAffectOwner()
{
// Arrange
var user1Client = CreateTestUser1Client();
var user2Client = CreateTestUser2Client();
var user1FlashcardId = TestDataSeeder.TestFlashcard1Id;
// Step 1: User1 標記詞卡為已掌握
var masteredResponse = await user1Client.PostAsync($"/api/flashcards/{user1FlashcardId}/mastered", null);
masteredResponse.StatusCode.Should().Be(HttpStatusCode.OK);
// Step 2: 驗證只影響 User1 的複習間隔
using var context = GetDbContext();
var user1Review = context.FlashcardReviews
.FirstOrDefault(r => r.FlashcardId == user1FlashcardId && r.UserId == TestDataSeeder.TestUser1Id);
var user2Reviews = context.FlashcardReviews
.Where(r => r.UserId == TestDataSeeder.TestUser2Id)
.ToList();
// Assert
user1Review.Should().NotBeNull("User1 應該有複習記錄");
user1Review!.SuccessCount.Should().BeGreaterThan(0, "User1 的成功次數應該增加");
// User2 的複習記錄不應受影響
user2Reviews.Should().NotContain(r => r.FlashcardId == user1FlashcardId, "User2 不應該有 User1 詞卡的複習記錄");
}
}