eCourt-Doc-Edit/backend/WordVersioning.Api.IntegrationTests/UploadVersionTests.cs

172 lines
8.4 KiB
C#
Raw Normal View History

2026-08-20 13:51:42 +07:00
using System.Net;
using System.Net.Http.Json;
using System.Security.Cryptography;
using System.Text.Json;
using Microsoft.EntityFrameworkCore;
namespace WordVersioning.Api.IntegrationTests;
/// <summary>FR-5 (upload), FR-6 (history), FR-7 (download) — ผ่าน HTTP จริง + ตรวจสถานะใน Postgres ตรงๆ</summary>
[Collection(IntegrationTestCollection.Name)]
public sealed class UploadVersionTests(IntegrationTestFixture fixture)
{
[Fact]
public async Task FirstUpload_CreatesDocumentAndVersionRow_FromJwtClaims()
{
var documentId = Guid.NewGuid();
var clientVersionId = Guid.NewGuid();
var bytes = TestDocx.BuildPlainDocx("version 1 content");
using var client = fixture.CreateClient();
using var response = await client.PostAsync(
$"api/documents/{documentId}/versions",
ApiExtensions.VersionForm(bytes, "เอกสารทดสอบ.docx", clientVersionId.ToString()));
Assert.Equal(HttpStatusCode.Created, response.StatusCode);
var result = await response.Content.ReadFromJsonAsync<UploadVersionResult>(IntegrationTestFixture.Json);
Assert.Equal(1, result!.VersionNumber);
Assert.NotEqual(Guid.Empty, result.VersionId);
// ตรวจ DB จริง: Document auto-create + ค่า tracking จาก JWT claims (AC-5)
await using var db = fixture.CreateDb();
var doc = await db.Documents.AsNoTracking().SingleAsync(d => d.Id == documentId);
Assert.Equal("เอกสารทดสอบ.docx", doc.FileName);
Assert.Null(doc.TemplateId); // track เองจาก add-in — ไม่ผูก template
var version = await db.DocumentVersions.AsNoTracking().SingleAsync(v => v.DocumentId == documentId);
Assert.Equal(1, version.VersionNumber);
Assert.Equal(fixture.TestUserId, version.CreatedByUserId); // sub จาก token เท่านั้น
Assert.Equal(Convert.ToHexString(SHA256.HashData(bytes)).ToLowerInvariant(), version.ContentHash);
Assert.Equal($"{documentId}/v0001_{clientVersionId}.docx", version.ObjectKey);
}
[Fact]
public async Task SecondUpload_IncrementsVersionNumber_AndHistorySortsNewestFirst()
{
var documentId = Guid.NewGuid();
using var client = fixture.CreateClient();
var v1 = TestDocx.BuildPlainDocx("v1");
using var first = await client.PostAsync(
$"api/documents/{documentId}/versions",
ApiExtensions.VersionForm(v1, "doc.docx", Guid.NewGuid().ToString()));
var firstResult = await first.Content.ReadFromJsonAsync<UploadVersionResult>(IntegrationTestFixture.Json);
var v2 = TestDocx.BuildPlainDocx("v2 — แก้ไขแล้ว");
using var second = await client.PostAsync(
$"api/documents/{documentId}/versions",
ApiExtensions.VersionForm(v2, "doc.docx", Guid.NewGuid().ToString()));
Assert.Equal(HttpStatusCode.Created, second.StatusCode);
var secondResult = await second.Content.ReadFromJsonAsync<UploadVersionResult>(IntegrationTestFixture.Json);
Assert.Equal(2, secondResult!.VersionNumber);
// FR-6: เรียงใหม่ → เก่า
using var history = await client.GetAsync($"api/documents/{documentId}/versions");
var items = await history.Content.ReadFromJsonAsync<List<VersionItem>>(IntegrationTestFixture.Json);
Assert.Equal(2, items!.Count);
Assert.Equal(secondResult.VersionId, items[0].VersionId);
Assert.Equal(2, items[0].VersionNumber);
Assert.Equal(firstResult!.VersionId, items[1].VersionId);
Assert.Equal(1, items[1].VersionNumber);
Assert.All(items, i => Assert.Equal(fixture.TestUserId, i.CreatedByUserId));
}
[Fact]
public async Task Download_ReturnsExactUploadedBytes_WithDocxContentType()
{
var documentId = Guid.NewGuid();
var bytes = TestDocx.BuildPlainDocx("ไบต์ต้องตรงกันตอนดาวน์โหลด (AC-8 ส่วน backend)");
using var client = fixture.CreateClient();
using var upload = await client.PostAsync(
$"api/documents/{documentId}/versions",
ApiExtensions.VersionForm(bytes, "roundtrip.docx", Guid.NewGuid().ToString()));
var uploaded = await upload.Content.ReadFromJsonAsync<UploadVersionResult>(IntegrationTestFixture.Json);
using var download = await client.GetAsync(
$"api/documents/{documentId}/versions/{uploaded!.VersionId}/download");
Assert.Equal(HttpStatusCode.OK, download.StatusCode);
Assert.Equal(TestDocx.DocxContentType, download.Content.Headers.ContentType!.MediaType);
var downloaded = await download.Content.ReadAsByteArrayAsync();
Assert.Equal(bytes, downloaded); // byte-for-byte ผ่าน MinIO ไป-กลับ
}
[Fact]
public async Task Download_UnknownVersion_Returns404()
{
using var client = fixture.CreateClient();
using var response = await client.GetAsync(
$"api/documents/{Guid.NewGuid()}/versions/{Guid.NewGuid()}/download");
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
public async Task GetVersions_UnknownDocument_Returns404()
{
using var client = fixture.CreateClient();
using var response = await client.GetAsync($"api/documents/{Guid.NewGuid()}/versions");
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
public async Task Upload_NonDocxFile_Returns400()
{
var documentId = Guid.NewGuid();
using var client = fixture.CreateClient();
using var form = new MultipartFormDataContent();
var file = new ByteArrayContent("นี่ไม่ใช่ word"u8.ToArray());
file.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("text/plain");
form.Add(file, "file", "notes.txt"); // ชื่อไม่ลงท้าย .docx
form.Add(new StringContent(Guid.NewGuid().ToString()), "clientVersionId");
using var response = await client.PostAsync($"api/documents/{documentId}/versions", form);
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
await using var db = fixture.CreateDb();
Assert.False(await db.Documents.AsNoTracking().AnyAsync(d => d.Id == documentId)); // ไม่มี side effect
}
[Fact]
public async Task Upload_InvalidClientVersionId_Returns400()
{
using var client = fixture.CreateClient();
using var response = await client.PostAsync(
$"api/documents/{Guid.NewGuid()}/versions",
ApiExtensions.VersionForm(TestDocx.BuildPlainDocx(), "doc.docx", "ไม่ใช่ GUID"));
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
[Fact]
public async Task Upload_InvalidFieldsJson_Returns400()
{
using var client = fixture.CreateClient();
using var response = await client.PostAsync(
$"api/documents/{Guid.NewGuid()}/versions",
ApiExtensions.VersionForm(TestDocx.BuildPlainDocx(), "doc.docx", Guid.NewGuid().ToString(), "not json"));
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
/// <summary>เอกสารไม่ผูก template → snapshot เก็บ map ดิบตามที่ส่งมา (behavior ของ Phase 2)</summary>
[Fact]
public async Task Upload_WithoutTemplate_StoresRawFieldsMapInSnapshot()
{
var documentId = Guid.NewGuid();
using var client = fixture.CreateClient();
using var upload = await client.PostAsync(
$"api/documents/{documentId}/versions",
ApiExtensions.VersionForm(
TestDocx.BuildPlainDocx(), "doc.docx", Guid.NewGuid().ToString(),
"""{"custom_tag":"ค่าที่ส่งมา"}"""));
Assert.Equal(HttpStatusCode.Created, upload.StatusCode);
using var history = await client.GetAsync($"api/documents/{documentId}/versions");
var items = await history.Content.ReadFromJsonAsync<List<VersionItem>>(IntegrationTestFixture.Json);
var fields = items!.Single().Fields!.Value;
Assert.Equal(JsonValueKind.Object, fields.ValueKind);
Assert.Equal("ค่าที่ส่งมา", fields.GetProperty("custom_tag").GetString());
}
}