Перейти до вмісту

Мок-сервер та локальне тестування

Тестування інтеграцій часто є однією з найскладніших частин побудови сучасних вебдодатків. Зовнішні пісочниці (наприклад, Stripe, Razorpay, Braintree, Paytm) можуть працювати повільно, мати ліміти на кількість запитів або їх важко примусово перевести в специфічні стани помилок (такі як 503 Service Unavailable або висока затримка мережі).

Для вирішення цієї проблеми MirApi Gateway надає виділене високопродуктивне середовище для тестування та мокінгу відповідей за адресою mock.mirapi.io. Це дозволяє безпечно будувати стійкі інтеграції та перевіряти поведінку системи за будь-яких умов.


Мок-сервер MirApi (https://mock.mirapi.io) призначений для імітації відповідей від сторонніх платіжних провайдерів та інших API без використання реальних API-ключів, мерчант-акаунтів або вичерпання лімітів на кількість запитів.

  • Імітація успішних та помилкових відповідей: Перевірте, як ваш додаток реагує, коли зовнішній API повертає статуси помилок, такі як HTTP 500 Internal Server Error, 502 Bad Gateway або 503 Service Unavailable.
  • Штучні затримки (параметр delay або заголовки): Додавайте затримки запитів для перевірки таймаутів вашого HTTP-клієнта, роботи петель повторних спроб (retry loops) та автоматичних вимикачів (circuit breakers).
  • Групи авторизації: Мок-сервер підтримує поділ на тестові ключі. Ви можете використовувати ключі SuperKey1, SuperKey2, SuperKey3 для розділення стану або тестування багатоорендних (multi-tenant) конфігурацій.

Усі ендпоінти мок-сервера задокументовані за стандартом OpenAPI (Swagger). Це дозволяє переглядати доступні ресурси, очікувані параметри та структури відповідей в інтерактивному режимі.

  • 📄 Пряме посилання на специфікацію: https://mock.mirapi.io/openapi.yaml
  • 🛠️ Інтерактивний Swagger Editor: Відкрийте https://editor.swagger.io та імпортуйте специфікацію за вказаним вище посиланням для швидкого тестування запитів прямо з браузера.

3. Тестування асинхронних вебхуків (Request Bin)

Section titled “3. Тестування асинхронних вебхуків (Request Bin)”

Для локального тестування асинхронних вебхуків (X-Webhook-Callback) зазвичай доводиться використовувати Ngrok для прокидання локальних портів або відправляти дані на публічні сервіси на кшталт webhook.site, що може призвести до витоку конфіденційних тестових токенів.

MirApi пропонує приватний безстейтовий утилітарний Request Bin для перехоплення, аналізу та очищення вебхуків під час локальної розробки та автоматизованого тестування (CI/CD).

Робота з ендпоінтами Request Bin:

Section titled “Робота з ендпоінтами Request Bin:”
  1. POST /webhooks/incoming?id=your-test-id
    Приймає та зберігає будь-які вебхуки від проксі-сервера. Використовуйте унікальний ідентифікатор your-test-id для уникнення колізій з іншими розробниками.
  2. GET /webhooks/inspect?id=your-test-id
    Повертає список усіх отриманих вебхуків для вказаного ідентифікатора у хронологічному порядку із заголовками та тілом запитів.
  3. POST /webhooks/clear?id=your-test-id
    Очищає збережені вебхуки з пам’яті сервера для вказаного ідентифікатора.

4. Приклад інтеграції редіректу на PHP

Section titled “4. Приклад інтеграції редіректу на PHP”

Функція автоматичного редіректу (X-Extract-Redirect) дозволяє проксі-серверу витягти URL-адресу для оплати (Checkout URL) із JSON-відповіді стороннього провайдера за допомогою JSONPath і відразу перенаправити туди браузер клієнта з кодом 302 Found. Це позбавляє необхідності писати код парсингу та редіректу на вашому бекенді.

Нижче наведено основну cURL-логіку інтеграції на PHP (перші 50 рядків). Якщо вам потрібен повний готовий до запуску веб-скрипт із графічним інтерфейсом та обробкою помилок, ви можете розгорнути блок нижче.

<?php
/**
* MirApi Proxy Gateway - Interactive PHP Redirect Extraction Example (Core Logic)
*
* Цей фрагмент показує основні cURL-заголовки та обробку 302/301 редіректу від проксі-шлюзу.
*/
// Перевіряємо чи активовано дію редіректу (GET parameter action=redirect)
if (isset($_GET['action']) && $_GET['action'] === 'redirect') {
$gatewayUrl = 'https://proxy.mirapi.io/'; // Адреса проксі-шлюзу
$apiKey = 'la_************************************************'; // Маскований API Key (замініть на свій X-MirApi-Key)
$mockTargetUrl = 'https://mock.mirapi.io/echo/get/json'; // Цільовий мок-ендпоінт
$ch = curl_init($gatewayUrl);
$headers = [
'X-MirApi-Key: ' . $apiKey,
'X-Target-URL: ' . $mockTargetUrl,
// Вказуємо проксі витягти $.urlResponse з мок-відповіді ("https://google.com") та повернути 302 redirect
'X-Extract-Redirect: $.urlResponse',
'Content-Type: application/json'
];
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_HEADER => true, // Повертати заголовки для парсингу Location
CURLOPT_FOLLOWLOCATION => false, // Не слідувати за редіректом автоматично
CURLOPT_TIMEOUT => 10,
]);
$response = curl_exec($ch);
if ($response === false) {
die('cURL error: ' . curl_error($ch));
}
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$redirectUrl = '';
// Парсимо заголовок Location з відповіді проксі
if ($httpCode === 302 || $httpCode === 301) {
$lines = explode("\r\n", $response);
foreach ($lines as $line) {
if (stripos($line, 'Location:') === 0) {
$redirectUrl = trim(substr($line, 9));
break;
}
}
}
// Виконуємо редірект у браузері користувача
if (!empty($redirectUrl)) {
header("Location: " . $redirectUrl);
exit;
}
}
?>
Розгорнути повний PHP скрипт (з HTML UI та сторінкою помилки)
<?php
/**
* MirApi Proxy Gateway - Interactive PHP Redirect Extraction Example
*
* This script combines a beautiful user interface with PHP cURL logic to test redirect extraction.
* You can open this file directly in your browser and click the button to run the test.
*/
// Check if the redirect action is triggered (GET parameter action=redirect)
if (isset($_GET['action']) && $_GET['action'] === 'redirect') {
$gatewayUrl = 'https://proxy.mirapi.io/'; // Proxy Gateway address
$apiKey = 'la_************************************************'; // Masked API Key (replace with your X-MirApi-Key)
$mockTargetUrl = 'https://mock.mirapi.io/echo/get/json'; // Target mock endpoint
$ch = curl_init($gatewayUrl);
$headers = [
'X-MirApi-Key: ' . $apiKey,
'X-Target-URL: ' . $mockTargetUrl,
// Instruct the proxy to extract $.urlResponse from mock response (which is "https://google.com") and return a 302 redirect
'X-Extract-Redirect: $.urlResponse',
'Content-Type: application/json'
];
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_HEADER => true, // Return headers in response to parse Location
CURLOPT_FOLLOWLOCATION => false, // Do not automatically follow redirects
CURLOPT_TIMEOUT => 10,
]);
$response = curl_exec($ch);
if ($response === false) {
die('cURL error: ' . curl_error($ch));
}
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$redirectUrl = '';
// Parse the Location header from proxy response
if ($httpCode === 302 || $httpCode === 301) {
$lines = explode("\r\n", $response);
foreach ($lines as $line) {
if (stripos($line, 'Location:') === 0) {
$redirectUrl = trim(substr($line, 9));
break;
}
}
}
// Perform final browser redirect if redirect URL was found
if (!empty($redirectUrl)) {
header("Location: " . $redirectUrl);
exit;
} else {
// Detailed error report page
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Redirect Error</title>
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
body { font-family: 'Plus Jakarta Sans', sans-serif; background: #020617; color: #f1f5f9; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; padding: 20px; }
.card { background: rgba(30, 41, 59, 0.7); backdrop-filter: blur(16px); padding: 2.5rem; border-radius: 20px; max-width: 600px; border: 1px solid rgba(255, 255, 255, 0.08); box-shadow: 0 10px 30px -10px rgba(0,0,0,0.5); }
h1 { color: #f43f5e; font-size: 1.5rem; margin-top: 0; }
pre { background: #090d16; padding: 1.2rem; border-radius: 12px; overflow-x: auto; color: #38bdf8; font-family: monospace; font-size: 0.85rem; border: 1px solid rgba(255,255,255,0.05); }
.btn { display: inline-block; background: #3b82f6; color: white; padding: 0.8rem 1.8rem; border-radius: 10px; text-decoration: none; font-weight: 600; margin-top: 1rem; transition: background 0.2s; border: none; cursor: pointer; }
.btn:hover { background: #2563eb; }
</style>
</head>
<body>
<div class="card">
<h1>Redirect Extraction Failed</h1>
<p>Proxy returned status <strong><?php echo $httpCode; ?></strong> instead of the expected 302 Found.</p>
<p>This may indicate that the proxy server has not been updated to the latest binary version, or the target response could not be parsed successfully.</p>
<pre><?php echo htmlspecialchars($response); ?></pre>
<a href="php_redirect_example.php" class="btn">Back to Test</a>
</div>
</body>
</html>
<?php
exit;
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MirApi PHP Redirect Test</title>
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
:root {
--bg-gradient: linear-gradient(135deg, #0f172a 0%, #020617 100%);
--panel-bg: rgba(30, 41, 59, 0.65);
--panel-border: rgba(255, 255, 255, 0.08);
--text-primary: #f8fafc;
--text-secondary: #94a3b8;
--primary: #3b82f6;
--primary-hover: #2563eb;
--shadow: 0 20px 40px -15px rgba(0, 0, 0, 0.6);
}
body {
font-family: 'Plus Jakarta Sans', sans-serif;
background: var(--bg-gradient);
color: var(--text-primary);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
margin: 0;
padding: 20px;
box-sizing: border-box;
}
.container {
background: var(--panel-bg);
backdrop-filter: blur(20px);
border: 1px solid var(--panel-border);
padding: 3.5rem 2.5rem;
border-radius: 24px;
max-width: 480px;
width: 100%;
text-align: center;
box-shadow: var(--shadow);
animation: fadeIn 0.5s ease-out;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.icon {
font-size: 3.5rem;
margin-bottom: 1.5rem;
display: inline-block;
animation: pulse 2.5s infinite;
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.08); }
100% { transform: scale(1); }
}
h1 {
font-size: 1.8rem;
font-weight: 700;
margin: 0 0 1rem 0;
background: linear-gradient(to right, #60a5fa, #a78bfa);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
p {
color: var(--text-secondary);
font-size: 0.95rem;
line-height: 1.6;
margin: 0 0 2.2rem 0;
}
.card-details {
background: rgba(15, 23, 42, 0.45);
border: 1px solid rgba(255, 255, 255, 0.05);
border-radius: 14px;
padding: 1.2rem;
margin-bottom: 2.2rem;
text-align: left;
font-size: 0.85rem;
}
.detail-row {
display: flex;
justify-content: space-between;
margin-bottom: 0.6rem;
}
.detail-row:last-child {
margin-bottom: 0;
}
.detail-label {
color: var(--text-secondary);
}
.detail-value {
font-family: monospace;
color: #38bdf8;
font-weight: 500;
}
.btn-action {
display: block;
width: 100%;
background: var(--primary);
color: white;
padding: 1.1rem 2rem;
border-radius: 12px;
text-decoration: none;
font-weight: 600;
font-size: 1rem;
border: none;
cursor: pointer;
box-shadow: 0 4px 18px 0 rgba(59, 130, 246, 0.35);
transition: all 0.25s ease-in-out;
box-sizing: border-box;
}
.btn-action:hover {
background: var(--primary-hover);
transform: translateY(-2px);
box-shadow: 0 6px 24px 0 rgba(59, 130, 246, 0.45);
}
.btn-action:active {
transform: translateY(0);
}
.footer-note {
margin-top: 1.8rem;
font-size: 0.75rem;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.05em;
}
</style>
</head>
<body>
<div class="container">
<span class="icon"></span>
<h1>MirApi Redirect Flow</h1>
<p>Click the button below to initiate the test. The PHP backend will send a request to the proxy, which will extract the <strong>google.com</strong> link from the mock response and instantly redirect your browser there.</p>
<div class="card-details">
<div class="detail-row">
<span class="detail-label">Proxy Gateway:</span>
<span class="detail-value">proxy.mirapi.io</span>
</div>
<div class="detail-row">
<span class="detail-label">Target Upstream:</span>
<span class="detail-value">mock.mirapi.io/...</span>
</div>
<div class="detail-row">
<span class="detail-label">JSONPath Header:</span>
<span class="detail-value">$.urlResponse</span>
</div>
</div>
<a href="?action=redirect" class="btn-action">Execute Redirect</a>
<div class="footer-note">
Demonstration of X-Extract-Redirect Header
</div>
</div>
</body>
</html>

5. Демонстрація Body Mapping (Правила Дашборду)

Section titled “5. Демонстрація Body Mapping (Правила Дашборду)”

Ви можете автоматично перейменовувати та реструктурувати поля у тілі запитів (request payloads) на рівні проксі, що позбавляє від необхідності писати класи-адаптери у ваших сервісах.

Налаштування в Дашборді:

Section titled “Налаштування в Дашборді:”
  1. Зайдіть у редагування вашого маршруту CascadeURL на дашборді MirApi.
  2. Для цілі (Target), яка веде на https://mock.mirapi.io/auth2/sameresponse або auth2/post, встановіть у полі Body Map наступне правило:
    data.amount=>data.sum, action=>event_type

Надішліть запит через cURL (замініть API-ключ на ваш власний X-MirApi-Key):

Terminal window
curl --location 'https://proxy.mirapi.io/' \
--header 'X-MirApi-Key: la_************************************************' \
--header 'X-Route-Key: CascadeURL' \
--header 'Content-Type: application/json' \
--data '{
"action": "test-webhook",
"data": {
"amount": 10222
}
}'

Оскільки мок-сервер робить ехо-відповідь, ви отримаєте JSON, в якому ключі були автоматично перейменовані проксі-сервером перед відправкою на мок-сервер:

{
"event_type": "test-webhook",
"data": {
"sum": 10222
}
}

6. Перевірка Extract JSONPath Map (Трансформація відповіді)

Section titled “6. Перевірка Extract JSONPath Map (Трансформація відповіді)”

Цю функцію можна протестувати динамічно за допомогою HTTP-заголовків запиту:

  • X-Extract-Map — мапить обрані JSONPath-поля у нову структуру відповіді.
  • X-Extract-Redirect — витягує одне поле (наприклад, посилання) і повертає статус 302 Found (Redirect) клієнту.

Для тесту використаємо мок-ендпоінт GET https://mock.mirapi.io/echo/get/json, який повертає таку структуру:

{
"success": "true",
"message": "reqres standard echo response",
"urlResponse": "https://google.com",
"amount": "100"
}

Тест 1: Зміна структури відповіді за допомогою X-Extract-Map

Section titled “Тест 1: Зміна структури відповіді за допомогою X-Extract-Map”

Ми хочемо дістати лише суму та повідомлення і перейменувати їх у total та info.

Terminal window
curl -i --location 'https://proxy.mirapi.io/' \
--header 'X-MirApi-Key: la_************************************************' \
--header 'X-Target-URL: https://mock.mirapi.io/echo/get/json' \
--header 'X-Extract-Map: $.amount=>total, $.message=>info'

Проксі перехопить оригінальну відповідь мок-сервера, відфільтрує її за вашими правилами та поверне новий JSON:

{
"total": "100",
"info": "reqres standard echo response"
}

Тест 2: Автоматичний редірект за допомогою X-Extract-Redirect

Section titled “Тест 2: Автоматичний редірект за допомогою X-Extract-Redirect”

Ми хочемо витягти поле urlResponse (значення якого https://google.com) і змусити проксі зробити автоматичний редірект клієнта туди.

(Використовуйте curl без прапорця --location, щоб побачити статус редіректу):

Terminal window
curl -i 'https://proxy.mirapi.io/' \
--header 'X-MirApi-Key: la_************************************************' \
--header 'X-Target-URL: https://mock.mirapi.io/echo/get/json' \
--header 'X-Extract-Redirect: $.urlResponse'

Проксі поверне HTTP-статус 302 Found із заголовком редіректу на Google:

HTTP/1.1 302 Found
Location: https://google.com
Content-Length: 0