> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fireweave.ai/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Official FireWeave SDK documentation.
> Product noun is control point. OpenFeature and the wire protocol use flagKey — do not invent controlPointKey.
> Do not invent APIs, packages, env vars, or endpoints. Guardrails are a typed stub (UnsupportedCapability). OpenFeature Tracking (spec section 6) is not implemented.
> registerTarget and identify exist on Node, Python, and Web only. Go and Java have no target-registration API on master.
> sendExposure defaults to false. Java close() does not flush exposures.

# Testing

> Deterministic FireWeave tests with InMemoryAdapter, the local/dev adapter, and the protocol test server. The stub does not implement /v1/targets/register.

Two FireWeave-provided tools mean unit and most integration tests never need a backend account:

1. **`InMemoryAdapter`** — fixture-driven `BackendAdapter` in every language. Bind the real runtime (and, if you want, the real OpenFeature client) to it.
2. **The protocol test server** (`test-server/`) — zero-dependency Node HTTP stub with scriptable faults. Use it when you need the real HTTP path of `FireweaveRemoteAdapter`.

A third option on **Node, Python, and Web only**: **`FireweaveLocalAdapter`** (web: `FireweaveLocalWebAdapter`) for a boolean `devFlags` map.

<Warning>
  The test server does **not** implement `POST /v1/targets/register`. The SDK’s own `docs/testing.md` claims it does; that is wrong relative to `test-server/implementation/server.mjs`. Register-target tests in the SDK use mocks or injected `fetch`, not this stub. `InMemoryAdapter` also does not persist registration (`UnsupportedCapability`).
</Warning>

## InMemoryAdapter

Same conceptual fixture in every language: `type`, `enabled`, `value`, optional `variant`, `payload`, `metadata`, and optional match conditions that gate on context attributes (no match → your default).

Java: artifact `ai.fireweave:fireweave-testing` (`<scope>test</scope>`). Web: `InMemoryWebAdapter`.

