178 lines
4.5 KiB
TypeScript
178 lines
4.5 KiB
TypeScript
import { describe, expect, test } from "vitest";
|
|
|
|
import { buildMarketCsv } from "../src/content/market/csv-exporter";
|
|
import type { MarketRecord } from "../src/content/market/types";
|
|
|
|
describe("csv-exporter", () => {
|
|
test("uses fallback header order when no page export fields are available", () => {
|
|
const csv = buildMarketCsv([]);
|
|
const [headerLine] = csv.split("\n");
|
|
|
|
expect(headerLine).toBe(
|
|
[
|
|
"达人ID",
|
|
"达人名称",
|
|
"地区",
|
|
"21-60s报价",
|
|
"单视频看后搜率",
|
|
"个人视频看后搜率",
|
|
"看后搜率",
|
|
"看后搜数",
|
|
"新增A3数",
|
|
"新增A3率",
|
|
"CPA3",
|
|
"cp_search"
|
|
].join(",")
|
|
);
|
|
});
|
|
|
|
test("uses page export field order and appends the two plugin columns", () => {
|
|
const csv = buildMarketCsv([
|
|
{
|
|
authorId: "123",
|
|
authorName: "Alice",
|
|
exportFields: {
|
|
达人信息: "Alice",
|
|
代表视频: "示例视频",
|
|
粉丝数: "100w",
|
|
"21-60s报价": "¥450,000"
|
|
},
|
|
status: "success",
|
|
backendMetrics: {
|
|
a3IncreaseCount: "78,366.22",
|
|
afterViewSearchCount: "9,689.96",
|
|
afterViewSearchRate: "0.36%",
|
|
cpSearch: "14.46",
|
|
cpa3: "1.79",
|
|
newA3Rate: "3.44%"
|
|
},
|
|
rates: {
|
|
singleVideoAfterSearchRate: "0.5%-1%",
|
|
personalVideoAfterSearchRate: "1% - 3%"
|
|
}
|
|
} satisfies MarketRecord
|
|
]);
|
|
|
|
const [headerLine, rowLine] = csv.split("\n");
|
|
expect(headerLine).toBe(
|
|
[
|
|
"达人信息",
|
|
"粉丝数",
|
|
"21-60s报价",
|
|
"单视频看后搜率",
|
|
"个人视频看后搜率",
|
|
"看后搜率",
|
|
"看后搜数",
|
|
"新增A3数",
|
|
"新增A3率",
|
|
"CPA3",
|
|
"cp_search"
|
|
].join(",")
|
|
);
|
|
expect(rowLine).toBe(
|
|
'Alice,100w,"¥450,000",0.5% - 1%,1% - 3%,0.36%,"9,689.96","78,366.22",3.44%,1.79,14.46'
|
|
);
|
|
});
|
|
|
|
test("omits the representative video column from exported page fields", () => {
|
|
const csv = buildMarketCsv([
|
|
{
|
|
authorId: "123",
|
|
authorName: "Alice",
|
|
exportFields: {
|
|
达人信息: "Alice",
|
|
代表视频: "示例视频",
|
|
粉丝数: "100w"
|
|
},
|
|
status: "success"
|
|
} satisfies MarketRecord
|
|
]);
|
|
|
|
const [headerLine, rowLine] = csv.split("\n");
|
|
|
|
expect(headerLine).toBe(
|
|
[
|
|
"达人信息",
|
|
"粉丝数",
|
|
"单视频看后搜率",
|
|
"个人视频看后搜率",
|
|
"看后搜率",
|
|
"看后搜数",
|
|
"新增A3数",
|
|
"新增A3率",
|
|
"CPA3",
|
|
"cp_search"
|
|
].join(",")
|
|
);
|
|
expect(rowLine).toBe("Alice,100w,,,,,,,,");
|
|
});
|
|
|
|
test("escapes commas and quotes", () => {
|
|
const csv = buildMarketCsv([
|
|
{
|
|
authorId: "123",
|
|
authorName: "Alice, \"A\"",
|
|
location: "Hangzhou",
|
|
price21To60s: "450000",
|
|
status: "success",
|
|
rates: {
|
|
singleVideoAfterSearchRate: "0.5%-1%",
|
|
personalVideoAfterSearchRate: "1% - 3%"
|
|
}
|
|
}
|
|
]);
|
|
|
|
const [, rowLine] = csv.split("\n");
|
|
expect(rowLine).toContain("\"Alice, \"\"A\"\"\"");
|
|
});
|
|
|
|
test("emits empty rate fields plus failed status for failed rows", () => {
|
|
const csv = buildMarketCsv([
|
|
{
|
|
authorId: "123",
|
|
authorName: "Alice",
|
|
status: "failed",
|
|
failureReason: "request-failed"
|
|
}
|
|
]);
|
|
|
|
const [, rowLine] = csv.split("\n");
|
|
expect(rowLine).toBe("123,Alice,,,,,,,,,,");
|
|
});
|
|
|
|
test("uses normalized display values in export rows", () => {
|
|
const csv = buildMarketCsv([
|
|
{
|
|
authorId: "123",
|
|
authorName: "Alice",
|
|
status: "success",
|
|
rates: {
|
|
singleVideoAfterSearchRate: "0.5%-1%",
|
|
personalVideoAfterSearchRate: "0.02 - 0.1%"
|
|
}
|
|
} satisfies MarketRecord
|
|
]);
|
|
|
|
const [, rowLine] = csv.split("\n");
|
|
expect(rowLine).toContain("0.5% - 1%");
|
|
expect(rowLine).toContain("0.02% - 0.1%");
|
|
});
|
|
|
|
test("emits empty backend metric cells when backend metrics are absent", () => {
|
|
const csv = buildMarketCsv([
|
|
{
|
|
authorId: "123",
|
|
authorName: "Alice",
|
|
status: "success",
|
|
rates: {
|
|
singleVideoAfterSearchRate: "0.5%-1%",
|
|
personalVideoAfterSearchRate: "0.02 - 0.1%"
|
|
}
|
|
} satisfies MarketRecord
|
|
]);
|
|
|
|
const [, rowLine] = csv.split("\n");
|
|
expect(rowLine).toBe("123,Alice,,,0.5% - 1%,0.02% - 0.1%,,,,,,");
|
|
});
|
|
});
|