› cavi-ai/
GitHub ↗

Test the behavior your application consumes

Test your application's response policy through the installed package. Replace only the upstream HTTP response with a fixture; keep the real provider adapter, capability facade, and application handler in the path.

Run a complete server contract test

Download consumer-tests.ts and server-handler.ts into an examples/ directory. The tests below exercise the documented handler. In your application, import your actual handler and keep expectations that describe your caller-facing contract.

Use an ESM Node project with "type": "module" in package.json. For a new test workspace:

sh
npm init -y
npm pkg set type=module
npm install @cavi-ai/api-client@0.21.0
npm install --save-dev typescript @types/node

After saving both downloads, compile and run them:

sh
npx tsc --target ES2022 --module NodeNext --moduleResolution NodeNext \
  --strict --skipLibCheck --types node --outDir .consumer-tests \
  examples/consumer-tests.ts examples/server-handler.ts
node --test .consumer-tests/consumer-tests.js

Expected result: seven passing tests and a zero exit code. Each test has its own client and HTTP fixture, and disposes the client through the test cleanup hook even when an assertion fails. No real provider credentials or network calls are needed.

ts
import assert from "node:assert/strict";
import { test, type TestContext } from "node:test";
import { createApiClient, createRuntimeProviderRegistry } from "@cavi-ai/api-client";
import { createCodexProviderModule } from "@cavi-ai/api-client/providers/codex/runtime";
import { createRunHandler } from "./server-handler.js";

function fixture(t: TestContext, reply: () => Response) {
  const requests: { method: string | undefined; body: Record<string, unknown> }[] = [];
  const diagnostics: unknown[] = [];
  const client = createApiClient("codex", {
    registry: createRuntimeProviderRegistry({
      modules: [createCodexProviderModule({ apiKey: "fixture-key" })],
    }),
    fetchImpl: async (_url, init) => {
      requests.push({ method: init?.method, body: JSON.parse(String(init?.body)) });
      return reply();
    },
  });
  t.after(() => client.dispose());
  const handle = createRunHandler(client, "server-model", (error) => { diagnostics.push(error); });
  return { handle, requests, diagnostics };
}

function question(body: unknown = { input: "Question" }) {
  return new Request("https://application.example/answer", {
    method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body),
  });
}

test("returns an answer and usage while keeping model selection server-owned", async (t) => {
  const app = fixture(t, () => Response.json({ id: "run-1", status: "completed", output_text: "Answer",
    usage: { input_tokens: 3, output_tokens: 4, total_tokens: 7 },
  }));
  const response = await app.handle(question({ input: "Question", model: "caller-model", apiKey: "caller-key" }));
  assert.equal(response.status, 200);
  const answer = await response.json();
  assert.equal(answer.runId, "run-1");
  assert.equal(answer.status, "completed");
  assert.equal(answer.text, "Answer");
  assert.equal(answer.tokens.totalTokens, 7);
  assert.equal(app.requests.length, 1);
  assert.equal(app.requests[0].method, "POST");
  assert.equal(app.requests[0].body.input, "Question");
  assert.equal(app.requests[0].body.model, "server-model");
  assert.ok(!("apiKey" in app.requests[0].body));
});

test("returns progress rather than an answer for an active background run", async (t) => {
  const app = fixture(t, () => Response.json({ id: "run-1", status: "queued" }));
  const response = await app.handle(question());
  assert.equal(response.status, 202);
  assert.deepEqual(await response.json(), { runId: "run-1", status: "started" });
});

for (const status of ["failed", "future-state"]) {
  test(`does not report success for ${status} execution state`, async (t) => {
    const app = fixture(t, () => Response.json({ id: "run-1", status,
      error: { message: "Private execution diagnostic" },
    }));
    const response = await app.handle(question());
    assert.equal(response.status, 502);
    assert.deepEqual(await response.json(), { runId: "run-1", status });
  });
}

test("returns a controlled response for a backend availability gap", async (t) => {
  const app = fixture(t, () => Response.json({ error: { message: "Private backend diagnostic" } }, { status: 503 }));
  const response = await app.handle(question());
  assert.equal(response.status, 503);
  assert.deepEqual(await response.json(), { error: "Run unavailable.", reason: "backend-unavailable" });
  assert.equal(app.diagnostics.length, 1);
  assert.equal(app.requests.length, 1, "an ambiguous submission must not be replayed");
});

test("treats provider authentication as server unavailability", async (t) => {
  const app = fixture(t, () => Response.json({ error: { message: "Private authentication diagnostic" } }, { status: 401 }));
  const response = await app.handle(question());
  assert.equal(response.status, 503);
  assert.deepEqual(await response.json(), { error: "Runtime authentication unavailable." });
  assert.equal(app.diagnostics.length, 1);
  assert.equal(app.requests.length, 1);
});

