Use tagged errors in tests

This commit is contained in:
elpresidank 2026-06-04 08:10:09 -05:00
parent c4500f216e
commit c48927b7c5
8 changed files with 104 additions and 23 deletions

View file

@ -2331,6 +2331,30 @@ Notes:
- `cd ts && bun run lint`
- `git diff --check`
### 2026-06-04: Tagged Test Error Cleanup Slice
- Status: migrated and package-verified.
- Completed:
- Removed the remaining native `new Error` / `extends Error` matches from
tests and client wire models.
- Polling helpers in embeddings, flow processor, and chunking tests now fail
with `S.TaggedErrorClass` timeout values.
- Runtime service fake failures and the embeddings provider failure fixture
now use tagged test errors.
- The NATS SDK error mock preserves `code` and `jsError()` behavior through a
`TaggedErrorClass`, and the client model no longer exports a wire type
alias named `Error`.
- Verification:
- `cd ts/packages/base && bunx --bun vitest run src/__tests__/nats-backend.test.ts src/__tests__/runtime-services.test.ts src/__tests__/embeddings-service.test.ts src/__tests__/flow-processor-runtime.test.ts`
- `cd ts/packages/flow && bunx --bun vitest run src/__tests__/chunking-service.test.ts`
- `cd ts && bun run --cwd packages/client build && bun run --cwd packages/base build && bun run --cwd packages/flow build`
- `rg -n "new Error\\(|extends Error|export type Error|error\\?: Error|S\\.ErrorClass" ts/packages --glob '*.ts' --glob '*.tsx'`
- `cd ts && bun run check:tsgo`
- `cd ts && bun run build`
- `cd ts && bun run test`
- `cd ts && bun run lint`
- `git diff --check`
## Subagent Findings To Preserve
- MCP/workbench:
@ -2521,6 +2545,9 @@ Notes:
standalone Librarian collection manager, prompt template cache, and
workbench explain triples module cache. Local traversal sets and test fakes
remain no-op boundaries.
- Fresh strict error-class sweep after the 2026-06-04 tagged test error
cleanup found no `new Error`, `extends Error`, `export type Error`,
`error?: Error`, or `S.ErrorClass` matches under `ts/packages`.
## Ranked Findings

View file

