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

# Provider 测试

> 用 vendor 调试 CLI 并针对运行中的服务器验证一个 C++ vendor 客户端。

## 概览

vendor 客户端调用真实的第三方 API，因此它们不属于单元套件。你以两种方式验证它们：**`vendor` 调试 CLI**（隔离演练某个客户端的操作，无需服务器）和**运行中服务器上的 HTTP 路由**（所有权门控的 bind → verify → fetch 路径）。

<Info>
  元数据（`VendorInfo`）无需凭据；传输操作需要针对该 vendor 的真实凭据。
</Info>

## `vendor` 调试 CLI

`cli/vendor.cpp` 工具（当 `MIROBODY_BUILD_TOOLS` 为 ON 时构建于 `cli/` 下 —— 默认开启）直接驱动某个 vendor 的契约操作。子命令：

```text theme={null}
vendor list                                          # all registered vendors
vendor <id> info                                     # full metadata for one vendor
vendor <id> auth-url  --redirect URL [--state S]     # build the consent URL
vendor <id> providers [--user U]                     # connectable data sources
vendor <id> fetch     --user U --domain sleep [--start ISO] [--end ISO]
vendor <id> webhook   --file payload.json            # body from --file/--data/stdin
vendor <id> revoke    --user U [--provider slug]
```

凭据来自服务器所用的同一批 `MIROBODY_VENDOR_<ID>_*` 环境变量。

<Steps>
  <Step title="元数据（无需凭据）">
    确认你的 `VendorInfo` 完整且 id 已注册：

    ```bash theme={null}
    ./build/cli/vendor list
    ./build/cli/vendor fitbit info
    ```
  </Step>

  <Step title="授权 URL">
    ```bash theme={null}
    MIROBODY_VENDOR_FITBIT_CLIENT_ID=... \
      ./build/cli/vendor fitbit auth-url --redirect https://localhost/cb --state abc
    ```

    检查该 URL 格式正确，并携带你的 scopes / `state`。
  </Step>

  <Step title="Fetch">
    用一个真实的 access token，在某个时间窗内获取一个域：

    ```bash theme={null}
    MIROBODY_VENDOR_FITBIT_API_KEY=... \
      ./build/cli/vendor fitbit fetch --user - --domain heart_rate --start 2026-06-01 --end 2026-06-07
    ```

    验证返回的 JSON 与该 vendor 有文档的形态相符，且**不支持的域会抛错**并给出清晰的消息。
  </Step>

  <Step title="Webhook（带签名的 vendor）">
    喂入一个捕获到的 payload 并确认签名验证：

    ```bash theme={null}
    ./build/cli/vendor fitbit webhook --file sample_notification.json
    ```

    再传入一个被篡改的请求体，确认它被拒绝。
  </Step>

  <Step title="Revoke">
    ```bash theme={null}
    MIROBODY_VENDOR_FITBIT_API_KEY=... MIROBODY_VENDOR_FITBIT_CLIENT_ID=... \
    MIROBODY_VENDOR_FITBIT_CLIENT_SECRET=... ./build/cli/vendor fitbit revoke --user -
    ```
  </Step>
</Steps>

<Note>
  对于桩操作，CLI 会打印该 vendor 的"not implemented"原因 —— 这是预期的，其本身也值得断言（该消息应点明它*为何*是桩）。
</Note>

## 针对运行中的服务器

演练所有权门控的 HTTP 路径（所有路由都需要 bearer JWT；服务器监听 `HTTP_PORT`，默认 `8080`）：

```bash theme={null}
# 1. bind the external account id (PENDING)
curl -X POST "http://localhost:8080/vendors/fitbit/bind" \
  -H "Authorization: Bearer $JWT" -H "Content-Type: application/json" \
  -d '{"external_user_id": "FITBIT_USER_ID"}'

# 2. verify ownership (-> VERIFIED)
curl -X POST "http://localhost:8080/vendors/fitbit/bind/verify" -H "Authorization: Bearer $JWT"

# 3. fetch a verified link's data
curl "http://localhost:8080/vendors/fitbit/data?domain=heart_rate&start=2026-06-01&end=2026-06-07" \
  -H "Authorization: Bearer $JWT"
```

确认在验证**之前**获取会被拒绝，且第二名用户不能 bind 同一个 `external_user_id`（`UNIQUE(vendor_id, external_user_id)` 这道防线）。

对于 EHR（SMART on FHIR），走一遍浏览器流程：`/health/ehr/providers` → `/health/ehr/authorize` → `/health/ehr/callback` → `/health/ehr/sync`，然后从 `/fhir/Observation` 读回已持久化的 `Observation`。

## 用于 OAuth 回调的本地 HTTPS

许多 vendor 会拒绝非 HTTPS 的 redirect URI，因此一个普通的 `http://localhost:8080` 回调无法用于真实的同意流程。本地开发的选项：

* 一个在 443 端口终止 TLS 的本地反向代理（nginx / Caddy），使用来自 **`mkcert`** 的受信任证书，转发到 `localhost:8080`，并在 hosts 文件中加一条把你的生产回调域名映射到 `127.0.0.1` 的记录 —— 从而让**同一个** redirect URI 在本地和生产都能工作。
* 一个隧道服务，如 **ngrok**（在本地测试期间也便于接收入站 **webhook** 以及用于 `MCP_PUBLIC_URL`）。

关键在于，向 vendor 呈现它所期望的确切 HTTPS 回调 URL，而无需为每次改动都部署一遍。

## 需要覆盖的内容

<AccordionGroup>
  <Accordion title="理想路径" icon="check">
    有效凭据 → `auth-url` 能构建，`fetch` 为每个支持的域返回有文档的 JSON，带签名的 `webhook` 能验证，`revoke` 成功。
  </Accordion>

  <Accordion title="缺失 / 错误配置" icon="triangle-exclamation">
    空的 `api_key` / `client_id` → 一个点明缺失环境变量的清晰 `VendorError`。必填的 `base_url`（自托管 / EHR）为空 → 拒绝执行，而非猜测一个主机。
  </Accordion>

  <Accordion title="边界情况" icon="code">
    不支持的 `DataDomain` 抛错；空的日期窗使用 vendor 默认；被篡改的 webhook 签名被拒绝；桩操作报告其原因。
  </Accordion>
</AccordionGroup>

## 下一步

<CardGroup cols={2}>
  <Card title="Provider 集成" icon="code" href="/zh/development/provider-integration">
    构建你正在测试的 vendor
  </Card>

  <Card title="测试指南" icon="flask" href="/zh/development/testing">
    Catch2 单元套件
  </Card>

  <Card title="OAuth 实现" icon="key" href="/zh/development/oauth-implementation">
    同意、webhook、撤销
  </Card>

  <Card title="使用 Provider" icon="link" href="/zh/providers/using-providers">
    bind / verify / data 路由
  </Card>
</CardGroup>
