2026-06-01 09:48:11 -04:00

333 lines
8.4 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("@/server/lib/runtime-env", () => ({
getRequiredEnvValue: vi.fn(async () => "test-api-key"),
}));
import { fetchQuestionsAnswers } from "@/server/lib/dataforseo/business";
import {
buildLlmTarget,
fetchLlmAggregatedMetrics,
fetchLlmMentionsSearch,
fetchLlmResponse,
fetchLlmTopPages,
} from "@/server/lib/dataforseo/ai";
import { fetchKeywordSearchVolume } from "@/server/lib/dataforseo/keywordsData";
function parseDataforseoRequestBody(init: RequestInit | undefined): unknown {
const body = init?.body;
if (typeof body !== "string") {
throw new Error("Expected DataForSEO request body to be a string");
}
return JSON.parse(body) as unknown;
}
describe("DataForSEO SDK-backed endpoints", () => {
beforeEach(() => {
vi.restoreAllMocks();
});
it("uses the live endpoint for Google Business Q&A and returns items + billing", async () => {
const fetchMock = vi.fn<typeof fetch>().mockResolvedValue(
Response.json({
status_code: 20000,
tasks: [
{
status_code: 20000,
path: [
"v3",
"business_data",
"google",
"questions_and_answers",
"live",
],
cost: 0.0006,
result_count: 1,
result: [
{
items: [
{
question_text: "Do you offer indoor storage?",
answer_text: "Yes.",
},
],
},
],
},
],
}),
);
vi.stubGlobal("fetch", fetchMock);
const result = await fetchQuestionsAnswers({
keyword: "Acme Storage",
locationCoordinate: "33.1234568,-84.9876543,5000",
languageCode: "en",
depth: 20,
});
expect(
fetchMock.mock.calls.map(([url]) =>
typeof url === "string" || url instanceof URL
? url.toString()
: url.url,
),
).toEqual([
"https://api.dataforseo.com/v3/business_data/google/questions_and_answers/live",
]);
expect(result.data).toEqual([
{ question_text: "Do you offer indoor storage?", answer_text: "Yes." },
]);
expect(result.billing).toEqual({
path: ["v3", "business_data", "google", "questions_and_answers", "live"],
costUsd: 0.0006,
});
});
it("does not send location_name for keyword search volume", async () => {
const fetchMock = vi.fn<typeof fetch>().mockResolvedValue(
Response.json({
status_code: 20000,
tasks: [
{
status_code: 20000,
path: [
"v3",
"keywords_data",
"google_ads",
"search_volume",
"live",
],
cost: 0.0001,
result_count: 1,
result: [
{
keyword: "storage units",
location_code: 2840,
language_code: "en",
search_volume: 1000,
},
],
},
],
}),
);
vi.stubGlobal("fetch", fetchMock);
await fetchKeywordSearchVolume({
keywords: ["storage units"],
locationCode: 2840,
languageCode: "en",
});
const payload = parseDataforseoRequestBody(fetchMock.mock.calls[0]?.[1]);
expect(payload).toEqual([
{
keywords: ["storage units"],
location_code: 2840,
language_code: "en",
},
]);
expect(JSON.stringify(payload)).not.toContain("location_name");
});
it("serializes LLM mentions domain targets for all live endpoints", async () => {
const fetchMock = vi.fn<typeof fetch>().mockImplementation((url) => {
const path =
typeof url === "string" || url instanceof URL
? url.toString()
: url.url;
const result = path.includes("/aggregated_metrics/")
? { total: { platform: [] } }
: { items: [] };
return Promise.resolve(
Response.json({
status_code: 20000,
tasks: [
{
status_code: 20000,
path: new URL(path).pathname.split("/").filter(Boolean),
cost: 0.0001,
result_count: 1,
result: [result],
},
],
}),
);
});
vi.stubGlobal("fetch", fetchMock);
const target = buildLlmTarget({
type: "domain",
value: "example.com",
});
await fetchLlmMentionsSearch({
target,
platform: "google",
locationCode: 2840,
languageCode: "en",
});
await fetchLlmAggregatedMetrics({
target,
platform: "google",
locationCode: 2840,
languageCode: "en",
});
await fetchLlmTopPages({
target,
platform: "google",
locationCode: 2840,
languageCode: "en",
});
const expectedTarget = [
{
search_scope: ["any"],
search_filter: "include",
domain: "example.com",
include_subdomains: true,
},
];
const payloads = fetchMock.mock.calls.map(([, init]) =>
parseDataforseoRequestBody(init),
);
expect(payloads).toEqual([
[
{
target: expectedTarget,
location_code: 2840,
language_code: "en",
platform: "google",
limit: 100,
},
],
[
{
target: expectedTarget,
location_code: 2840,
language_code: "en",
platform: "google",
internal_list_limit: 10,
},
],
[
{
target: expectedTarget,
location_code: 2840,
language_code: "en",
platform: "google",
links_scope: "sources",
items_list_limit: 10,
internal_list_limit: 5,
},
],
]);
});
it("serializes LLM mentions keyword targets", async () => {
const fetchMock = vi.fn<typeof fetch>().mockResolvedValue(
Response.json({
status_code: 20000,
tasks: [
{
status_code: 20000,
path: ["v3", "ai_optimization", "llm_mentions", "search", "live"],
cost: 0.0001,
result_count: 1,
result: [{ items: [] }],
},
],
}),
);
vi.stubGlobal("fetch", fetchMock);
await fetchLlmMentionsSearch({
target: buildLlmTarget({
type: "keyword",
value: "Acme Storage",
}),
platform: "chat_gpt",
locationCode: 2840,
languageCode: "en",
});
expect(parseDataforseoRequestBody(fetchMock.mock.calls[0]?.[1])).toEqual([
{
target: [
{
search_scope: ["any", "brand_entities"],
search_filter: "include",
keyword: "Acme Storage",
match_type: "word_match",
},
],
location_code: 2840,
language_code: "en",
platform: "chat_gpt",
limit: 100,
},
]);
});
it("preserves web_search for Perplexity LLM responses", async () => {
const fetchMock = vi.fn<typeof fetch>().mockResolvedValue(
Response.json({
status_code: 20000,
tasks: [
{
status_code: 20000,
path: [
"v3",
"ai_optimization",
"perplexity",
"llm_responses",
"live",
],
cost: 0.0001,
result_count: 1,
result: [
{
model_name: "sonar",
output_tokens: 12,
web_search: false,
items: [],
},
],
},
],
}),
);
vi.stubGlobal("fetch", fetchMock);
await fetchLlmResponse({
userPrompt: "What is OpenSEO?",
modelSlug: "perplexity",
modelName: "sonar",
webSearch: false,
webSearchCountryCode: "US",
});
expect(
fetchMock.mock.calls.map(([url]) =>
typeof url === "string" || url instanceof URL
? url.toString()
: url.url,
),
).toEqual([
"https://api.dataforseo.com/v3/ai_optimization/perplexity/llm_responses/live",
]);
expect(parseDataforseoRequestBody(fetchMock.mock.calls[0]?.[1])).toEqual([
{
user_prompt: "What is OpenSEO?",
model_name: "sonar",
web_search: false,
max_output_tokens: 1024,
web_search_country_iso_code: "US",
},
]);
});
});