star-chart-search-enhancer/tests/market-api-client.test.ts

166 lines
4.6 KiB
TypeScript

import { describe, expect, test } from "vitest";
import {
buildAuthorAseInfoUrl,
buildAuthorCommerceSeedBaseInfoUrl,
createMarketApiClient,
mapAuthorAseInfoResponse
} from "../src/content/market/api-client";
describe("market-api-client", () => {
test("builds the author ase info url with author id and range", () => {
expect(
buildAuthorAseInfoUrl("123", "https://xingtu.cn")
).toBe(
"https://xingtu.cn/gw/api/aggregator/get_author_ase_info?author_id=123&range=30"
);
});
test("maps a valid ASE payload into normalized rates", () => {
expect(
mapAuthorAseInfoResponse({
data: {
avg_search_after_view_rate: "<0.02%",
personal_avg_search_after_view_rate: "0.02 - 0.1%"
}
})
).toMatchObject({
success: true,
rates: {
singleVideoAfterSearchRate: "<0.02%",
personalVideoAfterSearchRate: "0.02% - 0.1%"
}
});
});
test("returns a missing-rate failure when the payload omits both rate fields", () => {
expect(
mapAuthorAseInfoResponse({
data: {}
})
).toMatchObject({
success: false,
reason: "missing-rate"
});
});
test("maps a partially populated payload into partial rates", () => {
expect(
mapAuthorAseInfoResponse({
data: {
personal_avg_search_after_view_rate: "0.02 - 0.1%"
}
})
).toMatchObject({
success: true,
rates: {
personalVideoAfterSearchRate: "0.02% - 0.1%"
}
});
});
test("returns a request-failed result for non-ok responses", async () => {
const client = createMarketApiClient({
fetchImpl: async () => ({
ok: false,
json: async () => ({})
})
});
await expect(client.loadAuthorAseInfo("123")).resolves.toMatchObject({
success: false,
reason: "request-failed"
});
});
test("loads rates from the commerce seed endpoint first", async () => {
const requestedUrls: string[] = [];
const client = createMarketApiClient({
fetchImpl: async (input) => {
requestedUrls.push(input);
return {
ok: true,
json: async () => ({
avg_search_after_view_rate: "0.1% - 0.25%",
personal_avg_search_after_view_rate: "0.02% - 0.1%"
})
};
}
});
await expect(client.loadAuthorAseInfo("7363217488772857856")).resolves.toMatchObject(
{
success: true,
rates: {
singleVideoAfterSearchRate: "0.1% - 0.25%",
personalVideoAfterSearchRate: "0.02% - 0.1%"
}
}
);
expect(requestedUrls).toEqual([
"https://xingtu.cn/gw/api/aggregator/get_author_commerce_seed_base_info?o_author_id=7363217488772857856&range=90"
]);
});
test("falls back to the ASE endpoint when the commerce seed endpoint fails", async () => {
const requestedUrls: string[] = [];
const client = createMarketApiClient({
fetchImpl: async (input) => {
requestedUrls.push(input);
if (input.includes("get_author_commerce_seed_base_info")) {
return {
ok: false,
json: async () => ({})
};
}
return {
ok: true,
json: async () => ({
data: {
avg_search_after_view_rate: "<0.02%",
personal_avg_search_after_view_rate: "0.02 - 0.1%"
}
})
};
}
});
await expect(client.loadAuthorAseInfo("7363217488772857856")).resolves.toMatchObject(
{
success: true,
rates: {
singleVideoAfterSearchRate: "<0.02%",
personalVideoAfterSearchRate: "0.02% - 0.1%"
}
}
);
expect(requestedUrls).toEqual([
"https://xingtu.cn/gw/api/aggregator/get_author_commerce_seed_base_info?o_author_id=7363217488772857856&range=90",
"https://xingtu.cn/gw/api/aggregator/get_author_ase_info?author_id=7363217488772857856&range=30"
]);
});
test("returns a timeout result when the request aborts", async () => {
const client = createMarketApiClient({
fetchImpl: async () => {
throw new DOMException("Timed out", "AbortError");
},
timeoutMs: 1
});
await expect(client.loadAuthorAseInfo("123")).resolves.toMatchObject({
success: false,
reason: "timeout"
});
});
test("builds the author commerce seed info url with author id and range", () => {
expect(
buildAuthorCommerceSeedBaseInfoUrl("7363217488772857856", "https://xingtu.cn")
).toBe(
"https://xingtu.cn/gw/api/aggregator/get_author_commerce_seed_base_info?o_author_id=7363217488772857856&range=90"
);
});
});