Files
arcrun-collector/convert_pdf_test.go
T
Leo 55ed2c86a7 feat(t73): PDF 抽取器接上 PDFium-via-wazero——leo 裁定的引擎,實測通過
 決定性實測:Chrome headless 列印的中文 PDF 完整抽出
   「船舶維修合約書/第一條 維修費用為新台幣 350,000 元整/2026 年 8 月 15 日」
   ——這正是 pdf-extraction-options.md §3 實測「純 Go 套件會整段亂碼」的那種檔,
   也是客戶最常丟的(瀏覽器列印)。選 PDFium 的理由至此坐實。

用戶不必裝任何東西(回答 leo「我的用戶可能不會自己裝 Python 環境」):
pdfium.wasm 由 wazero(純 Go WASM runtime)在本行程跑,embed 進執行檔。
實測 CGO_ENABLED=0 GOOS=windows 交叉編譯一行成功 → PE32+ executable x86-64,
證實「無 CGo」承諾為真,不加重 t72 負擔。

體積實測(有實際呼叫路徑,避開今天記過的 dead-code 陷阱):
  mac 14.53MB / windows 15.15MB(strip 後)
  leo 定調「全運作在客戶電腦上,肥一點喘一點也沒事」

設計決定:
- lazy init(sync.Once)——多數資料夾可能一個 PDF 都沒有,不該白付 WASM 啟動的記憶體
- pdfMu 互斥——pool 單 worker,別依賴「scan 是循序」這個假設
- 單頁失敗不放棄整份,但留「(第 N 頁讀取失敗)」痕跡
- 抽不出字 → ErrNoText(掃描件;PDFium 不做 OCR),絕不靜默送空內容

測試 4/4:Chrome PDF 中文正確/無文字回 ErrNoText/壞檔報錯可辨識
(『可能損壞或有密碼保護』)/連續多檔不爆(daemon 長駐會遇到)。
2026-07-27 21:07:11 +08:00

85 lines
2.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package main
import (
"errors"
"os"
"strings"
"testing"
"time"
)
const chromePDF = "/private/tmp/claude-501/-Users-youlinhsieh-Documents-tech-projects-InkStoneCo/92a75156-c295-4c79-bfeb-de1a20e4ed26/scratchpad/office-poc/chrome.pdf"
// 這是本抽取器存在的理由:調研實測「純 Go 的 PDF 套件對 Chrome 列印的 PDF 整段亂碼」
// pdf-extraction-options.md §3),而客戶最常丟的就是瀏覽器列印的 PDF。
// 所以測資刻意用 Chrome headless 產的,不是隨便一個 PDF。
func TestPDF_Chrome列印的中文PDF(t *testing.T) {
data, err := os.ReadFile(chromePDF)
if err != nil {
t.Skip("無 Chrome PDF 測資,跳過")
}
start := time.Now()
got, err := ConvertToText("合約.pdf", data)
elapsed := time.Since(start)
if err != nil {
t.Fatalf("Chrome 列印的 PDF 應該讀得了: %v", err)
}
for _, want := range []string{"船舶維修合約書", "350,000", "2026", "交期"} {
if !strings.Contains(got, want) {
t.Errorf("缺少 %q(若整段亂碼=引擎選錯了),實得:\n%s", want, got)
}
}
t.Logf("耗時 %v,抽出:\n%s", elapsed, got)
}
// 掃描件行為契約:PDFium 不做 OCR,影像 PDF 會抽不出字 → 必須是 ErrNoText,
// 不能靜默送空內容給 LLM(用戶會以為成功了,其實知識庫裡是空的)。
func TestPDF_無文字PDF回ErrNoText(t *testing.T) {
// 一個結構合法但沒有文字內容的最小 PDF
minimal := []byte(`%PDF-1.4
1 0 obj<</Type/Catalog/Pages 2 0 R>>endobj
2 0 obj<</Type/Pages/Kids[3 0 R]/Count 1>>endobj
3 0 obj<</Type/Page/Parent 2 0 R/MediaBox[0 0 612 792]>>endobj
xref
0 4
trailer<</Size 4/Root 1 0 R>>
startxref
0
%%EOF`)
_, err := ConvertToText("空白.pdf", minimal)
if err == nil {
t.Fatal("沒有文字的 PDF 應該回錯,不能當成空內容送出去")
}
// 打不開也可接受(訊息不同但都不是靜默成功);重點是不可回 nil error
if !errors.Is(err, ErrNoText) && !strings.Contains(err.Error(), "PDF") {
t.Errorf("錯誤訊息應說得出是 PDF 的問題,實得: %v", err)
}
t.Logf("如預期回錯: %v", err)
}
func TestPDF_壞檔要報錯不當機(t *testing.T) {
_, err := ConvertToText("壞的.pdf", []byte("這根本不是 PDF"))
if err == nil {
t.Error("壞檔應該報錯")
}
t.Logf("錯誤訊息: %v", err)
}
// 同一個 process 連續處理多個 PDF 不可爆(pool 重用)——
// daemon 是長駐程式,一次掃描可能有幾十個 PDF。
func TestPDF_連續多檔不爆(t *testing.T) {
data, err := os.ReadFile(chromePDF)
if err != nil {
t.Skip("無測資")
}
for i := 0; i < 3; i++ {
got, err := ConvertToText("a.pdf", data)
if err != nil {
t.Fatalf("第 %d 次失敗: %v", i+1, err)
}
if !strings.Contains(got, "船舶維修合約書") {
t.Fatalf("第 %d 次內容不對", i+1)
}
}
}