using System.Net; using System.Net.Http.Json; using System.Text.Json; using Microsoft.EntityFrameworkCore; namespace WordVersioning.Api.IntegrationTests; /// FR-16 — ผูก template ตอน Start Versioning: form field `templateId` ทางเลือกของ upload /// ใช้เฉพาะตอน auto-create Document · upload แรกของ Document ใหม่ = version 1 เสมอ + field sync เต็มระบบ [Collection(IntegrationTestCollection.Name)] public sealed class TemplateLinkingTests(IntegrationTestFixture fixture) { private static string DefaultFieldsJson() => JsonSerializer.Serialize(TestFlow.DefaultValues); [Fact] public async Task Upload_WithTemplateId_OnNewDocument_CreatesLinkedDocV1WithFieldSync() { using var client = fixture.CreateClient(); var (templateId, _) = await TestFlow.CreateConfiguredTemplateAsync(client, "linking หลัก"); // Start Versioning ของไฟล์ร่าง: upload แรกพร้อม templateId + fields ที่ extract จาก content controls var documentId = Guid.NewGuid(); using var upload = await client.PostAsync( $"api/documents/{documentId}/versions", ApiExtensions.VersionForm( TestDocx.BuildTemplateDocx(), "ไฟล์ร่างทดสอบ.docx", Guid.NewGuid().ToString(), fieldsJson: DefaultFieldsJson(), templateId: templateId.ToString())); Assert.Equal(HttpStatusCode.Created, upload.StatusCode); var v1 = (await upload.Content.ReadFromJsonAsync(IntegrationTestFixture.Json))!; // upload แรกของ Document ใหม่ = version 1 เสมอ (ไม่มี v0 ใน flow ไฟล์ร่าง) Assert.Equal(1, v1.VersionNumber); await using var db = fixture.CreateDb(); var document = await db.Documents.AsNoTracking() .Include(d => d.Versions) .SingleAsync(d => d.Id == documentId); Assert.Equal(templateId, document.TemplateId); // ผูก template ต้นทาง Assert.Equal(string.Empty, document.AddInSignature); // ฝั่ง add-in stamp ในไฟล์เอง — ค่าเดิมของ auto-create var version = Assert.Single(document.Versions); Assert.Equal(1, version.VersionNumber); Assert.NotNull(version.FieldsSnapshotJson); // snapshot แบบมีนิยาม (ไม่ใช่ raw map) // field sync เต็มระบบ (FR-11): DocumentFieldValue ครบทุก field ของ template var fieldValues = await db.DocumentFieldValues.AsNoTracking() .Where(v => v.DocumentId == documentId) .ToDictionaryAsync(v => v.FieldKey); Assert.Equal(4, fieldValues.Count); Assert.Equal("สมหญิง รักงาน", fieldValues["employee_name"].Value); Assert.Equal("75000", fieldValues["salary"].Value); Assert.All(fieldValues.Values, v => Assert.Equal(v1.VersionId, v.SourceVersionId)); Assert.All(fieldValues.Values, v => Assert.Equal(fixture.TestUserId, v.UpdatedByUserId)); // Save & Version ครั้งถัดไป (ไม่ส่ง templateId) = v2 และ TemplateId คงเดิม using var second = await client.PostAsync( $"api/documents/{documentId}/versions", ApiExtensions.VersionForm( TestDocx.BuildTemplateDocx(), "ไฟล์ร่างทดสอบ.docx", Guid.NewGuid().ToString(), fieldsJson: DefaultFieldsJson(), message: "แก้ไขรอบสอง")); Assert.Equal(HttpStatusCode.Created, second.StatusCode); var v2 = (await second.Content.ReadFromJsonAsync(IntegrationTestFixture.Json))!; Assert.Equal(2, v2.VersionNumber); await using var db2 = fixture.CreateDb(); var docAfter = await db2.Documents.AsNoTracking().SingleAsync(d => d.Id == documentId); Assert.Equal(templateId, docAfter.TemplateId); } [Fact] public async Task Upload_TemplateIdNotGuid_Returns400() { using var client = fixture.CreateClient(); var (templateId, _) = await TestFlow.CreateConfiguredTemplateAsync(client, "linking ไม่ใช่ GUID"); using var response = await client.PostAsync( $"api/documents/{Guid.NewGuid()}/versions", ApiExtensions.VersionForm( TestDocx.BuildPlainDocx(), "a.docx", Guid.NewGuid().ToString(), templateId: "not-a-guid")); Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); var error = await response.Content.ReadAsStringAsync(); Assert.Contains("templateId", error); } [Fact] public async Task Upload_TemplateIdUnknown_Returns400() { using var client = fixture.CreateClient(); _ = await TestFlow.CreateConfiguredTemplateAsync(client, "linking unknown"); // ตั้ง field defs ไว้ก่อน (ชุดเดียวกับที่ใช้จริง) using var response = await client.PostAsync( $"api/documents/{Guid.NewGuid()}/versions", ApiExtensions.VersionForm( TestDocx.BuildPlainDocx(), "a.docx", Guid.NewGuid().ToString(), templateId: Guid.NewGuid().ToString())); Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); } [Fact] public async Task Upload_TemplateIdDeactivated_StillLinks() { using var client = fixture.CreateClient(); var (templateId, _) = await TestFlow.CreateConfiguredTemplateAsync(client, "linking deactivate"); using var deactivate = await client.DeleteAsync($"api/templates/{templateId}"); Assert.Equal(HttpStatusCode.NoContent, deactivate.StatusCode); // ไฟล์ร่างถูกสร้างตอน template ยัง active — deactivate หลังจากนั้นต้องไม่ขวางการเริ่ม track (นโยบาย FR-9/FR-12) var documentId = Guid.NewGuid(); using var upload = await client.PostAsync( $"api/documents/{documentId}/versions", ApiExtensions.VersionForm( TestDocx.BuildTemplateDocx(), "ไฟล์ร่าง.docx", Guid.NewGuid().ToString(), fieldsJson: DefaultFieldsJson(), templateId: templateId.ToString())); Assert.Equal(HttpStatusCode.Created, upload.StatusCode); await using var db = fixture.CreateDb(); var document = await db.Documents.AsNoTracking().SingleAsync(d => d.Id == documentId); Assert.Equal(templateId, document.TemplateId); } [Fact] public async Task Upload_ExistingDocument_IgnoresTemplateId() { using var client = fixture.CreateClient(); var (templateId, _) = await TestFlow.CreateConfiguredTemplateAsync(client, "linking เฉย"); // upload แรกแบบ standalone (ไม่มี templateId) → Document ไม่ผูก template var documentId = Guid.NewGuid(); using var first = await client.PostAsync( $"api/documents/{documentId}/versions", ApiExtensions.VersionForm(TestDocx.BuildPlainDocx(), "plain.docx", Guid.NewGuid().ToString())); Assert.Equal(HttpStatusCode.Created, first.StatusCode); // upload ที่สองส่ง templateId มาด้วย — document มีอยู่แล้วจึงเฉย (TemplateId คง null) using var second = await client.PostAsync( $"api/documents/{documentId}/versions", ApiExtensions.VersionForm( TestDocx.BuildPlainDocx(), "plain.docx", Guid.NewGuid().ToString(), templateId: templateId.ToString())); Assert.Equal(HttpStatusCode.Created, second.StatusCode); var v2 = (await second.Content.ReadFromJsonAsync(IntegrationTestFixture.Json))!; Assert.Equal(2, v2.VersionNumber); await using var db = fixture.CreateDb(); var document = await db.Documents.AsNoTracking().SingleAsync(d => d.Id == documentId); Assert.Null(document.TemplateId); } [Fact] public async Task Upload_ExistingDocument_BadTemplateId_Returns400() { using var client = fixture.CreateClient(); _ = await TestFlow.CreateConfiguredTemplateAsync(client, "linking เฉย bad id"); var documentId = Guid.NewGuid(); using var first = await client.PostAsync( $"api/documents/{documentId}/versions", ApiExtensions.VersionForm(TestDocx.BuildPlainDocx(), "plain.docx", Guid.NewGuid().ToString())); Assert.Equal(HttpStatusCode.Created, first.StatusCode); // validate ทุกครั้งที่ส่งมา — แม้ document มีอยู่แล้วและค่าจะไม่ถูกใช้ก็ตาม using var second = await client.PostAsync( $"api/documents/{documentId}/versions", ApiExtensions.VersionForm( TestDocx.BuildPlainDocx(), "plain.docx", Guid.NewGuid().ToString(), templateId: "not-a-guid")); Assert.Equal(HttpStatusCode.BadRequest, second.StatusCode); } }