a49916b7fc
Accessibility (Pa11y live) / a11y (push) Has been cancelled
Набор полностью зелёный (55 to 0; 1713 pass + 4 skip). Всё тест-сторона: - AuditChainRaceConditionTest: advisory-lock в audit_chain_hash РЕАЛЬНО присутствует (миграция 2026_05_30 применяется) — падало наблюдение: bind-параметр в SQL-сдвиге (? >> 32) не сдвигал → classid не совпадал. Декомпозицию ключа считаем в PHP. NB: db/schema.sql хранит функцию БЕЗ блокировки (минорный дрейф канона; прод через миграцию защищён) — стоит перегенерить schema.sql отдельно. - SupplierConnectionTest WARN#2: matchEligibleProjects ограничен cap=LeadDistributor::CAP=3; ждать 3 из 6 видимых тенантов (кросс-tenant видимость под BYPASSRLS; при RLS было бы 0). - SupplierWebhookTest + ValidationFormatTest: фаза 3 намеренно приняла проект без B-префикса как DIRECT (не теряем заявки) — тесты под новый контракт (202 / 422 по vid). - SupplierPortalClientTest: fake-паттерн под старый URL /admin/rt-projects-load; клиент зовёт /admin/visit/rt-projects-load — обновлён паттерн. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
68 lines
2.9 KiB
PHP
68 lines
2.9 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
use App\Models\SystemSetting;
|
||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||
|
||
uses(DatabaseTransactions::class);
|
||
|
||
beforeEach(function () {
|
||
SystemSetting::query()
|
||
->where('key', 'supplier_webhook_secret')
|
||
->update(['value' => 'test-secret-32chars-aaaaaaaaaaaaaa']);
|
||
SystemSetting::query()
|
||
->where('key', 'supplier_ip_allowlist')
|
||
->update(['value' => '[]']);
|
||
});
|
||
|
||
it('returns 422 JSON when supplier posts invalid payload WITHOUT Accept: application/json header', function () {
|
||
// Воспроизводит реальное поведение crm.bp-gr.ru: POST без Accept-JSON.
|
||
// До фикса (302→422) Laravel редиректил на / с Set-Cookie, поставщик
|
||
// терял тело запроса. После фикса всегда JSON.
|
||
$response = $this->call(
|
||
'POST',
|
||
'/api/webhook/supplier/test-secret-32chars-aaaaaaaaaaaaaa',
|
||
[], // params
|
||
[], // cookies
|
||
[], // files
|
||
['HTTP_CONTENT_TYPE' => 'application/x-www-form-urlencoded'], // server: НЕТ Accept JSON
|
||
http_build_query([
|
||
// Фаза 3 сняла валидацию формата project (non-B → DIRECT), поэтому
|
||
// для проверки JSON-рендера 422 используем всё ещё обязательное поле:
|
||
// пропускаем vid → гарантированный 422.
|
||
'project' => 'B1_test.ru',
|
||
'phone' => '79991234567',
|
||
'time' => time(),
|
||
])
|
||
);
|
||
|
||
$response->assertStatus(422);
|
||
expect($response->headers->get('Content-Type'))->toContain('application/json');
|
||
$response->assertJsonStructure(['message', 'errors' => ['vid']]);
|
||
});
|
||
|
||
it('still works correctly for postJson clients (regression)', function () {
|
||
$response = $this->postJson('/api/webhook/supplier/test-secret-32chars-aaaaaaaaaaaaaa', [
|
||
// vid пропущен намеренно → 422 (project-формат больше не валидируется, фаза 3).
|
||
'project' => 'B1_test.ru',
|
||
'phone' => '79991234567',
|
||
'time' => time(),
|
||
]);
|
||
|
||
$response->assertStatus(422)->assertJsonValidationErrors('vid');
|
||
});
|
||
|
||
it('non-webhook routes still use default render (no JSON forced)', function () {
|
||
// Регрессионный тест: дефолтный render остальных routes не сломан
|
||
// (например /login — должен возвращать redirect, а не JSON).
|
||
$response = $this->call(
|
||
'POST',
|
||
'/login',
|
||
['email' => 'bad', 'password' => ''],
|
||
[], [], [],
|
||
);
|
||
// Любой не-200 кроме 422-JSON допустим — главное чтобы наш fix не перехватил
|
||
expect($response->headers->get('Content-Type'))->not->toContain('application/json');
|
||
});
|