feat(qbittorrent): login compat for v4/v5.0-5.2 and three auth modes
- Login success = HTTP 200 "Ok." or 204 (5.2+); session cookie taken from
Set-Cookie by name (SID / QBT_SID_<port> / custom) and sent back as-is.
- Failure classes: "Fails." / 401 -> auth; 403 IP ban / 429 -> rate;
wrong API Key (403) -> auth with hint; 5xx -> protocol.
- Downloader instance auth kind: password / api_key (qB 5.2+ Bearer, no
/auth/login) / none (whitelisted subnet); stored in settings.authKind,
credential write-only in secret_ref, cleared when switching to none.
One qbAuthConfig() shared by sync, agent capability and connection test.
- Settings form: auth kind select, password / API Key fields (no echo).
- testkit: mock qB 5.2 login behaviour, IP ban, bypassAuth;
makeTorrent({ realPieces }) builds real piece hashes for real qB checks.
- Real-container matrix (QB_REAL, skipped by default): 5.1.2 and 5.2.4,
login / add / query / tags / pause-resume / delete keeping data.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
c846e91cc4
commit
74f1566985
14 files changed
+756
-59
No files matched your search
@@ -3,7 +3,8 @@ import { mkdtemp, readFile, rm } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { createMockQb, type MockQb } from "../src/index.ts";
|
||||
import { createMockQb, type MockQb, makeTorrent } from "../src/index.ts";
|
||||
import { bdecodeLoose } from "../src/qbittorrent/bencode.ts";
|
||||
|
||||
let mock: MockQb | null = null;
|
||||
let dir: string | null = null;
|
||||
@@ -88,3 +89,46 @@ describe("mock qB 落盘(B8)", () => {
|
||||
expect(readdirSync(dir)).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("makeTorrent realPieces", () => {
|
||||
it("按内容计算真实 piece hash:跨文件切片,片数正确,内容确定", async () => {
|
||||
const { createHash } = await import("node:crypto");
|
||||
const t = makeTorrent({
|
||||
name: "Real.Pieces",
|
||||
pieceLength: 16384,
|
||||
files: [
|
||||
{ path: "a.mkv", length: 20000 },
|
||||
{ path: "Subs/a.ass", length: 30000 },
|
||||
],
|
||||
realPieces: true,
|
||||
});
|
||||
expect(t.contents?.map((c) => [c.path, c.bytes.length])).toEqual([
|
||||
["Real.Pieces/a.mkv", 20000],
|
||||
["Real.Pieces/Subs/a.ass", 30000],
|
||||
]);
|
||||
const all = Buffer.concat(t.contents!.map((c) => Buffer.from(c.bytes)));
|
||||
const want = Buffer.concat(
|
||||
[0, 1, 2, 3].map((i) =>
|
||||
createHash("sha1")
|
||||
.update(all.subarray(i * 16384, (i + 1) * 16384))
|
||||
.digest(),
|
||||
),
|
||||
);
|
||||
const info = (bdecodeLoose(t.bytes) as { info: { pieces: Buffer } }).info;
|
||||
expect(info.pieces.equals(want)).toBe(true);
|
||||
const again = makeTorrent({
|
||||
name: "Real.Pieces",
|
||||
pieceLength: 16384,
|
||||
files: [
|
||||
{ path: "a.mkv", length: 20000 },
|
||||
{ path: "Subs/a.ass", length: 30000 },
|
||||
],
|
||||
realPieces: true,
|
||||
});
|
||||
expect(again.hash).toBe(t.hash);
|
||||
expect(
|
||||
makeTorrent({ name: "x", length: 10, realPieces: () => new Uint8Array(10) }).contents?.[0]?.bytes,
|
||||
).toHaveLength(10);
|
||||
expect(() => makeTorrent({ name: "x", length: 10, realPieces: () => new Uint8Array(3) })).toThrow();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user