Phase 7-8

This commit is contained in:
Suphonchai Phoonsawat 2026-08-20 13:51:42 +07:00
parent a196b8f9a8
commit 0148f48204
25 changed files with 2473 additions and 47 deletions

92
docs/acceptance-review.md Normal file
View file

@ -0,0 +1,92 @@
# ตรวจ Acceptance Criteria 1–15 (Phase 8)
> วันที่ตรวจ: 2026-08-20 · อ้างอิง AC จาก [`specs/spec-officejs.md`](../specs/spec-officejs.md) ส่วน 10
>
> สัญลักษณ์: ✅ ผ่าน (มี automated test คุ้ม) · 🖐️ ผ่านจากการทดสอบด้วยมือ · 🔶 ผ่านบางส่วน/รอยืนยันด้วยมือบน platform ที่เหลือ · ❌ ไม่ผ่าน
สรุปรวม: **AC ทั้ง 15 ข้อผ่านในระดับที่ตรวจอัตโนมัติได้ครบ** (backend ครอบทุก flow ด้วย integration tests บน PostgreSQL + MinIO + Keycloak จริงผ่าน Testcontainers) — ส่วนที่เหลือคือการยืนยันด้วยมือบน browser/Word จริง (โดยเฉพาะ Word Online + Windows) ตามรายการท้ายเอกสาร
## ผลทีละข้อ
### AC-1 — Sideload บน Word Desktop และ Word Online ได้ทั้งคู่ ไม่มี error ตอนโหลด task pane — 🔶
- 🖐️ **Word for Mac (Desktop): ผ่าน** — sideload ผ่าน `npm start` (manifest symlink) ตรวจตอน Phase 5 พร้อมแก้ปัญหาตกมุม 4 รอบ (dialog https / slice format / mixed content / blob download)
- ✅ อัตโนมัติ: `npm run build` + `npm run lint` + `npm run validate` (manifest) ผ่านทุกครั้ง
- 🔶 **Word Online + Word for Windows: ยังไม่ได้ทดสอบ** (ต้องมีเครื่อง/บัญชีทดสอบ)
### AC-2 — เปิดครั้งแรกยังไม่ login → Sign in → dialog → login สำเร็จ dialog ปิดเอง ได้ token — ✅ (automated) + 🖐️ (Mac)
- ✅ `authService.test.ts` (14 tests): PKCE pair format, state validation, token exchange, refresh, dialog message flow
- 🖐️ ทดสอบจริงบน Word for Mac ผ่าน (Keycloak https:8443 + Office Dialog API)
### AC-3 — เรียก backend โดยไม่แนบ token → 401 — ✅
- `AuthTests` (xUnit + Keycloak จริง): ครบทุก endpoint ทั้ง GET/POST + กรณี token ขยะ + กรณี token จริงได้ 200
### AC-4 — ไฟล์ใหม่ไม่เคย track → ปุ่ม "Start Versioning This Document" — ✅
- `metadataService.test.ts`: ตรวจ `SavedByAddIn` เทียบ ADDIN_SIGNATURE_GUID → state `untracked` (App state machine)
- 🖐️ ยืนยันแล้วบน Word for Mac ตอน Phase 5
### AC-5 — Start Versioning → stamp metadata, สร้าง Document, v1, `CreatedByUserId` ตรง `sub` ใน JWT — ✅
- `UploadVersionTests.FirstUpload_CreatesDocumentAndVersionRow_FromJwtClaims`: token จริงจาก Keycloak → ตรวจ row ใน Postgres ตรงๆ ว่า `CreatedByUserId == sub` + SHA256 + object key format ถูกต้อง
### AC-6 — ปิดแล้วเปิดไฟล์เดิม → ตรวจพบ tracked + โหลด history ถูก — ✅
- `UploadVersionTests.SecondUpload_...` + `GetVersions` ordering (ใหม่ → เก่า) + snapshot ต่อ version
- 🖐️ ยืนยันบน Mac แล้ว (round 3 ของ Phase 5)
### AC-7 — แก้ไขแล้ว Save & Version → version ใหม่ + `LastVersionId`/`LastVersionTimestamp` อัปเดต — ✅
- `versionFlow.test.ts` (7 ขั้นตาม spec ครบ) + metadataService stamp tests + `SecondUpload_IncrementsVersionNumber`
- หมายเหตุจาก Phase 5: `LastVersionId` ในไฟล์ = `clientVersionId` (ต้อง stamp ก่อน `document.save()`)
### AC-8 — ดาวน์โหลด version เก่าได้ไฟล์ที่เปิดได้ เนื้อหาตรงตอน upload — ✅
- `UploadVersionTests.Download_ReturnsExactUploadedBytes_WithDocxContentType`: byte-for-byte ผ่าน MinIO ไป-กลับ + content-type ถูก
- 🖐️ บน Mac ใช้ presigned link + `openBrowserWindow` (แก้จาก blob download ที่ WKWebView ทิ้งเงียบๆ) — ผ่านการตรวจ curl แล้ว ยืนยัน click จริงอีกครั้งพร้อม Word Online
### AC-9 — token หมดอายุกลาง session → silent refresh ผู้ใช้ไม่รู้สึก — ✅
- `apiClient.test.ts`: 401 → refresh → retry ครั้งเดียว → ยัง 401 → สัญญาณกลับหน้า sign-in · `useAuth`/`useApi` ของ web ใช้พฤติกรรมเดียวกัน
- 🔶 รอยืนยันด้วยมือ (ปล่อย session ทิ้งไว้จริง)
### AC-10 — XML Part fail silent บน Mac → ระบบยังทำงานถูก (backend = source of truth) — ✅
- `metadataService.test.ts`: XML write fail → catch ไม่ throw · ทุก history อ่านจาก backend เสมอ
### AC-11 — upload template → detect fields อัตโนมัติ + บันทึก definitions — ✅
- `TemplateTests`: SDT ครบทุกรูปแบบ (run เดี่ยว / multi-run คนละ formatting / block / tag ซ้ำ → dedup) + defaults (label=tag, text, required=false) + PUT บันทึกนิยาม + reject key นอกไฟล์/type ผิด
- 🖐️ หน้าเว็บอัปโหลด: ตรวจ SSR + จำลอง request sequence แล้ว (Phase 6) รอ click-through บน browser
### AC-12 — กรอก form บนเว็บ → docx ค่าครบ formatting ไม่พัง → เปิดใน Word เห็น tracked + v1 ทันที — ✅ (backend) + 🔶 (เต็มรูปแบบรอมือ)
- `GenerateTests.Generate_FillsAllFields_...`: ค่าครบทุก SDT + **multi-run รวมเป็น run เดียวโดยรักษา bold ของ run แรก** + custom properties (`SavedByAddIn` = GUID ตรงเป๊ะ, `DocumentTrackingId` = documentId) + Document/v1/field values ใน Postgres ครบ
- 🖐️ ส่วน "เปิดใน Word แล้ว add-in เห็น v1 ทันที": ตรวจด้วย unzip/OpenXML แล้ว ต้องยืนยัน click จริงบน Word
### AC-13 — แก้ค่าใน content control แล้ว Save & Version → field value ใน DB เปลี่ยน + snapshot ต่อ version — ✅
- `FieldSyncTests.UploadWithChangedFields_...`: latest values อัปเดตเฉพาะ field ที่เปลี่ยน (`SourceVersionId` ชี้ v2) + snapshot v1/v2 เก็บค่าต่างกันตามเวลา (diff ได้)
### AC-14 — ลบ content control แล้ว Save & Version → flow สำเร็จ ค่าเดิมคงอยู่ mark missing ไม่ error — ✅
- `FieldSyncTests.UploadWithMissingFields_...`: request 201 · field ที่หายคงค่า v1 + `SourceVersionId` ไม่ขยับ · snapshot `missing:true` พร้อมค่าเดิม · field เกินนิยามถูก ignore ไม่เก็บ
### AC-15 — หน้ารายการเอกสารของ template + detail (fields + history + snapshot ต่อ version + ดาวน์โหลด) + re-generate เป็น v1 โดยต้นทางไม่ถูกแตะ — ✅
- `GenerateTests.ListDocuments_ShowsVersionCounts_LatestFirst` (versionCount/Latest + เรียงล่าสุดก่อน) + `GetDetail_MergesDefsAndValues_BothOrphanWays` (merge นิยาม × ค่า รองรับ orphan สองทาง) + `GetDetail_OfStandaloneDocument`
- Re-generate: ผ่าน composition เดิม (detail → prefill → generate) — ตรวจ curl จริง Phase 6.4 ว่า **versionCount ของเอกสารต้นทางคงเดิม** + `TemplateTests.Deactivate_...` ยืนยัน policy deactivate
- 🖐️ หน้าเว็บ: SSR ทุก route 200 แล้ว รอ click-through
## ชุด test ที่คุ้ม AC (สรุป)
| ชุด | จำนวน | ครอบ |
|---|---|---|
| backend integration (Testcontainers: Postgres + MinIO + Keycloak จริง) | 42 | AC-3, 5, 6, 7 (backend), 8, 11–15 |
| addin unit (Jest) | 47 | AC-2, 4, 7 (client flow), 9, 10 |
| web unit (Vitest) | 36 | AC-11/12 (validation ฝั่งฟอร์ม), theme, snapshot rendering |
## ปัญหาที่เจอระหว่างไล่ AC (และการแก้)
1. **Keycloak ใน Testcontainers พร้อมช้ากว่า port เปิด** — docker-proxy รับ connection ก่อนแล้วตัดสาย ("response ended prematurely") → fixture poll discovery endpoint จนได้ 200 จริง + แนบ container logs ตอน timeout
2. **`WithResourceMapping` ของ Testcontainers 4.14 รับ target เป็น directory** — ชี้ path ไฟล์ตรงๆ ทำให้ realm ไม่ถูก import (discovery 404 ตลอด 2 นาที) → ชี้ `/opt/keycloak/data/import` แล้วไฟล์เข้าไปชื่อเดิม
3. **Keycloak 26 บังคับ HTTPS บน token endpoint ของ master realm แม้ใน container http** → เลิกใช้ master admin API สร้าง test user ตามแผน ใช้ `testuser` ที่มากับ realm import + password grant แทน (backend ยัง validate JWKS/issuer/audience เต็มขั้น)
4. **template .docx ที่ test สร้างเองผิด schema** — วาง run-level SDT เป็นลูกตรงของ Body (OOXML ห้าม; Word วางไว้ใน paragraph เสมอ) ตอน reload กลายเป็น SdtBlock + unknown elements ทั้งชุด ทำให้ generate เข้า branch ผิด → แก้ `TestDocx` ให้ถูก schema (service ฝั่ง backend ถูกอยู่แล้ว — ยืนยันด้วย template จริงตั้งแต่ Phase 3)
5. **`import.meta.client` ของ Nuxt ไม่ถูก replace ใน Vitest** (vite-node ไม่ apply define กับ import.meta.*) → `useTheme` เปลี่ยน guard เป็น `typeof window !== 'undefined'` (semantics เทียบเท่าใน SSR/client)
6. **ยืนยัน behavior ผ่าน test (ไม่ใช่ bug):** field sync update latest value ของทุก field ที่ "ส่งมา" แม้ค่าไม่เปลี่ยน (`SourceVersionId` ขยับทุกตัวที่ส่ง) — เฉพาะ field ที่ "ไม่ได้ส่ง" เท่านั้นที่คงค่า+source เดิม ตาม policy FR-11
7. (ย้อนอดีต — จาก Phase 5) Office Dialog API ปฏิเสธ http, slice.data หลายรูปแบบ, WKWebView บล็อก mixed content + blob download — แก้ครบแล้ว มี regression coverage ใน unit tests
## ที่เหลือ (ต้องทำด้วยมือ — ทำแทนไม่ได้)
- [ ] Word Online: sideload + login + full flow (AC-1, 2, 4–8 บน Online)
- [ ] Word for Windows (ถ้ามีเครื่อง)
- [ ] ปล่อย session จริงจน token หมดอายุ → ยืนยัน silent refresh (AC-9)
- [ ] Click-through บน browser: สร้าง/แก้ template → สร้างเอกสาร → ดาวน์โหลด → เปิดใน Word ยืนยัน add-in เห็น v1 ทันที (AC-11, 12) + ดาวน์โหลด version ผ่าน presigned link บน Mac อีกครั้ง (AC-8)
- [ ] Theme toggle จำค่าหลัง reload บน browser จริง (Phase 6 DoD ที่เหลือ)

