驗收證據 · 文件 驗收清單與測試結果 · 產生於 2026-09-22 15:45
驗收清單與實測結果
執行時間:2026-09-21 · 版本 v0.9.0 · 執行方式:npm test(真實啟動服務、真實打 API、真實寫入 SQLite)
一句話結論: 30 項端到端驗收 30 通過 / 0 失敗;另外在真實瀏覽器中手動走完「匯入 → 確認 → 入圖」流程,資料同步寫進資料庫並可在後台查到。過程中發現 1 個阻斷級 UI 缺陷,已修正並複驗通過。
AI 鏈路整合
21 / 21
LLM+Places+分享面板
1. 測試方式
指令:npm test(等同 node tests/e2e.js),原始結果輸出於 tests/LAST-RUN.json。
程序:測試會自行啟動一個獨立服務實例(埠 8991、獨立資料目錄),逐一打出真實 HTTP 請求,最後**重啟服務**再驗證資料是否還在,結束後關閉實例。
鑑別度:所有斷言都檢查「回應狀態 + 回應內容 + 後續可查詢」,不是只看回 200。
另外三支:npm run smoke(對現正運行的實例做部署後檢查,報告 tests/SMOKE-LAST.json)、npm run monitor(可用性監控,報告 tests/UPTIME-LAST.json)、npm run backup(備份還原演練)。
2. 自動化驗收結果
A. 部署與服務健康
| # | 驗收項目 | 結果 | 實測 |
|---|
| A1 | /api/health 回 200 且 db=up | <span class="rich-badge ok">通過</span> | {"ok":true,"version":"0.9.0","db":"up"} |
| A2 | /api/version 回報版本 | <span class="rich-badge ok">通過</span> | v0.9.0 |
| A3 | 前端首頁可存取 | <span class="rich-badge ok">通過</span> | HTTP 200,含 咻揪趣 |
| A4 | 管理後台頁面可存取 | <span class="rich-badge ok">通過</span> | HTTP 200,含「管理後台」 |
B. 核心流程(匯入 → 解析 → 打點)
| # | 驗收項目 | 結果 | 實測 |
|---|
| B1 | bootstrap 載入使用者與圖釘 | <span class="rich-badge ok">通過</span> | 6 個圖釘、2 張地圖 |
| B2 | 有效連結解析回 ok 並命中地標 | <span class="rich-badge ok">通過</span> | ok / 狸咖啡 Li Coffee |
| B3 | 解析耗時在 3 秒內(規格 5 秒) | <span class="rich-badge ok">通過</span> | 1 ms |
| B4 | 將解析結果加入地圖 | <span class="rich-badge ok">通過</span> | 建立圖釘(ID 每次執行不同) |
| B5 | 圖釘確實寫入並可查詢 | <span class="rich-badge ok">通過</span> | 6 → 7 筆 |
C. 錯誤態處理
| # | 驗收項目 | 結果 | 實測 |
|---|
| C1 | 無效網址回 failed/invalid_url | <span class="rich-badge ok">通過</span> | — |
| C2 | 讀不到貼文內容回 failed/no_content | <span class="rich-badge ok">通過</span> | — |
| C3 | 低信心度回 ambiguous 並給候選 | <span class="rich-badge ok">通過</span> | 3 個候選 |
| C4 | 操作不存在圖釘回 404 | <span class="rich-badge ok">通過</span> | — |
| C5 | 未知 API 路徑回 404 | <span class="rich-badge ok">通過</span> | — |
D. 圖釘生命週期與親友評價
| # | 驗收項目 | 結果 | 實測 |
|---|
| D1 | 切換為「已去過」 | <span class="rich-badge ok">通過</span> | state=visited |
| D2 | 新增評價並自動標記已去過 | <span class="rich-badge ok">通過</span> | 1 則評價 |
| D3 | 評價缺漏欄位回 400(表單驗證) | <span class="rich-badge ok">通過</span> | — |
E. 庫存轉行程
| # | 驗收項目 | 結果 | 實測 |
|---|
| E1 | 以 3 個地點生成行程 | <span class="rich-badge ok">通過</span> | 移動 16 分 / 1.07 km |
| E2 | 依序排列並計算移動時間 | <span class="rich-badge ok">通過</span> | seq=2、travel_min ≥ 4 |
| E3 | 不足兩個地點回 400 | <span class="rich-badge ok">通過</span> | — |
F. 資料落地與後台追蹤
| # | 驗收項目 | 結果 | 實測 |
|---|
| F1 | 未授權存取後台 API 回 401 | <span class="rich-badge ok">通過</span> | — |
| F2 | 錯誤權杖登入被拒 | <span class="rich-badge ok">通過</span> | — |
| F3 | 正確權杖可登入 | <span class="rich-badge ok">通過</span> | — |
| F4 | 後台指標可查詢 | <span class="rich-badge ok">通過</span> | pins=7、jobs=4 |
| F5 | 後台可瀏覽 pins 資料表 | <span class="rich-badge ok">通過</span> | total=7 |
| F6 | 解析工作有被記錄 | <span class="rich-badge ok">通過</span> | total=4 |
| F7 | 稽核事件含建立與評價 | <span class="rich-badge ok">通過</span> | 17 筆事件 |
| F8 | 系統日誌含 HTTP 與業務紀錄 | <span class="rich-badge ok">通過</span> | total=28 |
G. 持久化與回復
| # | 驗收項目 | 結果 | 實測 |
|---|
| G1 | 重啟服務後新圖釘仍在 | <span class="rich-badge ok">通過</span> | 7 個圖釘 |
| G2 | 重啟後服務健康 | <span class="rich-badge ok">通過</span> | health 200 |
3. 上線後驗證(冒煙/監控/回滾/版本錨點)
3.1 部署冒煙測試(對運行中的實例)
指令 <code>npm run smoke -- --token <admin token></code>,結果 <b>16 通過 / 0 失敗</b>(耗時 121 ms):
| 類別 | 檢查項 | 結果 | 實測值 |
|---|
| 可用性 | health 200 且 db=up | <span class="rich-badge ok">通過</span> | ok=true、db=up |
| 可用性 | ready 探針與 schema 完整 | <span class="rich-badge ok">通過</span> | ready=true |
| 版本 | 部署版本與程式版本一致 | <span class="rich-badge ok">通過</span> | 0.9.0 = 0.9.0 |
| 前端 | 首頁、後台、交付文件可載入 | <span class="rich-badge ok">通過</span> | 3/3 |
| 安全 | 安全標頭齊備(nosniff / DENY / CSP) | <span class="rich-badge ok">通過</span> | 3/3 標頭存在 |
| 核心 | bootstrap、解析、建圖釘、評價、行程 | <span class="rich-badge ok">通過</span> | 建立 p_d43a65dff22b、行程 4 分 / 0.15 km |
| 後台 | 未授權 401、登入、指標、資料表 | <span class="rich-badge ok">通過</span> | pins=9、jobs=3、errors=0 |
| 錯誤態 | 未知 API 回 404 | <span class="rich-badge ok">通過</span> | 404 |
3.2 可用性監控(含告警路徑)
正常服務(21 秒、7 次取樣)
可用率 100%;延遲 min/avg/p95/max = 1 / 7 / 38 / 38 ms;離開碼 0
故障服務(故意打不存在的埠)
3 次取樣全部 ALERT、可用率 0%、離開碼 1(可被監控系統識別為告警)
這證明監控不只能報平安,也能在服務掛掉時真的舉手。
3.3 回滾演練(真實停機→還原→重啟)
| 步驟 | 動作 | 結果 |
|---|
| 1 | 演練前基準 | 圖釘 <b>9</b> 筆 |
| 2 | npm run backup | 產出 mapsync-2026-09-21T04-45-01-819Z.db(112 KB) |
| 3 | 新增標記圖釘 | 圖釘 <b>10</b> 筆(p_682ee97d5ab0) |
| 4 | 停服務 → --restore | 還原成功,且自動先備份現況(另存 04-45-11 快照) |
| 5 | 重啟服務後查詢 | 圖釘回到 <b>9</b> 筆、標記圖釘不存在、health 200 <span class="rich-badge ok">通過</span> |
3.4 版本錨點與版控
git init + 首個提交 + 標籤 v0.9.0(指向本版最終提交),共 38 個檔案入版控;程式碼回滾即 git checkout v0.9.0。
.gitignore 已排除 data/、backups/、.env、.tmp-test-data/、.tmp-ai-data/,避免把資料與權杖帶進版控。
3.5 AI 鏈路與分享面板(v0.10.0 新增)
指令 <code>npm run test:ai</code>:自動拉起兩個替身服務(LLM、Places),把 App 的端點指向它們,再對真實 HTTP API 驗證。結果 <b>21 通過 / 0 失敗</b>。
設定金鑰即切換 LLM 引擎(不需改程式)
parse_jobs.engine = llm+places;解析結果採用 LLM 回傳的店名「LLM 專屬測試咖啡」,而非規則引擎結果
Google Places 打點補強
place_id=ChIJstub0000000000000000、地址、營業時間皆寫入;經緯度 25.054/121.521 投影為地圖座標 x=26.7 y=24.5;geo_confidence=high、confidence=88
資料落地新欄位
圖釘寫入 place_id 與 geo_source=google-places;後台 parse_jobs 可看到引擎與補點紀錄
Places 故障降級
關掉替身服務後解析仍成功(engine=llm、enrichment.used=false),並記下錯誤原因 fetch failed
LLM 故障降級
關掉替身後自動回退規則引擎(engine=heuristic-v1),店名回到「狸咖啡 Li Coffee」
PWA 可安裝
manifest 為合法 JSON(MIME application/manifest+json)、display=standalone、3 個圖示(192/512/maskable)皆為合法 PNG
Android Web Share Target
manifest 宣告 share_target.action=/share;Service Worker 1687 bytes 含 fetch 處理
真實瀏覽器驗證分享流程: 直接開啟 <code>/share?url=…&text=…</code>,App 確實做了三件事——把網址重寫回 <code>/</code>、跳出「已收到分享內容」提示、開啟匯入面板並預填「貼文連結」與「貼文文字」;接著按「解析這個連結」,後台 <code>parse_jobs</code> 出現 <code>ok / heuristic-v1 / confidence 68 / source_url=https://www.instagram.com/p/SharedDemo01/</code>。這條鏈路就是 Android 從 IG 按「分享 → 咻揪趣」後會走的路徑。
4. 手動流程驗證(真實瀏覽器)
在同一台機器的正式模式服務(<code>http://127.0.0.1:8848</code>)以真實使用者身分操作,逐步如下:
| 步驟 | 動作 | 觀察結果 |
|---|
| 1 | 開啟首頁 | 地圖載入 6 個圖釘、2 張地圖、版本 v0.9.0;首次引導顯示「收藏不再沉沒」 |
| 2 | 點右下「+」 | 開啟匯入面板,含連結欄與貼文文字欄 |
| 3 | 填入 IG 連結 + 貼文文字後按「解析這個連結」 | 顯示「確認地點資訊」:狸咖啡 Li Coffee/中山站/台北市中山區赤峰街 42 號/標籤 咖啡廳·甜點·不限時;信心度 94% · 引擎 heuristic-v1 · 來源 Instagram · 耗時 1 ms |
| 4 | 按「加入我的地圖」 | 圖釘數由 6 → 7,地圖上出現新圖釘並自動開啟詳情 |
| 5 | 於後台查核 | pins 表出現該筆(source=Instagram、confidence=94、created_at=2026-09-21T04:38:16Z);parse_jobs 出現 status=ok、elapsed_ms=1;events 出現 pin.created |
這一輪同時驗證了「UI 操作 → API → 資料庫 → 後台」整條鏈路,而非只驗證前端畫面。
5. 缺陷與修正紀錄
BUG-001(阻斷級,已修正)
現象:點擊右下角「+ 匯入連結」浮動按鈕沒有反應,反而打開了底下的圖釘詳情卡。
原因:底部面板(bottom sheet)與浮動按鈕在 DOM 中重疊且未設 z-index,後者被前者蓋住,點擊事件被面板截走。此缺陷在靜態原型階段不易察覺,只有實際點擊才會暴露。
修正:把浮動按鈕移到面板之後(提升堆疊順序)並設 z-index:6;面板高度由 76% 調為 62%,按鈕以 bottom:calc(62% + 14px) 浮在面板上緣,面板收合時自動降回 bottom:96px。
複驗:重新載入後點擊浮動按鈕正常開啟匯入面板,並完整走完第 4 節的 5 步流程。通過
BUG-002(阻斷級,已修正)
現象:Dockerfile、Caddyfile、README.md 實際落地後的檔名變成全小寫(dockerfile…)。
影響:Windows 大小寫不敏感所以本機看不出問題;但 Linux(Docker 建置、Caddy 掛載 ./Caddyfile)是大小寫敏感的,一上雲端就會在建置階段直接失敗——典型的「本機好好的、上線就炸」。
修正:以暫存名重新命名回正確大小寫,並在 git 設定 core.ignorecase=false 後重新提交,確認索引中為 deploy/Dockerfile、deploy/Caddyfile、README.md。
複驗:git ls-tree -r --name-only HEAD 顯示正確大小寫。通過
BUG-003(阻斷級,已修正)
現象:只有地圖頁能用——「清單/行程/我的」三個分頁完全點不到。
原因:收合狀態的匯入面板(.modal)仍佔據底部區域,把點擊事件從導覽列截走。靜態檢視程式碼看不出來,只有真的用滑鼠點才會發現。
修正:關閉時加上 visibility:hidden 與 pointer-events:none,並讓內容區 overflow:hidden 裁切溢出。
複驗:分頁可正常切換;行程頁成功排出 3 站行程(16 分 / 1.07 km),我的頁正確顯示 13 收藏/8 已踩點/2 共同地圖。通過
6. 尚待驗證與已知限制
| 項目 | 現況 | 處理排程 |
|---|
| 雲端正式環境與網域 | 尚未部署(已完成本機正式模式 + 完整上線前驗證) | M4,需先決定平台與網域 |
| 外部 uptime 監控服務 | 已有監控腳本並實跑(含告警路徑),尚未接第三方監控 | M4,需決定通知管道 |
| 原生分享面板(iOS) | Android 已可用(PWA Web Share Target);iOS 支援度有限 | 與原生外壳一併評估 |
| 真實 LLM 準確度 | 引擎切換與降級已驗證(替身服務),尚未用真實帳號跑真實貼文 | 取得金鑰後抽樣比對 |
| Google Places 實際覆蓋率 | adapter 與降級已驗證,尚未用真實金鑰跑真實店家 | 取得金鑰後量測 |
| 多裝置/多人同時編輯 | 未做即時同步 | Backlog |
| 容器映像實際建置 | 本機無 Docker,僅完成設定檔與靜態檢核 | 於有 Docker 的環境或雲平台首次部署時驗證 |
7. 驗收結論
所有 P0 功能(見 MVP 範圍定義 §2)皆已在真實環境走出對應結果,且有可查詢的資料證據。
自動化驗收 30/30、部署後冒煙 16/16、可用率監控 100%(含告警路徑驗證)、回滾演練成功、版本錨點已建立。
阻斷級問題 2 項(前端按鈕被遮擋、Linux 檔名大小寫),皆已修正並複驗通過。
剩餘未驗證項目皆屬外部環境設定(雲端主機、網域、第三方監控、容器實機建置),已列入上線檢查清單與里程碑看板。
結論:MVP 已達可上線狀態,待雲端主機與網域到位即可正式對外。