@crvouga/mockingbird-service-healthie (英语)
本地模拟器。 真正的API合同. 部分 嘲笑鸟。 。 。 .
国家模拟器 健康图QL API覆盖着遗产 仍然暴露我们的后端 电话:
- 签名、用户、当前用户以及配置、密码和外观更新
- 客户端更新和地址
- 文档和文件夹,包括多部分上传,下载由模拟器提供
- 请求的窗体和窗体答案
- 提供和收费项目
- iP 允许列表状态的 Webhooks
图QL 文档运行在引用上 graphql
与Healthie用手写的一套计划相对应 选择集,别名,
碎片,变量, __typename 和验证错误的行为
健康, 模拟器只返回文档要求的字段 .
- 行动范围: 支助
- 健康出版SDL但无OpenAPI.
openapi.yaml描述 HTTP 侧: 一个 GraphQL 操作和文件下载路径。 GraphQL 计划为HEALTHIE_SDL。 。 。 .
这值得测试吗? 目录(S23)将"健康"标为"遗产". 任用和 供应商搬到了Medplum。 剩下的是遗留的签名、密码重置、配置和 地址、ODX文档上传、管理导出和迁移。 检查一个套房仍然 练习这些路径,然后将模拟器接入堆栈。 已充分执行 无论是哪种方式.
安装
npm install -D @crvouga/mockingbird-service-healthie
仅限无害环境管理。 节点 22或 Bun 1.2. 单程依赖之外 hono 是大众
graphql 软件包(以16.11.0为基准,我们的后端使用版本)。 见
为什么 graphql{\fn黑体\fs22\bord1\shad0\3aHBE\4aH00\fscx67\fscy66\2cHFFFFFF\3cH808080}为它服务
npx mockingbird-healthie serve, (中文(简体) ), createServer 从 ./server (节点),或 createRuntime
与任何获取服务器.
使用量
点 HEALTHIE_API_URL 时间 http://127.0.0.1:8816/graphql。这需要缝合 G-H1级 第一条:
后端的Joi和zod计划目前只接受中转或推进的URL,以及
HEALTHIE_WEBHOOK_IP_ADDRESS 只有两个IP列表。 设定 HEALTHIE_API_AUTH_TOKEN 在应用程序中
并传递与 --api-key。 。 。 .
npx mockingbird-healthie serve --port 8816 \
--api-key "$HEALTHIE_API_AUTH_TOKEN" \
--webhook-base-url http://127.0.0.1:3000 \
--webhook-ip 18.206.70.225
import { GraphQLClient, gql } from "graphql-request"
import { createRuntime, SEED } from "@crvouga/mockingbird-service-healthie"
// Test setup: the emulator, in this process.
const emulator = createRuntime({
settings: { orgApiKeys: ["gh_sbox_test_org"] },
webhooks: { baseUrl: "http://127.0.0.1:3000", ip: "18.206.70.225" },
})
// Application code: the GraphQL client the app already uses (here graphql-request). Only its
// fetch changes: requests reach the emulator instead of api.gethealthie.com.
const healthie = new GraphQLClient("https://api.gethealthie.com/graphql", {
headers: { AuthorizationSource: "API" },
fetch: ((url: RequestInfo | URL, init?: RequestInit) =>
emulator.fetch(new Request(url, init))) as typeof fetch,
})
// The seeded demo patient signs in and gets an API key, as the member app does.
const signedIn = await healthie.request<{ signIn: { api_key: string; user: { id: string } } }>(
gql`
mutation ($e: String, $p: String) {
signIn(input: { email: $e, password: $p, generate_api_token: true }) {
api_key
user { id active }
}
}
`,
{ e: SEED.patientEmail, p: SEED.patientPassword },
)
// Test control: ask Healthie to request a form from the patient. POST /forms/webhooks/status
// follows.
await emulator.fetch(
new Request("http://healthie.test/__admin/requested-forms", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ recipient_id: SEED.patientId }),
}),
)
路线
| 路线 | 行为 |
|---|---|
POST /graphql | 以JSON形式发出的每一份请求文件 {query, variables, operationName} 或作为 图QL 多部分请求 (单位:千美元)operations, (中文(简体) ), map,文件部分)用于 Upload 变量。 这个 Authorization 标题将 API 密钥作为 Bearer <key>, (中文(简体) ), Basic <key> 或空钥匙. AuthorizationSource: API 被接受和忽略。 错误会随着HTTP 200而出现,如Healthie. 一个不是GraphQL的身体得到一个400. |
GET /files/{token} | 后面的字节 document.expiring_url 财务报告和审定财务报表 user.avatar_url,这里关于请求本身的来源(与 /__admin/ns/<name> 当名称空格。 链接在模拟器时钟上签名并过期( L)expiringUrlSeconds,默认为300). 被篡改或过期的链接得到一个403,被删除的文件得到一个404. 每次下载都会增加一个条目 document.opens。 。 。 . |
图QL 根字段 : currentUser, (中文(简体) ), user(id), (中文(简体) ), users(keywords, should_paginate, …), (中文(简体) ),
location(id), (中文(简体) ), locations, (中文(简体) ), documents(…), (中文(简体) ), document(id), (中文(简体) ), folders(…), (中文(简体) ),
requestedFormCompletion(id), (中文(简体) ), formAnswerGroups(user_id, …), (中文(简体) ), formAnswerGroup(id), (中文(简体) ),
initialFormAnswers(custom_module_form_id, incomplete_form_id, user_id), (中文(简体) ),
offerings(client_visibility, offering_id, …) 财务报告和审定财务报表
billingItems(offerings_only, client_id, status)。 。 。 .
变异 :
signIn(input)数字 :generate_api_token财务报告和审定财务报表allow_multiple_api_keys将受优待namespace当一个被设定的时候.updateUser(input): 设置配置字段, 上传一个外观为Upload,用avatar: null,或更改密码。 密码变体发送出否id检查current_password财务报告和审定财务报表password_confirmation。 。 。 .updateClient(input)数字 :metadata, (中文(简体) ),phone_number, (中文(简体) ),password, (中文(简体) ),other_provider_ids, (中文(简体) ),user_group_id, (中文(简体) ),dietitian_id, (中文(简体) ),active,以及location作为ClientLocationInput。 。 。 .createLocation财务报告和审定财务报表updateLocation。 。 。 .createFolder: 剖析share_users以"user-<id>,…"。 。 。 .createDocument: 取一个Upload或一个file_string数据 URL,加share_users财务报告和审定财务报表rel_user_id。 。 。 .deleteDocument。 。 。 .updateBillingItem数字 :is_paused, (中文(简体) ),is_canceled, (中文(简体) ),note财务报告和审定财务报表state我们的客户发送它作为 两个别名在一个文档中的变异; 每个发出billing_item.updated。 。 。 .
行为我们的代码分支:
- 坏键 : 未知的 API 密钥返回
errors: [{message: "API Key is Invalid"}],哪个 我们的客户端地图401. - 无密钥 :
currentUser实值null,列表为空和变异,但signIn失败. 我们listFiles转动null变成401. - 验证 : 失败归来作为
messages: [{field, message}]带有null有效载荷, 我们的客户标到400.messages实值null成功;我们getFolderByPath治疗 任何非虚值为失败. - 签名 : 错误的电子邮件或密码返回
user: null,我们的客户端地图INVALID_SIGN_IN_CREDENTIALS。一个存档的用户标志active: false,哪个地图USER_ARCHIVED。 。 。 . - 谁看到什么: 患者键只看到自己的用户、护理团队、自己的文件以及 任何与它分享的东西。 组织的关键看到了一切.
网络呼号
保健活动作为 {resource_id, resource_id_type, event_type, changed_fields?}。 。 。 .
resource_id 作为字符串发送,因为我们的 DTO 验证 @IsString()。每项请求
携带 x-forwarded-for: <ip>,而那个信头是 我们的接收器的唯一检查
HEALTHIE_WEBHOOK_IP_ADDRESS没有签名.
| 活动 | 接收人(--webhook-base-url +路径) | 发件人 |
|---|---|---|
patient.created, (中文(简体) ), patient.updated | /users/webhook/status | updateUser, (中文(简体) ), updateClient, (中文(简体) ), POST /__admin/users,存档和无存档 |
requested_form_completion.created, (中文(简体) ), form_answer_group.created | /forms/webhooks/status | POST /__admin/requested-forms, (中文(简体) ), POST /__admin/form-answer-groups |
billing_item.updated | (默认无;添加一个为 PUT /__admin/webhook-endpoints(中文(简体) ) | updateBillingItem |
POST /__admin/events {event_type, resource_id, resource_id_type} 发出任何事件。 交货
然后在5秒、5分、30分和2分之后再审。 标准管理员路线
还适用: GET /__admin/webhooks, (中文(简体) ), …/events, (中文(简体) ), …/:id/replay 财务报告和审定财务报表 …/flush。 。 。 .
管理员(超过标准合同)
| 路线 | 效果 |
|---|---|
GET /__admin/users, (中文(简体) ), POST /__admin/users | 列表用户(从未密码). 从 {email, password, first_name?, last_name?, role?: patient | provider, active?, dietitian_id?, …} 回来 {user, api_key}。 。 。 . |
POST /__admin/users/:id/archive | /unarchive | 设定 active (存档用户与 active: false) (中文(简体) ). |
POST /__admin/users/:id/api-keys | 为用户发行另一个 API 密钥 . |
POST /__admin/offerings | {name, price?, billing_frequency?, visibility_status?}。 。 。 . |
POST /__admin/billing-items | {sender_id, offering_id?, amount_paid?, state?, is_recurring?, next_payment_date?}。 。 。 . |
POST /__admin/form-answer-groups | {user_id, custom_module_form_id?, filler_id?, answers?: {<label or module id>: answer}} (内容为webhook表格). |
POST /__admin/requested-forms | {recipient_id, sender_id?, custom_module_form_id?} (内容为webhook表格). |
POST /__admin/events | 放弃任何健康活动. |
GET /__admin/documents | 命名空间的文档元数据 . |
GET | PUT /__admin/settings | {orgApiKeys?, namespace?: string | null, expiringUrlSeconds?}。 。 。 . |
种子数据SEED),在重置时重制:
100001: 组织管理员,与每个组织挂钩orgApiKeys条目(默认)gh_sbox_org_api_key) (中文(简体) ).100002(原始内容存档于2018-03-26). : Datitian Dana Rivera, MD.100003: 演示病人[email protected]编号 :Password123!。 。 。 .- 提供
200001和200002(加上,隐去也. - 申请表
300001。 。 。 .
错误预设( E)POST /__admin/faults {"preset": "<name>", "count"?: n}:
invalid_api_keygraphql_500: 包含500为了我们的500个分支.validation_messagescurrent_user_nullexpired_urlshttp_500rate_limited(429个)server_errorwebhook_duplicate, (中文(简体) ),webhook_reorder, (中文(简体) ),webhook_drop
命名空间
选择三种命名空间:
- 页:1
x-mockingbird-namespace页眉; - (单位:千美元)
/__admin/ns/<name>/graphql前缀在HEALTHIE_API_URL(文件 URL 保留前缀); - 按 API 键,使用
PUT /__admin/credentials {"credentials": {"<key>": "<namespace>"}}。 。 。 .
signIn 发送没有密钥, 所以并行的套件, 在需要时签名头或路径前缀 .
客户
健康没有正式的节点SDK. 我们的后端通过它说话 graphql-request 7.2.0 联合国
页:1 awesome-graphql-client 0.14.1(多个部分) Upload 页:1 这个
接受测试将模拟器通过这些精确的版本,被固定为devdependents
后端的文档逐字复制( q)test/consumer.ts) (中文(简体) ). 一个单独的 SDK 滴入测试
重复他们.
为什么 graphql 是一个运行时间依赖
我们消费者的文件需要真正的GraphQL语义: 验证一个计划,
每个选择的分辨率,别名(两个) updateBillingItems 在一个文件中, __typename, (中文(简体) ),
和多变的强迫。 包括投放未申报变量的图ql-js,其中之一
我们的文件在不知不觉中依赖.
一个手卷式的解析器会从这些规则中漂移出来,而这正是平等必须达到的目的
保护. graphql 是一个公开的,无依赖的 npm 软件包, 绑在版本我们的
后端使用,所以它是正常的 dependency 而不是被捆绑.
在我们的消费者中发现的差别(模仿者跟随销售商,测试结果为他们定下钉子)
createLocation发送city: line2永远不要宣布$line2因此,健康储存 第二地址线作为城市.updateCheckoutPatientById发送一个timezone变量,文档从未声明, 所以健康忽略了这一点.FormsService.formRequestStatus读取recipient.id,其查询从不选择。 这个 因此它删除的缓存密钥是…:undefined。 。 。 .pauseBillingItem类型isPaused作为弦. 一个字符串发送到$is_paused: Boolean会被GraphQL可变强制拒绝. 该方法今天没有呼叫者.
故意不模拟
- 我们的消费者不再用预约 提供商 计量标准,
目标 图表 聊天 网络游戏管理 花脸卡突变,
createClient编号 :signUp, (中文(简体) ),userGroup, (中文(简体) ),offeringCoupons, (中文(简体) ),createApiKey, (中文(简体) ),updateFolder编号 :deleteFolder, (中文(简体) ),updateDocument。查询一个失败 GraphQL 验证,就像一个打字机那样. - 健康网的签名(
Content-Digest编号 :Signature) (中文(简体) ). 我们的接收器只验证 源IP,所以交付没有签名. - 真实的页形光标 :
should_paginate10页offset。模糊的关键字搜索是 在电子邮件和姓名上进行大小写不敏感子字符串匹配. - 上载流量 苹果健康与Google适切链接:永远
null。 。 。 . - 真实兽类的活数据. 种子数据是合成的; 通过管理员添加用户和记录 路线.
API 密码
| 导出 | 仁爱 | 说明 |
|---|---|---|
HealthieAPI | 类 | 进程模拟器 : fetch(request), (中文(简体) ), reset(), (中文(简体) ), userForKey(key), (中文(简体) ), emit(event), (中文(简体) ), fileUrl(origin, fileId), (中文(简体) ), state选项: sqlite, (中文(简体) ), now, (中文(简体) ), namespace, (中文(简体) ), publicNamespace, (中文(简体) ), settings, (中文(简体) ), onEvent。 。 。 . |
createRuntime | 函数 | 全面服务合同的仿真器(健康、管理、命名空间、证书、预设、网络呼号)。 选项 : settings, (中文(简体) ), webhooks: {baseUrl, ip?, retryDelaysMs?, fetch?}, (中文(简体) ), clock, (中文(简体) ), seed, (中文(简体) ), adminKey, (中文(简体) ), onLog。 。 。 . |
HEALTHIE_PRESETS | 对象 | 每个命名的断层预设. |
HEALTHIE_EVENT_ROUTES | 对象 | 接收路径 – 事件类型 . |
healthieEndpoints | 函数 | 后端基址和源 IP 的 Webhook 端点 . |
DEFAULT_WEBHOOK_IP | 字符串 | 18.206.70.225默认值 x-forwarded-for。 。 。 . |
HEALTHIE_NAMESPACE | 字符串 | 服务名称, "healthie"。 。 。 . |
INVALID_API_KEY | 字符串 | "API Key is Invalid"。 。 。 . |
HEALTHIE_SDL, (中文(简体) ), healthieSchema | 数值 | GraphQL 子集( SDL 和已建) GraphQLSchema) (中文(简体) ). |
Upload | 类 | 文件绑入 Upload 变量由多段运算. |
apiKeyOf | 函数 | API 密钥 a Authorization 头带( 如何将证书映射到命名空间) . |
healthieTimestamp | 函数 | 卫生 YYYY-MM-DD HH:MM:SS +0000 时间戳格式. |
parseShareUsers | 函数 | "user-1,user-2" → ["1", "2"]。 。 。 . |
SEED, (中文(简体) ), DEFAULT_SETTINGS | 对象 | 种子代号与证书; 默认设置. |
document, (中文(简体) ), operationIds, (中文(简体) ), supportedOperationIds | 数值 | HTTP合同及其操作代号. |
createServer, (中文(简体) ), serveTarget, (中文(简体) ), DEFAULT_PORT (单位:千美元)./server(中文(简体) ) | 节点 | 服务过来 node:http; 数据 serve CLI目标; 第8816口. |
部分 嘲笑鸟。 。 。 .