Skip to Content
Card SDKТесты с мок-хостом

Тесты с мок-хостом

В @neurosquad/card-sdk/testing есть хост в памяти, который говорит на настоящем протоколе и применяет проверки приложения в том же порядке — схемы параметров, разрешения, видимость и, по желанию, ограничения частоты. Хранилище, настройки, агенты, порты, инструменты, сеть и файлы имитируются. Код вашей карточки работает с ним без изменений.

Модульный тест

import { createMockHost } from '@neurosquad/card-sdk/testing' import type { CardManifest } from '@neurosquad/card-sdk' import { describe, expect, it } from 'vitest' const manifest: CardManifest = { manifestVersion: 1, name: 'test-radar', displayName: 'Test radar', version: '1.0.0', protocol: 1, permissions: ['agents.read'], ports: { outputs: [{ id: 'summary', label: 'Summary', type: 'ns:markdown' }] }, tools: [{ name: 'summary', description: 'Returns the summary.', inputSchema: { type: 'object' } }] } describe('test radar', () => { it('sends its summary to a connected note and answers the tool', async () => { const host = createMockHost({ manifest, agents: [{ id: 'a1', name: 'Claude Code', harness: 'claude-code', kind: 'ai', status: 'idle', connected: true }], peers: [ { cardId: 'note-1', kind: 'note', name: 'Notes', direction: 'downstream', inputs: [{ id: 'append', label: 'Append', type: 'ns:markdown', mode: 'stream', retain: false, default: true }], outputs: [] } ] }) const card = await host.connect() // The code under test would normally live in your card's module. card.tools.handle('summary', () => '# All green') await card.ports.emit('summary', '# All green') expect(host.deliveries).toEqual([{ to: 'note-1', output: 'summary', input: 'append', data: '# All green' }]) await expect(host.callTool('summary', {})).resolves.toEqual({ content: [{ type: 'text', text: '# All green' }] }) }) it('is refused what it did not declare', async () => { const card = await createMockHost({ manifest }).connect() await expect(card.agents.prompt('a1', 'hi')).rejects.toMatchObject({ code: 'PERMISSION_DENIED' }) }) })

Моку нужен MessageChannel — он есть в Node 18+ и в любом браузере.

Параметры

createMockHost(options):

ПараметрЗначение
manifestВаш манифест; проверяется так же, как в приложении (неверный — исключение). По умолчанию — минимальный.
grantВыданные разрешения: по умолчанию все обязательные; 'all' — вместе с необязательными; или список идентификаторов.
contextПереопределения контекста, отправляемого при подключении (visibility, i18n, workspace, instance…).
agentsAgentInfo[] в воркспейсе.
screens, repliesЧто возвращают readScreen и lastReply для каждого агента.
peersПодключённые карточки (PeerInfo, плюс сохраняемые значения retained и функция respond для запросов).
filesПапка воркспейса: { 'path/to/file': 'text' or Uint8Array }.
fetchОбрабатывает net.fetch после проверок URL и разрешений: (req) => ({ status?, headers?, body?, stream?, error? }). По умолчанию — NETWORK_ERROR.
confirmОтвечает на ui.confirm, openLink и промпты в опасном режиме. По умолчанию — «да».
requestPermissionsОтвечает на permissions.request. По умолчанию выдаёт всё, что просят.
hostProtocolПритвориться более старым или новым приложением (чтобы проверить PROTOCOL_MISMATCH).
enforceRateLimitsПрименять ограничения частоты методов. По умолчанию false, чтобы тесты были детерминированными.
usageUsageSummary, который возвращает usage.summary.
settingsНачальные значения настроек, как будто пользователь их сохранил.
secretsЗначения секретных настроек, которые ввёл пользователь: подставляются в заголовки {{secret:key}}; объявленный секрет, которого здесь нет, падает с INVALID_PARAMS, как в приложении.
verboseПечатать каждый запрос и событие.

Управление карточкой

МетодДелает
connect(options?)Настоящий Card, подключённый к этому хосту.
setVisibility(state), setExpanded(bool), resize({ w, h }), suspend(graceMs?)События жизненного цикла.
setTheme(theme), setLanguage('ru')Смена темы и языка.
setSettings(values, secrets?)Пользователь сохранил форму настроек (secrets — какие заданы).
setSecret(key, value)Пользователь ввёл (или, с null, очистил) секретную настройку.
setGrants(ids), setPeers(peers)Меняет выдачу разрешений или стрелки (с соответствующими событиями).
sendPortMessage(input, data, from?)На вход приходит значение.
requestPort(input, data)Обращается к входу-запросу; возвращает ответ карточки.
callTool(tool, args, { agent?, timeoutMs? })Вызывает инструмент, как агент; возвращает результат. cancelTool(callId) отменяет.
setAgentStatus(id, status), agentOutput(id, text)События агентов (доставляются, если карточка подписана); смена статуса порождает и agents.turn по правилу приложения.
touchFile(path, data?), readFile(path)Меняет файл (уведомляя наблюдателей), читает то, что записала карточка.
emit(event, data)Отправляет любое событие.
ping()Пульс; разрешается, когда карточка ответит.
handle(method, fn)Заменяет поведение метода (выполняется после проверок).

Что он записывает

calls (каждый запрос), events, storage.instance / storage.package (Map), deliveries, retained, prompts, terminal, logs, toasts, clipboard, spawned, aborted, pongs, subscriptions, chrome (title, status, badge, overview, attention, menu, enabledTools), files, а также текущие hostContext и grants.

Превью в браузере

Оба шаблона запускают мок-хост, если страницу открыть вне приложения, так что npx serve . или npm run dev показывают рабочую карточку с примерами данных. Если настраиваете сами, схема такая:

import { connect, type Card, type CardManifest } from '@neurosquad/card-sdk' async function start(): Promise<Card> { if (window.parent !== window) return connect() // inside NeuroSquad const { createMockHost } = await import('@neurosquad/card-sdk/testing') const manifest = (await (await fetch('./neurosquad-card.json')).json()) as CardManifest const host = createMockHost({ manifest, grant: 'all' }) Object.assign(window, { mockHost: host }) // drive it from the DevTools console return host.connect() } const app = await start() render(app.instance.displayName)

Мок точен в правилах, но не в остальном приложении: он не запускает агентов, не рисует стрелки и не показывает вашу плитку обзора. Перед публикацией запустите карточку по-настоящему через neurosquad-card dev.

Чего мок не имитирует

Мок повторяет правила приложения — разрешения, видимость (NOT_VISIBLE для диалогов и запросов разрешений), схемы значений портов и аргументов инструментов, сообщения портов только от карточек выше по стрелке, события хода, выведенные из смены статуса, подстановку секретов в заголовки, — но он не песочница браузера и не интерфейс приложения:

  • ваша страница не в песочнице, поэтому формы могут уводить страницу, обработчики copy работают, вложенные фреймы и воркеры из файлов загружаются — проверяйте это в приложении через neurosquad-card dev;
  • диалоги, шапка и плитка обзора приложения не рисуются — смотрите host.chrome;
  • настоящих агентов, терминалов и стрелок нет — это данные, которые вы передаёте.