mirror of
https://github.com/kennethnym/aris.git
synced 2026-06-15 20:11:18 +01:00
124 lines
3.5 KiB
TypeScript
124 lines
3.5 KiB
TypeScript
import { Context } from "@freya/core"
|
|
import { describe, expect, test } from "bun:test"
|
|
|
|
import type { WebSearchClient, WebSearchRequest, WebSearchResponse } from "./types.ts"
|
|
|
|
import { WebSearchAction } from "./types.ts"
|
|
import { WebSearchSource } from "./web-search-source.ts"
|
|
|
|
class RecordingSearchClient implements WebSearchClient {
|
|
requests: WebSearchRequest[] = []
|
|
|
|
async search(request: WebSearchRequest): Promise<WebSearchResponse> {
|
|
this.requests.push(request)
|
|
return {
|
|
query: request.query,
|
|
requestId: "request-1",
|
|
results: [
|
|
{
|
|
id: "https://example.com/a",
|
|
url: "https://example.com/a",
|
|
title: "Example result",
|
|
publishedDate: "2026-01-01T00:00:00.000Z",
|
|
author: "Example Author",
|
|
image: null,
|
|
favicon: "https://example.com/favicon.ico",
|
|
text: null,
|
|
highlights: ["Relevant excerpt"],
|
|
highlightScores: [0.8],
|
|
summary: null,
|
|
},
|
|
],
|
|
}
|
|
}
|
|
}
|
|
|
|
describe("WebSearchSource", () => {
|
|
test("has correct id", () => {
|
|
const source = new WebSearchSource({ client: new RecordingSearchClient() })
|
|
|
|
expect(source.id).toBe(WebSearchSource.id)
|
|
})
|
|
|
|
test("does not provide context or feed items", async () => {
|
|
const source = new WebSearchSource({ client: new RecordingSearchClient() })
|
|
|
|
expect("fetchItems" in source).toBe(false)
|
|
expect(await source.fetchContext(new Context())).toBeNull()
|
|
})
|
|
|
|
test("lists search action", async () => {
|
|
const source = new WebSearchSource({ client: new RecordingSearchClient() })
|
|
const actions = await source.listActions()
|
|
|
|
expect(actions[WebSearchAction.Search]).toBeDefined()
|
|
expect(actions[WebSearchAction.Search]!.id).toBe(WebSearchAction.Search)
|
|
expect(actions[WebSearchAction.Search]!.input).toBeDefined()
|
|
})
|
|
|
|
test("executes search action with normalized params", async () => {
|
|
const client = new RecordingSearchClient()
|
|
const source = new WebSearchSource({ client })
|
|
|
|
const result = await source.executeAction(WebSearchAction.Search, {
|
|
query: " latest personal assistant research ",
|
|
includeDomains: ["exa.ai"],
|
|
type: "fast",
|
|
userLocation: "gb",
|
|
moderation: true,
|
|
})
|
|
|
|
expect(result.requestId).toBe("request-1")
|
|
expect(result.results).toHaveLength(1)
|
|
expect(client.requests).toEqual([
|
|
{
|
|
query: "latest personal assistant research",
|
|
numResults: 10,
|
|
includeDomains: ["exa.ai"],
|
|
type: "fast",
|
|
userLocation: "GB",
|
|
moderation: true,
|
|
},
|
|
])
|
|
})
|
|
|
|
test("allows per-call numResults override", async () => {
|
|
const client = new RecordingSearchClient()
|
|
const source = new WebSearchSource({ client })
|
|
|
|
await source.executeAction(WebSearchAction.Search, {
|
|
query: "freya",
|
|
numResults: 2,
|
|
})
|
|
|
|
expect(client.requests[0]!.numResults).toBe(2)
|
|
})
|
|
|
|
test("throws for invalid action", async () => {
|
|
const source = new WebSearchSource({ client: new RecordingSearchClient() })
|
|
|
|
await expect(source.executeAction("missing", {})).rejects.toThrow("Unknown action")
|
|
})
|
|
|
|
test("throws for invalid search params", async () => {
|
|
const source = new WebSearchSource({ client: new RecordingSearchClient() })
|
|
|
|
await expect(
|
|
source.executeAction(WebSearchAction.Search, {
|
|
query: "",
|
|
}),
|
|
).rejects.toThrow("query must not be empty")
|
|
|
|
await expect(
|
|
source.executeAction(WebSearchAction.Search, {
|
|
query: "x",
|
|
numResults: 101,
|
|
}),
|
|
).rejects.toThrow("numResults must be an integer")
|
|
})
|
|
|
|
test("throws if neither client nor apiKey is provided", () => {
|
|
expect(() => new WebSearchSource({})).toThrow("Either client or apiKey must be provided")
|
|
})
|
|
})
|