test("rejects invalid input before starting provider work", async (t) => {
  const app = fixture(t, () => { throw new Error("Invalid input reached the provider"); });
  for (const body of [null, {}, { input: " " }, { input: 42 }]) {
    assert.equal((await app.handle(question(body))).status, 400);
  }
  const malformed = new Request("https://application.example/answer", { method: "POST", body: "{" });
  assert.equal((await app.handle(malformed)).status, 400);
  const wrongMethod = await app.handle(new Request("https://application.example/answer"));
  assert.equal(wrongMethod.status, 405);
  assert.equal(wrongMethod.headers.get("Allow"), "POST");
  assert.equal(app.requests.length, 0);
});

The success test checks your public answer and normalized usage, and proves that a caller cannot override the server-selected model or forward a provider key through the body. The active-run test prevents a background handle from being displayed as a completed answer. Failed and unknown states remain failures even when the provider HTTP request itself succeeds.

Gap and authentication tests verify controlled caller messages while the original diagnostics stay in server telemetry. They also check that an ambiguous submission is made once. Invalid-input tests fail if provider work starts before validation.

Extend coverage for the workflow you own

Application behaviorAdd a check for
Streaming UIPartial deltas, failed/cancelled terminal events, missing completion, and controlled error display
Background jobsStored-owner authorization, retained run IDs, bounded waiting, and explicit cancellation
Capability visibilitySupported and unsupported controls; handle the result even after successful discovery
Batch workersItem correlation by custom ID and retention of errored/cancelled/expired items
Client ownershipReuse within a credential scope and shutdown after in-flight work settles

The batch collection tests cover resumed jobs, terminal states, mixed item outcomes, and ambiguous correlation IDs through the installed Claude and Codex adapters.

The streaming tests cover snapshot/delta handling, missing answers, partial failures, availability gaps, and authentication.

The file lifecycle tests cover multipart uploads, saved IDs, explicit deletion receipts, and cleanup failures through the installed public file client.

The React workflow tests mount the UI against the server handler and cover local cancellation, stale responses, endpoint changes, and unmount cleanup.

The authorized background example includes development checks for access denial, revocation before polling, retrieval gaps, and local abort without backend cancellation.

Keep package versions pinned and import public entry points. Source-relative imports into the SDK can conceal missing published exports or pull Node-only dependencies into a browser build. The relative import in this test targets your application's downloaded handler, not SDK internals.

Test adapters and deployed backends separately

Use @cavi-ai/api-client/testing for runner-neutral conformance helpers when writing a provider adapter. Its reference describes the public inspectors and reports.

These fixtures verify application decisions against native response shapes. They do not establish that a deployed backend currently has a plugin, permission, model, or endpoint. Keep deployment integration checks scoped to the backend and version you actually use; reconcile known work before retrying ambiguous submissions.

Compatibility · Failure handling · Server integration

Referenced examples

Verified example · TypeScript

Consumer tests

examples/consumer-tests.ts

@cavi-ai/api-clientv0.21.007f0ee45
TypeScript · complete source
import assert from "node:assert/strict";
import { test, type TestContext } from "node:test";
import { createApiClient, createRuntimeProviderRegistry } from "@cavi-ai/api-client";
import { createCodexProviderModule } from "@cavi-ai/api-client/providers/codex/runtime";
import { createRunHandler } from "./server-handler.js";

function fixture(t: TestContext, reply: () => Response) {
  const requests: { method: string | undefined; body: Record<string, unknown> }[] = [];
  const diagnostics: unknown[] = [];
  const client = createApiClient("codex", {
    registry: createRuntimeProviderRegistry({
      modules: [createCodexProviderModule({ apiKey: "fixture-key" })],
    }),
    fetchImpl: async (_url, init) => {
      requests.push({ method: init?.method, body: JSON.parse(String(init?.body)) });
      return reply();
    },
  });
  t.after(() => client.dispose());
  const handle = createRunHandler(client, "server-model", (error) => { diagnostics.push(error); });
  return { handle, requests, diagnostics };
}

function question(body: unknown = { input: "Question" }) {
  return new Request("https://application.example/answer", {
    method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body),
  });
}

test("returns an answer and usage while keeping model selection server-owned", async (t) => {
  const app = fixture(t, () => Response.json({ id: "run-1", status: "completed", output_text: "Answer",
    usage: { input_tokens: 3, output_tokens: 4, total_tokens: 7 },
  }));
  const response = await app.handle(question({ input: "Question", model: "caller-model", apiKey: "caller-key" }));
  assert.equal(response.status, 200);
  const answer = await response.json();
  assert.equal(answer.runId, "run-1");
  assert.equal(answer.status, "completed");
  assert.equal(answer.text, "Answer");
  assert.equal(answer.tokens.totalTokens, 7);
  assert.equal(app.requests.length, 1);
  assert.equal(app.requests[0].method, "POST");
  assert.equal(app.requests[0].body.input, "Question");
  assert.equal(app.requests[0].body.model, "server-model");
  assert.ok(!("apiKey" in app.requests[0].body));
});

