package core import ( "os" "path/filepath" "strings" "testing" "eh-downloader-wails/internal/eh" ) func sampleGallery() *eh.Gallery { return &eh.Gallery{ GID: "4239274", Title: "[Noxe] 0820 Qingxiao [AI Generated]", Meta: eh.GalleryMeta{ Artist: "Noxe", Group: "CriticalHAI", Series: "wuthering waves", Category: "misc", Lang: "chinese", Uploader: "velius11", Tags: []string{"qingxiao", "rover", "big breasts", "x-ray", "ai generated"}, }, } } func TestBuildDir(t *testing.T) { g := sampleGallery() tests := []struct { tpl string want string }{ {"{title}", "[Noxe] 0820 Qingxiao [AI Generated]"}, {"{artist}/{title}", `Noxe/[Noxe] 0820 Qingxiao [AI Generated]`}, {"{group}/{title}", `CriticalHAI/[Noxe] 0820 Qingxiao [AI Generated]`}, {"{category}/{group}/{title}", `misc/CriticalHAI/[Noxe] 0820 Qingxiao [AI Generated]`}, {"{tags:2}/{artist}/{title}", `qingxiao_rover/Noxe/[Noxe] 0820 Qingxiao [AI Generated]`}, {"{tags}/{artist}/{title}", `qingxiao_rover_big breasts_x-ray_ai generated/Noxe/[Noxe] 0820 Qingxiao [AI Generated]`}, {"{tag1}-{tag2}", "qingxiao-rover"}, {"{artist} - {gid} - {title}", "Noxe - 4239274 - [Noxe] 0820 Qingxiao [AI Generated]"}, {"{gid}", "4239274"}, {"{lang}/{title}", `chinese/[Noxe] 0820 Qingxiao [AI Generated]`}, {"{unknown}/{title}", `{unknown}/[Noxe] 0820 Qingxiao [AI Generated]`}, // 未知占位符原样保留 } for _, tt := range tests { t.Run(tt.tpl, func(t *testing.T) { got := BuildDir(tt.tpl, g) // 期望值用 / 书写,再换成本机路径分隔符(Windows 是 \) want := strings.ReplaceAll(tt.want, "/", string(filepath.Separator)) if got != want { t.Errorf("\n got: %q\nwant: %q", got, want) } }) } } // 空模板回落到 {title} func TestBuildDirDefaults(t *testing.T) { g := sampleGallery() for _, tpl := range []string{"", " "} { if got := BuildDir(tpl, g); !strings.Contains(got, "Noxe") { t.Errorf("空模板应回落到标题, got %q", got) } } } // 占位符兜底值 func TestBuildDirFallbacks(t *testing.T) { g := &eh.Gallery{GID: "12345678", Title: "Plain Title"} got := BuildDir("{artist}/{group}/{series}/{category}/{lang}/{uploader}", g) for _, want := range []string{"未知作者", "未知社团", "无系列", "未分类", "nolang", "unknown"} { if !strings.Contains(got, want) { t.Errorf("缺兜底值 %q: %s", want, got) } } } // 路径穿越必须被挡住 func TestSanitizeSegmentSecurity(t *testing.T) { cases := []struct{ in, want string }{ {"..", "_"}, {".", "_"}, {"../../etc/passwd", ".._.._etc_passwd"}, {`a/b\c:d*e?f"gi|j`, "a_b_c_d_e_f_g_h_i_j"}, {"CON", "_CON"}, // Windows 保留设备名 {"nul.txt", "_nul.txt"}, // 保留名带扩展也不行 {"正常名字", "正常名字"}, {"trailing dots...", "trailing dots"}, {" spaced ", "spaced"}, } for _, c := range cases { if got := SanitizeSegment(c.in, "fb"); got != c.want { t.Errorf("SanitizeSegment(%q) = %q, want %q", c.in, got, c.want) } } } // 超长截断 func TestSanitizeSegmentTruncate(t *testing.T) { long := strings.Repeat("长", 300) got := SanitizeSegment(long, "fb") if len([]rune(got)) != 110 { t.Errorf("应截到 110 字,实际 %d", len([]rune(got))) } } // 分隔符混用(用户可能用 / 或 \) func TestBuildDirMixedSeparators(t *testing.T) { g := sampleGallery() for _, tpl := range []string{`{artist}/{title}`, `{artist}\{title}`, `{artist}//{title}`} { got := BuildDir(tpl, g) if strings.Contains(got, "/") || strings.Contains(got, "\\") && strings.Count(got, "\\") > 1 { continue // 各平台分隔符不同,只要求不出现空段 } if strings.Contains(got, "__") { t.Errorf("出现空段: %q (tpl=%q)", got, tpl) } } } func TestListPlaceholders(t *testing.T) { p := ListPlaceholders() if len(p) == 0 { t.Fatal("占位符列表不该为空") } found := false for _, x := range p { if x == "{tags:3}" { found = true } } if !found { t.Error("缺 {tags:3}") } } // ---- 代理探测:必须真的等连接结果(JS 版是坏的,永远返回 7890)---- func TestDetectLocalProxy(t *testing.T) { // 起一个真实的监听端口 ln, err := net_listen_loopback() if err != nil { t.Skipf("无法监听端口,跳过: %v", err) } defer ln.Close() port := ln.port() got := DetectLocalProxy([]int{port}) if got == "" { t.Error("有端口在监听,应该探测得到") return } if !strings.Contains(got, "127.0.0.1") { t.Errorf("地址不对: %q", got) } } func TestDetectLocalProxyNone(t *testing.T) { // 用一个几乎肯定没人监听的端口 if got := DetectLocalProxy([]int{1, 2, 3}); got != "" { t.Errorf("没端口监听时不该返回结果, got %q", got) } } func TestDefaultDownloadRoot(t *testing.T) { root := DefaultDownloadRoot() if root == "" { t.Fatal("下载目录不该为空") } if !strings.HasSuffix(root, "Downloads") && !strings.Contains(root, "Downloads") { t.Errorf("路径可疑: %q", root) } if _, err := os.Stat(root); err == nil { // 目录存在也没关系,只确认路径合法 t.Logf("目录已存在: %s", root) } }