Рецепты API тест-планов
Готовые к вставке рецепты для каждого endpoint-а Test Plans. Каждый раздел показывает вызов cURL и эквивалент в SDK Go, Python и Java.
Об URL в примерах: все примеры используют
http://localhost:5770. Замените на адрес вашего Mockarty (напримерhttps://mockarty.company.com), если инстанс живёт в другом месте. Подробности — в Полезных функциях и советах.
Связанные страницы: Тест-планы · Тест-планы в CI/CD · Справочник API
Аутентификация
Все endpoint-ы требуют API-токен в заголовке X-API-Key. Токены выпускаются в Settings → API Tokens (или через POST /api/v1/auth/tokens) с ролью, ограниченной namespace-ом.
export MOCKARTY_URL=http://localhost:5770
export MOCKARTY_API_TOKEN=mk_7_...
export NS=default
SDK-клиенты
Go
import (
"context"
"github.com/mockarty/mockarty-go/mockarty"
)
client, err := mockarty.NewClient(mockarty.Config{
BaseURL: "http://localhost:5770",
Token: os.Getenv("MOCKARTY_API_TOKEN"),
})
ctx := context.Background()
Python
from mockarty import MockartyClient, TestPlan, TestPlanItem
client = MockartyClient(
base_url="http://localhost:5770",
token=os.environ["MOCKARTY_API_TOKEN"],
)
Java
import com.mockarty.MockartyClient;
import com.mockarty.model.*;
MockartyClient client = MockartyClient.builder()
.baseUrl("http://localhost:5770")
.token(System.getenv("MOCKARTY_API_TOKEN"))
.build();
Карта endpoint-ов
| Verb | Путь | Что делает |
|---|---|---|
| POST | /api/v1/test-plans |
Создать план. |
| GET | /api/v1/test-plans |
Список планов (с фильтром по namespace). |
| GET | /api/v1/test-plans/:id |
Получить план по UUID или числовому ID. |
| PUT | /api/v1/test-plans/:id |
Полная замена. |
| PATCH | /api/v1/namespaces/:ns/test-plans/:idOrNumericID |
Частичное обновление с If-Match. |
| DELETE | /api/v1/test-plans/:id |
Мягкое удаление (в Корзину). |
| POST | /api/v1/test-plans/:id/run |
Запустить прогон. |
| GET | /api/v1/test-runs/:runID |
Статус прогона. |
| POST | /api/v1/test-runs/:runID/cancel |
Отменить идущий прогон. |
| GET | /api/v1/test-plans/:planID/runs |
Список прогонов плана. |
| GET | /api/v1/namespaces/:ns/test-plans/:planRef/runs/:runID/stream |
SSE-поток прогресса. |
| GET | /api/v1/namespaces/:ns/test-plans/:idOrNumericID/runs/:runID/report |
Allure JSON-сводка. |
| GET | /api/v1/namespaces/:ns/test-plans/:idOrNumericID/runs/:runID/report.zip |
Allure ZIP-архив. |
| GET | /api/v1/namespaces/:ns/test-plans/:idOrNumericID/runs/:runID/report.junit.xml |
JUnit XML (для CI). |
| GET | /api/v1/namespaces/:ns/test-plans/:idOrNumericID/runs/:runID/report.md |
Markdown-сводка (PR-комментарии). |
| GET | /api/v1/namespaces/:ns/test-plans/:idOrNumericID/runs/:runID/report.html |
Самодостаточный HTML (открыть в браузере, Save-as-PDF). |
| GET | /api/v1/namespaces/:ns/test-plans/:idOrNumericID/runs/:runID/report.unified.json |
Унифицированный JSON-отчёт. |
| POST | /api/v1/namespaces/:ns/test-runs/ad-hoc |
Ad-hoc прогон (скрытый план). |
| GET / POST / PATCH / DELETE | /api/v1/test-plans/:id/schedules(/:schedId) |
CRUD расписаний. |
| GET / POST / PATCH / DELETE | /api/v1/test-plans/:id/webhooks(/:whId) |
CRUD вебхуков. |
| POST | /api/v1/test-plans/:id/webhooks/:whId/test |
Синтетическая проверка вебхука. |
Создание плана
cURL
curl -X POST "$MOCKARTY_URL/api/v1/test-plans" \
-H "X-API-Key: $MOCKARTY_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"namespace": "default",
"name": "Nightly regression",
"description": "Functional + fuzz + chaos against staging",
"items": [
{"order": 1, "type": "functional", "refId": "11111111-1111-1111-1111-111111111111"},
{"order": 2, "type": "fuzz", "refId": "22222222-2222-2222-2222-222222222222"},
{"order": 3, "type": "chaos", "refId": "33333333-3333-3333-3333-333333333333"}
]
}'
Ответ:
{
"id": "5c0f13e4-...",
"numericId": 42,
"namespace": "default",
"name": "Nightly regression",
"items": [],
"updatedAt": "2026-04-19T10:00:00Z"
}
Go
plan, err := client.TestPlans().Create(ctx, mockarty.TestPlan{
Namespace: "default",
Name: "Nightly regression",
Description: "Functional + fuzz + chaos against staging",
Items: []mockarty.TestPlanItem{
{Order: 1, Type: mockarty.PlanItemTypeFunctional, ResourceID: "11111111-..."},
{Order: 2, Type: mockarty.PlanItemTypeFuzz, ResourceID: "22222222-..."},
{Order: 3, Type: mockarty.PlanItemTypeChaos, ResourceID: "33333333-..."},
},
})
Python
plan = client.test_plans.create(TestPlan(
namespace="default",
name="Nightly regression",
description="Functional + fuzz + chaos against staging",
items=[
TestPlanItem(order=1, type="functional", ref_id="11111111-..."),
TestPlanItem(order=2, type="fuzz", ref_id="22222222-..."),
TestPlanItem(order=3, type="chaos", ref_id="33333333-..."),
],
))
Java
TestPlan plan = client.testPlans().create(new TestPlan()
.setNamespace("default")
.setName("Nightly regression")
.setItems(List.of(
new TestPlanItem().setOrder(1).setType("functional").setResourceId("11111111-..."),
new TestPlanItem().setOrder(2).setType("fuzz").setResourceId("22222222-..."),
new TestPlanItem().setOrder(3).setType("chaos").setResourceId("33333333-...")
)));
Список, получение и удаление
cURL
# Список
curl -H "X-API-Key: $MOCKARTY_API_TOKEN" \
"$MOCKARTY_URL/api/v1/test-plans?namespace=$NS&limit=50"
# Получить по числовому ID (или UUID)
curl -H "X-API-Key: $MOCKARTY_API_TOKEN" \
"$MOCKARTY_URL/api/v1/test-plans/42"
# Мягкое удаление (в Корзину)
curl -X DELETE \
-H "X-API-Key: $MOCKARTY_API_TOKEN" \
"$MOCKARTY_URL/api/v1/test-plans/42"
Go
plans, err := client.TestPlans().List(ctx, mockarty.ListPlansOptions{
Namespace: "default", Limit: 50,
})
plan, err := client.TestPlans().Get(ctx, "42")
err = client.TestPlans().Delete(ctx, plan.ID)
Python
plans = client.test_plans.list(namespace="default", limit=50)
plan = client.test_plans.get("42")
client.test_plans.delete(plan.id)
Java
List<TestPlan> plans = client.testPlans().list(
ListPlansOptions.builder().namespace("default").limit(50).build());
TestPlan p = client.testPlans().get("42");
client.testPlans().delete(p.getId());
Частичное обновление (PATCH с If-Match)
PATCH /api/v1/namespaces/:ns/test-plans/:idOrNumericID требует заголовок If-Match с текущим сильным валидатором плана (updatedAt в миллисекундах Unix, в кавычках).
cURL
ETAG=$(curl -s -H "X-API-Key: $MOCKARTY_API_TOKEN" \
"$MOCKARTY_URL/api/v1/test-plans/42" \
| python3 -c 'import sys,json,datetime;d=json.load(sys.stdin); t=datetime.datetime.fromisoformat(d["updatedAt"].replace("Z","+00:00")); print(int(t.timestamp()*1000))')
curl -X PATCH "$MOCKARTY_URL/api/v1/namespaces/$NS/test-plans/42" \
-H "X-API-Key: $MOCKARTY_API_TOKEN" \
-H "Content-Type: application/json" \
-H "If-Match: \"$ETAG\"" \
-d '{"description": "Updated nightly suite"}'
Устаревший If-Match даёт 412 Precondition Failed.
Go
updated, err := client.TestPlans().Patch(ctx, "42",
mockarty.PatchPlanRequest{Description: mockarty.Ptr("Updated nightly suite")},
mockarty.PatchOptions{Namespace: "default"}, // IfMatch вычисляется автоматически
)
Python
updated = client.test_plans.patch(
"42",
{"description": "Updated nightly suite"},
namespace="default", # if_match берётся из текущего плана
)
Java
TestPlan updated = client.testPlans().patch("42",
new PatchPlanRequest().setDescription("Updated nightly suite"),
PatchOptions.builder().namespace("default").build());
Запуск прогона и ожидание
cURL
# Триггер
RUN=$(curl -s -X POST "$MOCKARTY_URL/api/v1/test-plans/42/run" \
-H "X-API-Key: $MOCKARTY_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"mode": "parallel"}')
RUN_ID=$(echo "$RUN" | jq -r .runId)
# Опрос до терминала
while :; do
STATUS=$(curl -s -H "X-API-Key: $MOCKARTY_API_TOKEN" \
"$MOCKARTY_URL/api/v1/test-runs/$RUN_ID/status" \
| jq -r .status)
echo "status=$STATUS"
case "$STATUS" in
completed|failed|cancelled) break ;;
esac
sleep 5
done
Go
run, _ := client.TestPlans().Run(ctx, "42", mockarty.RunOptions{Mode: "parallel"})
final, err := client.TestPlans().WaitForRun(ctx, run.ID, 3*time.Second)
// err == nil → completed
// errors.Is(err, mockarty.ErrRunFailed) → failed
// errors.Is(err, mockarty.ErrRunCancelled) → cancelled
Python
run = client.test_plans.run("42", mode="parallel")
final = client.test_plans.wait_for_run(run.id, poll_interval=3)
Java
TestPlanRun run = client.testPlans().run("42",
RunOptions.builder().mode("parallel").build());
TestPlanRun final_ = client.testPlans().waitForRun(run.getId(), Duration.ofSeconds(3));
Запуск подмножества элементов
curl -X POST "$MOCKARTY_URL/api/v1/test-plans/42/run" \
-H "X-API-Key: $MOCKARTY_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"items": [1, 3], "mode": "sequential"}'
Отмена идущего прогона
curl -X POST "$MOCKARTY_URL/api/v1/test-runs/$RUN_ID/cancel" \
-H "X-API-Key: $MOCKARTY_API_TOKEN"
Стрим прогресса (SSE)
cURL
curl -N -H "X-API-Key: $MOCKARTY_API_TOKEN" \
"$MOCKARTY_URL/api/v1/namespaces/$NS/test-plans/42/runs/$RUN_ID/stream"
Последовательность событий: run.started → N × (item.started / item.finished) → run.completed, плюс периодический heartbeat.
Go
events, _ := client.TestPlans().StreamRun(ctx, runID)
for ev := range events {
log.Printf("%s %s %s", ev.Type, ev.ItemID, ev.Status)
}
Python
for ev in client.test_plans.stream_run(run.id):
print(ev.type, ev.item_id, ev.status)
Java
client.testPlans().streamRun(run.getId(), ev ->
System.out.println(ev.getType() + " " + ev.getItemId() + " " + ev.getStatus()));
Скачивание отчётов прогона
Mockarty отдаёт пять форматов отчёта с одного набора endpoint-ов. Allure JSON и ZIP кэшируются как артефакты и поддерживают If-None-Match; JUnit XML, Markdown и Unified JSON строятся на лету из результатов прогона.
| Формат | Endpoint | CLI | Назначение |
|---|---|---|---|
| Allure JSON | GET .../runs/:runID/report |
mockarty-cli testplan report <runID> --format json |
Программный обход, богатая модель Allure. |
| Allure ZIP | GET .../runs/:runID/report.zip |
mockarty-cli testplan report <runID> --format zip |
Загрузка в Allure Server / Allure TestOps. |
| JUnit XML | GET .../runs/:runID/report.junit.xml |
mockarty-cli testplan report <runID> --format junit |
GitHub Actions / GitLab CI / Jenkins JUnit-виджеты. |
| Markdown | GET .../runs/:runID/report.md |
mockarty-cli testplan report <runID> --format markdown |
Комментарии к PR, Slack, email. |
| HTML | GET .../runs/:runID/report.html |
mockarty-cli testplan report <runID> --format html |
Самодостаточный документ для печати в PDF и архива. |
| Unified JSON | GET .../runs/:runID/report.unified.json |
mockarty-cli testplan report <runID> --format unified |
Стабильная схема Mockarty для дашбордов и автоматизации. |
cURL
# Allure JSON-сводка
curl -s -H "X-API-Key: $MOCKARTY_API_TOKEN" \
"$MOCKARTY_URL/api/v1/namespaces/$NS/test-plans/42/runs/$RUN_ID/report" \
-o report.json
# Allure ZIP
curl -s -H "X-API-Key: $MOCKARTY_API_TOKEN" \
"$MOCKARTY_URL/api/v1/namespaces/$NS/test-plans/42/runs/$RUN_ID/report.zip" \
-o allure.zip
# JUnit XML
curl -s -H "X-API-Key: $MOCKARTY_API_TOKEN" \
"$MOCKARTY_URL/api/v1/namespaces/$NS/test-plans/42/runs/$RUN_ID/report.junit.xml" \
-o report.junit.xml
# Markdown
curl -s -H "X-API-Key: $MOCKARTY_API_TOKEN" \
"$MOCKARTY_URL/api/v1/namespaces/$NS/test-plans/42/runs/$RUN_ID/report.md" \
-o report.md
# Самодостаточный HTML (открыть в браузере, Save-as-PDF)
curl -s -H "X-API-Key: $MOCKARTY_API_TOKEN" \
"$MOCKARTY_URL/api/v1/namespaces/$NS/test-plans/42/runs/$RUN_ID/report.html" \
-o report.html
# Unified JSON
curl -s -H "X-API-Key: $MOCKARTY_API_TOKEN" \
"$MOCKARTY_URL/api/v1/namespaces/$NS/test-plans/42/runs/$RUN_ID/report.unified.json" \
-o report.unified.json
Allure-endpoint-ы поддерживают If-None-Match, так что CI-пайплайны, опрашивающие готовность, платят только за HTTP round-trip.
Go
allure, _ := client.TestPlans().GetRunReport(ctx, "default", "42", runID)
zipRC, _ := client.TestPlans().GetRunReportZIP(ctx, "default", "42", runID)
defer zipRC.Close()
io.Copy(outFile, zipRC)
junitXML, _ := client.TestPlans().GetRunReportJUnit(ctx, "default", "42", runID)
markdown, _ := client.TestPlans().GetRunReportMarkdown(ctx, "default", "42", runID)
html, _ := client.TestPlans().GetRunReportHTML(ctx, "default", "42", runID)
unified, _ := client.TestPlans().GetRunReportUnified(ctx, "default", "42", runID)
// unified.Raw содержит исходные байты для дальнейшей передачи.
Python
summary = client.test_plans.get_run_report("42", run.id, namespace="default")
with open("allure.zip", "wb") as f:
client.test_plans.get_run_report_zip("42", run.id, f, namespace="default")
junit_xml = client.test_plans.get_run_report_junit("42", run.id, namespace="default")
markdown = client.test_plans.get_run_report_markdown("42", run.id, namespace="default")
html = client.test_plans.get_run_report_html("42", run.id, namespace="default")
unified = client.test_plans.get_run_report_unified("42", run.id, namespace="default")
# unified.raw содержит исходные байты, unified.counts.failed и т. д. — типизированные поля.
Java
AllureReport summary = client.testPlans().getRunReport("default", "42", runID);
try (InputStream zip = client.testPlans().getRunReportZip("default", "42", runID);
OutputStream out = Files.newOutputStream(Path.of("allure.zip"))) {
zip.transferTo(out);
}
byte[] junitXml = client.testPlans().getRunReportJUnit("default", "42", runID);
byte[] markdown = client.testPlans().getRunReportMarkdown("default", "42", runID);
byte[] html = client.testPlans().getRunReportHTML("default", "42", runID);
UnifiedReport unified = client.testPlans().getRunReportUnified("default", "42", runID);
// unified.getRaw() — исходные байты, unified.getCounts().getFailed() — типизированные поля.
Ad-hoc прогон
POST /api/v1/namespaces/:ns/test-runs/ad-hoc создаёт скрытый план и диспетчеризует прогон одним вызовом — идеально для динамически собираемых CI-пайплайнов.
cURL
curl -X POST "$MOCKARTY_URL/api/v1/namespaces/$NS/test-runs/ad-hoc" \
-H "X-API-Key: $MOCKARTY_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "pr-1234 smoke",
"items": [
{"order": 1, "type": "functional", "ref_id": "11111111-..."},
{"order": 2, "type": "contract", "ref_id": "44444444-..."}
]
}'
Ответ:
{
"run_id": "8a1f62d0-...",
"plan_id": "9c0e...",
"status": "running",
"_links": {
"self": "/api/v1/test-runs/8a1f62d0-...",
"status": "/api/v1/test-runs/8a1f62d0-.../status",
"report": "/api/v1/namespaces/default/test-plans/9c0e.../runs/8a1f62d0-.../report"
}
}
Endpoint ad-hoc отвечает 503 на узлах, где оркестратор не подключён (например, в упрощённых desktop-сборках).
Необязательное "tags": ["release-42", "smoke"] помечает прогон при создании. Ответ содержит нормализованные метки; endpoint чтения прогона возвращает те же значения. Некорректные метки дают 400 до создания плана. Ограничения описаны в разделе Разовые прогоны.
Добавьте "idempotency_key":"job-4211", если CI может повторить POST. Прежнее тело и ключ вернут исходный запуск с "replayed":true; изменение тела с тем же ключом даст 409. Новому заданию нужен новый ключ. Сервер хранит только хеш ключа.
Для существующего прогона добавьте метки атомарно через POST /api/v1/namespaces/<namespace>/test-plan-runs/<runId>/tags/merge с телом {"tags":["smoke"]}. Ответ содержит полный список tags и признак changed. Повтор запроса безопасен; метка релиза и метки других процессов сохраняются. Существующий endpoint /tags сохраняет режим полной замены.
Go
resp, err := client.TestPlans().CreateAdHocRun(ctx, mockarty.CreateAdHocRunRequest{
Namespace: "default",
Name: "pr-1234 smoke",
Items: []mockarty.TestPlanItem{
{Order: 1, Type: "functional", ResourceID: "11111111-..."},
{Order: 2, Type: "contract", ResourceID: "44444444-..."},
},
})
Python
resp = client.test_plans.create_ad_hoc_run(
namespace="default",
name="pr-1234 smoke",
items=[
TestPlanItem(order=1, type="functional", ref_id="11111111-..."),
TestPlanItem(order=2, type="contract", ref_id="44444444-..."),
],
)
Java
AdHocRunResponse resp = client.testPlans().createAdHocRun(
CreateAdHocRunRequest.builder()
.namespace("default")
.name("pr-1234 smoke")
.items(List.of(
new TestPlanItem().setOrder(1).setType("functional").setResourceId("11111111-..."),
new TestPlanItem().setOrder(2).setType("contract").setResourceId("44444444-...")
))
.build());
Расписания
Список расписаний
curl -H "X-API-Key: $MOCKARTY_API_TOKEN" \
"$MOCKARTY_URL/api/v1/test-plans/42/schedules"
Создать cron-расписание
cURL
curl -X POST "$MOCKARTY_URL/api/v1/test-plans/42/schedules" \
-H "X-API-Key: $MOCKARTY_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "nightly",
"kind": "cron",
"timezone": "Europe/Moscow",
"payload": {"expr": "0 2 * * *"}
}'
Go
sch, _ := client.TestPlans().AddSchedule(ctx, planID, mockarty.Schedule{
Name: "nightly", Kind: "cron", Timezone: "Europe/Moscow",
Payload: mockarty.SchedulePayload{"expr": "0 2 * * *"},
})
Python
sch = client.test_plans.add_schedule(plan.id, Schedule(
name="nightly", kind="cron", timezone="Europe/Moscow",
payload={"expr": "0 2 * * *"},
))
Java
Schedule sch = client.testPlans().addSchedule(plan.getId(), new Schedule()
.setName("nightly").setKind("cron").setTimezone("Europe/Moscow")
.setPayload(Map.of("expr", "0 2 * * *")));
Одноразовое и интервальное расписание
# Once (сработает 2026-05-01 00:00 UTC и сам отключится)
curl -X POST "$MOCKARTY_URL/api/v1/test-plans/42/schedules" \
-H "X-API-Key: $MOCKARTY_API_TOKEN" -H "Content-Type: application/json" \
-d '{"name":"launch","kind":"once","payload":{"fire_at":"2026-05-01T00:00:00Z"}}'
# Interval (каждые 15 минут)
curl -X POST "$MOCKARTY_URL/api/v1/test-plans/42/schedules" \
-H "X-API-Key: $MOCKARTY_API_TOKEN" -H "Content-Type: application/json" \
-d '{"name":"smoke","kind":"interval","payload":{"every_seconds":900}}'
Обновление / удаление
# Временно отключить
curl -X PATCH "$MOCKARTY_URL/api/v1/test-plans/42/schedules/$SCHED_ID" \
-H "X-API-Key: $MOCKARTY_API_TOKEN" -H "Content-Type: application/json" \
-d '{"enabled": false}'
curl -X DELETE "$MOCKARTY_URL/api/v1/test-plans/42/schedules/$SCHED_ID" \
-H "X-API-Key: $MOCKARTY_API_TOKEN"
Вебхуки
Создать вебхук
cURL
curl -X POST "$MOCKARTY_URL/api/v1/test-plans/42/webhooks" \
-H "X-API-Key: $MOCKARTY_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "ci-slack",
"url": "https://hooks.example.com/mockarty",
"secret": "keep-me-safe",
"events": ["run_finished", "item_failed"],
"retryCount": 3,
"backoffSeconds": 5
}'
Секрет — write-only: сервер возвращает пустую строку при чтении. Ротация — PATCH { "secret": "<new>" }.
Валидация исходящего URL (защита от SSRF): требуется HTTPS, встроенные учётные данные запрещены, запрещены также любые литералы и DNS-адреса из loopback / RFC 1918 / link-local / multicast, а также хосты, совпадающие с localhost, *.internal, *.cluster.local.
Go
wh, _ := client.TestPlans().AddWebhook(ctx, planID, mockarty.Webhook{
Name: "ci-slack",
URL: "https://hooks.example.com/mockarty",
Secret: "keep-me-safe",
Events: []string{"run_finished", "item_failed"},
RetryCount: 3,
BackoffSeconds: 5,
Enabled: true,
})
Python
wh = client.test_plans.add_webhook(plan.id, Webhook(
name="ci-slack",
url="https://hooks.example.com/mockarty",
secret="keep-me-safe",
events=["run_finished", "item_failed"],
retry_count=3, backoff_seconds=5,
))
Java
Webhook wh = client.testPlans().addWebhook(plan.getId(), new Webhook()
.setName("ci-slack")
.setUrl("https://hooks.example.com/mockarty")
.setSecret("keep-me-safe")
.setEvents(List.of("run_finished", "item_failed"))
.setRetryCount(3).setBackoffSeconds(5));
Проверка вебхука (dry-run)
curl -X POST "$MOCKARTY_URL/api/v1/test-plans/42/webhooks/$WH_ID/test" \
-H "X-API-Key: $MOCKARTY_API_TOKEN"
Помещает в очередь синтетический payload run_started, чтобы получатель мог end-to-end проверить подпись и HTTPS-коннект.
Проверка подписи на получателе (Go)
sig := r.Header.Get("X-Mockarty-Signature") // "sha256=<hex>" или чистый hex
ts := r.Header.Get("X-Mockarty-Timestamp") // RFC3339Nano
body, _ := io.ReadAll(r.Body)
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(body)
expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))
if !hmac.Equal([]byte(expected), []byte(sig)) {
http.Error(w, "bad signature", http.StatusUnauthorized)
return
}
t, err := time.Parse(time.RFC3339Nano, ts)
if err != nil || time.Since(t) > 5*time.Minute {
http.Error(w, "stale request", http.StatusUnauthorized)
return
}
Получатель должен отбрасывать всё, что выходит за окно ±5 минут от текущих часов — это защита от replay.
Список, обновление и удаление вебхуков
# Список
curl -H "X-API-Key: $MOCKARTY_API_TOKEN" \
"$MOCKARTY_URL/api/v1/test-plans/42/webhooks"
# Ротация секрета + отключение
curl -X PATCH "$MOCKARTY_URL/api/v1/test-plans/42/webhooks/$WH_ID" \
-H "X-API-Key: $MOCKARTY_API_TOKEN" -H "Content-Type: application/json" \
-d '{"secret":"rotated-secret","enabled":false}'
# Удаление
curl -X DELETE "$MOCKARTY_URL/api/v1/test-plans/42/webhooks/$WH_ID" \
-H "X-API-Key: $MOCKARTY_API_TOKEN"
Справочник ошибок
| HTTP | Значение |
|---|---|
400 |
Плохой JSON, неверный enum, неуникальный order, отсутствует обязательное поле. Тело содержит {"error":"..."}. |
401 |
X-API-Key отсутствует или невалиден. |
403 |
Токен валиден, но у роли нет прав на операцию. |
404 |
Ресурс не найден или принадлежит чужому namespace (без утечки факта существования). |
409 |
Конфликт имени / числового ID. |
412 |
Валидатор If-Match не совпал на PATCH — перечитайте и повторите. |
422 |
Ошибка валидации (например, every_seconds < 10, небезопасный URL вебхука). |
503 |
Оркестратор не подключён на этом admin-узле (ad-hoc, SSE). |
Куда дальше
- Концепции тест-планов — подробное описание каждого примитива.
- Тест-планы в CI/CD — готовые рецепты для GitHub Actions / GitLab CI / Jenkins.
- Справочник API — полный swagger-сгенерированный каталог endpoint-ов.