<Tabs>
  <Tab title="Node">
    Adapted from the SDK testing doc (OpenFeature + `lazyReady: false`):

    ```ts theme={null}
    import { strict as assert } from 'node:assert';
    import { test } from 'node:test';
    import { OpenFeature } from '@openfeature/server-sdk';
    import { FireweaveProvider, FireweaveRuntime, InMemoryAdapter } from '@fireweaveai/sdk';

    test('beta cohort gets the new checkout', async () => {
      const adapter = new InMemoryAdapter({
        flags: {
          'new-checkout': {
            type: 'boolean',
            enabled: true,
            value: true,
            variant: 'on',
            matchAttribute: { cohort: 'beta' },
          },
        },
      });
      const runtime = new FireweaveRuntime(adapter);
      await OpenFeature.setProviderAndWait(
        't',
        new FireweaveProvider(runtime, { lazyReady: false }),
      );
      const client = OpenFeature.getClient('t');

      assert.equal(
        await client.getBooleanValue('new-checkout', false, {
          targetingKey: 'u1',
          cohort: 'beta',
        }),
        true,
      );
      assert.equal(
        await client.getBooleanValue('new-checkout', false, { targetingKey: 'u2' }),
        false,
      );
      await OpenFeature.close();
    });
    ```

    Node extras: `fault: { kind: 'Timeout' }` fails every resolve with that kind; `initError: 'Configuration'` fails `initialize()` (runtime → `FATAL`); `initGate` holds init open; `setFlags()` / `setFault()` mutate live.
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    from fireweave import (
        EvaluationContext,
        FireweaveClient,
        FireweaveRuntime,
        InMemoryAdapter,
    )

    def test_beta_cohort_gets_new_checkout():
        adapter = InMemoryAdapter({
            "new-checkout": {
                "type": "boolean",
                "enabled": True,
                "value": True,
                "variant": "on",
                "matchAttribute": {"cohort": "beta"},
            },
        })
        runtime = FireweaveRuntime(adapter)
        runtime.initialize()
        with FireweaveClient(runtime) as client:
            assert client.control_points.get_boolean_value(
                "new-checkout", False, EvaluationContext("u1", {"cohort": "beta"})
            ) is True
            assert client.control_points.get_boolean_value(
                "new-checkout", False, EvaluationContext("u2", {})
            ) is False
    ```

    `adapter.set_flags({...})` swaps definitions live. `fireweave.aio.AsyncFireweaveClient` plus the FastAPI example (`examples/python/fastapi_app.py`) shows injecting the runtime into an async app.
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    func TestBetaCohortGetsNewCheckout(t *testing.T) {
        adapter := inmemory.New(inmemory.WithFlags(map[string]inmemory.Flag{
            "new-checkout": {
                Type:            fireweave.FlagTypeBoolean,
                Enabled:         true,
                Value:           true,
                Variant:         "on",
                MatchAttributes: map[string]any{"cohort": "beta"},
            },
        }))
        runtime := fireweave.NewRuntime(adapter, fireweave.Config{})
        if err := openfeature.SetProviderAndWait(
            fwprovider.NewProvider(fireweave.NewClient(runtime)),
        ); err != nil {
            t.Fatal(err)
        }
        client := openfeature.NewClient("t")
        if !client.Boolean(context.Background(), "new-checkout", false,
            openfeature.NewEvaluationContext("u1", map[string]any{"cohort": "beta"})) {
            t.Fatal("expected true for beta cohort")
        }
    }
    ```

    `inmemory.WithInitError(err)` simulates init failure. The adapter implements `fireweave.TelemetrySink`, so flushed exposures and signals are capturable.
  </Tab>

  <Tab title="Java">
    ```java theme={null}
    Map<String, FlagDefinition> flags = Map.of("new-checkout",
        FlagDefinition.fromJson(mapper.readTree(
            "{\"type\":\"boolean\",\"enabled\":true,\"variant\":\"on\",\"value\":true}")));
    InMemoryAdapter adapter = new InMemoryAdapter(flags);
    FireweaveRuntime runtime = new FireweaveRuntime(FireweaveConfig.builder().build(), adapter);
    OpenFeatureAPI.getInstance().setProviderAndWait("t", new FireweaveProvider(runtime));

    boolean enabled = OpenFeatureAPI.getInstance().getClient("t")
        .getBooleanValue("new-checkout", false, new MutableContext("u1"));
    ```

    Faults: `new InMemoryAdapter(flags, FaultConfig...)` simulates HTTP-status faults, invalid JSON, network errors, offline, quota-limiting, and delays (delay compares against the configured timeout — nothing sleeps). Helpers: `evaluateCallCount()`, `lastContext()`, `deliveredExposures()`, `setStale(true)`.
  </Tab>
</Tabs>

`cohort: 'beta'` in these snippets is an **example attribute name**, not an SDK type. See [Targeting](/concepts/targeting).

## Local / dev adapter

Node, Python, and Web only. Go and Java have **no** local adapter on `master`.

Resolution:

| Key                   | Result                                                                                       |
| --------------------- | -------------------------------------------------------------------------------------------- |
| Present in `devFlags` | Mapped **boolean**, reason `STATIC`                                                          |
| Absent                | Caller default, reason `DEFAULT` (`FLAG_NOT_FOUND` rewritten to `DEFAULT` on this path only) |

Reading a `devFlags` key as a string or number is `TYPE_MISMATCH`. `devFlags` is `Record<string, boolean>`.

```ts theme={null}
import { makeFireweaveLocalProvider, getFwLocalCaptures, resetFwLocalCaptures } from '@fireweaveai/sdk';

const provider = makeFireweaveLocalProvider({
  echo: true,
  devFlags: { 'new-checkout': true },
});
```

Python: `make_fireweave_local_provider()`, `get_fw_local_captures()`, `reset_fw_local_captures()`. Web: `FireweaveLocalWebAdapter` behind `FireweaveWebRuntime`.

