diff --git a/direct.go b/direct.go index 2dbbf11..e946c48 100644 --- a/direct.go +++ b/direct.go @@ -61,7 +61,7 @@ type DirectConfig struct { } // librarySlug 把資料夾名轉成合法庫名(A-Za-z0-9_-;中文等非 ASCII 轉為底線分段)。 -// 空結果(如純中文名)回 "",交由 libraryFor 退回後備值——不硬造出無意義的庫名。 +// 空結果(如純中文名)回 "",由 libraryFor 改生路徑穩定雜湊鍵(t89)。 func librarySlug(folder string) string { base := filepath.Base(strings.TrimRight(folder, string(filepath.Separator))) var b strings.Builder @@ -85,7 +85,12 @@ func librarySlug(folder string) string { } // libraryFor 決定某個看守資料夾的資料該蓋哪個庫章(t52)。 -// 優先序:config.Libraries 明列 > 資料夾名 slug > config.Library(後備,預設 kb)。 +// 優先序:config.Libraries 明列 > 資料夾名 slug > 路徑穩定雜湊鍵(t89)。 +// +// t89(leo 2026-07-28 實測):純中文資料夾名(如「官方總圖」)slug 後為空字串, +// 若退到 c.Library/"kb",兩個不同純中文資料夾會塌縮進同一個庫(靜默混庫)。 +// 後端庫名鍵限 A-Za-z0-9_-(Arcrun KBDB 約束),故改生 lib_+sha256(絕對路徑) 前 6 hex—— +// 路徑穩定所以鍵穩定;兩個不同路徑必然不同鍵。 func (c *DirectConfig) libraryFor(absRoot string) string { if c.Libraries != nil { if lib, ok := c.Libraries[absRoot]; ok && strings.TrimSpace(lib) != "" { @@ -95,10 +100,8 @@ func (c *DirectConfig) libraryFor(absRoot string) string { if slug := librarySlug(absRoot); slug != "" { return slug } - if c.Library != "" { - return c.Library - } - return "kb" + sum := sha256.Sum256([]byte(absRoot)) + return "lib_" + hex.EncodeToString(sum[:3]) } // expandHome 把開頭的 `~/`(或單獨的 `~`)展開成使用者家目錄的絕對路徑。 diff --git a/t52_library_test.go b/t52_library_test.go index 1fa730e..f3950e5 100644 --- a/t52_library_test.go +++ b/t52_library_test.go @@ -1,14 +1,25 @@ package main -import "testing" +import ( + "crypto/sha256" + "encoding/hex" + "testing" +) + +// stableHashLib 重現 libraryFor 對純中文資料夾的穩定雜湊鍵(t89 驗收用)。 +func stableHashLib(absPath string) string { + sum := sha256.Sum256([]byte(absPath)) + return "lib_" + hex.EncodeToString(sum[:3]) +} func TestLibraryPerFolder(t *testing.T) { cfg := &DirectConfig{Library: "kb"} cases := map[string]string{ - "/Users/x/Desktop/finance": "finance", - "/Users/x/Desktop/HR Docs": "hr_docs", - "/Users/x/Desktop/官方總圖": "kb", - "/Users/x/Desktop/test 01": "test_01", + "/Users/x/Desktop/finance": "finance", + "/Users/x/Desktop/HR Docs": "hr_docs", + // t89:純中文資料夾名不再退 kb,改生穩定唯一鍵 + "/Users/x/Desktop/官方總圖": stableHashLib("/Users/x/Desktop/官方總圖"), + "/Users/x/Desktop/test 01": "test_01", } for in, want := range cases { if got := cfg.libraryFor(in); got != want { @@ -20,3 +31,65 @@ func TestLibraryPerFolder(t *testing.T) { t.Errorf("明列對映應優先,got %q", got) } } + +// ── t89 五條驗收測試 ──────────────────────────────────────────────────────── + +// ①純中文資料夾 → lib_+6hex,非 "kb" +func TestT89ChineseFolderNotKb(t *testing.T) { + cfg := &DirectConfig{Library: "kb"} + got := cfg.libraryFor("/Users/x/Desktop/官方總圖") + if got == "kb" { + t.Errorf("純中文資料夾不應退到 'kb',got %q", got) + } + if len(got) != len("lib_")+6 || got[:4] != "lib_" { + t.Errorf("預期格式 lib_<6hex>,got %q", got) + } +} + +// ②兩個不同純中文資料夾 → 不同鍵(不混庫) +func TestT89TwoChineseFoldersDifferentKeys(t *testing.T) { + cfg := &DirectConfig{Library: "kb"} + k1 := cfg.libraryFor("/Users/x/Desktop/官方總圖") + k2 := cfg.libraryFor("/Users/x/Desktop/人事資料") + if k1 == k2 { + t.Errorf("不同純中文資料夾必須對映不同鍵,got k1=%q k2=%q", k1, k2) + } +} + +// ③同一路徑重跑 → 鍵穩定(冪等) +func TestT89SameFolderStableKey(t *testing.T) { + cfg := &DirectConfig{Library: "kb"} + k1 := cfg.libraryFor("/Users/x/Desktop/官方總圖") + k2 := cfg.libraryFor("/Users/x/Desktop/官方總圖") + if k1 != k2 { + t.Errorf("同路徑重跑必須回傳同一鍵,got %q vs %q", k1, k2) + } +} + +// ④ASCII 資料夾行為不變(test 01 → test_01) +func TestT89ASCIIFolderUnchanged(t *testing.T) { + cfg := &DirectConfig{Library: "kb"} + cases := map[string]string{ + "/Users/x/Desktop/finance": "finance", + "/Users/x/Desktop/test 01": "test_01", + "/Users/x/Desktop/HR Docs": "hr_docs", + } + for in, want := range cases { + if got := cfg.libraryFor(in); got != want { + t.Errorf("libraryFor(%q) = %q, want %q", in, got, want) + } + } +} + +// ⑤明列 Libraries 對映仍最優先(即使是純中文資料夾) +func TestT89ExplicitMappingWins(t *testing.T) { + cfg := &DirectConfig{ + Library: "kb", + Libraries: map[string]string{ + "/Users/x/Desktop/官方總圖": "official", + }, + } + if got := cfg.libraryFor("/Users/x/Desktop/官方總圖"); got != "official" { + t.Errorf("明列對映應優先,got %q", got) + } +}