Fix silent market CSV export
This commit is contained in:
@@ -9,6 +9,7 @@ describe("manifest", () => {
|
||||
expect.stringMatching(/^https:\/\/(\*\.|www\.)?xingtu\.cn\/ad\/creator\/market\*$/)
|
||||
])
|
||||
);
|
||||
expect(manifest.content_scripts?.[0]?.run_at).toBe("document_start");
|
||||
});
|
||||
|
||||
test("declares the downloads and auth permissions plus background worker", () => {
|
||||
|
||||
@@ -11,6 +11,7 @@ describe("market-content-entry", () => {
|
||||
beforeEach(() => {
|
||||
document.body.innerHTML = "";
|
||||
document.documentElement.removeAttribute("data-sces-market-rows");
|
||||
document.documentElement.removeAttribute("data-sces-market-request-snapshot");
|
||||
document.documentElement.removeAttribute("data-test-page-index");
|
||||
window.history.replaceState({}, "", "/");
|
||||
});
|
||||
@@ -33,7 +34,13 @@ describe("market-content-entry", () => {
|
||||
__SCES_MARKET_PAGE_BRIDGE_INSTALLED__?: boolean;
|
||||
}
|
||||
).__SCES_MARKET_PAGE_BRIDGE_INSTALLED__;
|
||||
delete (
|
||||
globalThis as typeof globalThis & {
|
||||
fetch?: unknown;
|
||||
}
|
||||
).fetch;
|
||||
document.documentElement.removeAttribute("data-sces-market-rows");
|
||||
document.documentElement.removeAttribute("data-sces-market-request-snapshot");
|
||||
document.documentElement.removeAttribute("data-test-page-index");
|
||||
vi.resetModules();
|
||||
|
||||
@@ -95,6 +102,40 @@ describe("market-content-entry", () => {
|
||||
).not.toBeNull();
|
||||
});
|
||||
|
||||
test("installs the market bridge before auth state resolves", async () => {
|
||||
const createMarketController = vi.fn(() => ({
|
||||
ready: Promise.resolve()
|
||||
}));
|
||||
let resolveAuthState: ((value: unknown) => void) | null = null;
|
||||
|
||||
window.history.replaceState({}, "", "/ad/creator/market");
|
||||
|
||||
const { bootContentScript } = await import("../src/content/index");
|
||||
const bootPromise = bootContentScript({
|
||||
createMarketController,
|
||||
sendAuthMessage: vi.fn(
|
||||
() =>
|
||||
new Promise((resolve) => {
|
||||
resolveAuthState = resolve;
|
||||
})
|
||||
)
|
||||
});
|
||||
|
||||
expect(
|
||||
document.documentElement.querySelector('[data-sces-market-bridge="script"]')
|
||||
).not.toBeNull();
|
||||
expect(createMarketController).not.toHaveBeenCalled();
|
||||
|
||||
resolveAuthState?.({
|
||||
ok: true,
|
||||
type: "auth:state",
|
||||
value: { isAuthenticated: true }
|
||||
});
|
||||
await bootPromise;
|
||||
|
||||
expect(createMarketController).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test("boots the market controller on the www Xingtu market URL", async () => {
|
||||
const createMarketController = vi.fn(() => ({
|
||||
ready: Promise.resolve()
|
||||
@@ -657,6 +698,104 @@ describe("market-content-entry", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
test("keeps all plugin columns in loading state until backend metrics are ready", async () => {
|
||||
document.body.innerHTML = buildRealMarketFixture([
|
||||
{
|
||||
authorId: "111",
|
||||
authorName: "达人 A",
|
||||
price21To60s: "¥450,000"
|
||||
},
|
||||
{
|
||||
authorId: "222",
|
||||
authorName: "达人 B",
|
||||
price21To60s: "¥20,000"
|
||||
}
|
||||
]);
|
||||
const backendDeferred = createDeferred<
|
||||
Array<{
|
||||
afterViewSearchRate: string;
|
||||
starId: string;
|
||||
}>
|
||||
>();
|
||||
|
||||
const { createMarketController } = await import("../src/content/market/index");
|
||||
const controller = trackController(createMarketController({
|
||||
document,
|
||||
loadAuthorMetrics: async (authorId) => ({
|
||||
success: true,
|
||||
rates:
|
||||
authorId === "111"
|
||||
? {
|
||||
singleVideoAfterSearchRate: "0.02%",
|
||||
personalVideoAfterSearchRate: "0.03% - 0.2%"
|
||||
}
|
||||
: {
|
||||
singleVideoAfterSearchRate: "0.5%-1%",
|
||||
personalVideoAfterSearchRate: "0.01% - 0.1%"
|
||||
}
|
||||
}),
|
||||
searchBackendMetrics: () => backendDeferred.promise,
|
||||
window
|
||||
}));
|
||||
|
||||
await flushWithTimers();
|
||||
|
||||
expect(readDivPluginRowTexts(0)).toEqual([
|
||||
"加载中...",
|
||||
"加载中...",
|
||||
"加载中...",
|
||||
"加载中...",
|
||||
"加载中...",
|
||||
"加载中...",
|
||||
"加载中...",
|
||||
"加载中..."
|
||||
]);
|
||||
expect(readDivPluginRowTexts(1)).toEqual([
|
||||
"加载中...",
|
||||
"加载中...",
|
||||
"加载中...",
|
||||
"加载中...",
|
||||
"加载中...",
|
||||
"加载中...",
|
||||
"加载中...",
|
||||
"加载中..."
|
||||
]);
|
||||
|
||||
backendDeferred.resolve([
|
||||
{
|
||||
afterViewSearchRate: "0.36%",
|
||||
starId: "111"
|
||||
},
|
||||
{
|
||||
afterViewSearchRate: "1.4%",
|
||||
starId: "222"
|
||||
}
|
||||
]);
|
||||
|
||||
await controller.ready;
|
||||
|
||||
expect(readDivPluginRowTexts(0)).toEqual([
|
||||
"0.02%",
|
||||
"0.03% - 0.2%",
|
||||
"0.36%",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
""
|
||||
]);
|
||||
expect(readDivPluginRowTexts(1)).toEqual([
|
||||
"0.5% - 1%",
|
||||
"0.01% - 0.1%",
|
||||
"1.4%",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
""
|
||||
]);
|
||||
});
|
||||
|
||||
test("hydrates real rows from serialized market rows when vue state is unavailable", async () => {
|
||||
document.body.innerHTML = buildRealMarketFixtureWithoutAuthorIds([
|
||||
{
|
||||
@@ -1050,6 +1189,125 @@ describe("market-content-entry", () => {
|
||||
15000
|
||||
);
|
||||
|
||||
test(
|
||||
"default export replays captured market requests silently without paging the visible table",
|
||||
async () => {
|
||||
const pages = [
|
||||
[{ authorId: "111", authorName: "达人 A", price21To60s: "¥11,000" }],
|
||||
[{ authorId: "222", authorName: "达人 B", price21To60s: "¥22,000" }],
|
||||
[{ authorId: "333", authorName: "达人 C", price21To60s: "¥33,000" }],
|
||||
[{ authorId: "444", authorName: "达人 D", price21To60s: "¥44,000" }],
|
||||
[{ authorId: "555", authorName: "达人 E", price21To60s: "¥55,000" }]
|
||||
];
|
||||
|
||||
document.body.innerHTML = buildRealMarketFixture(pages[0]);
|
||||
const pagination = installAsyncPaginationHarness(pages);
|
||||
const buildCsv = vi.fn(() => "csv-output");
|
||||
const onCsvReady = vi.fn();
|
||||
const fetchMock = vi.fn(async (_input: string, init?: RequestInit) => {
|
||||
const body = JSON.parse(String(init?.body ?? "{}")) as { page?: number };
|
||||
const pageIndex = Math.max((body.page ?? 1) - 1, 0);
|
||||
return {
|
||||
json: async () => ({
|
||||
data: {
|
||||
marketList: buildMarketListResponseRows(pages[pageIndex] ?? [])
|
||||
}
|
||||
}),
|
||||
ok: true
|
||||
};
|
||||
});
|
||||
|
||||
(
|
||||
globalThis as typeof globalThis & {
|
||||
fetch?: typeof fetchMock;
|
||||
}
|
||||
).fetch = fetchMock;
|
||||
document.documentElement.setAttribute(
|
||||
"data-sces-market-request-snapshot",
|
||||
JSON.stringify({
|
||||
body: JSON.stringify({
|
||||
page: 1
|
||||
}),
|
||||
method: "POST",
|
||||
url: "https://xingtu.cn/api/mock-market-search"
|
||||
})
|
||||
);
|
||||
|
||||
const { createMarketController } = await import("../src/content/market/index");
|
||||
const controller = trackController(createMarketController({
|
||||
buildCsv,
|
||||
document,
|
||||
loadAuthorMetrics: async () => ({
|
||||
success: false,
|
||||
reason: "request-failed"
|
||||
}),
|
||||
onCsvReady,
|
||||
window
|
||||
}));
|
||||
|
||||
await controller.ready;
|
||||
click('[data-plugin-export="button"]');
|
||||
await waitForMockCall(buildCsv, 120, 100);
|
||||
|
||||
expect(pagination.getClicks()).toBe(0);
|
||||
expect(fetchMock).toHaveBeenCalledTimes(5);
|
||||
expect(buildCsv.mock.calls[0][0].map((record) => record.authorId)).toEqual([
|
||||
"111",
|
||||
"222",
|
||||
"333",
|
||||
"444",
|
||||
"555"
|
||||
]);
|
||||
expect(onCsvReady).toHaveBeenCalledWith("csv-output");
|
||||
},
|
||||
15000
|
||||
);
|
||||
|
||||
test(
|
||||
"default export falls back to visible pagination when no captured market request is available",
|
||||
async () => {
|
||||
const pages = [
|
||||
[{ authorId: "111", authorName: "达人 A", price21To60s: "¥11,000" }],
|
||||
[{ authorId: "222", authorName: "达人 B", price21To60s: "¥22,000" }],
|
||||
[{ authorId: "333", authorName: "达人 C", price21To60s: "¥33,000" }],
|
||||
[{ authorId: "444", authorName: "达人 D", price21To60s: "¥44,000" }],
|
||||
[{ authorId: "555", authorName: "达人 E", price21To60s: "¥55,000" }]
|
||||
];
|
||||
|
||||
document.body.innerHTML = buildRealMarketFixture(pages[0]);
|
||||
const pagination = installAsyncPaginationHarness(pages);
|
||||
const buildCsv = vi.fn(() => "csv-output");
|
||||
|
||||
document.documentElement.removeAttribute("data-sces-market-request-snapshot");
|
||||
|
||||
const { createMarketController } = await import("../src/content/market/index");
|
||||
const controller = trackController(createMarketController({
|
||||
buildCsv,
|
||||
document,
|
||||
loadAuthorMetrics: async () => ({
|
||||
success: false,
|
||||
reason: "request-failed"
|
||||
}),
|
||||
onCsvReady: vi.fn(),
|
||||
window
|
||||
}));
|
||||
|
||||
await controller.ready;
|
||||
click('[data-plugin-export="button"]');
|
||||
await waitForMockCall(buildCsv, 120, 100);
|
||||
|
||||
expect(pagination.getClicks()).toBe(4);
|
||||
expect(buildCsv.mock.calls[0][0].map((record) => record.authorId)).toEqual([
|
||||
"111",
|
||||
"222",
|
||||
"333",
|
||||
"444",
|
||||
"555"
|
||||
]);
|
||||
},
|
||||
15000
|
||||
);
|
||||
|
||||
test(
|
||||
"default export waits for the next page rows instead of only the pager state",
|
||||
async () => {
|
||||
@@ -2663,6 +2921,29 @@ function attachMarketListState(
|
||||
});
|
||||
}
|
||||
|
||||
function buildMarketListResponseRows(
|
||||
rows: Array<{
|
||||
authorId: string;
|
||||
authorName: string;
|
||||
price21To60s: string;
|
||||
}>
|
||||
): Array<Record<string, unknown>> {
|
||||
return rows.map((row) => ({
|
||||
attribute_datas: {
|
||||
items: JSON.stringify([
|
||||
{
|
||||
title: `代表视频${row.authorName}`
|
||||
}
|
||||
]),
|
||||
nick_name: row.authorName,
|
||||
nickname: row.authorName,
|
||||
price_20_60: Number(row.price21To60s.replace(/[^\d]/g, ""))
|
||||
},
|
||||
nick_name: row.authorName,
|
||||
star_id: row.authorId
|
||||
}));
|
||||
}
|
||||
|
||||
function installPaginationHarness(
|
||||
pages: Array<
|
||||
Array<{
|
||||
@@ -3638,3 +3919,18 @@ async function waitForMockCall(
|
||||
await flushWithTimers();
|
||||
}
|
||||
}
|
||||
|
||||
function createDeferred<T>() {
|
||||
let resolve!: (value: T | PromiseLike<T>) => void;
|
||||
let reject!: (reason?: unknown) => void;
|
||||
const promise = new Promise<T>((nextResolve, nextReject) => {
|
||||
resolve = nextResolve;
|
||||
reject = nextReject;
|
||||
});
|
||||
|
||||
return {
|
||||
promise,
|
||||
reject,
|
||||
resolve
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
// @vitest-environment jsdom
|
||||
// @vitest-environment-options {"url":"https://www.xingtu.cn/ad/creator/market"}
|
||||
|
||||
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
|
||||
describe("market-page-bridge", () => {
|
||||
beforeEach(() => {
|
||||
document.body.innerHTML = '<div class="base-author-list"></div>';
|
||||
document.documentElement.removeAttribute("data-sces-market-request-snapshot");
|
||||
vi.spyOn(window, "setInterval").mockReturnValue(0 as unknown as number);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
delete (
|
||||
window as Window & {
|
||||
__SCES_MARKET_PAGE_BRIDGE_INSTALLED__?: boolean;
|
||||
}
|
||||
).__SCES_MARKET_PAGE_BRIDGE_INSTALLED__;
|
||||
vi.restoreAllMocks();
|
||||
vi.resetModules();
|
||||
});
|
||||
|
||||
test("ignores non-search market list responses when capturing the export snapshot", async () => {
|
||||
const originalFetch = vi.fn(async () => ({
|
||||
clone() {
|
||||
return this;
|
||||
},
|
||||
json: async () => ({
|
||||
authors: [
|
||||
{
|
||||
attribute_datas: {
|
||||
nickname: "推荐达人"
|
||||
},
|
||||
star_id: "recommend-1"
|
||||
}
|
||||
],
|
||||
pagination: {
|
||||
limit: 20,
|
||||
page: 1,
|
||||
total_count: 20
|
||||
}
|
||||
})
|
||||
}));
|
||||
|
||||
(
|
||||
window as Window & {
|
||||
fetch: typeof fetch;
|
||||
}
|
||||
).fetch = originalFetch as unknown as typeof fetch;
|
||||
|
||||
await import("../src/content/market/page-bridge");
|
||||
|
||||
await window.fetch("/gw/api/gauthor/demander_get_recommend_author_lists_v2", {
|
||||
headers: {
|
||||
Accept: "application/json, text/plain, */*"
|
||||
},
|
||||
method: "GET"
|
||||
});
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
|
||||
expect(
|
||||
document.documentElement.getAttribute("data-sces-market-request-snapshot")
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
test("captures the search_for_author_square request for silent export", async () => {
|
||||
const originalFetch = vi.fn(async () => ({
|
||||
clone() {
|
||||
return this;
|
||||
},
|
||||
json: async () => ({
|
||||
authors: [
|
||||
{
|
||||
attribute_datas: {
|
||||
nickname: "搜索达人"
|
||||
},
|
||||
star_id: "search-1"
|
||||
}
|
||||
],
|
||||
pagination: {
|
||||
limit: 20,
|
||||
page: 1,
|
||||
total_count: 20
|
||||
}
|
||||
})
|
||||
}));
|
||||
|
||||
(
|
||||
window as Window & {
|
||||
fetch: typeof fetch;
|
||||
}
|
||||
).fetch = originalFetch as unknown as typeof fetch;
|
||||
|
||||
await import("../src/content/market/page-bridge");
|
||||
|
||||
await window.fetch("/gw/api/gsearch/search_for_author_square", {
|
||||
body: JSON.stringify({
|
||||
page_param: {
|
||||
page: "1"
|
||||
}
|
||||
}),
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
method: "POST"
|
||||
});
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
|
||||
expect(
|
||||
JSON.parse(
|
||||
document.documentElement.getAttribute(
|
||||
"data-sces-market-request-snapshot"
|
||||
) ?? "null"
|
||||
)
|
||||
).toEqual(
|
||||
expect.objectContaining({
|
||||
body: JSON.stringify({
|
||||
page_param: {
|
||||
page: "1"
|
||||
}
|
||||
}),
|
||||
method: "POST",
|
||||
url: "/gw/api/gsearch/search_for_author_square"
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,263 @@
|
||||
// @vitest-environment jsdom
|
||||
|
||||
import { describe, expect, test } from "vitest";
|
||||
|
||||
import { createSilentExportController } from "../src/content/market/silent-export-controller";
|
||||
|
||||
describe("silent-export-controller", () => {
|
||||
test("replays exports from the live market page state when the request snapshot attribute is missing", async () => {
|
||||
document.body.innerHTML = '<div class="base-author-list"></div>';
|
||||
const marketRoot = document.querySelector(".base-author-list") as HTMLElement & {
|
||||
__vue__?: {
|
||||
_setupState?: Record<string, unknown>;
|
||||
};
|
||||
};
|
||||
marketRoot.__vue__ = {
|
||||
_setupState: {
|
||||
__$temp_1: {
|
||||
reqParams: {
|
||||
scene_param: {
|
||||
platform_source: 1
|
||||
},
|
||||
page_param: {
|
||||
limit: "20",
|
||||
page: "2"
|
||||
},
|
||||
search_param: {
|
||||
seach_type: 3
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const requestedPages: number[] = [];
|
||||
const requestedHeaders: Array<HeadersInit | undefined> = [];
|
||||
const requestedUrls: string[] = [];
|
||||
const controller = createSilentExportController({
|
||||
document,
|
||||
fetchImpl: async (url, init) => {
|
||||
const body = JSON.parse(String(init?.body ?? "{}")) as {
|
||||
page_param?: { page?: number | string };
|
||||
};
|
||||
const pageNo = Number(body.page_param?.page ?? 0);
|
||||
requestedPages.push(pageNo);
|
||||
requestedHeaders.push(init?.headers);
|
||||
requestedUrls.push(url);
|
||||
|
||||
return {
|
||||
json: async () => ({
|
||||
authors: [
|
||||
{
|
||||
attribute_datas: {
|
||||
nickname: `达人${pageNo}`,
|
||||
price_20_60: pageNo * 1000
|
||||
},
|
||||
star_id: String(pageNo)
|
||||
}
|
||||
],
|
||||
pagination: {
|
||||
limit: 20,
|
||||
page: pageNo,
|
||||
total_count: 100
|
||||
}
|
||||
}),
|
||||
ok: true
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const records = await controller.exportRecords({
|
||||
mode: "count",
|
||||
pageCount: 2
|
||||
});
|
||||
|
||||
expect(requestedPages).toEqual([2, 3]);
|
||||
expect(
|
||||
requestedUrls.every((url) =>
|
||||
url.endsWith("/gw/api/gsearch/search_for_author_square")
|
||||
)
|
||||
).toBe(true);
|
||||
expect(requestedHeaders).toEqual([
|
||||
{
|
||||
Accept: "application/json, text/plain, */*",
|
||||
"Agw-Js-Conv": "str",
|
||||
"Content-Type": "application/json",
|
||||
"x-login-source": "1"
|
||||
},
|
||||
{
|
||||
Accept: "application/json, text/plain, */*",
|
||||
"Agw-Js-Conv": "str",
|
||||
"Content-Type": "application/json",
|
||||
"x-login-source": "1"
|
||||
}
|
||||
]);
|
||||
expect(records?.map((record) => record.authorId)).toEqual(["2", "3"]);
|
||||
});
|
||||
|
||||
test("replays paged exports when the page number is nested inside the request body", async () => {
|
||||
document.documentElement.setAttribute(
|
||||
"data-sces-market-request-snapshot",
|
||||
JSON.stringify({
|
||||
body: JSON.stringify({
|
||||
page_param: {
|
||||
page: 2
|
||||
}
|
||||
}),
|
||||
method: "POST",
|
||||
url: "https://xingtu.cn/api/mock-market-search"
|
||||
})
|
||||
);
|
||||
|
||||
const requestedPages: number[] = [];
|
||||
const controller = createSilentExportController({
|
||||
document,
|
||||
fetchImpl: async (_url, init) => {
|
||||
const body = JSON.parse(String(init?.body ?? "{}")) as {
|
||||
page_param?: { page?: number };
|
||||
};
|
||||
const pageNo = body.page_param?.page ?? 0;
|
||||
requestedPages.push(pageNo);
|
||||
|
||||
return {
|
||||
json: async () => ({
|
||||
authors: [
|
||||
{
|
||||
attribute_datas: {
|
||||
nickname: `达人${pageNo}`,
|
||||
price_20_60: pageNo * 1000
|
||||
},
|
||||
star_id: String(pageNo)
|
||||
}
|
||||
],
|
||||
pagination: {
|
||||
limit: 20,
|
||||
page: pageNo,
|
||||
total_count: 100
|
||||
}
|
||||
}),
|
||||
ok: true
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const records = await controller.exportRecords({
|
||||
mode: "count",
|
||||
pageCount: 2
|
||||
});
|
||||
|
||||
expect(requestedPages).toEqual([2, 3]);
|
||||
expect(records?.map((record) => record.authorId)).toEqual(["2", "3"]);
|
||||
});
|
||||
|
||||
test("starts from page 1 when the captured request omits an explicit page number", async () => {
|
||||
document.documentElement.setAttribute(
|
||||
"data-sces-market-request-snapshot",
|
||||
JSON.stringify({
|
||||
body: JSON.stringify({
|
||||
filters: {
|
||||
keyword: "test"
|
||||
}
|
||||
}),
|
||||
method: "POST",
|
||||
url: "https://xingtu.cn/api/mock-market-search"
|
||||
})
|
||||
);
|
||||
|
||||
const requestedPages: number[] = [];
|
||||
const controller = createSilentExportController({
|
||||
document,
|
||||
fetchImpl: async (_url, init) => {
|
||||
const body = JSON.parse(String(init?.body ?? "{}")) as { page?: number };
|
||||
const page = body.page ?? 0;
|
||||
requestedPages.push(page);
|
||||
|
||||
return {
|
||||
json: async () => ({
|
||||
data: {
|
||||
marketList: [
|
||||
{
|
||||
attribute_datas: {
|
||||
nickname: `达人${page}`,
|
||||
price_20_60: page * 1000
|
||||
},
|
||||
star_id: String(page)
|
||||
}
|
||||
]
|
||||
}
|
||||
}),
|
||||
ok: true
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const records = await controller.exportRecords({
|
||||
mode: "count",
|
||||
pageCount: 2
|
||||
});
|
||||
|
||||
expect(requestedPages).toEqual([1, 2]);
|
||||
expect(records?.map((record) => record.authorId)).toEqual(["1", "2"]);
|
||||
});
|
||||
|
||||
test("accepts snapshot headers that contain non-string values from the live XHR capture", async () => {
|
||||
document.body.innerHTML = "";
|
||||
document.documentElement.setAttribute(
|
||||
"data-sces-market-request-snapshot",
|
||||
JSON.stringify({
|
||||
body: JSON.stringify({
|
||||
page_param: {
|
||||
page: "1",
|
||||
limit: "20"
|
||||
}
|
||||
}),
|
||||
headers: {
|
||||
Accept: "application/json, text/plain, */*",
|
||||
"Content-Type": "application/json",
|
||||
"x-login-source": 1,
|
||||
"Agw-Js-Conv": "str"
|
||||
},
|
||||
method: "POST",
|
||||
url: "/gw/api/gsearch/search_for_author_square"
|
||||
})
|
||||
);
|
||||
|
||||
const requestedPages: number[] = [];
|
||||
const controller = createSilentExportController({
|
||||
document,
|
||||
fetchImpl: async (_url, init) => {
|
||||
const body = JSON.parse(String(init?.body ?? "{}")) as {
|
||||
page_param?: { page?: number | string };
|
||||
};
|
||||
requestedPages.push(Number(body.page_param?.page ?? 0));
|
||||
|
||||
return {
|
||||
json: async () => ({
|
||||
authors: [
|
||||
{
|
||||
attribute_datas: {
|
||||
nickname: "达人1"
|
||||
},
|
||||
star_id: "1"
|
||||
}
|
||||
],
|
||||
pagination: {
|
||||
limit: 20,
|
||||
page: 1,
|
||||
total_count: 20
|
||||
}
|
||||
}),
|
||||
ok: true
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const records = await controller.exportRecords({
|
||||
mode: "count",
|
||||
pageCount: 1
|
||||
});
|
||||
|
||||
expect(requestedPages).toEqual([1]);
|
||||
expect(records?.map((record) => record.authorId)).toEqual(["1"]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user