116 lines
4.0 KiB
TypeScript
116 lines
4.0 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from "vitest";
|
||
import {
|
||
hasOpenCredentials,
|
||
openApiBase,
|
||
openCommentXml,
|
||
openHeaders,
|
||
openMatch,
|
||
readOpenCredentials,
|
||
} from "./client.js";
|
||
|
||
afterEach(() => {
|
||
vi.unstubAllEnvs();
|
||
vi.unstubAllGlobals();
|
||
});
|
||
|
||
describe("readOpenCredentials", () => {
|
||
it("reads from injected env", () => {
|
||
expect(
|
||
readOpenCredentials({ OPEN_DANMAKU_APP_ID: "id", OPEN_DANMAKU_APP_SECRET: "s" }),
|
||
).toEqual({
|
||
appId: "id",
|
||
appSecret: "s",
|
||
});
|
||
});
|
||
it("returns null when missing", () => {
|
||
expect(readOpenCredentials({})).toBeNull();
|
||
expect(readOpenCredentials({ OPEN_DANMAKU_APP_ID: "id" })).toBeNull();
|
||
});
|
||
it("defaults to process.env", () => {
|
||
vi.stubEnv("OPEN_DANMAKU_APP_ID", "pid");
|
||
vi.stubEnv("OPEN_DANMAKU_APP_SECRET", "ps");
|
||
expect(readOpenCredentials()).toEqual({ appId: "pid", appSecret: "ps" });
|
||
expect(hasOpenCredentials()).toBe(true);
|
||
});
|
||
});
|
||
|
||
describe("openApiBase", () => {
|
||
it("strips trailing slashes", () => {
|
||
vi.stubEnv("OPEN_DANMAKU_API_BASE", "https://example.test///");
|
||
expect(openApiBase()).toBe("https://example.test");
|
||
});
|
||
it("defaults to dandanplay api", () => {
|
||
vi.stubEnv("OPEN_DANMAKU_API_BASE", undefined);
|
||
expect(openApiBase()).toBe("https://api.dandanplay.net");
|
||
});
|
||
});
|
||
|
||
describe("openHeaders", () => {
|
||
it("signs request when credentials present", () => {
|
||
const h = openHeaders("/api/v2/match", { appId: "id", appSecret: "s" });
|
||
expect(h["Content-Type"]).toBe("application/json");
|
||
expect(h["X-AppId"]).toBe("id");
|
||
expect(h["X-Timestamp"]).toMatch(/^\d+$/);
|
||
expect(h["X-Signature"]).toBeTruthy();
|
||
});
|
||
it("only content-type without credentials", () => {
|
||
expect(openHeaders("/api/v2/match", null)).toEqual({ "Content-Type": "application/json" });
|
||
});
|
||
});
|
||
|
||
function stubFetchJson(json: unknown) {
|
||
const fetchMock = vi.fn().mockResolvedValue({
|
||
ok: true,
|
||
status: 200,
|
||
json: async () => json,
|
||
});
|
||
vi.stubGlobal("fetch", fetchMock);
|
||
return fetchMock;
|
||
}
|
||
|
||
describe("openCommentXml 请求契约", () => {
|
||
it("请求路径含 ?withRelated=true(关联弹幕计入)", async () => {
|
||
const fetchMock = stubFetchJson({ success: true, comments: [] });
|
||
await openCommentXml(555);
|
||
expect(fetchMock).toHaveBeenCalledTimes(1);
|
||
const url = String(fetchMock.mock.calls[0]?.[0]);
|
||
expect(url).toBe("https://api.dandanplay.net/api/v2/comment/555?withRelated=true");
|
||
});
|
||
|
||
it("comments 序列化为 xml,HTTP 失败返回 null", async () => {
|
||
const fetchMock = stubFetchJson({
|
||
success: true,
|
||
comments: [{ p: "12.5,1,0,0,0,0,,", m: "hi" }],
|
||
});
|
||
await expect(openCommentXml(555)).resolves.toContain("<d ");
|
||
expect(fetchMock).toHaveBeenCalledTimes(1);
|
||
|
||
fetchMock.mockResolvedValue({ ok: false, status: 500, json: async () => ({}) });
|
||
await expect(openCommentXml(555)).resolves.toBeNull();
|
||
});
|
||
});
|
||
|
||
describe("openMatch 请求契约", () => {
|
||
it("POST /api/v2/match,matchMode 随 hash 切换", async () => {
|
||
const fetchMock = stubFetchJson({ success: true, isMatched: true, matches: [] });
|
||
|
||
await openMatch("Show - 02", 100, "abc");
|
||
const [url1, init1] = fetchMock.mock.calls[0] ?? [];
|
||
expect(String(url1)).toBe("https://api.dandanplay.net/api/v2/match");
|
||
expect(init1?.method).toBe("POST");
|
||
expect(JSON.parse(String(init1?.body))).toEqual({
|
||
fileName: "Show - 02",
|
||
fileHash: "abc",
|
||
fileSize: 100,
|
||
matchMode: "hashAndFileName",
|
||
});
|
||
|
||
await openMatch("Show - 02", 100, null);
|
||
const init2 = fetchMock.mock.calls[1]?.[1];
|
||
expect(JSON.parse(String(init2?.body))).toMatchObject({
|
||
fileHash: "",
|
||
matchMode: "fileNameOnly",
|
||
});
|
||
});
|
||
});
|