303 lines
8.5 KiB
TypeScript
303 lines
8.5 KiB
TypeScript
import { describe, expect, test, vi } from "vitest";
|
|
|
|
import {
|
|
buildSpreadInfoColumns,
|
|
buildSpreadInfoUrl,
|
|
createSpreadInfoClient,
|
|
DEFAULT_SPREAD_INFO_CONFIGS,
|
|
filterUnloadedSpreadInfoConfigs,
|
|
mapSpreadInfoResponse,
|
|
selectSpreadInfoConfigsForHeaders
|
|
} from "../src/content/market/spread-info";
|
|
|
|
describe("spread-info", () => {
|
|
test("builds the spread info url with all request parameters", () => {
|
|
expect(
|
|
buildSpreadInfoUrl(
|
|
"7361012802036695050",
|
|
{
|
|
flowType: 1,
|
|
onlyAssign: true,
|
|
range: 2,
|
|
type: 2
|
|
},
|
|
"https://www.xingtu.cn"
|
|
)
|
|
).toBe(
|
|
"https://www.xingtu.cn/gw/api/data_sp/get_author_spread_info?o_author_id=7361012802036695050&platform_source=1&platform_channel=1&type=2&flow_type=1&only_assign=true&range=2"
|
|
);
|
|
});
|
|
|
|
test("defines personal video ranges with fixed non-prefix parameters", () => {
|
|
const personalConfigs = DEFAULT_SPREAD_INFO_CONFIGS.filter(
|
|
(config) => config.type === 1
|
|
);
|
|
|
|
expect(personalConfigs).toEqual([
|
|
{
|
|
flowType: 0,
|
|
onlyAssign: false,
|
|
range: 2,
|
|
type: 1
|
|
},
|
|
{
|
|
flowType: 0,
|
|
onlyAssign: false,
|
|
range: 3,
|
|
type: 1
|
|
}
|
|
]);
|
|
expect(buildSpreadInfoColumns(personalConfigs).slice(0, 2)).toEqual([
|
|
"内容数据-个人视频-近30天-完播率",
|
|
"内容数据-个人视频-近30天-播放量中位数"
|
|
]);
|
|
});
|
|
|
|
test("defines all xingtu video assign flow and range combinations", () => {
|
|
const xingtuConfigs = DEFAULT_SPREAD_INFO_CONFIGS.filter(
|
|
(config) => config.type === 2
|
|
);
|
|
|
|
expect(xingtuConfigs).toHaveLength(8);
|
|
expect(xingtuConfigs).toContainEqual({
|
|
flowType: 1,
|
|
onlyAssign: true,
|
|
range: 2,
|
|
type: 2
|
|
});
|
|
expect(xingtuConfigs).toContainEqual({
|
|
flowType: 0,
|
|
onlyAssign: false,
|
|
range: 3,
|
|
type: 2
|
|
});
|
|
expect(buildSpreadInfoColumns([
|
|
{
|
|
flowType: 1,
|
|
onlyAssign: true,
|
|
range: 2,
|
|
type: 2
|
|
}
|
|
])).toEqual([
|
|
"内容数据-只看指派-排除营销流量-星图视频-近30天-完播率",
|
|
"内容数据-只看指派-排除营销流量-星图视频-近30天-播放量中位数",
|
|
"内容数据-只看指派-排除营销流量-星图视频-近30天-互动率",
|
|
"内容数据-只看指派-排除营销流量-星图视频-近30天-作品平均时长",
|
|
"内容数据-只看指派-排除营销流量-星图视频-近30天-作品平均评论数",
|
|
"内容数据-只看指派-排除营销流量-星图视频-近30天-作品平均点赞数",
|
|
"内容数据-只看指派-排除营销流量-星图视频-近30天-作品平均转发数"
|
|
]);
|
|
});
|
|
|
|
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({
|
|
avg_duration: "5600",
|
|
comment_avg: "7502",
|
|
interact_rate: {
|
|
value: 402
|
|
},
|
|
item_rate: {
|
|
play_mid: {
|
|
value: 10913233
|
|
}
|
|
},
|
|
like_avg: "494458",
|
|
play_over_rate: {
|
|
value: 2824
|
|
},
|
|
share_avg: "188267"
|
|
})
|
|
).toEqual({
|
|
averageCommentCount: "7502",
|
|
averageDuration: "56",
|
|
averageLikeCount: "494458",
|
|
averageShareCount: "188267",
|
|
finishRate: "28.24%",
|
|
interactionRate: "4.02%",
|
|
playMedian: "10913233"
|
|
});
|
|
});
|
|
|
|
test("loads each configured spread metric column for one author", async () => {
|
|
const fetchImpl = vi.fn(async () => ({
|
|
json: async () => ({
|
|
avg_duration: "5600",
|
|
comment_avg: "7502",
|
|
interact_rate: {
|
|
value: 402
|
|
},
|
|
like_avg: "494458",
|
|
play_mid: "10913233",
|
|
play_over_rate: {
|
|
value: 2824
|
|
},
|
|
share_avg: "188267"
|
|
}),
|
|
ok: true
|
|
}));
|
|
const client = createSpreadInfoClient({
|
|
configs: [
|
|
{
|
|
flowType: 0,
|
|
onlyAssign: false,
|
|
range: 2,
|
|
type: 1
|
|
}
|
|
],
|
|
fetchImpl
|
|
});
|
|
|
|
await expect(
|
|
client.loadAuthorSpreadMetrics("7361012802036695050")
|
|
).resolves.toEqual({
|
|
"内容数据-个人视频-近30天-互动率": "4.02%",
|
|
"内容数据-个人视频-近30天-作品平均点赞数": "494458",
|
|
"内容数据-个人视频-近30天-作品平均评论数": "7502",
|
|
"内容数据-个人视频-近30天-作品平均时长": "56",
|
|
"内容数据-个人视频-近30天-作品平均转发数": "188267",
|
|
"内容数据-个人视频-近30天-完播率": "28.24%",
|
|
"内容数据-个人视频-近30天-播放量中位数": "10913233"
|
|
});
|
|
expect(fetchImpl).toHaveBeenCalledWith(
|
|
"https://www.xingtu.cn/gw/api/data_sp/get_author_spread_info?o_author_id=7361012802036695050&platform_source=1&platform_channel=1&type=1&flow_type=0&only_assign=false&range=2",
|
|
expect.objectContaining({
|
|
credentials: "include",
|
|
method: "GET"
|
|
})
|
|
);
|
|
});
|
|
|
|
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.loadAuthorSpreadMetrics("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.loadAuthorSpreadMetrics("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.loadAuthorSpreadMetrics("7361012802036695050", [
|
|
{
|
|
flowType: 0,
|
|
onlyAssign: true,
|
|
range: 2,
|
|
type: 2
|
|
}
|
|
])
|
|
).rejects.toThrow("network unavailable");
|
|
});
|
|
});
|