| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145 |
- package main
- import (
- "errors"
- "os"
- "os/exec"
- "path/filepath"
- "strings"
- "testing"
- "time"
- )
- func TestBuildRemote(t *testing.T) {
- env, err := buildRemote(remoteSpec{Type: "s3", Params: map[string]string{"provider": "Minio", "endpoint": "http://127.0.0.1:9198", "bucket": "kb", "prefix": "/docs/2024/"}, Secrets: map[string]string{"accessKeyId": "ak", "secretAccessKey": "sk"}}, "")
- if err != nil {
- t.Fatal(err)
- }
- if env.path != "kb:kb/docs/2024" {
- t.Errorf("远程路径:%s", env.path)
- }
- joined := strings.Join(env.env, "\n")
- for _, want := range []string{"RCLONE_CONFIG_KB_TYPE=s3", "RCLONE_CONFIG_KB_PROVIDER=Minio", "RCLONE_CONFIG_KB_ENDPOINT=http://127.0.0.1:9198", "RCLONE_CONFIG_KB_SECRET_ACCESS_KEY=sk", "RCLONE_CONFIG_KB_ENV_AUTH=false"} {
- if !strings.Contains(joined, want) {
- t.Errorf("缺少 %s", want)
- }
- }
- bad := []remoteSpec{
- {Type: "s3", Params: map[string]string{"endpoint": "http://x", "bucket": "kb"}, Secrets: map[string]string{"accessKeyId": "ak"}},
- {Type: "s3", Params: map[string]string{"endpoint": "ftp://x", "bucket": "kb"}, Secrets: map[string]string{"accessKeyId": "a", "secretAccessKey": "b"}},
- {Type: "s3", Params: map[string]string{"endpoint": "http://x", "bucket": "-rf"}, Secrets: map[string]string{"accessKeyId": "a", "secretAccessKey": "b"}},
- {Type: "s3", Params: map[string]string{"endpoint": "http://x", "bucket": "kb", "prefix": "a/../../b"}, Secrets: map[string]string{"accessKeyId": "a", "secretAccessKey": "b"}},
- {Type: "s3", Params: map[string]string{"endpoint": "http://x\nRCLONE_X=1", "bucket": "kb"}, Secrets: map[string]string{"accessKeyId": "a", "secretAccessKey": "b"}},
- {Type: "webdav", Params: map[string]string{"url": "http://u:p@dav.local/"}},
- {Type: "webdav", Params: map[string]string{"url": "http://dav.local/", "vendor": "evil"}},
- {Type: "local", Params: map[string]string{"path": "/"}},
- {Type: "sftp"},
- }
- for i, r := range bad {
- if _, err := buildRemote(r, ""); err == nil {
- t.Errorf("第 %d 个配置应该被拒绝:%+v", i, r)
- }
- }
- }
- func TestClassify(t *testing.T) {
- cases := map[string]string{
- "ERROR : api error SignatureDoesNotMatch: The request signature we calculated does not match": "auth_failed",
- "ERROR : https response error StatusCode: 403, api error AccessDenied: Access Denied.": "auth_failed",
- "ERROR : api error NoSuchBucket: The specified bucket does not exist": "path_missing",
- `ERROR : request send failed, Get "http://127.0.0.1:1/x": dial tcp 127.0.0.1:1: connect: connection refused`: "unreachable",
- "ERROR : dial tcp: lookup nas.local: no such host": "unreachable",
- }
- for in, want := range cases {
- if st, msg := classify(in); st != want || msg == "" {
- t.Errorf("%q → %s %q,应为 %s", in, st, msg, want)
- }
- }
- if _, msg := classify("2026/11/06 ERROR : api error AccessDenied: Access Denied."); msg != "认证失败或没有权限:AccessDenied:Access Denied." {
- t.Errorf("错误信息:%q", msg)
- }
- long := `ERROR : error listing: operation error S3: ListObjectsV2, exceeded maximum number of attempts, 1, request send failed, Get "http://127.0.0.1:1/kb?x=1": dial tcp 127.0.0.1:1: connect: connection refused`
- if _, msg := classify(long); msg != "无法连接:dial tcp 127.0.0.1:1: connect: connection refused" {
- t.Errorf("网络错误信息:%q", msg)
- }
- }
- // 需要 rclone:RCLONE_BIN 或 PATH 中的 rclone;用 rclone 的 local 后端,不需要网络
- func rcloneBin(t *testing.T) string {
- if b := os.Getenv("RCLONE_BIN"); b != "" {
- return b
- }
- if b, err := exec.LookPath("rclone"); err == nil {
- return b
- }
- t.Skip("没有 rclone(设置 RCLONE_BIN)")
- return ""
- }
- func TestRemoteMount(t *testing.T) {
- needFuse(t)
- bin := rcloneBin(t)
- allowLocalRemote = true
- defer func() { allowLocalRemote = false }()
- base, roots := tempRoots(t)
- data := filepath.Join(base, "remote-data")
- os.MkdirAll(filepath.Join(data, "产品手册", ".git"), 0o755)
- os.WriteFile(filepath.Join(data, "产品手册", "规格.csv"), []byte("型号,功率\nA100,5\n"), 0o644)
- os.WriteFile(filepath.Join(data, ".env"), []byte("SECRET=1"), 0o644)
- dir := filepath.Join(base, "mounter")
- mntDir := filepath.Join(base, "kbmnt")
- for _, d := range []string{dir, mntDir, filepath.Join(dir, "remote"), filepath.Join(dir, "cache")} {
- os.MkdirAll(d, 0o755)
- }
- conf := filepath.Join(dir, "rclone.conf")
- os.WriteFile(conf, nil, 0o600)
- m := newManager(realSystem{}, roots, mntDir, filepath.Join(dir, "state.json")).withRemote(&rcloneRunner{bin: bin, config: conf, home: dir}, filepath.Join(dir, "remote"), filepath.Join(dir, "cache"))
- defer m.unmountAll()
- spec := mountSpec{ID: "kb_r1", Kind: "remote", Remote: &remoteSpec{Type: "local", Params: map[string]string{"path": data}}}
- st, err := m.sync([]mountSpec{spec}, true)
- if err != nil || len(st) != 1 || st[0].State != "ok" {
- t.Fatalf("远程存储挂载:%+v %v", st, err)
- }
- mp := filepath.Join(mntDir, "kb_r1")
- if got := names(t, mp); len(got) != 1 || got[0] != "产品手册" {
- t.Errorf("目录列表(隐藏文件不可见):%v", got)
- }
- if b, err := os.ReadFile(filepath.Join(mp, "产品手册", "规格.csv")); err != nil || !strings.Contains(string(b), "A100") {
- t.Errorf("读取:%q %v", b, err)
- }
- if _, err := os.Stat(filepath.Join(mp, ".env")); !errors.Is(err, os.ErrNotExist) {
- t.Errorf(".env 应该不存在:%v", err)
- }
- if err := os.WriteFile(filepath.Join(mp, "x.txt"), []byte("x"), 0o644); err == nil {
- t.Error("应该不能写入")
- }
- // 凭据不出现在命令行
- m.mu.Lock()
- cmd := m.mounts["kb_r1"].remote.cmd
- m.mu.Unlock()
- if strings.Contains(strings.Join(cmd.Args, " "), "RCLONE_CONFIG") {
- t.Errorf("命令行中不应有配置:%v", cmd.Args)
- }
- // 配置错误:状态为挂载失败
- bad := mountSpec{ID: "kb_r2", Kind: "remote", Remote: &remoteSpec{Type: "s3", Params: map[string]string{"bucket": "x"}}}
- st, _ = m.sync([]mountSpec{spec, bad}, false)
- if st[1].State != "mount_failed" {
- t.Errorf("配置错误:%+v", st[1])
- }
- // 删除:停止 rclone、卸载、清理缓存
- if _, err := m.sync(nil, false); err != nil {
- t.Fatal(err)
- }
- time.Sleep(200 * time.Millisecond)
- for _, p := range []string{mp, filepath.Join(dir, "remote", "kb_r1"), filepath.Join(dir, "cache", "kb_r1")} {
- if _, err := os.Stat(p); !errors.Is(err, os.ErrNotExist) {
- t.Errorf("%s 应该已删除:%v", p, err)
- }
- }
- if cmd.ProcessState == nil {
- t.Error("rclone 应该已退出")
- }
- }
|