咻揪趣 MVP · v0.19.0
驗收證據 · 文件 驗收清單與測試結果 · 產生於 2026-09-22 15:45

驗收清單與實測結果

執行時間:2026-09-21 · 版本 v0.9.0 · 執行方式:npm test(真實啟動服務、真實打 API、真實寫入 SQLite)

一句話結論: 30 項端到端驗收 30 通過 / 0 失敗;另外在真實瀏覽器中手動走完「匯入 → 確認 → 入圖」流程,資料同步寫進資料庫並可在後台查到。過程中發現 1 個阻斷級 UI 缺陷,已修正並複驗通過。

端到端驗收
30 / 30
通過率 100%
上線冒煙
16 / 16
對運行中實例
可用率監控
100%
7 次取樣 · 告警路徑已驗
阻斷級缺陷
3 → 0
已修正並複驗
回滾演練
10 → 9
備份還原成功
AI 鏈路整合
21 / 21
LLM+Places+分享面板
版本錨點
v0.10.0
38 檔入版控

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. 核心流程(匯入 → 解析 → 打點)

    #驗收項目結果實測
    B1bootstrap 載入使用者與圖釘<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 &lt;admin token&gt;</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> 筆
    2npm 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=highconfidence=88
    資料落地新欄位
    圖釘寫入 place_idgeo_source=google-places;後台 parse_jobs 可看到引擎與補點紀錄
    Places 故障降級
    關掉替身服務後解析仍成功(engine=llmenrichment.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=…&amp;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=Instagramconfidence=94created_at=2026-09-21T04:38:16Z);parse_jobs 出現 status=okelapsed_ms=1events 出現 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(阻斷級,已修正)
  • 現象:DockerfileCaddyfileREADME.md 實際落地後的檔名變成全小寫(dockerfile…)。
  • 影響:Windows 大小寫不敏感所以本機看不出問題;但 Linux(Docker 建置、Caddy 掛載 ./Caddyfile)是大小寫敏感的,一上雲端就會在建置階段直接失敗——典型的「本機好好的、上線就炸」。
  • 修正:以暫存名重新命名回正確大小寫,並在 git 設定 core.ignorecase=false 後重新提交,確認索引中為 deploy/Dockerfiledeploy/CaddyfileREADME.md
  • 複驗:git ls-tree -r --name-only HEAD 顯示正確大小寫。通過
  • BUG-003(阻斷級,已修正)
  • 現象:只有地圖頁能用——「清單/行程/我的」三個分頁完全點不到。
  • 原因:收合狀態的匯入面板(.modal)仍佔據底部區域,把點擊事件從導覽列截走。靜態檢視程式碼看不出來,只有真的用滑鼠點才會發現。
  • 修正:關閉時加上 visibility:hiddenpointer-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 已達可上線狀態,待雲端主機與網域到位即可正式對外。