Stateful mock of CareTalk's external API (/externalapi) for test suites: client login,
form definitions (GetForm) and saved form rounds (SavePatientForm, the form-submission
queue's call), patient search and insert (the account backfill), states, free slots and
appointments. The backend requires CareTalk keys at boot, so the mock lets a stack boot and run
the CareTalk paths without the beta environment.
- Operation coverage: SUPPORT.md
- The vendor publishes no spec: the contract (
openapi.yaml) is hand-authored from the consumer's zod schemas and interfaces (caretalk.types.ts,caretalk-forms.type.ts).
Install
npm install -D @crvouga/mockingbird-service-caretalkESM only. Node >= 22 or Bun >= 1.2. No native dependencies. Serve it with
npx mockingbird-caretalk serve, createServer from ./server (Node), or createRuntime
with any Fetch server.
Usage
Point CARETALK_API_URL at the mock (the backend validates it as https-only, so relax that
for loopback or front the mock with TLS). getFormData hardcodes
https://api.caretalkbeta.com (seam G-Y1). CARETALK_USERNAME, CARETALK_PASSWORD and
CARETALK_API_KEY can be any values unless --api-user / --api-key pin them.
npx mockingbird-caretalk serve --port 8823 --api-user "$CARETALK_USERNAME:$CARETALK_PASSWORD" --api-key "$CARETALK_API_KEY"import { createRuntime } from "@crvouga/mockingbird-service-caretalk"
const caretalk = createRuntime()
const call = (path: string, init: RequestInit = {}) =>
caretalk.fetch(new Request(`http://caretalk.test${path}`, init))
const { token } = (await (
await call("/externalapi/Auth/client-login", {
method: "POST",
headers: { "content-type": "application/json" },
// Any credentials log in unless --api-user pins them.
body: JSON.stringify({ userName: "geviti-api", password: crypto.randomUUID() }),
})
).json()) as { token: string }
const [form] = (await (
await call("/externalapi/Forms/GetForm/health-history", {
headers: { authorization: `Bearer ${token}` },
})
).json()) as { fullFormDto: { id: number } }[]
// After the app's form-submission queue runs, assert what reached CareTalk:
const { submissions } = (await (await call("/__admin/form-submissions")).json()) as {
submissions: unknown[]
}Routes
| Route | Behaviour |
|---|---|
POST /externalapi/Auth/client-login |
{userName, password} → {token, expiration}. Tokens last tokenTtlSeconds (default 3600, what our client caches for) on the mock clock. |
GET /externalapi/Forms/GetForm/{formName} |
By name or slug, case-insensitive: [{formRoundId, patientAppointmentId, submitDate, fullFormDto}]; an unknown form is []. With PatientId (and AppointmentId), the patient's latest saved round: chosen answers isChecked, free text echoed. Accepts a login token or a static API key. |
POST /externalapi/Forms/SavePatientForm?patientId=&patientAppointmentId= |
The submission (fullFormDto.groups[].groupQuestions[]). Question ids must belong to the form, answer ids to the question (text questions take id: 0 with the typed value), single-choice questions one answer; violations are 400 ProblemDetails, an unknown patient or form 404. → {success: true, message}. |
GET /externalapi/Patients/SearchForPatient?FirstName&LastName&zipCode&DateOfBirth |
Case-insensitive names, ZIP, and the date in any of YYYY-MM-DD, MM/DD/YYYY, ISO → {isExists: true, eligibleId, programId}; no match is 400 {isExists: false, message} (our client reads 400 as "no such patient"). |
GET /externalapi/States |
51 states {id, stateUid, name, abbreviation, …} (UT is 45). |
POST /externalapi/Patients |
Creates a patient and echoes CareTalk's full record (id, eligibilityId = id, userState, formattedUserMobile, clientId, …). |
GET /externalapi/PatientAppointments/GetFreeSlots?date&programId&eligibleId |
Half-hour slots 09:00–16:30 for two physicians, minus booked ones. |
POST /externalapi/PatientAppointments |
{doctorId, patientId (the eligible id), from, to} → {id, patientId: null, eligibilityId, appointmentStatus: 1, physicianId}; a taken or unknown slot is 400. |
GET /externalapi/PatientAppointments/GetPatientAppointmentsByEligibleId/{id} |
That patient's appointments. |
Unauthenticated or expired calls are an empty 401 with WWW-Authenticate: Bearer (our client
logs in again once and retries). Validation errors are ASP.NET ProblemDetails
{type, title, status, errors, traceId}.
Admin (beyond the standard contract)
| Route | Effect |
|---|---|
GET /__admin/form-submissions[?patientId=] |
Saved rounds: {formRoundId, formId, patientId, patientAppointmentId, submitDate, answers: [{questionId, answerIds, freeAnswerText}]}. |
GET / POST /__admin/patients |
List patients, or create one directly (the Patients POST body) so a search finds it. |
GET /__admin/forms, PUT /__admin/forms/:id |
List or add/replace a form definition (fullFormDto shape). |
POST /__admin/appointments/:id/status |
{appointmentStatus}: change an appointment's status code. |
GET/PUT /__admin/settings |
{tokenTtlSeconds?, users?: [{userName, password}], apiKeys?, programId?}. |
Fault presets (POST /__admin/faults {"preset": "<name>", "count"?: n}): login_failure,
invalid_credentials, token_expired (count applies per operation), server_error,
gateway_html, form_not_found, patient_not_found, save_rejected, connection_drop,
slow.
Namespaces
x-mockingbird-namespace, a /ns/<name> prefix on CARETALK_API_URL, or by credential: tokens
carry the API user they were issued to, so PUT /__admin/credentials {"credentials": {"<CARETALK_USERNAME>": "<ns>", "<CARETALK_API_KEY>": "<ns>"}} routes both auth styles.
Deliberately not modelled
- CareTalk's clinical workflows behind the forms (reviews, form rounds created by staff), eligibility files, Health Gorilla retrieval, medications and diagnostics.
- Real physician calendars: slots are synthesised; time zones are fixed to Mountain.
- The live form catalogue:
DEFAULT_FORMSis synthesised in CareTalk's shape (the live-parity script seeds the mock from the beta environment's forms instead).
API
| Export | Kind | Description |
|---|---|---|
CareTalkAPI |
class | The in-process mock: fetch(request), reset(), addPatient(body), slots(date), setAppointmentStatus(id, status), upsertForm(form), rounds(), patients(). Options: sqlite, now, namespace, forms, settings. |
createRuntime |
function | The mock with the full service contract (health, admin, namespaces, credentials, presets). Options: forms, settings, clock, seed, adminKey, onLog, sqlite. |
CARETALK_PRESETS |
object | Every named fault preset. |
CARETALK_NAMESPACE |
string | The service name, "caretalk". |
DEFAULT_FORMS |
array | The seeded form definitions (Health History 101, AOE Questions 102). |
DEFAULT_DOCTORS |
array | The physicians free slots are generated for. |
US_STATES |
array | The /externalapi/States rows. |
tokenCredential |
function | The API user a token was issued to, or the static key (how credentials map to namespaces). |
normalizeDate |
function | YYYY-MM-DD from ISO, YYYY-MM-DD or MM/DD/YYYY. |
problem |
function | Build an ASP.NET ProblemDetails response. |
document, operationIds, supportedOperationIds |
values | The OpenAPI contract and its operation ids. |
createServer, serveTarget, DEFAULT_PORT (./server) |
Node | Serve over node:http; the serve CLI target (--api-user, --api-key); port 8823. |
Part of mockingbird.