fix: validate Firecrawl requests before fallback
This commit is contained in:
@@ -124,21 +124,145 @@ test("search rejects a mismatched provider-specific options block before provide
|
||||
assert.equal(callCount, 0);
|
||||
});
|
||||
|
||||
test("fetch rejects Firecrawl highlights before provider execution", async () => {
|
||||
let callCount = 0;
|
||||
test("search rejects Firecrawl requests with multiple includeDomains before provider execution", async () => {
|
||||
const calls: string[] = [];
|
||||
|
||||
const runtime = createWebSearchRuntime({
|
||||
loadConfig: async () => ({
|
||||
path: "test.json",
|
||||
defaultProviderName: "firecrawl-main",
|
||||
defaultProvider: { name: "firecrawl-main", type: "firecrawl", apiKey: "fc" },
|
||||
providers: [{ name: "firecrawl-main", type: "firecrawl", apiKey: "fc" }],
|
||||
providersByName: new Map([["firecrawl-main", { name: "firecrawl-main", type: "firecrawl", apiKey: "fc" }]]),
|
||||
defaultProvider: {
|
||||
name: "firecrawl-main",
|
||||
type: "firecrawl",
|
||||
apiKey: "fc",
|
||||
fallbackProviders: ["exa-fallback"],
|
||||
},
|
||||
providers: [
|
||||
{
|
||||
name: "firecrawl-main",
|
||||
type: "firecrawl",
|
||||
apiKey: "fc",
|
||||
fallbackProviders: ["exa-fallback"],
|
||||
},
|
||||
{ name: "exa-fallback", type: "exa", apiKey: "exa" },
|
||||
],
|
||||
providersByName: new Map([
|
||||
[
|
||||
"firecrawl-main",
|
||||
{ name: "firecrawl-main", type: "firecrawl", apiKey: "fc", fallbackProviders: ["exa-fallback"] },
|
||||
],
|
||||
["exa-fallback", { name: "exa-fallback", type: "exa", apiKey: "exa" }],
|
||||
]),
|
||||
}),
|
||||
createProvider(providerConfig) {
|
||||
return createProvider(providerConfig.name, providerConfig.type, {
|
||||
search: async () => {
|
||||
calls.push(providerConfig.name);
|
||||
throw new Error(`boom:${providerConfig.name}`);
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
() =>
|
||||
runtime.search({
|
||||
query: "pi docs",
|
||||
provider: "firecrawl-main",
|
||||
includeDomains: ["pi.dev", "exa.ai"],
|
||||
}),
|
||||
/Provider "firecrawl-main" accepts at most one includeDomains entry/,
|
||||
);
|
||||
assert.deepEqual(calls, []);
|
||||
});
|
||||
|
||||
test("search rejects Firecrawl category conflicts before provider execution", async () => {
|
||||
const calls: string[] = [];
|
||||
|
||||
const runtime = createWebSearchRuntime({
|
||||
loadConfig: async () => ({
|
||||
path: "test.json",
|
||||
defaultProviderName: "firecrawl-main",
|
||||
defaultProvider: {
|
||||
name: "firecrawl-main",
|
||||
type: "firecrawl",
|
||||
apiKey: "fc",
|
||||
fallbackProviders: ["exa-fallback"],
|
||||
},
|
||||
providers: [
|
||||
{
|
||||
name: "firecrawl-main",
|
||||
type: "firecrawl",
|
||||
apiKey: "fc",
|
||||
fallbackProviders: ["exa-fallback"],
|
||||
},
|
||||
{ name: "exa-fallback", type: "exa", apiKey: "exa" },
|
||||
],
|
||||
providersByName: new Map([
|
||||
[
|
||||
"firecrawl-main",
|
||||
{ name: "firecrawl-main", type: "firecrawl", apiKey: "fc", fallbackProviders: ["exa-fallback"] },
|
||||
],
|
||||
["exa-fallback", { name: "exa-fallback", type: "exa", apiKey: "exa" }],
|
||||
]),
|
||||
}),
|
||||
createProvider(providerConfig) {
|
||||
return createProvider(providerConfig.name, providerConfig.type, {
|
||||
search: async () => {
|
||||
calls.push(providerConfig.name);
|
||||
throw new Error(`boom:${providerConfig.name}`);
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
() =>
|
||||
runtime.search({
|
||||
query: "pi docs",
|
||||
provider: "firecrawl-main",
|
||||
category: "research",
|
||||
firecrawl: { categories: ["github"] },
|
||||
}),
|
||||
/Provider "firecrawl-main" does not accept both top-level category and firecrawl.categories/,
|
||||
);
|
||||
assert.deepEqual(calls, []);
|
||||
});
|
||||
|
||||
test("fetch rejects Firecrawl highlights before provider execution", async () => {
|
||||
const calls: string[] = [];
|
||||
|
||||
const runtime = createWebSearchRuntime({
|
||||
loadConfig: async () => ({
|
||||
path: "test.json",
|
||||
defaultProviderName: "firecrawl-main",
|
||||
defaultProvider: {
|
||||
name: "firecrawl-main",
|
||||
type: "firecrawl",
|
||||
apiKey: "fc",
|
||||
fallbackProviders: ["exa-fallback"],
|
||||
},
|
||||
providers: [
|
||||
{
|
||||
name: "firecrawl-main",
|
||||
type: "firecrawl",
|
||||
apiKey: "fc",
|
||||
fallbackProviders: ["exa-fallback"],
|
||||
},
|
||||
{ name: "exa-fallback", type: "exa", apiKey: "exa" },
|
||||
],
|
||||
providersByName: new Map([
|
||||
[
|
||||
"firecrawl-main",
|
||||
{ name: "firecrawl-main", type: "firecrawl", apiKey: "fc", fallbackProviders: ["exa-fallback"] },
|
||||
],
|
||||
["exa-fallback", { name: "exa-fallback", type: "exa", apiKey: "exa" }],
|
||||
]),
|
||||
}),
|
||||
createProvider(providerConfig) {
|
||||
return createProvider(providerConfig.name, providerConfig.type, {
|
||||
fetch: async () => {
|
||||
callCount += 1;
|
||||
calls.push(providerConfig.name);
|
||||
return {
|
||||
providerName: providerConfig.name,
|
||||
results: [],
|
||||
@@ -149,10 +273,80 @@ test("fetch rejects Firecrawl highlights before provider execution", async () =>
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
() => runtime.fetch({ urls: ["https://pi.dev"], highlights: true }),
|
||||
() => runtime.fetch({ urls: ["https://pi.dev"], provider: "firecrawl-main", highlights: true }),
|
||||
/does not support generic fetch option "highlights"/,
|
||||
);
|
||||
assert.equal(callCount, 0);
|
||||
assert.deepEqual(calls, []);
|
||||
});
|
||||
|
||||
test("fetch rejects Firecrawl format mismatches before provider execution", async () => {
|
||||
const calls: string[] = [];
|
||||
|
||||
const runtime = createWebSearchRuntime({
|
||||
loadConfig: async () => ({
|
||||
path: "test.json",
|
||||
defaultProviderName: "firecrawl-main",
|
||||
defaultProvider: {
|
||||
name: "firecrawl-main",
|
||||
type: "firecrawl",
|
||||
apiKey: "fc",
|
||||
fallbackProviders: ["exa-fallback"],
|
||||
},
|
||||
providers: [
|
||||
{
|
||||
name: "firecrawl-main",
|
||||
type: "firecrawl",
|
||||
apiKey: "fc",
|
||||
fallbackProviders: ["exa-fallback"],
|
||||
},
|
||||
{ name: "exa-fallback", type: "exa", apiKey: "exa" },
|
||||
],
|
||||
providersByName: new Map([
|
||||
[
|
||||
"firecrawl-main",
|
||||
{ name: "firecrawl-main", type: "firecrawl", apiKey: "fc", fallbackProviders: ["exa-fallback"] },
|
||||
],
|
||||
["exa-fallback", { name: "exa-fallback", type: "exa", apiKey: "exa" }],
|
||||
]),
|
||||
}),
|
||||
createProvider(providerConfig) {
|
||||
return createProvider(providerConfig.name, providerConfig.type, {
|
||||
fetch: async () => {
|
||||
calls.push(providerConfig.name);
|
||||
return {
|
||||
providerName: providerConfig.name,
|
||||
results: [],
|
||||
};
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
() =>
|
||||
runtime.fetch({
|
||||
urls: ["https://pi.dev"],
|
||||
provider: "firecrawl-main",
|
||||
summary: true,
|
||||
firecrawl: { formats: ["markdown"] },
|
||||
}),
|
||||
/Provider "firecrawl-main" requires firecrawl.formats to include "summary" when summary is true/,
|
||||
);
|
||||
assert.deepEqual(calls, []);
|
||||
});
|
||||
|
||||
test("search throws a clear error for unknown provider types", async () => {
|
||||
const runtime = createWebSearchRuntime({
|
||||
loadConfig: async () => ({
|
||||
path: "test.json",
|
||||
defaultProviderName: "mystery-main",
|
||||
defaultProvider: { name: "mystery-main", type: "mystery", apiKey: "??" } as any,
|
||||
providers: [{ name: "mystery-main", type: "mystery", apiKey: "??" } as any],
|
||||
providersByName: new Map([["mystery-main", { name: "mystery-main", type: "mystery", apiKey: "??" } as any]]),
|
||||
}),
|
||||
});
|
||||
|
||||
await assert.rejects(() => runtime.search({ query: "pi docs" }), /Unknown provider type: mystery/);
|
||||
});
|
||||
|
||||
test("search starts with the explicitly requested provider and still follows its fallback chain", async () => {
|
||||
|
||||
Reference in New Issue
Block a user