using DramaLing.Api.Tests.Integration.Fixtures;
using System.Net;
using System.Net.Http.Json;
using System.Text.Json;
namespace DramaLing.Api.Tests.Integration.EndToEnd;
///
/// 使用者資料隔離測試
/// 驗證多用戶環境下的資料安全和隔離機制
///
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(user1Content);
var user2Json = JsonSerializer.Deserialize(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(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(user1StatsContent);
var user2Stats = JsonSerializer.Deserialize(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 詞卡的複習記錄");
}
}