Тестирование плагинов
Это руководство объясняет, что нужно проверить перед работой плагина с реальным контентом: как использовать тестовое ядро в своём наборе тестов, запустить оба сервиса на одной машине и намеренно воспроизвести каждый сбой.
Проверяемые операции описаны в Protocol v1, а требования, соблюдение которых подтверждают эти тесты, — в разделе Безопасность плагинов.
Контракт
Заголовок раздела «Контракт»Каждый плагин должен проверять следующие сценарии. Они относятся к свойствам протокола, а не к особенностям конкретного плагина, поэтому должны входить в ваш набор тестов независимо от его назначения.
| Случай | Что должно произойти |
|---|---|
| Активация | Одноразовый код расходуется ровно один раз. Неизвестный, использованный или просроченный код отклоняется. Сырой токен хранится зашифрованным и никогда не логируется. Обработчик отвечает с хорошим запасом до 5 секунд и сам не делает вызовов протокола |
| Ротация | kind: rotate заменяет сохранённый токен. Старый перестаёт использоваться каждым воркером. Отклонённая ротация оставляет старый токен на месте |
| Запуск | Код обменивается один раз, через бэкенд плагина. Второй обмен не удаётся. Код, выпущенный для другой инсталляции, не проходит. Страница управления отправляет Referrer-Policy: no-referrer и убирает dee_wan_launch из URL после обмена |
| Сессия | Браузерная сессия плагина создаётся при обмене, хранится в его собственном cookie и не содержит учётных данных Dee Wan |
| Закрепление | Публикация закрепляет current_version_id, снятие с публикации закрепляет published_version_id, оба закрепляют workflow.revision, а выбранная пара (kind, to) получена из делегированного чтения |
| Идемпотентность | Ключ выводится из записи и её ревизии, одинаков при каждом повторе и меняется, когда запись перепланируется |
| Не более одного раза | Доставку начинает только текущий владелец аренды. Срок выполнения запроса должен истечь раньше срока аренды. Восстановление может повторить запрос с теми же ключом и телом. Ядро применяет эффект не более одного раза в пределах хранения ключа. Таймаут не доказывает остановку удалённого запроса |
| Повтор | idempotent_replay завершается как успех, а не как новый эффект |
| Терминальные отказы | invalid_request, plugin_unauthorized, capability_refused, requester_unauthorized, content_not_found, scheduled_target_stale, workflow_conflict, dependent_content, idempotency_key_reused, transition_unavailable и transition_refused — каждый завершается без повтора, под собственным именем |
| Повторы | Корректные структурированные rate_limited/429 и temporary_failure/503, ответы 429/500/502/503/504 без outcome и сетевые сбои/таймауты повторяются в пределах лимитов попыток И возраста. Соблюдайте допустимый ограниченный Retry-After. Иначе используйте backoff с джиттером |
| Недоверенные ответы | Присутствующий, но некорректный outcome, неизвестный код, неверный флаг retry или несовпадение статуса и кода терминальны даже на повторяемом статусе. Тело без outcome терминально вне перечисленных резервных статусов. Перенаправления не выполняются |
| Изоляция | Каждая таблица и каждый запрос ограничены инсталляцией. Одна инсталляция не может читать, отменять или выполнять записи другой |
| Отзыв | После удаления ожидающее задание завершается с plugin_unauthorized. Плагин сообщает об отказе и не повторяет запрос |
| Секреты | Ни одна тестовая фикстура, строка журнала, ответ с ошибкой или снимок экрана не содержит токена, кода запуска или кода активации |
| Запись от имени пользователя | Сохранение полей, работа с черновиком, загрузка медиа или создание предложения указывает делегирование пользователя, от имени которого выполняется операция, фиксирует прочитанную версию или ревизию черновика и передаёт Idempotency-Key. Конфликты version_conflict и draft_conflict требуют решения пользователя, а не автоматического повтора |
Исходный код плагина Scheduling (plugins/scheduling в репозитории Dee Wan) содержит набор тестов, который является
рабочим примером каждой строки, в test/activation.test.ts, launch.test.ts, schedules.test.ts,
runner.test.ts, recovery.test.ts, time.test.ts, isolation.test.ts и flow.test.ts. Файл test/conformance.test.ts проверяет плагин на сценариях протокола из @dee-wan/plugin-conformance.
Проверка на заглушке ядра
Заголовок раздела «Проверка на заглушке ядра»Не запускайте свой набор тестов против работающей установки Dee Wan. Тестовое ядро даёт воспроизводимые результаты, позволяет намеренно вызывать сбои, которые сложно получить в настоящем ядре, и выполняет проверки за миллисекунды.
@dee-wan/plugin-conformance — устанавливаемый набор тестов протокола v1. Его исходный код —
packages/plugin-conformance в репозитории Dee Wan, где он собирается и тестируется. Он ЕЩЁ НЕ
опубликован в реестре npm; публикация требует полномочий. Пока это так, плагин в другом
репозитории собирает его из checkout Dee Wan и устанавливает tarball:
node scripts/install-dependencies.mjs # from the Dee Wan checkout rootnpm --prefix packages/plugin-conformance run buildcd packages/plugin-conformancenpm pack # writes dee-wan-plugin-conformance-0.1.0.tgzcd /path/to/your-pluginnpm install --save-dev /path/to/dee-wan-plugin-conformance-0.1.0.tgzВ пакет включены опубликованные схемы, поэтому при выполнении он ничего не читает из рабочей копии Dee Wan. Он экспортирует:
| Экспорт | Что это |
|---|---|
createFakeCore() |
Реализация fetch, которая играет роль ядра Dee Wan для одной или нескольких инсталляций |
runConformance({ describe, it }, subject) |
Регистрирует случаи протокола в vitest, node:test или jest |
conformanceCases(subject) |
Те же случаи как обычные асинхронные функции |
validateManifest(value), validateShape(name, value) |
Валидация по опубликованной схеме манифеста и по $defs протокола |
OUTCOMES, CAPABILITIES, TRANSITION_KINDS, PROTOCOL_VERSIONS, INBOUND_RETRY_STATUSES, SECRET_PATTERNS |
Опубликованные данные протокола |
Тестовое ядро:
- аутентифицирует bearer-токен для каждой инсталляции и иначе отвечает
plugin_unauthorized - отвечает на
/context,/content/:idи/launch/exchangeтелами, соответствующими структуре протокола, включая блокworkflowи переходы, которые разрешают возможности инсталляции и делегирование - проверяет выданные установке плагина возможности и доступ к моделям и, как ядро, отклоняет делегирование, которое не разрешает доступ к записи
- отклоняет тело команды, не являющееся опубликованным
transition_command, сinvalid_request - проверяет закрепление и ревизию workflow в команде и отвечает
scheduled_target_staleилиworkflow_conflict, когда они больше не выполняются, иdependent_contentдля элемента, помеченного как имеющий зависимые - хранит запись идемпотентности для каждого ключа, так что точный повтор отвечает
idempotent_replayсо всем первым результатом, а повторно использованный ключ с другим вводом отвечаетidempotency_key_reused - сообщает
redirectв каждом результате публикации, из поляredirectэлемента, и не сообщает при снятии с публикации - перемещает один язык, когда команда называет
locale, а у инсталляции естьworkflow:locale, и перечисляет каждый язык вworkflow.localesдля этой инсталляции - расходует код запуска один раз и отклоняет его для другой инсталляции
- выпускает конверты вызова через
createInvocationи расходует код вызова на/invocations/exchangeодин раз, только для инсталляции, для которой он выпущен, и до истечения срока - обрабатывает маршруты записи —
PUT /content/:id,PUT /drafts,POST /drafts/discard,GET /drafts,POST /media,POSTиGET /content/:id/proposals,GET /proposals/:id— по опубликованным структурам запросов и ответов; проверяет возможности, выданные права, делегирование, закреплённую версию и ревизию черновика, поддерживает повтор по ключу; значения полей заменяют версию записи, черновик принадлежит делегировавшему пользователю, а предложение ничего не меняет - закрепляет публикацию одного языка за текущей версией группы, а снятие с публикации одного языка — за собственной живой версией этого языка, как это делает ядро
- внедряет сбои по запросу — сетевую ошибку, ответ, потерянный после применения команды, любой
статус, любой
outcome.code, заголовокRetry-Afterили намеренно некорректное тело - записывает запросы отдельно от применённых эффектов, так что восстановление может повторно отправить тот же ключ, а эффект происходит не более одного раза.
Постраничный список /content оно не обслуживает.
runConformance принимает subject: ваш манифест, вашу копию данных протокола, ваш классификатор
ответов и необязательные адаптеры для активации, обмена кодом запуска, доставки, вызова операций
(обязателен, если манифест объявляет operations) и вызова источника медиа (mediaSource, обязателен,
если манифест объявляет media_sources). Плагин, который не отправляет команд, например источник медиа,
не передаёт копию протокола и классификатор. Каждый адаптер
запускает свежий экземпляр вашего плагина, подключённый к тестовому ядру. Минимальный файл vitest:
import { runConformance } from '@dee-wan/plugin-conformance';import { describe, it } from 'vitest';import { subject } from './conformance-subject';
runConformance({ describe, it }, subject);Набор проверяет соответствие опубликованному протоколу: таблицу исходов, HTTP-статусы для повторов,
возможности, виды переходов, версию протокола и форматы секретов. Манифест должен проходить валидацию.
Каждый код исхода, неверный статус, неверный флаг повтора, некорректный outcome и статус без outcome
должны классифицироваться по правилам протокола. Проверяется одноразовость кодов активации и запуска,
а также привязка кодов ротации к своей установке плагина.
Через ваш собственный механизм отправки проверяется, что закреплённая команда впервые отправляется ровно один раз. При потере ответа повтор использует те же ключ и тело, а изменения применяются один раз. Каждый окончательный исход завершает задание под собственным именем и исключает дальнейшие отправки; исходы, допускающие повтор, приводят к повтору. Устаревшая закреплённая версия, изменённый процесс работы, зависимый контент, потеря пользователем нужного права и удаление плагина должны приводить к отказу.
Для каждой объявленной операции плагин должен отвечать только после однократного обмена кодом.
Ответ должен соответствовать опубликованной схеме и содержать только объявленные компоненты результата.
Ответ completed недопустим для отклонённого кода, кода другой установки плагина или конверта с неизвестным
ключом. Для каждого объявленного столбца списка контента проверяется, что обмен не создаёт делегирования,
а ответ содержит один блок cells только для запрошенных строк, по одной ячейке на строку. Каждая ячейка
отображается в объявленном виде cell: url допустим только у ссылки, tone — только у метки.
Для каждого объявленного источника медиа ответ на просмотр допустим только после обмена. Все URL превью
и скачивания должны находиться на origin манифеста, а все URL выдачи файлов — на объявленном origin
выдачи. Объявление должно указывать свои modes и содержать delivery_origins тогда и только тогда,
когда объявлен reference. Его next_cursor должен вести на страницу без повторных элементов.
При запросе элемента из списка должен возвращаться именно этот элемент. Ответ completed недопустим
для отклонённого кода или кода другой установки плагина. Полная реализация subject есть в
test/conformance.test.ts плагина Scheduling; реализация для источника медиа — в
plugins/media-source-reference/test/conformance.test.ts.
У тестового ядра два ограничения: оно не заменяет настоящее ядро и поддерживает только процесс
из двух состояний — draft и published. Проверяйте outcome.code и HTTP-статус, а не текст
сообщения: сообщения ядра предназначены для людей, и их формулировки могут меняться.
test/helpers/d1.ts плагина Scheduling — короткая прослойка над better-sqlite3 с интерфейсом D1
prepare/bind/first/all/run/batch, так что настоящий SQL плагина — включая его
захваты compare-and-set и его частичный уникальный индекс — выполняется в наборе тестов. Она применяет
каждый файл из migrations/ плагина. Скопируйте её и направьте на свои миграции.
Подготовьте локальную ссылку на пакет по инструкции разработки Scheduling, затем запустите набор тестов эталонного плагина:
cd plugins/schedulingnpm test # vitestnpm run typecheckВнутри репозитория Dee Wan его инструменты берутся из установки в корне репозитория
(node scripts/install-dependencies.mjs в корне), а @dee-wan/plugin-conformance подключается
из packages/plugin-conformance: сначала выполните там npm run build.
Валидация по опубликованным схемам
Заголовок раздела «Валидация по опубликованным схемам»Два документа JSON Schema генерируются из определений, по которым валидирует ядро, и публикуются с каждым релизом:
schemas/manifest-v1.schema.json— валидируйте свой манифест в собственном CI, ещё до того, как администратор когда-либо его получит.schemas/protocol-v1.schema.json— структуры запросов и ответов под$defs, а также машиночитаемая таблицаoutcomes(HTTP-статус и флаг повтора для каждого кода),capabilities,installation_statesиlimits.
Особенно важно проверять таблицу outcomes. Если плагин хранит собственную копию этой таблицы, сравнивайте
её с опубликованной: расхождение статуса или флага повтора должно останавливать сборку, а не
проявляться во время работы. Эталонный плагин хранит такую копию в src/protocol.ts, поскольку
не может импортировать её из ядра. Проверки «protocol copy» в runConformance выполняют это сравнение.
Локальная конфигурация из двух сервисов
Заголовок раздела «Локальная конфигурация из двух сервисов»Оба сервиса на одной машине, без аккаунта Cloudflare и без настоящих учётных данных. Это единственная локальная конфигурация для работы с плагинами. Планирование от начала до конца начинается с неё.
Вам нужны версия Node, закреплённая в .nvmrc в репозитории Dee Wan, openssl и эти порты:
8787 (admin Worker Dee Wan), 5173 (admin UI) и 8788 (плагин).
Порт 8788 — также порт по умолчанию для npm run local:public, публичного Worker чтения Dee Wan. Если вы
запускаете и его, дайте одному из них другой порт: npm run local:public -- --port 8789, либо запустите
плагин на другом порту и используйте этот порт везде, где ниже встречается 8788.
Dee Wan
Заголовок раздела «Dee Wan»В backend/.dev.vars в репозитории Dee Wan (создайте его, если он не существует) добавьте origin
плагина:
PLUGIN_DEV_ORIGINS=http://localhost:8788Это единственная настройка, которую вы добавляете. Конфигурация локального admin Worker уже задаёт
ENVIRONMENT=development, благодаря чему PLUGIN_DEV_ORIGINS вообще читается, и
BACKEND_URL=http://localhost:8787 — именно его ядро отправляет вашему плагину как dee_wan_base_url.
PLUGIN_DEV_ORIGINS — список разрешённых точных origin, без шаблонов и без сокращений для приватных диапазонов, — проверяемый
для URL манифеста, базового URL, URL активации и управления. Без этой записи установка с
http://localhost:8788 отклоняется как url_not_https.
Затем из корня репозитория запустите каждую долгоработающую команду в отдельном терминале:
node scripts/install-dependencies.mjsnode scripts/install-dependencies.mjs frontendnpm run local:migratenpm run local:api # admin Worker on http://localhost:8787 (leave running)npm run local:seed # needs local:api running; prints the sign-in it createsnpm run local:admin # admin UI on http://localhost:5173 (leave running)Для эталонного плагина, из корня репозитория:
cd plugins/schedulingcp wrangler.example.jsonc wrangler.jsoncЗаглушка database_id в скопированном файле допустима: --local никогда не обращается к Cloudflare, поэтому
шага wrangler d1 create нет. Рядом с ним создайте .dev.vars:
ENVIRONMENT=developmentPUBLIC_ORIGIN=http://localhost:8788DEE_WAN_DEV_ORIGINS=http://localhost:8787ADMIN_SECRET=local-admin-secret-0123456789TOKEN_KEY=<the output of: openssl rand -base64 32>.dev.varsпереопределяет production-значения изwrangler.jsoncдляwrangler dev.DEE_WAN_DEV_ORIGINS— зеркальное отражениеPLUGIN_DEV_ORIGINSна стороне плагина: единственный базовый URL Dee Wan без HTTPS, который он принимает, и только когдаENVIRONMENT=development.ADMIN_SECRET— bearer оператора для маршрутов плагина/admin/*, длиной не менее 16 символов.TOKEN_KEY— base64 от 32 случайных байт. Шифрует сохранённые токены инсталляций при хранении. Потеряете его — придётся ротировать каждую инсталляцию.
npx wrangler d1 migrations apply dee-wan-scheduling --localnpx wrangler dev --port 8788 --test-scheduled # leave runningcurl http://localhost:8788/dee-wan/manifest.json # the manifest names http://localhost:8788Выпустите одноразовый код активации:
curl -X POST -H "Authorization: Bearer local-admin-secret-0123456789" \ http://localhost:8788/admin/activation-codesОн отвечает 201 с { "code", "purpose", "installation_id", "expires_at" }: purpose равен
activate, installation_id равен null, а expires_at — на 15 минут вперёд, в миллисекундах от
начала эпохи.
Вашему собственному плагину нужны те же три вещи: отдавайте его на разрешённом вами origin, принимайте
http://localhost:8787 как базовый URL только в разработке и имейте способ выпустить код
активации.
Установка
Заголовок раздела «Установка»Войдите в admin UI по адресу http://localhost:5173 с учётной записью, которую вывел local:seed, затем
откройте Настройки → Плагины: проверьте http://localhost:8788/dee-wan/manifest.json, выдайте набор возможностей,
выберите модели и установите с кодом. Откройте запись модели, к которой плагину предоставлен доступ, и используйте
действие, объявленное вашим манифестом.
Устанавливайте плагин через интерфейс: административным маршрутам нужны действительная
сессия, заголовок сайта и разрешение site:plugins. Интерфейс уже передаёт всё необходимое.
Две особенности локального запуска:
- Cookie сессии
__Host-/Secureтребует HTTPS или точноlocalhost— не127.0.0.1. - Cron-триггер не срабатывает в
wrangler dev, если вы об этом не попросите. С--test-scheduledcurl "http://localhost:8788/__scheduled?cron=*+*+*+*+*"выполняет один тик.
Автоматизированный межсервисный прогон
Заголовок раздела «Автоматизированный межсервисный прогон»Эталонный плагин и ядро запускаются друг против друга, оба вживую, одной командой. С версией Node,
закреплённой в .nvmrc, подготовьте checkout Dee Wan, затем запустите скрипт плагина:
node scripts/install-dependencies.mjsnpm run build:packagesnpm --workspace backend run prisma:generatecd plugins/schedulingnpm run test:cross-serviceТест живёт в плагине, в test/cross-service/cross-service.test.mjs. Вне репозитория Dee Wan задайте
DEE_WAN_CORE_DIR как подготовленный checkout Dee Wan. Ему не нужны аккаунт Cloudflare, учётные
данные и сеть за пределами 127.0.0.1. Он занимает две-три минуты, потому что
назначает выполнение на конкретную минуту и дожидается её наступления.
Тест запускает следующие сервисы, для каждого выбирает свободный порт, а затем останавливает их и удаляет свой временный каталог:
- admin Worker Dee Wan под
wrangler dev, на свежей локальной D1 с применёнными миграциями, сPLUGIN_DEV_ORIGINS, в котором указан плагин. - плагин Scheduling под
wrangler dev --test-scheduled, из сгенерированной копииwrangler.example.jsonc, на свежей локальной D1 с применёнными миграциями. - HTTP-прокси в процессе теста. На него указывает
BACKEND_URLядра, поэтому каждый вызов плагина к ядру проходит через него. Он пересылает запросы и ответы без изменений, кроме заголовкаHost. Он может отбросить запрос до пересылки или отбросить ответ после того, как ядро ответило, и записывает каждую увиденную команду перехода. Это сеть, а не дублёр: каждый ответ приходит от ядра.
Всё проходит через настоящие маршруты: активация установки и вход, вызовы /api/plugins из раздела
«Настройки → Плагины», делегированные запуски, собственные HTML-формы плагина и тики /__scheduled.
Он проверяет:
| Случай | Что он проверяет |
|---|---|
| Активация | Установка через ядро активирует плагин. Повторное использование кода активации отклоняется с activation_refused |
| Делегированный запуск | Каждое расписание создаётся через POST /api/plugins/:id/launch, обмен плагина и его форму. Подтверждение показывает локальное время, его часовой пояс и момент в UTC |
| До срока | Тик до назначенной минуты ничего не отправляет |
| Публикация, ответ потерян | Ядро применяет публикацию, прокси отбрасывает ответ, плагин повторяет с тем же ключом и побайтно одинаковым телом, и ядро отвечает idempotent_replay с теми же данными результата, что и в потерянном ответе, включая redirect. Одно событие workflow плагина, приписанное инсталляции и делегировавшему человеку |
| Публикация, запрос потерян | Прокси отбрасывает первый запрос, не переслав его. Плагин повторяет, и ядро отвечает transition_applied |
| Снятие с публикации | Закреплённая опубликованная версия снимается с публикации |
scheduled_target_stale |
Черновик отредактирован после закрепления |
workflow_conflict |
Элемент опубликован и снят с публикации вручную после закрепления |
transition_unavailable |
Элемент опубликован вручную после того, как была закреплена публикация |
dependent_content |
Опубликованный элемент связан с тем, который снимается с публикации |
| Отмена | Отменено в UI плагина. Ядро не вызывается ни разу |
| Изоляция | Сессия одной инсталляции не может прочитать или отменить расписание другой инсталляции |
| Отзыв | Ядро удаляет инсталляцию с ожидающим расписанием. Плагин завершает его с plugin_unauthorized |
| Завершённое остаётся завершённым | Каждый отклонённый или отменённый случай оставляет состояние ядра, ревизию и указатель публикации без изменений, а последующий тик ничего не отправляет |
CI Dee Wan его не запускает. Собственный CI плагина, .github/workflows/ci.yml в каталоге плагина,
запускает его против checkout Dee Wan, когда у плагина появится свой репозиторий. До тех пор
запускайте его вручную, когда изменение затрагивает любую из сторон protocol v1.
Прогон в браузере с живым плагином
Заголовок раздела «Прогон в браузере с живым плагином»frontend/cypress/e2e/real-scheduling-releases.cy.ts проходит страницы релизов плагина в браузере,
против настоящего admin Worker и настоящего плагина. Раннер e2e (frontend/scripts/run-e2e.mjs)
запускает плагин для прогона с E2E_REAL_BACKEND=1, который может дойти до этого spec: весь набор или
--spec, совпадающий с ним. E2E_SCHEDULING_PLUGIN=1 включает его принудительно, 0 выключает.
E2E_REAL_BACKEND=1 npm --prefix frontend run test:e2e -- --spec cypress/e2e/real-scheduling-releases.cy.tsЧто делает раннер, чтобы вы не повторяли настройку выше вручную:
- генерирует
wrangler.jsoncи.dev.varsплагина во временный каталог, которым владеет сам, изwrangler.example.jsonc, с абсолютнымиmainиmigrations_dirи заданными переменными (PUBLIC_ORIGIN,ENVIRONMENT=development,DEE_WAN_DEV_ORIGINS= admin Worker этого прогона). Ничего не пишется вplugins/scheduling/. Каталог удаляется, когда прогон останавливается, а каталог, оставленный убитым прогоном (его сокет владельца/tmp/dwe2e-<suffix>.sock, названный по каталогу, больше никто не слушает), удаляется, когда следующий прогон запускает плагин; - применяет миграции плагина к свежей локальной D1 в этом каталоге, затем запускает его через
wrangler dev --test-scheduled; - запускает admin Worker с
PLUGIN_DEV_ORIGINS, в котором указан плагин; - генерирует
ADMIN_SECRETиTOKEN_KEYдля каждого прогона и передаёт spec origin и admin secret какCYPRESS_E2E_PLUGIN_ORIGINиCYPRESS_E2E_PLUGIN_ADMIN_SECRET. Spec выпускает свой код активации этим секретом, устанавливает черезPOST /api/pluginsи открывает каждую страницу плагина по URL с кодом изPOST /api/plugins/:id/launch, то есть тем вызовом, который делают кнопки запуска плагина в настройках и в редакторе записи.
Порт плагина — E2E_PLUGIN_PORT (по умолчанию 43190). Если он занят, прогон сразу отказывает, как и для
каждого другого порта, который использует прогон. Раннер следит за плагином: если тот падает во время
живого прогона в браузере, прогон останавливается и называет его.
Фикстуры сбоев
Заголовок раздела «Фикстуры сбоев»Ниже описано, как намеренно воспроизвести каждый сбой. В колонке тестового ядра указаны действия вашего теста, а в колонке локального Dee Wan — способ получить то же состояние в настоящем ядре для проверки всего сценария.
Исходы, определённые протоколом
Заголовок раздела «Исходы, определённые протоколом»С ними встречается каждый плагин, что бы он ни делал. Каждый имеет собственный outcome.code с фиксированным HTTP-статусом
и флагом повтора.
| Исход | На тестовом ядре | На локальной установке Dee Wan |
|---|---|---|
invalid_request |
Отправьте неизвестный ключ тела или опустите Idempotency-Key |
То же |
plugin_unauthorized |
Используйте неизвестный токен | Отключите или удалите инсталляцию, затем отправьте команду |
capability_refused |
Уберите возможность у инсталляции | Ограничьте выданные права в разделе Настройки → Плагины |
requester_unauthorized |
Укажите, что пользователь, которому принадлежит делегирование, больше не имеет нужного разрешения | Уберите роль человека или деактивируйте его после создания делегирования |
content_not_found |
Запросите неизвестный экземпляр | Используйте id с другого сайта или исключите модель из выданных прав |
scheduled_target_stale |
Измените current_version_id контента |
Отредактируйте и сохраните черновик после его закрепления |
workflow_conflict |
Увеличьте revision контента |
Опубликуйте, а затем снимите с публикации элемент вручную после закрепления публикации |
dependent_content |
Ответьте dependent_content через внедрение |
Свяжите с ним другой опубликованный элемент, затем попробуйте снять с публикации |
idempotency_key_reused |
Повторно используйте ключ с другим телом | То же |
transition_unavailable |
Запросите to, который состояние не предлагает |
Опубликуйте вручную элемент, публикация которого закреплена. Ядро проверяет переход раньше ревизии |
rate_limited |
Внедрите 429 с Retry-After — в секундах и как HTTP-дату |
120 запросов за минуту для одной инсталляции |
temporary_failure |
Внедрите структурированный ответ 503, затем пропустите повтор | Трудно вызвать. Ядро также отвечает так, пока исход перенаправления публикации ещё не записан; повтор с тем же ключом возвращает весь результат. Доверьтесь внедрённому случаю |
Сбои без кода исхода
Заголовок раздела «Сбои без кода исхода»У них нет outcome.code, поэтому протокол фиксирует только правило — никогда не считайте такой сбой успехом и
никогда не повторяйте бесконечно. Как их записывать — решение вашего плагина. Последняя колонка — то, что делает
эталонный плагин.
| Сбой | На тестовом ядре | На локальной установке Dee Wan | Эталонный плагин |
|---|---|---|---|
| временный HTTP без outcome | Внедрите 429, 500, 502, 503 и 504 без тела outcome | Трудно вызвать. Доверьтесь внедрённым случаям | Повторяет с ограниченным backoff или допустимым ограниченным Retry-After |
| транспортный сбой | Внедрите сетевую ошибку и таймаут | Остановите Worker Dee Wan, затем дайте плагину отправить команду | Повторяет как no_response, с задержкой и в пределах своих лимитов |
| недоверенный ответ | Внедрите 200, чей outcome.code — код 409, тело не JSON и 3xx |
Не воспроизводится. В этом и смысл внедрения | Отказывает как untrusted_response |
| восстановление аренды | Оставьте запись в running с истёкшей арендой |
Убейте плагин посреди команды и дайте следующему тику её забрать | Отправляет повторно с тем же ключом идемпотентности, в пределах лимита опоздания |
Проверяйте окончательный код результата и отсутствие новых запросов после завершения. Отдельно проверяйте восстановление: потеря ответа может привести к повторам с тем же ключом и побайтно одинаковым телом, но изменения должны примениться только один раз. Запустите два конкурирующих воркера и убедитесь, что прежний владелец права на обработку не сможет отправить запрос после потери этого права. Сам журнал команд не доказывает, что изменения применились не более одного раза.
Что тесты пока не покрывают
Заголовок раздела «Что тесты пока не покрывают»- Пакет контракта не опубликован.
@dee-wan/plugin-conformanceсобирается и тестируется в репозитории Dee Wan. Публикация в npm требует полномочий. До тех пор плагин в другом репозитории устанавливает tarball, созданныйnpm pack. - Межсервисный прогон покрывает случаи из своей таблицы и ничего больше. Он не проверяет
requester_unauthorized,capability_refused,rate_limited, ротацию токена,retry_exhausted, падение плагина посреди команды или cron-триггер, который срабатывает сам. На каждой стороне они проверяются с помощью заглушки либо вручную в локальной среде, описанной выше. CI Dee Wan его не запускает, а собственный CI плагина запустится, только когда у плагина будет свой репозиторий. - Ничто здесь не является свидетельством развёртывания. Оба сервиса работают под локальным
wrangler dev.
Стабильная версия ещё не выпущена (pre-MVP)
Коммит исходного кода: e207e63239a627fbd2ae9c414ef7c6eb19fcfe5a (с локальными изменениями)
Дата сборки (UTC): 2026-10-21T09:28:24.214Z
Коммит для обновления: не определён: в рабочей копии есть изменения. Примечания к выпуску