# Conformance tests (https://www.cal-sdk.com/docs/build-a-provider/conformance)

An all-day event that comes back a day early or a 412 that surfaces as a generic error is easy to miss until a user finds it. Run `@cal-sdk/provider-tests` against your provider to catch round-trip and error-mapping mistakes like these. Most official providers use the same suite, and it skips anything your provider doesn't declare as supported.

```ts title="test/conformance.test.ts"
import { describeProviderConformance } from '@cal-sdk/provider-tests';
import { acme } from '../src/acme';

describeProviderConformance({
  name: 'acme',
  createProvider: ({ fetch }) => acme({ accessToken: process.env.ACME_TOKEN ?? 'replay', fetch }),
  recording: {
    path: 'test/fixtures/conformance.json',
    mode: process.env.CALSDK_RECORD ? 'record' : 'replay',
  },
});
```

Pass the suite's `fetch` through to your provider. That's how the suite records, replays and stubs error responses.

The tests cover calendars, event create, read, update and delete, all-day and time zone round trips, pagination, recurring series and their exceptions, etags, sync, free/busy and HTTP error mapping. A provider without recurring events, say, is never asked to split a series.

## Record once, replay everywhere [#record-once-replay-everywhere]

| Mode | What happens |
| --- | --- |
| `'replay'` (default) | No network and no credentials. Fails when a request has no recorded response |
| `'record'` | Calls the real API and saves the responses per test |
| No `recording` | Calls the real API live and saves nothing |

```bash
CALSDK_RECORD=1 ACME_TOKEN=... bunx vitest run test/conformance.test.ts
```

Record against a calendar made for testing, set with `calendarId`. The tests delete what they create, but they do write to it.

Credentials aren't saved: only the few request headers that change what a request does, and no token query parameters. Response bodies are saved as the API returned them, though, so they can hold names and email addresses. Read a recording before you commit it.

## Options [#options]

<TypeTable
  type={{
    name: { type: 'string', description: "Suite name, such as 'acme'.", required: true },
    createProvider: { type: '(settings: { fetch }) => CalendarProviderV1', description: "Creates the provider under test. Pass fetch through.", required: true },
    calendarId: { type: 'string', description: 'Calendar the tests write to.', default: "'primary'" },
    recording: { type: "{ path: string; mode?: 'record' | 'replay' }", description: 'Record and replay HTTP traffic. Leave out to call the API live.' },
    http: { type: 'boolean', description: 'Run the tests that stub HTTP responses. Set `false` for providers that do not call `fetch`.', default: 'true' },
    firstDay: { type: 'string', description: "First day test events are placed on, 'YYYY-MM-DD'. Fixed rather than relative to today, so recordings replay.", default: "'2030-01-07'" },
  }}
/>

## Your own tests [#your-own-tests]

For tests of behavior specific to your API, `createRecordingFetch` is exported with the same record and replay modes. The suite needs Vitest 5 and a runtime with `node:fs`.
