test(core): add config and watcher test services (#39157)

This commit is contained in:
Kit Langton 2026-07-27 14:32:38 -04:00 committed by GitHub
commit 713658c07b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 468 additions and 498 deletions

View file

@ -4,7 +4,7 @@ import { makeLocationNode } from "@opencode-ai/util/effect/app-node"
import path from "path"
import { isDeepStrictEqual } from "node:util"
import { type ParseError, parse } from "jsonc-parser"
import { Context, Effect, Fiber, Layer, Option, PubSub, Schema, Semaphore, Stream } from "effect"
import { Context, Effect, Fiber, Layer, Option, PubSub, Ref, Schema, Semaphore, Stream } from "effect"
import { Permission } from "@opencode-ai/schema/permission"
import { Config as ConfigSchema } from "@opencode-ai/schema/config"
import { Integration } from "@opencode-ai/schema/integration"
@ -176,6 +176,31 @@ export type Options = typeof Options.Type
export class Service extends Context.Service<Service, Interface>()("@opencode/Config") {}
export interface TestInterface extends Interface {
/** Replaces the entries returned by subsequent entries() calls. */
readonly setEntries: (entries: Entry[]) => Effect.Effect<void>
/** Emits one filesystem update to every changes() subscriber. */
readonly emitChange: (update: Watcher.Update) => Effect.Effect<void>
}
export class Test extends Context.Service<Test, TestInterface>()("@opencode/Config/Test") {}
/** In-memory config for tests: static entries with replaceable state and a test-driven change feed. */
export const testLayer = (initial: Entry[] = []) =>
Layer.effectContext(
Effect.gen(function* () {
const entries = yield* Ref.make(initial)
const updates = yield* PubSub.unbounded<Watcher.Update>()
const service = Test.of({
entries: () => Ref.get(entries),
changes: () => Stream.fromPubSub(updates),
setEntries: (next) => Ref.set(entries, next),
emitChange: (update) => PubSub.publish(updates, update).pipe(Effect.asVoid),
})
return Context.empty().pipe(Context.add(Service, service), Context.add(Test, service))
}),
)
export const layer = (options?: Options) => Layer.effect(
Service,
Effect.gen(function* () {

View file

@ -56,6 +56,32 @@ export type Options = typeof Options.Type
export class Service extends Context.Service<Service, Interface>()("@opencode/Watcher") {}
export interface TestInterface extends Interface {
/** Broadcasts one update to every subscriber. */
readonly emit: (update: Update) => Effect.Effect<void>
/** Returns every subscribe call observed so far, in order. */
readonly subscriptions: () => Effect.Effect<readonly WatchInput[]>
}
export class Test extends Context.Service<Test, TestInterface>()("@opencode/Watcher/Test") {}
/** In-memory watcher for tests: records subscribe calls and broadcasts emitted updates. */
export const testLayer = Layer.effectContext(
Effect.gen(function* () {
const updates = yield* PubSub.unbounded<Update>()
const subscriptions: WatchInput[] = []
const service = Test.of({
subscribe: (input) => {
subscriptions.push(input)
return Stream.fromPubSub(updates)
},
emit: (update) => PubSub.publish(updates, update).pipe(Effect.asVoid),
subscriptions: () => Effect.sync(() => [...subscriptions]),
})
return Context.empty().pipe(Context.add(Service, service), Context.add(Test, service))
}),
)
export const layer = (options?: Options) => Layer.effect(
Service,
Effect.gen(function* () {