test("returns progress rather than an answer for an active background run", async (t) => {
  const app = fixture(t, () => Response.json({ id: "run-1", status: "queued" }));
  const response = await app.handle(question());
  assert.equal(response.status, 202);
  assert.deepEqual(await response.json(), { runId: "run-1", status: "started" });
});

for (const status of ["failed", "future-state"]) {
  test(`does not report success for ${status} execution state`, async (t) => {
    const app = fixture(t, () => Response.json({ id: "run-1", status,
      error: { message: "Private execution diagnostic" },
    }));
    const response = await app.handle(question());
    assert.equal(response.status, 502);
    assert.deepEqual(await response.json(), { runId: "run-1", status });
  });
}

test("returns a controlled response for a backend availability gap", async (t) => {
  const app = fixture(t, () => Response.json({ error: { message: "Private backend diagnostic" } }, { status: 503 }));
  const response = await app.handle(question());
  assert.equal(response.status, 503);
  assert.deepEqual(await response.json(), { error: "Run unavailable.", reason: "backend-unavailable" });
  assert.equal(app.diagnostics.length, 1);
  assert.equal(app.requests.length, 1, "an ambiguous submission must not be replayed");
});

test("treats provider authentication as server unavailability", async (t) => {
  const app = fixture(t, () => Response.json({ error: { message: "Private authentication diagnostic" } }, { status: 401 }));
  const response = await app.handle(question());
  assert.equal(response.status, 503);
  assert.deepEqual(await response.json(), { error: "Runtime authentication unavailable." });
  assert.equal(app.diagnostics.length, 1);
  assert.equal(app.requests.length, 1);
});

test("rejects invalid input before starting provider work", async (t) => {
  const app = fixture(t, () => { throw new Error("Invalid input reached the provider"); });
  for (const body of [null, {}, { input: " " }, { input: 42 }]) {
    assert.equal((await app.handle(question(body))).status, 400);
  }
  const malformed = new Request("https://application.example/answer", { method: "POST", body: "{" });
  assert.equal((await app.handle(malformed)).status, 400);
  const wrongMethod = await app.handle(new Request("https://application.example/answer"));
  assert.equal(wrongMethod.status, 405);
  assert.equal(wrongMethod.headers.get("Allow"), "POST");
  assert.equal(app.requests.length, 0);
});

Package imports

Referenced by

Artifact provenance
Package
@cavi-ai/api-client@0.21.0
Release
v0.21.0
Commit
07f0ee45424c267278f7b0c89a7dcea4e2492cc1
Source
examples/consumer-tests.ts
Tarball SHA-256
0924b5d3a7a40869bd44fedc47a9b8eb9844c2af1fd5c7ca77469d8b7c0b8558
Content SHA-256
24eff463c41582eaf5e70c58f42e5ae13bd12d02fa14ae2a886afa3abf5cdf48

Verified example · TypeScript

Server handler

examples/server-handler.ts

@cavi-ai/api-clientv0.21.007f0ee45
TypeScript · complete source
import { isAuthError, type CapabilityClient } from "@cavi-ai/api-client";

export function createRunHandler(
  client: CapabilityClient,
  model: string,
  reportError: (error: unknown) => void,
) {
  return async function handle(request: Request): Promise<Response> {
    if (request.method !== "POST") {
      return Response.json({ error: "Use POST." }, { status: 405, headers: { Allow: "POST" } });
    }
    let body: unknown;
    try {
      body = await request.json();
    } catch {
      return Response.json({ error: "Expected JSON." }, { status: 400 });
    }
    if (!body || typeof body !== "object" || !("input" in body) ||
        typeof body.input !== "string" || body.input.trim() === "") {
      return Response.json({ error: "Provide a non-empty input string." }, { status: 400 });
    }
    try {
      const result = await client.startRun({ model, input: body.input });
      if (!result.ok) {
        reportError(result.gap);
        return Response.json({ error: "Run unavailable.", reason: result.gap.reason }, { status: 503 });
      }
      const run = result.data;
      const active = ["started", "running", "stopping"].includes(run.status);
      const status = active ? 202 : run.status === "completed" ? 200 : 502;
      return Response.json({
        runId: run.run_id,
        status: run.status,
        text: run.output ?? run.response,
        tokens: run.tokens,
      }, { status });
    } catch (error) {
      reportError(error);
      if (isAuthError(error)) {
        // These are server-owned provider credentials, not the caller's login.
        return Response.json({ error: "Runtime authentication unavailable." }, { status: 503 });
      }
      return Response.json({ error: "Run request failed." }, { status: 502 });
    }
  };
}

Package imports

Referenced by

Artifact provenance
Package
@cavi-ai/api-client@0.21.0
Release
v0.21.0
Commit
07f0ee45424c267278f7b0c89a7dcea4e2492cc1
Source
examples/server-handler.ts
Tarball SHA-256
0924b5d3a7a40869bd44fedc47a9b8eb9844c2af1fd5c7ca77469d8b7c0b8558
Content SHA-256
24eff463c41582eaf5e70c58f42e5ae13bd12d02fa14ae2a886afa3abf5cdf48