Files
arcrun-collector/convert_pdf_test.go
Leo 4d3a6a09a6 v0.18.9:collector 併進同一支執行檔——磁碟上不再攤出第二支 exe
leo 08-06 裁決:「不要兩支,寫成一支檔案」。

## 為什麼
v0.18.7-8 的「單一 exe」其實是**一支包著另一支**:collector.exe 被 go:embed
進 Arcrun.exe,執行時攤到 ~/.arcrun-rag/bin/ 再跑。
那正是防毒軟體眼中的 dropper 特徵 —— 封測者實撞
`Trojan:Win32/Sabsik.FL.A!ml`,檔案當場被隔離、自動刪除。

⚠️ 誠實界定:`!ml` 結尾=**機器學習判定**,Sabsik 是最常見的通用誤判家族,
   主因是「未簽章+下載次數少」,**不是**特別指向 dropper 行為。
   所以本次改動**不保證**解除誤判——真正的解是上架 MS Store(微軟簽章)。
   但「執行時把第二支 PE 寫到磁碟再執行」本來就該拿掉,這是對的方向且順手變小。

## 怎麼做
- `collector/` 39 個檔 `package main` → `package collector`,`main()` → 匯出的 `Run(args) int`
- 新增 `collector/cmd/collector/`(薄殼 CLI,讓單獨跑 collector 這條路仍可用)
- App 直接 import 該套件;`main()` 第一件事就判 `--collector`,是的話走 `collector.Run` 不碰 GUI
- `supervisor` 加 `ArgPrefix`,App 把 `BinPath` 指向 `os.Executable()` 自己
- 刪掉 `bundled_collector_{windows,other}.go`(embed + 攤檔那套)
- 三支打包腳本不再編/複製第二支;版本注入同時打到兩個 package
- build-win.sh 的機械閘改成**直接問它**:`--collector --version` 回得出版本才放行
  (舊閘是比大小,只能證明「有 embed」,證明不了「分派是對的」)

## 驗(真機實跑)
· `.app/Contents/MacOS/` 只有 **一個** 執行檔(原本兩個)
· 跑起來兩個行程是**同一個 exe**:
    …/MacOS/arcrun-app
    …/MacOS/arcrun-app --collector direct --config …
· `~/.arcrun-rag/bin` **不存在**(沒有任何東西被攤出來)
· 端到端:丟檔進看守資料夾 → collector.log `"status":"ingested","http_status":200`
· `lsappinfo` 仍是 `type="UIElement"`、`Version="0.18.9"`
· collector 39 檔測試全過;app 測試過;go vet 全綠;mac + windows 交叉編譯皆過
· 單檔 26MB → 22MB(不再夾帶第二份完整程式)
2026-08-06 16:00:47 +08:00

85 lines
2.8 KiB
Go
Raw Permalink 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 collector
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)
}
}
}