package core // 桥口 /tasks 接口测试:油猴脚本靠它把「已推送」的画廊标题标灰 import ( "encoding/json" "fmt" "io" "net" "net/http" "strings" "testing" "time" "eh-downloader-wails/internal/eh" ) // 起一个真实监听的桥口,返回 base URL func startTestBridge(t *testing.T, s *Store) string { t.Helper() return startTestBridgeWith(t, s, nil) } // listenLoopbackPort 拿一个空闲端口(监听后立刻关掉,把端口号交出去) func listenLoopbackPort() (func() int, error) { ln, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { return nil, err } port := ln.Addr().(*net.TCPAddr).Port _ = ln.Close() return func() int { return port }, nil } // startTestBridgeWith 起桥口,可选挂推送历史 func startTestBridgeWith(t *testing.T, s *Store, p *PushedStore) string { t.Helper() b := NewBridge(s, func(string, string) {}, func(Task, bool, bool) {}) if p != nil { b.SetPushedStore(p) } // 端口用 0 让系统分配,避免和真实例撞端口 ln, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatalf("监听失败: %v", err) } port := ln.Addr().(*net.TCPAddr).Port _ = ln.Close() b.Start(port) // 等端口就绪 deadline := time.Now().Add(3 * time.Second) for time.Now().Before(deadline) { resp, err := http.Get(fmt.Sprintf("http://127.0.0.1:%d/ping", port)) if err == nil { resp.Body.Close() break } time.Sleep(20 * time.Millisecond) } t.Cleanup(b.Stop) return fmt.Sprintf("http://127.0.0.1:%d", port) } func getWithToken(t *testing.T, url, token string) (int, []byte) { t.Helper() req, err := http.NewRequest("GET", url, nil) if err != nil { t.Fatalf("建请求失败: %v", err) } if token != "" { req.Header.Set("X-EHDL-Token", token) } resp, err := http.DefaultClient.Do(req) if err != nil { t.Fatalf("请求失败: %v", err) } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) return resp.StatusCode, body } func TestBridgeTasks(t *testing.T) { s, _ := newTestStore(t) base := startTestBridge(t, s) // 空的:返回 0 条 code, body := getWithToken(t, base+"/tasks", BridgeToken) if code != http.StatusOK { t.Fatalf("空清单状态码应为 200,实际 %d: %s", code, body) } var j struct { OK bool `json:"ok"` Count int `json:"count"` Tasks []struct { GID string `json:"gid"` Token string `json:"token"` Title string `json:"title"` } `json:"tasks"` } if err := json.Unmarshal(body, &j); err != nil { t.Fatalf("解析失败: %v (%s)", err, body) } if !j.OK || j.Count != 0 || len(j.Tasks) != 0 { t.Fatalf("空清单应为空: %s", body) } // 加两个任务 s.AddTasks([]eh.GalleryRef{ {GID: "111", Token: "aaa", Raw: "https://e-hentai.org/g/111/aaa/", Title: "T1"}, {GID: "222", Token: "bbb", Raw: "https://e-hentai.org/g/222/bbb/", Title: "T2"}, }) s.SetTitle("111", "aaa", "T1") code, body = getWithToken(t, base+"/tasks", BridgeToken) if code != http.StatusOK { t.Fatalf("状态码应为 200,实际 %d: %s", code, body) } if err := json.Unmarshal(body, &j); err != nil { t.Fatalf("解析失败: %v (%s)", err, body) } if j.Count != 2 || len(j.Tasks) != 2 { t.Fatalf("应有 2 条任务,实际 count=%d len=%d: %s", j.Count, len(j.Tasks), body) } gids := map[string]string{} for _, task := range j.Tasks { gids[task.GID] = task.Token if task.GID == "111" && task.Title != "T1" { t.Errorf("标题没带回来: %+v", task) } } if gids["111"] != "aaa" || gids["222"] != "bbb" { t.Errorf("gid/token 对不上: %+v", gids) } } func TestBridgeTasksRejectsBadToken(t *testing.T) { s, _ := newTestStore(t) base := startTestBridge(t, s) // 不带 token -> 403 code, _ := getWithToken(t, base+"/tasks", "") if code != http.StatusForbidden { t.Errorf("缺 token 应 403,实际 %d", code) } // 错 token -> 403 code, _ = getWithToken(t, base+"/tasks", "wrong-token") if code != http.StatusForbidden { t.Errorf("错 token 应 403,实际 %d", code) } // 对 token -> 200 code, _ = getWithToken(t, base+"/tasks", BridgeToken) if code != http.StatusOK { t.Errorf("对 token 应 200,实际 %d", code) } } func TestBridgeAddThenTasks(t *testing.T) { s, _ := newTestStore(t) base := startTestBridge(t, s) // 通过 /add 推一个画廊,再从 /tasks 里查出来 payload := strings.NewReader(`{"gid":"333","token":"ccc","url":"https://e-hentai.org/g/333/ccc/","title":"T3"}`) req, _ := http.NewRequest("POST", base+"/add", payload) req.Header.Set("Content-Type", "application/json") req.Header.Set("X-EHDL-Token", BridgeToken) resp, err := http.DefaultClient.Do(req) if err != nil { t.Fatalf("POST /add 失败: %v", err) } resp.Body.Close() if resp.StatusCode != http.StatusOK { t.Fatalf("POST /add 应 200,实际 %d", resp.StatusCode) } code, body := getWithToken(t, base+"/tasks", BridgeToken) if code != http.StatusOK { t.Fatalf("/tasks 应 200,实际 %d", code) } if !strings.Contains(string(body), "333") { t.Errorf("/tasks 里应能看到刚推的 333: %s", body) } }