// 路径选择器相关 package main import ( "encoding/json" "net/http" "net/http/httptest" "net/url" "os" "path/filepath" "runtime" "testing" ) // 造一棵目录树:两个名字大小写不同的文件夹、两个文件、一个隐藏文件夹 func setupBrowseTree(t *testing.T) string { t.Helper() dir := t.TempDir() for _, sub := range []string{"beta", "Alpha", ".hidden"} { if err := os.MkdirAll(filepath.Join(dir, sub), 0755); err != nil { t.Fatal(err) } } for _, f := range []string{"b.txt", "a.mp4"} { if err := os.WriteFile(filepath.Join(dir, f), []byte("x"), 0644); err != nil { t.Fatal(err) } } return dir } func browseNames(entries []browseEntry) []string { out := make([]string, 0, len(entries)) for _, e := range entries { out = append(out, e.Name) } return out } func browseHas(entries []browseEntry, name string) bool { for _, e := range entries { if e.Name == name { return true } } return false } func doBrowseQuery(t *testing.T, v url.Values) browseResp { t.Helper() q := "" if v != nil { q = "?" + v.Encode() } rec := httptest.NewRecorder() browseHandler(rec, httptest.NewRequest(http.MethodGet, "/api/browse"+q, nil)) if rec.Code != http.StatusOK { t.Fatalf("状态码是 %d,想要 200", rec.Code) } var resp browseResp if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil { t.Fatalf("解析响应失败:%v(%s)", err, rec.Body.String()) } return resp } func doBrowse(t *testing.T, path, kind string, hidden bool) browseResp { t.Helper() v := url.Values{} if path != "" { v.Set("path", path) } if kind != "" { v.Set("kind", kind) } if hidden { v.Set("hidden", "1") } return doBrowseQuery(t, v) } func TestBrowseListDirsOnly(t *testing.T) { dir := setupBrowseTree(t) entries, parent, truncated, err := browseList(dir, false, false) if err != nil { t.Fatalf("列目录失败:%v", err) } if truncated { t.Fatal("条目没超上限,不该标成截断") } if parent != filepath.Dir(dir) { t.Fatalf("上级目录不对:%q", parent) } want := []string{"Alpha", "beta"} if len(entries) != len(want) { t.Fatalf("只该列出文件夹:%q", browseNames(entries)) } for i, e := range entries { if e.Name != want[i] { t.Fatalf("第 %d 项是 %q,想要 %q(文件夹排前面、按名字不区分大小写排)", i, e.Name, want[i]) } if !e.Dir || e.Path != filepath.Join(dir, e.Name) { t.Fatalf("条目内容不对:%+v", e) } } } func TestBrowseListWithFiles(t *testing.T) { dir := setupBrowseTree(t) entries, _, _, err := browseList(dir, true, false) if err != nil { t.Fatalf("列目录失败:%v", err) } want := []string{"Alpha", "beta", "a.mp4", "b.txt"} if len(entries) != len(want) { t.Fatalf("条目不对:%q", browseNames(entries)) } for i, e := range entries { if e.Name != want[i] { t.Fatalf("第 %d 项是 %q,想要 %q", i, e.Name, want[i]) } } // 选文件时文件夹也要列出来,不然没法点进去 if !entries[0].Dir { t.Fatal("选文件时也该列出文件夹") } if entries[2].Dir || entries[2].Size != 1 { t.Fatalf("文件条目不对:%+v", entries[2]) } } func TestBrowseListHidden(t *testing.T) { dir := setupBrowseTree(t) entries, _, _, err := browseList(dir, false, false) if err != nil { t.Fatalf("列目录失败:%v", err) } if browseHas(entries, ".hidden") { t.Fatal("默认不该列出隐藏文件夹") } entries, _, _, err = browseList(dir, false, true) if err != nil { t.Fatalf("列目录失败:%v", err) } if !browseHas(entries, ".hidden") { t.Fatalf("开了显示隐藏项就该列出来:%q", browseNames(entries)) } } func TestBrowseParentAtRoot(t *testing.T) { root := "/" if runtime.GOOS == "windows" { root = `C:\` } if got := browseParent(root); got != "" { t.Fatalf("根位置不该有上级:%q", got) } if got := browseParent(filepath.Join(root, "a")); got != root { t.Fatalf("子目录的上级应该是根:%q", got) } } func TestBrowseRootsNotEmpty(t *testing.T) { if len(browseRoots()) == 0 { t.Fatal("根列表不该是空的,否则用户没法从盘符开始选") } } func TestBrowseHandlerDir(t *testing.T) { dir := setupBrowseTree(t) resp := doBrowse(t, dir, "dir", false) if resp.Error != "" { t.Fatalf("不该报错:%s", resp.Error) } if resp.Path != dir { t.Fatalf("回显的路径不对:%q", resp.Path) } if resp.Parent != filepath.Dir(dir) { t.Fatalf("上级目录不对:%q", resp.Parent) } if len(resp.Roots) == 0 { t.Fatal("根列表不该为空") } if got := browseNames(resp.Entries); len(got) != 2 || got[0] != "Alpha" || got[1] != "beta" { t.Fatalf("条目不对:%q", got) } } func TestBrowseHandlerFileKind(t *testing.T) { dir := setupBrowseTree(t) resp := doBrowse(t, dir, "file", false) if resp.Error != "" { t.Fatalf("不该报错:%s", resp.Error) } if !browseHas(resp.Entries, "a.mp4") || !browseHas(resp.Entries, "b.txt") { t.Fatalf("选文件时该列出文件:%q", browseNames(resp.Entries)) } for _, e := range resp.Entries { if e.Name == "a.mp4" { if e.Dir { t.Fatal("文件不该被标成文件夹") } if e.Path != filepath.Join(dir, "a.mp4") { t.Fatalf("文件路径不对:%q", e.Path) } } } } func TestBrowseHandlerMissingPath(t *testing.T) { dir := setupBrowseTree(t) resp := doBrowse(t, filepath.Join(dir, "没有这个目录"), "dir", false) if resp.Error == "" { t.Fatal("不存在的目录要给出错误提示,而不是静默返回空列表") } // 打不开的时候仍要能往上走,不然用户就卡在一个不存在的路径里了 if resp.Parent != dir { t.Fatalf("错误时也该带上上级目录:%q", resp.Parent) } if len(resp.Entries) != 0 { t.Fatalf("打不开的目录不该有内容:%q", browseNames(resp.Entries)) } } func TestBrowseHandlerPathIsFile(t *testing.T) { dir := setupBrowseTree(t) resp := doBrowse(t, filepath.Join(dir, "a.mp4"), "dir", false) if resp.Error == "" { t.Fatal("把文件当目录展开应该报错") } } func TestBrowseHandlerEmptyPathUsesWorkDir(t *testing.T) { resp := doBrowse(t, "", "dir", false) wd, err := os.Getwd() if err != nil { t.Skip("取不到工作目录") } if normalizePath(resp.Path) != normalizePath(wd) { t.Fatalf("没给路径时该从程序所在目录开始:%q", resp.Path) } // 程序目录里至少有自己的源码,不该是空的 if len(resp.Entries) == 0 { t.Fatal("程序所在目录不该是空的") } }