Compare commits

...

1 Commits

Author SHA1 Message Date
Shoubhit Dash dc819e04cc test(ai): simplify TestLLM setup 2026-08-03 17:07:08 +05:30
5 changed files with 32 additions and 9 deletions
+5 -7
View File
@@ -214,22 +214,20 @@ the requests sent by code under test:
import { Effect } from "effect" import { Effect } from "effect"
import { TestLLM } from "@opencode-ai/ai/testing" import { TestLLM } from "@opencode-ai/ai/testing"
const testLLM = TestLLM.layer({ const testLLM = TestLLM.layerWithClient({
fallback: TestLLM.text("Hello from the test model", "text-1"), fallback: TestLLM.text("Hello from the test model"),
}) })
// TestLLM.clientLayer provides LLMClient.Service and consumes TestLLM.Service.
const programWithTestClient = Effect.gen(function* () { const programWithTestClient = Effect.gen(function* () {
const result = yield* program const result = yield* program
const test = yield* TestLLM.Service console.log(yield* TestLLM.requests)
console.log(test.requests)
return result return result
}).pipe(Effect.provide(TestLLM.clientLayer), Effect.provide(testLLM)) }).pipe(Effect.provide(testLLM))
``` ```
`TestLLM.push(...)` scripts one-shot responses, `TestLLM.always(...)` changes the fallback, and `TestLLM.push(...)` scripts one-shot responses, `TestLLM.always(...)` changes the fallback, and
`TestLLM.wait(...)` lets concurrent tests wait until a request has arrived. Every received canonical request is `TestLLM.wait(...)` lets concurrent tests wait until a request has arrived. Every received canonical request is
available on the yielded `TestLLM.Service`. available from `TestLLM.requests`.
## Caching ## Caching
+5 -1
View File
@@ -53,7 +53,7 @@ const textEvents = (value: string, id: string) => [
LLMEvent.textEnd({ id }), LLMEvent.textEnd({ id }),
] ]
export const text = (value: string, id: string) => stop(...textEvents(value, id)) export const text = (value: string, id = "text-0") => stop(...textEvents(value, id))
export const textWithUsage = (value: string, id: string, inputTokens: number) => export const textWithUsage = (value: string, id: string, inputTokens: number) =>
complete( complete(
@@ -147,6 +147,10 @@ export const clientLayer = Layer.effect(
Effect.map(Service, (service) => service.client), Effect.map(Service, (service) => service.client),
) )
export const layerWithClient = (options: LayerOptions = {}) => clientLayer.pipe(Layer.provideMerge(layer(options)))
export const requests = Service.use((service) => Effect.succeed(service.requests))
export const push = (...responses: readonly Response[]) => Service.use((service) => service.push(...responses)) export const push = (...responses: readonly Response[]) => Service.use((service) => service.push(...responses))
export const always = (response: Response) => Service.use((service) => service.always(response)) export const always = (response: Response) => Service.use((service) => service.always(response))
+2
View File
@@ -32,6 +32,8 @@ describe("public exports", () => {
expect(Provider.make).toBeFunction() expect(Provider.make).toBeFunction()
expect(ProviderSubpath.make).toBe(Provider.make) expect(ProviderSubpath.make).toBe(Provider.make)
expect(TestLLM.layer).toBeFunction() expect(TestLLM.layer).toBeFunction()
expect(TestLLM.layerWithClient).toBeFunction()
expect(TestLLM.requests).toBeDefined()
}) })
test("route barrel exposes route-authoring APIs", () => { test("route barrel exposes route-authoring APIs", () => {
+19
View File
@@ -0,0 +1,19 @@
import { expect } from "bun:test"
import { Effect } from "effect"
import { LLM, LLMClient, LLMEvent } from "../src"
import { OpenAIChat } from "../src/protocols"
import { TestLLM } from "../src/testing"
import { testEffect } from "./lib/effect"
const model = OpenAIChat.route.model({ id: "test" })
const it = testEffect(TestLLM.layerWithClient({ fallback: TestLLM.text("Hello") }))
it.effect("provides a client and exposes received requests", () =>
Effect.gen(function* () {
const response = yield* LLMClient.generate(LLM.request({ model, prompt: "Say hello" }))
expect(response.text).toBe("Hello")
expect(response.events.filter(LLMEvent.is.textDelta)).toEqual([{ type: "text-delta", id: "text-0", text: "Hello" }])
expect(yield* TestLLM.requests).toHaveLength(1)
}),
)
+1 -1
View File
@@ -65,7 +65,7 @@ const aisdk = Layer.mock(AISDK.Service, {
}, },
model: () => Effect.succeed(runtime), model: () => Effect.succeed(runtime),
}) })
const client = TestLLM.clientLayer.pipe(Layer.provide(TestLLM.layer({ fallback: TestLLM.text("OK", "generate") }))) const client = TestLLM.layerWithClient({ fallback: TestLLM.text("OK") })
const resolver = ModelResolver.layer.pipe(Layer.provide(Layer.mergeAll(catalog, integrations, npm, aisdk))) const resolver = ModelResolver.layer.pipe(Layer.provide(Layer.mergeAll(catalog, integrations, npm, aisdk)))
const it = testEffect(Generate.layer.pipe(Layer.provide(Layer.merge(resolver, client)))) const it = testEffect(Generate.layer.pipe(Layer.provide(Layer.merge(resolver, client))))