@ -1,5 +1,6 @@
import { describe, expect, it } from "@effect/vitest";
import { ConfigProvider, Effect, Fiber } from "effect";
import * as S from "effect/Schema";
import {
Embeddings,
EmbeddingsService,
@ -18,6 +19,18 @@ import {
type PubSubBackend,
} from "../index.js";
class WaitForTimeout extends S.TaggedErrorClass<WaitForTimeout>()(
"WaitForTimeout",
{ label: S.String },
) {}
class TestProviderUnavailable extends S.TaggedErrorClass<TestProviderUnavailable>()(
"TestProviderUnavailable",
{ message: S.String },
) {}
const isWaitForTimeout = S.is(WaitForTimeout);
function createMessage<T>(value: T, properties: Record<string, string> = {}): Message<T> {
return {
value: () => value,
@ -36,14 +49,14 @@ const waitFor = (condition: () => boolean, label: string) =>
return;
}
if (Date.now() > deadline) {
reject(new Error(`Timed out waiting for ${label}`));
reject(WaitForTimeout.make({ label }));
return;
}
setTimeout(check, 5);
};
check();
}),
catch: (error) => error,
catch: (error) => isWaitForTimeout(error) ? error : WaitForTimeout.make({ label }),
});
class RecordingProducer<T> implements BackendProducer<T> {
@ -206,7 +219,13 @@ describe("EmbeddingsService", () => {
const backend = new EmbeddingsBackend();
const embeddings = Embeddings.of({
embed: Effect.fn("FailingEmbeddings.embed")(() =>
Effect.fail(embeddingsError("test.embed", new Error("provider unavailable"), "test")),
Effect.fail(
embeddingsError(
"test.embed",
TestProviderUnavailable.make({ message: "provider unavailable" }),
"test",
),
),
),
});

View file

@ -1,5 +1,6 @@
import { describe, expect, it } from "@effect/vitest";
import { ConfigProvider, Effect, Fiber } from "effect";
import * as S from "effect/Schema";
import {
FlowProcessor,
MessagingRuntimeLive,
@ -17,6 +18,13 @@ import {
type PubSubBackend,
} from "../index.js";
class WaitForTimeout extends S.TaggedErrorClass<WaitForTimeout>()(
"WaitForTimeout",
{ label: S.String },
) {}
const isWaitForTimeout = S.is(WaitForTimeout);
function createMessage<T>(value: T, properties: Record<string, string> = {}): Message<T> {
return {
value: () => value,
@ -35,14 +43,14 @@ const waitFor = (condition: () => boolean, label: string) =>
return;
}
if (Date.now() > deadline) {
reject(new Error(`Timed out waiting for ${label}`));
reject(WaitForTimeout.make({ label }));
return;
}
setTimeout(check, 5);
};
check();
}),
catch: (error) => error,
catch: (error) => isWaitForTimeout(error) ? error : WaitForTimeout.make({ label }),
});
class RecordingProducer<T> implements BackendProducer<T> {

View file

@ -2,18 +2,24 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
import { makeNatsBackend } from "../backend/nats.js";
const natsMock = vi.hoisted(() => {
const S = require("effect/Schema");
const encoder = new TextEncoder();
const decoder = new TextDecoder();
class MockNatsError extends Error {
readonly code: string;
private readonly apiCode: number | undefined;
class MockNatsError extends S.TaggedErrorClass()(
"MockNatsError",
{
apiCode: S.optional(S.Number),
code: S.String,
message: S.String,
},
) {
constructor(code: string, apiCode?: number) {
super(code);
this.name = "NatsError";
this.code = code;
this.apiCode = apiCode;
super({
apiCode,
code,
message: code,
});
}
jsError() {

View file

@ -1,5 +1,6 @@
import { describe, expect, it } from "@effect/vitest";
import { Effect } from "effect";
import * as S from "effect/Schema";
import {
PubSub,
makeAsyncProcessor,
@ -13,6 +14,11 @@ import {
type PubSubBackend,
} from "../index.js";
class RuntimeServicesTestError extends S.TaggedErrorClass<RuntimeServicesTestError>()(
"RuntimeServicesTestError",
{ message: S.String },
) {}
class FakeProducer<T> implements BackendProducer<T> {
readonly sent: Array<{ readonly message: T; readonly properties?: Record<string, string> }> = [];
closeCount = 0;
@ -75,7 +81,7 @@ class FakePubSubBackend implements PubSubBackend {
class FailingProducerBackend extends FakePubSubBackend {
override async createProducer<T>(): Promise<BackendProducer<T>> {
throw new Error("producer unavailable");
throw RuntimeServicesTestError.make({ message: "producer unavailable" });
}
}
@ -99,7 +105,7 @@ const makeRecordingProcessor = (
const makeFailingProcessor = (config: ProcessorConfig) =>
makeAsyncProcessor(config, {
run: async () => {
throw new Error("processor failed");
throw RuntimeServicesTestError.make({ message: "processor failed" });
},
});

View file

@ -324,7 +324,10 @@ describe("Message Types", () => {
describe("LibraryResponse", () => {
it("should have correct structure", () => {
const response: LibraryResponse = {
error: new Error(),
error: {
message: "Library unavailable",
type: "library-error",
},
"document-metadatas": [
{
id: "doc-1",
@ -334,7 +337,10 @@ describe("Message Types", () => {
],
};
expect(response.error).toBeInstanceOf(Error);
expect(response.error).toMatchObject({
message: "Library unavailable",
type: "library-error",
});
expect(response["document-metadatas"]).toHaveLength(1);
expect(response["document-metadatas"]![0].id).toBe("doc-1");
});

View file

@ -2,13 +2,14 @@ import type { Term, Triple } from "./Triple.js";
export type Request = object;
export type Response = object;
export type Error = object | string;
export interface ResponseError {
type?: string;
message: string;
}
export type WireError = object | string;
export interface RequestMessage {
id: string;
service: string;
@ -315,7 +316,7 @@ export interface LibraryRequest {
}
export interface LibraryResponse {
error?: Error;
error?: WireError;
"document-metadata"?: DocumentMetadata;
documentMetadata?: DocumentMetadata;
content?: string;
@ -340,7 +341,7 @@ export interface KnowledgeRequest {
}
export interface KnowledgeResponse {
error?: Error;
error?: WireError;
ids?: string[];
eos?: boolean;
triples?: Triple[];
@ -371,7 +372,7 @@ export interface FlowResponse {
| {
message?: string;
}
| Error;
| WireError;
}
export interface PromptRequest {

View file

@ -1,6 +1,7 @@
import { describe, expect, it } from "@effect/vitest";
import { ConfigProvider, Effect, Fiber } from "effect";
import * as EffectChunk from "effect/Chunk";
import * as S from "effect/Schema";
import {
MessagingRuntimeLive,
PubSub,
@ -18,6 +19,13 @@ import {
import { ChunkingService } from "../chunking/service.js";
import { recursiveSplit } from "../chunking/recursive-splitter.js";
class WaitForTimeout extends S.TaggedErrorClass<WaitForTimeout>()(
"WaitForTimeout",
{ label: S.String },
) {}
const isWaitForTimeout = S.is(WaitForTimeout);
function createMessage<T>(value: T, properties: Record<string, string> = {}): Message<T> {
return {
value: () => value,
@ -36,14 +44,14 @@ const waitFor = (condition: () => boolean, label: string) =>
return;
}
if (Date.now() > deadline) {
reject(new Error(`Timed out waiting for ${label}`));
reject(WaitForTimeout.make({ label }));
return;
}
setTimeout(check, 5);
};
check();
}),
catch: (error) => error,
catch: (error) => isWaitForTimeout(error) ? error : WaitForTimeout.make({ label }),
});
class RecordingProducer<T> implements BackendProducer<T> {