feat: make audience export fields selective
This commit is contained in:
@@ -117,6 +117,31 @@ describe("csv-exporter", () => {
|
||||
);
|
||||
});
|
||||
|
||||
test("omits unselected columns from the standard CSV after field settings are saved", () => {
|
||||
const csv = buildMarketCsv(
|
||||
[
|
||||
{
|
||||
authorId: "123",
|
||||
authorName: "Alice",
|
||||
exportFields: {
|
||||
达人信息: "Alice",
|
||||
粉丝数: "100w",
|
||||
预期CPM: "120"
|
||||
},
|
||||
status: "success"
|
||||
} satisfies MarketRecord
|
||||
],
|
||||
{
|
||||
selectedHeaders: ["粉丝数"]
|
||||
}
|
||||
);
|
||||
|
||||
const [headerLine, rowLine] = csv.split("\n");
|
||||
|
||||
expect(headerLine).toBe("达人ID,达人名称,导出状态,失败原因,粉丝数");
|
||||
expect(rowLine).toBe("123,Alice,成功,,100w");
|
||||
});
|
||||
|
||||
test("escapes commas and quotes", () => {
|
||||
const csv = buildMarketCsv([
|
||||
{
|
||||
|
||||
+301
-1779
File diff suppressed because it is too large
Load Diff
+129
-1
@@ -6,9 +6,11 @@ import {
|
||||
buildSpreadInfoUrl,
|
||||
createSpreadInfoClient,
|
||||
DEFAULT_SPREAD_INFO_CONFIGS,
|
||||
filterUnloadedSpreadInfoConfigs,
|
||||
matchesSpreadMetricRule,
|
||||
normalizeSpreadInfoConfig,
|
||||
mapSpreadInfoResponse
|
||||
mapSpreadInfoResponse,
|
||||
selectSpreadInfoConfigsForHeaders
|
||||
} from "../src/content/market/spread-info";
|
||||
|
||||
describe("spread-info", () => {
|
||||
@@ -90,6 +92,49 @@ describe("spread-info", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
test("selects only unique request configs required by chosen columns", () => {
|
||||
expect(
|
||||
selectSpreadInfoConfigsForHeaders([
|
||||
"内容数据-个人视频-近30天-完播率",
|
||||
"内容数据-个人视频-近30天-作品平均评论数",
|
||||
"内容数据-只看指派-排除营销流量-星图视频-近90天-互动率",
|
||||
"效果预估-20-60s视频-预期CPM"
|
||||
])
|
||||
).toEqual([
|
||||
{
|
||||
flowType: 0,
|
||||
onlyAssign: false,
|
||||
range: 2,
|
||||
type: 1
|
||||
},
|
||||
{
|
||||
flowType: 1,
|
||||
onlyAssign: true,
|
||||
range: 3,
|
||||
type: 2
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
test("keeps newly selected configs when other content configs are cached", () => {
|
||||
const personalConfig = DEFAULT_SPREAD_INFO_CONFIGS[0];
|
||||
const xingtuConfig = DEFAULT_SPREAD_INFO_CONFIGS.find(
|
||||
(config) =>
|
||||
config.type === 2 &&
|
||||
config.onlyAssign &&
|
||||
config.flowType === 1 &&
|
||||
config.range === 3
|
||||
);
|
||||
|
||||
expect(xingtuConfig).toBeDefined();
|
||||
expect(
|
||||
filterUnloadedSpreadInfoConfigs(
|
||||
[personalConfig, xingtuConfig!],
|
||||
{ "内容数据-个人视频-近30天-完播率": "28%" }
|
||||
)
|
||||
).toEqual([xingtuConfig]);
|
||||
});
|
||||
|
||||
test("maps spread info response values into display values", () => {
|
||||
expect(
|
||||
mapSpreadInfoResponse({
|
||||
@@ -169,6 +214,89 @@ describe("spread-info", () => {
|
||||
);
|
||||
});
|
||||
|
||||
test("requests only the explicitly selected spread configs", async () => {
|
||||
const fetchImpl = vi.fn(async () => ({
|
||||
json: async () => ({ play_over_rate: { value: 2824 } }),
|
||||
ok: true
|
||||
}));
|
||||
const client = createSpreadInfoClient({ fetchImpl });
|
||||
const selectedConfig = {
|
||||
flowType: 1 as const,
|
||||
onlyAssign: true,
|
||||
range: 3 as const,
|
||||
type: 2 as const
|
||||
};
|
||||
|
||||
await client.loadAuthorSpreadMetrics("7361012802036695050", [selectedConfig]);
|
||||
|
||||
expect(fetchImpl).toHaveBeenCalledTimes(1);
|
||||
expect(fetchImpl.mock.calls[0][0]).toContain(
|
||||
"type=2&flow_type=1&only_assign=true&range=3"
|
||||
);
|
||||
});
|
||||
|
||||
test("throws an HTTP error instead of returning empty metrics", async () => {
|
||||
const client = createSpreadInfoClient({
|
||||
fetchImpl: async () => ({
|
||||
json: async () => ({}),
|
||||
ok: false,
|
||||
status: 429,
|
||||
statusText: "Too Many Requests"
|
||||
})
|
||||
});
|
||||
|
||||
await expect(
|
||||
client.loadAuthorSpreadMetricSnapshot("7361012802036695050", {
|
||||
flowType: 0,
|
||||
onlyAssign: true,
|
||||
range: 2,
|
||||
type: 2
|
||||
})
|
||||
).rejects.toThrow("HTTP 429: Too Many Requests");
|
||||
});
|
||||
|
||||
test("throws an explicit error when Xingtu reports business rate limiting", async () => {
|
||||
const client = createSpreadInfoClient({
|
||||
fetchImpl: async () => ({
|
||||
json: async () => ({
|
||||
base_resp: {
|
||||
status_code: 31157,
|
||||
status_message: "您访问过于频繁,请24小时后重试"
|
||||
}
|
||||
}),
|
||||
ok: true
|
||||
})
|
||||
});
|
||||
|
||||
await expect(
|
||||
client.loadAuthorSpreadMetricSnapshot("7361012802036695050", {
|
||||
flowType: 0,
|
||||
onlyAssign: true,
|
||||
range: 2,
|
||||
type: 2
|
||||
})
|
||||
).rejects.toThrow(
|
||||
"星图传播数据接口被限流 (status_code=31157): 您访问过于频繁,请24小时后重试"
|
||||
);
|
||||
});
|
||||
|
||||
test("propagates network errors instead of returning empty metrics", async () => {
|
||||
const client = createSpreadInfoClient({
|
||||
fetchImpl: async () => {
|
||||
throw new Error("network unavailable");
|
||||
}
|
||||
});
|
||||
|
||||
await expect(
|
||||
client.loadAuthorSpreadMetricSnapshot("7361012802036695050", {
|
||||
flowType: 0,
|
||||
onlyAssign: true,
|
||||
range: 2,
|
||||
type: 2
|
||||
})
|
||||
).rejects.toThrow("network unavailable");
|
||||
});
|
||||
|
||||
test("normalizes fixed personal-video parameters before grouping", () => {
|
||||
expect(
|
||||
normalizeSpreadInfoConfig({
|
||||
|
||||
Reference in New Issue
Block a user