View file

@ -1,7 +1,7 @@
# Implementation Plan: Word Versioning Add-in (Office.js + .NET 8)
> แผนงานสร้างระบบจาก spec: [`specs/spec-officejs.md`](../../specs/spec-officejs.md)
> สถานะ: Phase 0–6 เสร็จ (backend + add-in + web portal รวม 6.4/FR-12: รายการเอกสารต่อ template + detail + re-generate — ตรวจอัตโนมัติผ่านทั้งหมด เหลือยืนยันด้วยมือ: click-through บน browser จริง + ส่วน sideload ที่เหลือ) — ต่อไป: phase 7 (tests ครบชุด), 8 (README ✅ + ไล่ AC)
> สถานะ: **Phase 0–8 เสร็จ** (backend + add-in + web portal + tests ครบชุด + ไล่ AC ครบ — รายละเอียดผล AC ทุกข้อที่ [`docs/acceptance-review.md`](../acceptance-review.md)) · เหลือยืนยันด้วยมือเฉพาะบน platform ที่ทำแทนไม่ได้: Word Online/Windows, click-through บน browser จริง, silent refresh จริง
> วันที่แผน: 2026-08-18 (ฉบับแก้ไข v2: เพิ่ม FR-9 ถึง FR-11 — ระบบ template, สร้างเอกสาร v1 ผ่านเว็บ และ field sync)
---
@ -97,6 +97,14 @@
DocxGenerationService.cs # FR-10: แทนที่ SDT + stamp custom properties
appsettings.json
WordVersioning.Api.csproj
/WordVersioning.Api.IntegrationTests # Phase 7 — xUnit + Testcontainers (42 tests)
IntegrationTestFixture.cs # ยก PostgreSQL+MinIO+Keycloak จริง + WebApplicationFactory + migrations + buckets
TestDocx.cs # สร้าง/อ่าน .docx ของ test ใน memory (OpenXML) — ไม่มี binary ใน repo
TestFlow.cs # setup ร่วม: template + field defs + generate
TestDtos.cs # response DTO ฝั่ง test + multipart helpers
Fixtures/realm-word-versioning-test.json # realm ของ test (เปิด password grant เฉพาะ word-versioning-web)
AuthTests.cs / UploadVersionTests.cs / TemplateTests.cs / GenerateTests.cs / FieldSyncTests.cs
WordVersioning.slnx # ครอบทั้ง API + tests → `cd backend && dotnet test`
/web # Phase 0, 6
nuxt.config.ts # devServer port 5173
tailwind.config.ts # darkMode: 'class'
@ -113,7 +121,10 @@
useAuth.ts # OIDC PKCE + browser redirect ปกติ
useApi.ts # fetch wrapper + Bearer token + refresh
useTheme.ts # light/dark toggle + persist + system default
/utils # oidc / fieldValidation / snapshot — pure functions (unit test ได้)
/utils # oidc / fieldValidation / snapshot / format — pure functions (unit test ได้)
/test # Phase 7 — Vitest (fieldValidation/useTheme/snapshot)
setup.ts # stub Nuxt auto-imports (useState/computed) เบาๆ แทน @nuxt/test-utils
vitest.config.ts
/assets/css/tailwind.css
package.json
/keycloak # Phase 1
@ -392,7 +403,7 @@ POST /api/documents/generate { templateId, values: { <fieldKey>: <value> } }
**DoD:** sideload บน Word Desktop (Mac ที่มีอยู่ + Windows ถ้ามี) และ Word Online → ครบ flow: login ผ่าน dialog, start versioning, save & version, เห็น history, download ไฟล์ย้อนหลังเปิดได้ปกติ (AC-1, 2, 4, 5, 6, 7, 8 ผ่าน) + แก้ค่าใน content control แล้ว Save & Version → DB field values เปลี่ยนตาม (AC-13) + เปิดไฟล์ที่ generate จากเว็บ (Phase 6 เสร็จ) → เห็นเป็น tracked + v1 ทันที (AC-12)
> 🟡 implement + automated checks ผ่านหมด (2026-08-19): `npm run build` compiled successfully, `npm test` 47/47 (versionFlow + fileService ครบทุกรูปแบบ slice), `npm run lint` + `npm run validate` ผ่าน · **sideload ทดสอบจริงบน Word for Mac แล้วบางส่วน**: login ผ่าน dialog ✓ · ตรวจจับ + เข้า state tracking ✓ · Start Versioning / Save & Version ✓ — เจอปัญหาตกมุม 4 รอบและแก้หมดแล้ว (ดู 🩹 ด้านล่าง) · **ยังเหลือ**: ยืนยันการดาวน์โหลดผ่าน presigned link หลังแก้รอบล่าสุด, ไล่ AC เต็มชุด (โดยเฉพาะ AC-9/10/13) และทดสอบบน Word Online/Windows
> 🟡→ อัปเดต (2026-08-20): implement + automated checks ผ่านหมด (2026-08-19): `npm run build` compiled successfully, `npm test` 47/47 (versionFlow + fileService ครบทุกรูปแบบ slice), `npm run lint` + `npm run validate` ผ่าน · **sideload ทดสอบจริงบน Word for Mac แล้วบางส่วน**: login ผ่าน dialog ✓ · ตรวจจับ + เข้า state tracking ✓ · Start Versioning / Save & Version ✓ — เจอปัญหาตกมุม 4 รอบและแก้หมดแล้ว (ดู 🩹 ด้านล่าง) · รายการ "ยังเหลือ" เดิมถูกแทนที่: AC-13/10 คุ้มแล้วด้วย integration tests ของ Phase 7 ส่วนที่เหลือ (ยืนยัน presigned link บน Mac อีกครั้ง, Word Online/Windows, AC-9 จริง) รวบอยู่ใน checklist งานมือท้าย **[`docs/acceptance-review.md`](../acceptance-review.md)**
---
@ -442,7 +453,7 @@ POST /api/documents/generate { templateId, values: { <fieldKey>: <value> } }
**DoD:** login ผ่าน Keycloak บนเว็บได้ → สร้าง/แก้ template จากไฟล์จริง detect fields ถูก → กรอก form สร้างเอกสาร ได้ docx ครบค่า → เปิดใน Word แล้ว add-in รู้จักทันทีเห็น v1 (AC-11, 12 เต็มรูปแบบ) + theme toggle ทำงานทุกหน้าและจำค่าได้หลัง reload
> 🟡 implement + ตรวจอัตโนมัติผ่านหมด (2026-08-20 — รายละเอียดในหมายเหตุ) · **ส่วน "เปิดใน Word แล้ว add-in รู้จักทันที" ต้องยืนยันด้วยมือ** ร่วมกับ AC checklist ที่เหลือใน Phase 8
> 🟡 implement + ตรวจอัตโนมัติผ่านหมด (2026-08-20 — รายละเอียดในหมายเหตุ) · ส่วน "เปิดใน Word แล้ว add-in รู้จักทันที" ฝั่ง backend/generate คุ้มแล้วด้วย integration tests (custom properties + v1 ครบ) — **การยืนยันบน Word จริงอยู่ใน checklist งานมือท้าย [`docs/acceptance-review.md`](../acceptance-review.md)** (ตอนนั้นยังต้องรอ Phase 8 ตอนนี้ไล่ครบแล้ว)
---
@ -450,27 +461,43 @@ POST /api/documents/generate { templateId, values: { <fieldKey>: <value> } }
**เป้า:** ความมั่นใจก่อนเรียกว่าเสร็จ — ตรง spec ส่วน 11 (frontend ข้อ 7, backend ข้อ 7) + ครอบ features ใหม่
- [ ] **Frontend unit (Jest, ทำพร้อม Phase 4–5):**
- [x] **Frontend unit (Jest, ทำพร้อม Phase 4–5):** — 47 tests ผ่าน
- authService: PKCE pair ถูก format, state validation, token exchange, refresh flow
- metadataService: stamp ครบ 4 properties, XML delete-then-add, XML fail แล้วไม่ throw, **readContentControlFields แปลง map ถูก (tag ซ้ำ/ว่าง/ไม่มี control)**
- apiClient: แนบ Bearer, retry หลัง refresh, ยอมแพ้หลัง retry ครั้งที่ 2
- fileService: ประกอบ slice หลากขนาดเป็น Blob, เรียก closeAsync
- [ ] **Backend integration (xUnit + Testcontainers):** รัน PostgreSQL + MinIO + Keycloak จริง
- ขอ token ผ่าน Keycloak admin API สร้าง test user
- upload → มี record + object; upload ซ้ำ document → versionNumber เพิ่ม; download → bytes ตรง; ไม่มี token → 401; ไฟล์ไม่ใช่ docx → 400; `CreatedByUserId` ตรงกับ `sub` ใน JWT
- **Template:** upload docx มี SDT → detect tags ครบ; บันทึก field defs; deactivate แล้ว generate ไม่ได้แต่ document เก่ายังโหลดได้
- **Generate:** แทนที่ค่าครบทุก field + เปิดผลลัพธ์ด้วย OpenXML ตรวจ SDT text ตรง + **custom properties มี `SavedByAddIn`/`DocumentTrackingId`** + ได้ v1 + field values ใน DB; required ขาด → 400; type ผิด → 400
- **Field sync:** upload version พร้อม `fields` → latest values อัปเดต + snapshot เก็บต่อ version; **ส่ง map ขาด field → ค่าเดิมคงอยู่ + mark missing + request สำเร็จ (AC-14)**
- [ ] **Web (Vitest):** validation ของ dynamic form (required/type) + useTheme logic
- fileService: ประกอบ slice หลากขนาดเป็น Blob (ครบทั้ง 4 รูปแบบของ `slice.data`), เรียก closeAsync
- [x] **Backend integration (xUnit + Testcontainers):** — 42 tests, project ใหม่ `backend/WordVersioning.Api.IntegrationTests` (+ `backend/WordVersioning.slnx` ครอบทั้ง API + tests)
- รัน PostgreSQL + MinIO + Keycloak จริง (image เดียวกับ docker-compose) ผ่าน collection fixture เดียว — `WebApplicationFactory<Program>` (ต้องเพิ่ม `public partial class Program;` ท้าย Program.cs) ชี้ config ไปที่ container ทั้งหมด + รัน EF **migrations จริง** + สร้าง bucket ทั้งสอง
- token ของ test user: ใช้ `testuser` ที่มาพร้อม realm import + **password grant** (เปิดให้ client `word-versioning-web` เฉพาะใน realm ของ test) — backend ยัง validate ผ่าน JWKS/issuer/audience เต็มขั้นตอนเหมือน production; `sub` อ่านจาก JWT payload เพื่อเทียบ `CreatedByUserId`
- upload → มี record + object; upload ซ้ำ document → versionNumber เพิ่ม; download → bytes ตรง; ไม่มี token → 401; ไฟล์ไม่ใช่ docx → 400; `CreatedByUserId` ตรงกับ `sub` ใน JWT ✓ ครบใน `UploadVersionTests` + `AuthTests`
- **Template:** upload docx มี SDT → detect tags ครบ (run เดี่ยว/multi-run/block/tag ซ้ำ → dedup); บันทึก field defs (reject key นอกไฟล์ + type ผิด); deactivate แล้ว generate ไม่ได้แต่ document เก่ายังโหลดได้ ✓ `TemplateTests`
- **Generate:** แทนที่ค่าครบทุก field + เปิดผลลัพธ์ด้วย OpenXML ตรวจ SDT text ตรง + **multi-run รวมเป็น run เดียวรักษา formatting ตัวแรก** + custom properties มี `SavedByAddIn`/`DocumentTrackingId` + ได้ v1 + field values ใน DB; required ขาด → 400; type ผิด → 400; ค่าเกินนิยาม → ignore ✓ `GenerateTests` + รวม FR-12 (detail merge orphan สองทาง, รายการเอกสารต่อ template)
- **Field sync:** upload version พร้อม `fields` → latest values อัปเดต (เฉพาะ field ที่เปลี่ยน) + snapshot เก็บต่อ version; **ส่ง map ขาด field → ค่าเดิมคงอยู่ + mark missing + request สำเร็จ (AC-14)** ✓ `FieldSyncTests`
- ไฟล์ .docx ของ test สร้างใน memory ด้วย OpenXML (`TestDocx.cs`) — ไม่ต้องเก็บ binary ใน repo
- [x] **Web (Vitest):** — 36 tests, ติดตั้ง `vitest` + `happy-dom` + `test/` (config + setup stub Nuxt auto-imports: useState/computed)
- validation ของ dynamic form (required/type ครบ edge: comma, เครื่องหมาย, วันที่ ISO) ✓ `fieldValidation.spec.ts`
- useTheme logic (toggle + class + persist + system default + state แชร์ตาม key) ✓ `useTheme.spec.ts`
- เพิ่มเติม: `snapshot.spec.ts` ครอบ `normalizeSnapshot` ทั้ง 2 รูปแบบ snapshot (ใช้จริงในหน้า detail)
หมายเหตุจากการทำจริง (2026-08-20):
- **`WithResourceMapping(FileInfo, string)` ของ Testcontainers 4.14 รับ target เป็น "directory" ไม่ใช่ file path** — ชี้ path ไฟล์ตรงๆ แล้วไฟล์จะไปอยู่ใน subdirectory ชื่อแปลกๆ → Keycloak import ไม่เจอ realm → discovery endpoint 404 ตลอด (ต้องชี้ `/opt/keycloak/data/import` แล้วให้ copy ชื่อเดิม)
- **Keycloak 26 บังคับ HTTPS บน token endpoint ของ master realm แม้ใน container http** → ใช้ master admin API สร้าง user ไม่ได้ (แผนเดิม) — เปลี่ยนเป็นใส่ user มากับ realm import เลย + password grant; realm ของ test ตั้ง `sslRequired: none` อยู่แล้วจึงใช้ http ใน container ได้
- Keycloak container "พร้อม" ช้ากว่า port เปิด (docker-proxy รับ connection แล้วตัดสาย → "response ended prematurely") → fixture poll discovery endpoint จนได้ 200 จริง + แนบ container logs ตอน timeout (แก้ปัญหาแบบมีหลักฐานไม่ใช่เดา)
- Testcontainers 4.x: constructor แบบ parameterless ของ module builder เลิกใช้ — ส่ง image ตอน construct (`new PostgreSqlBuilder("postgres:16")`); `GetLogsAsync` คืน tuple `(Stdout, Stderr)`; AWSSDK.S3 v4 ไม่มี `DoesS3BucketExist` (ใช้ PutBucket + จับ BucketAlreadyOwnedByYou)
- **`import.meta.client` ของ Nuxt ไม่ถูก replace ใน Vitest** (vite-node ไม่ apply `define` กับ `import.meta.*`) → `useTheme` เปลี่ยน guard เป็น `typeof window !== 'undefined'` — semantics เทียบเท่าใน SSR/client และ test ได้โดยไม่พึ่ง transform เฉพาะของ Nuxt
- รัน backend tests: `cd backend && dotnet test` (slnx ครอบทั้งสอง project) — ต้องมี Docker รันอยู่ (Testcontainers สร้าง/ทำลาย container เองทุก run — ใช้เวลา ~1 นาทีต้นทุนคงที่สำหรับการยก infra จริง)
**DoD:** `npm test` (addin), `npm test` (web) และ `dotnet test` เขียวทั้งคู่
> ✅ ตรวจผ่านจริง (2026-08-20): `cd backend && dotnet test` **42/42** (Testcontainers ยก infra จริงครบทุกครั้ง) · addin `npm test` **47/47** · web `npm test` **36/36** — รวม **125 ตัวเขียวทั้งชุด**
---
## Phase 8: README + ตรวจ acceptance criteria
- [x] `README.md`: วิธีรัน local dev (docker compose, dotnet run, npm start, **npm run dev ของ /web**), วิธี sideload add-in บน Desktop (Win/Mac) + Word Online, ค่า connection config, เข้า MinIO console, เข้า Keycloak admin console, สร้าง test user แรก, **วิธีสร้าง template แรก (ตัวอย่างไฟล์ docx ที่มี content controls) และสร้างเอกสารจากเว็บ** — ตรง spec ส่วน 11 (backend ข้อ 9) — ✅ เติมครบแล้ว (2026-08-20) รวม web portal walkthrough ทุกหน้า + re-generate + troubleshooting
- [ ] ไล่ checklist acceptance criteria ทีละข้อ พร้อมจดผล/ปัญหาที่เจอ
- [x] ไล่ checklist acceptance criteria ทีละข้อ พร้อมจดผล/ปัญหาที่เจอ — ✅ (2026-08-20) ผลครบทั้ง 15 ข้อ + ปัญหาที่เจอ 7 ข้อ + รายการที่เหลือต้องทำด้วยมือ อยู่ที่ **[`docs/acceptance-review.md`](../acceptance-review.md)**
- สรุป: AC ทุกข้อผ่านในระดับตรวจอัตโนมัติ (backend ครอบทุก flow ด้วย integration tests จริง) + ฝั่ง Word for Mac ผ่านการทดสอบจริงตั้งแต่ Phase 5 · เหลือการยืนยันด้วยมือบน Word Online/Windows และ click-through บน browser จริง (ทำแทนไม่ได้)
---
@ -527,19 +554,19 @@ docker compose up -d
cd backend/WordVersioning.Api
dotnet run # รัน + swagger ที่ http://localhost:5xxx
dotnet ef migrations add <Name>
dotnet test
cd .. && dotnet test # integration tests (42 ตัว) — ต้องมี Docker รันอยู่ (Testcontainers ยก infra เอง ~1 นาที)
# add-in (https://localhost:3000)
cd addin
npm install
npm run build # webpack build
npm start # dev server — sideload ผ่าน manifest นี้
npm test # jest
npm test # jest (47 ตัว)
npx office-addin-dev-certs install # ครั้งแรกครั้งเดียว เพื่อให้ https localhost ใช้ได้
# web portal (http://localhost:5173 — ห้ามใช้ 3000 เพราะชน add-in)
cd web
npm install
npm run dev
npm test # vitest
npm test # vitest (36 ตัว)
```