import { it } from "@effect/vitest";import * as Effect from "effect/Effect";import * as PubSub from "effect/PubSub";import * as Result from "effect/Result";import * as Stream from "effect/Stream";import { describe, expect } from "vite-plus/test"; import { ProviderInstanceId } from "@t3tools/contracts";import { createModelSelection } from "@t3tools/shared/model"; import type { ProviderInstance } from "../provider/ProviderDriver.ts";import * as ProviderInstanceRegistry from "../provider/Services/ProviderInstanceRegistry.ts";import * as TextGeneration from "./TextGeneration.ts"; const makeStubTextGeneration = ( overrides: Partial<TextGeneration.TextGeneration["Service"]>,): TextGeneration.TextGeneration["Service"] => TextGeneration.TextGeneration.of({ generateCommitMessage: () => Effect.die("generateCommitMessage stub not configured for this test"), generatePrContent: () => Effect.die("generatePrContent stub not configured for this test"), generateBranchName: () => Effect.die("generateBranchName stub not configured for this test"), generateThreadTitle: () => Effect.die("generateThreadTitle stub not configured for this test"), ...overrides, }); const makeStubInstance = ( instanceId: ProviderInstanceId, textGeneration: TextGeneration.TextGeneration["Service"],): ProviderInstance => ({ instanceId, driverKind: instanceId as unknown as ProviderInstance["driverKind"], continuationIdentity: { driverKind: instanceId as unknown as ProviderInstance["driverKind"], continuationKey: `${instanceId}:test`, }, displayName: undefined, enabled: true, snapshot: {} as ProviderInstance["snapshot"], adapter: {} as ProviderInstance["adapter"], textGeneration, }) satisfies ProviderInstance; const makeStubRegistry = ( instances: ReadonlyArray<ProviderInstance>,): ProviderInstanceRegistry.ProviderInstanceRegistry["Service"] => { const byId = new Map(instances.map((instance) => [instance.instanceId, instance] as const)); return { getInstance: (id) => Effect.succeed(byId.get(id)), listInstances: Effect.succeed(instances), listUnavailable: Effect.succeed([]), streamChanges: Stream.empty, // Tests never drive changes through this stub; acquire a throwaway // subscription on an unused PubSub so the shape is satisfied. subscribeChanges: Effect.flatMap(PubSub.unbounded<void>(), (pubsub) => PubSub.subscribe(pubsub), ), };}; describe("makeTextGenerationFromRegistry", () => { it.effect("delegates to the matching instance's textGeneration closure", () => Effect.gen(function* () { const personalId = ProviderInstanceId.make("codex_personal"); const personalCalls: string[] = []; const personal = makeStubInstance( personalId, makeStubTextGeneration({ generateBranchName: (input) => { personalCalls.push(input.message); return Effect.succeed({ branch: "personal-branch" }); }, }), ); const workId = ProviderInstanceId.make("codex_work"); const work = makeStubInstance( workId, makeStubTextGeneration({ generateBranchName: () => Effect.succeed({ branch: "work-branch" }), }), ); const tg = TextGeneration.makeTextGenerationFromRegistry(makeStubRegistry([personal, work])); const result = yield* tg.generateBranchName({ cwd: process.cwd(), message: "Refactor the routing layer", modelSelection: createModelSelection(ProviderInstanceId.make("codex_personal"), "gpt-5"), }); expect(result.branch).toBe("personal-branch"); expect(personalCalls).toEqual(["Refactor the routing layer"]); }), ); it.effect("fails with TextGenerationError when the instance is unknown", () => Effect.gen(function* () { const tg = TextGeneration.makeTextGenerationFromRegistry(makeStubRegistry([])); const result = yield* tg .generateBranchName({ cwd: process.cwd(), message: "anything", modelSelection: createModelSelection( ProviderInstanceId.make("missing_instance"), "gpt-5", ), }) .pipe(Effect.result); expect(Result.isFailure(result)).toBe(true); if (Result.isFailure(result)) { expect(result.failure._tag).toBe("TextGenerationError"); expect(result.failure.operation).toBe("generateBranchName"); expect(result.failure.detail).toContain("missing_instance"); } }), );});
import { it } from "@effect/vitest";
import * as Effect from "effect/Effect";
import * as PubSub from "effect/PubSub";
import * as Result from "effect/Result";
import * as Stream from "effect/Stream";
import { describe, expect } from "vite-plus/test";
import { ProviderInstanceId } from "@t3tools/contracts";
import { createModelSelection } from "@t3tools/shared/model";
import type { ProviderInstance } from "../provider/ProviderDriver.ts";
import * as ProviderInstanceRegistry from "../provider/Services/ProviderInstanceRegistry.ts";
import * as TextGeneration from "./TextGeneration.ts";
const makeStubTextGeneration = (
overrides: Partial<TextGeneration.TextGeneration["Service"]>,
): TextGeneration.TextGeneration["Service"] =>
TextGeneration.TextGeneration.of({
generateCommitMessage: () =>
Effect.die("generateCommitMessage stub not configured for this test"),
generatePrContent: () => Effect.die("generatePrContent stub not configured for this test"),
generateBranchName: () => Effect.die("generateBranchName stub not configured for this test"),
generateThreadTitle: () => Effect.die("generateThreadTitle stub not configured for this test"),
...overrides,
});
const makeStubInstance = (
instanceId: ProviderInstanceId,
textGeneration: TextGeneration.TextGeneration["Service"],
): ProviderInstance =>
({
instanceId,
driverKind: instanceId as unknown as ProviderInstance["driverKind"],
continuationIdentity: {
driverKind: instanceId as unknown as ProviderInstance["driverKind"],
continuationKey: `${instanceId}:test`,
},
displayName: undefined,
enabled: true,
snapshot: {} as ProviderInstance["snapshot"],
adapter: {} as ProviderInstance["adapter"],
textGeneration,
}) satisfies ProviderInstance;
const makeStubRegistry = (
instances: ReadonlyArray<ProviderInstance>,
): ProviderInstanceRegistry.ProviderInstanceRegistry["Service"] => {
const byId = new Map(instances.map((instance) => [instance.instanceId, instance] as const));
return {
getInstance: (id) => Effect.succeed(byId.get(id)),
listInstances: Effect.succeed(instances),
listUnavailable: Effect.succeed([]),
streamChanges: Stream.empty,
// Tests never drive changes through this stub; acquire a throwaway
// subscription on an unused PubSub so the shape is satisfied.
subscribeChanges: Effect.flatMap(PubSub.unbounded<void>(), (pubsub) =>
PubSub.subscribe(pubsub),
),
};
};
describe("makeTextGenerationFromRegistry", () => {
it.effect("delegates to the matching instance's textGeneration closure", () =>
Effect.gen(function* () {
const personalId = ProviderInstanceId.make("codex_personal");
const personalCalls: string[] = [];
const personal = makeStubInstance(
personalId,
makeStubTextGeneration({
generateBranchName: (input) => {
personalCalls.push(input.message);
return Effect.succeed({ branch: "personal-branch" });
},
}),
);
const workId = ProviderInstanceId.make("codex_work");
const work = makeStubInstance(
workId,
makeStubTextGeneration({
generateBranchName: () => Effect.succeed({ branch: "work-branch" }),
}),
);
const tg = TextGeneration.makeTextGenerationFromRegistry(makeStubRegistry([personal, work]));
const result = yield* tg.generateBranchName({
cwd: process.cwd(),
message: "Refactor the routing layer",
modelSelection: createModelSelection(ProviderInstanceId.make("codex_personal"), "gpt-5"),
});
expect(result.branch).toBe("personal-branch");
expect(personalCalls).toEqual(["Refactor the routing layer"]);
}),
);
it.effect("fails with TextGenerationError when the instance is unknown", () =>
Effect.gen(function* () {
const tg = TextGeneration.makeTextGenerationFromRegistry(makeStubRegistry([]));
const result = yield* tg
.generateBranchName({
cwd: process.cwd(),
message: "anything",
modelSelection: createModelSelection(
ProviderInstanceId.make("missing_instance"),
"gpt-5",
),
})
.pipe(Effect.result);
expect(Result.isFailure(result)).toBe(true);
if (Result.isFailure(result)) {
expect(result.failure._tag).toBe("TextGenerationError");
expect(result.failure.operation).toBe("generateBranchName");
expect(result.failure.detail).toContain("missing_instance");
}
}),
);
});