<Note>
  Call-site defaults stay `false`. Do not write `getBooleanValue(key, true)` to dogfood locally — that same `true` is the production fallback when a control point is absent.
</Note>

## Protocol test server

Zero-dependency Node stub, loopback-only by default:

```bash theme={null}
node test-server/implementation/server.mjs            # http://127.0.0.1:3901
node test-server/implementation/server.mjs --port 4000
```

### Routes that exist

FireWeave-native (`test-server/implementation/server.mjs` and `PATHS.md`):

| Method | Path                 | Role                      |
| ------ | -------------------- | ------------------------- |
| `POST` | `/v1/flags/evaluate` | Evaluate                  |
| `POST` | `/v1/capture`        | Exposures / signals batch |
| `GET`  | `/health`            | `{ "ok": true }`          |

Vendor (PostHog) routes, for languages that still ship a vendor adapter:

| Method | Path                                         |
| ------ | -------------------------------------------- |
| `POST` | `/flags?v=2`                                 |
| `GET`  | `/api/feature_flag/local_evaluation?token=…` |
| `POST` | `/batch/`                                    |

**Not implemented:** `POST /v1/targets/register`.

Auth on FireWeave routes: `Authorization: Bearer <key>`. Any non-empty key is accepted unless you configure one. Use obviously fake keys (`project-api-key_dev`).

The stub serves **its own** fixture keys (`fw-bool-on`, and others from `test-server/fixtures/flags-v2-success.json`). Keys your app expects resolve `FLAG_NOT_FOUND` → default unless you replace fixtures.

```bash theme={null}
FW_API_URL=http://127.0.0.1:3901 FW_PROJECT_API_KEY=project-api-key_dev \
  node examples/node/index.mjs --remote
```

Go harness env: `FIREWEAVE_TEST_SERVER_URL` or `FW_TEST_SERVER_URL`.

### Faults and assertions

| Call                  | Effect                                                                                                                                                                                  |
| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `POST /_test/fault`   | Inject `delay`, `401`, `429`, `500`, `invalid_json`, `truncated`, `quota_limited`. `applyTo`: `evaluate` / `capture` (FireWeave) or `flags` / `definitions` / `batch` (vendor) or `all` |
| `POST /_test/flags`   | Replace the `/flags?v=2` success body                                                                                                                                                   |
| `POST /_test/reset`   | Restore fixtures, clear faults/events                                                                                                                                                   |
| `GET /_test/events`   | `{ events }` from legacy batch, `{ fwEvents }` from `POST /v1/capture`                                                                                                                  |
| `GET /_test/requests` | Which routes the adapter called                                                                                                                                                         |

Per-request: header `X-Fw-Test-Fault: <mode>` or query `?fault=<mode>`.

## What to assert

* On / off / no-match → value, variant, `reason`.
* Missing key → default + `FLAG_NOT_FOUND` (never a throw).
* Type mismatch → default + `TYPE_MISMATCH`.
* Missing `targetingKey` with `requireTargetingKey` → default + `TARGETING_KEY_MISSING`.
* Before init / after shutdown → default + `PROVIDER_NOT_READY` (`NotReady` vs `AlreadyClosed` in `fireweave.errorKind`).
* Exposures: `sendExposure` default false; explicit `record` + `flush`; dedup on `(targetingKey, flagKey, variant, value)`.
* Do **not** assert target registration against InMemory or the test-server stub.

## CI

Cross-language fixtures live in `contracts/` (65 shared; web has 10 in `contracts/web/`). Runners per language; CI job `differential` compares reports. That suite is for SDK contributors — application tests should use `InMemoryAdapter` (or the stub for HTTP).

## Next

<Columns cols={2}>
  <Card title="Adapters" href="/concepts/adapters" icon="plug">
    InMemory vs remote vs local vs PostHog extras
  </Card>

  <Card title="Troubleshooting" href="/troubleshooting" icon="wrench">
    Defaults, fixtures, and install mismatches
  </Card>
</Columns>
