a5c278adaf
Прогон рекламной папки оставлял в базе 44 записи. Теперь ноль, папка целиком
зелёная — 351 из 351. Полная tests/Feature: 3282 теста, 0 падений, и после неё
в базе не остаётся НИЧЕГО, кроме справочников, которые кладутся при сборке.
Два места прошлая смена считала неизлечимыми — оба вылечились, потому что
диагноз был не перепроверен:
1. CampaignBannerEndpointsTest оставлял 24 записи, больше половины всей грязи.
Он нарочно ловит отказ базы, а после отказа внутри транзакции Postgres
глушит все следующие команды. Лечение — ловить отказ в ОТДЕЛЬНОЙ точке
сохранения: DB::transaction внутри уже открытой ставит savepoint и
откатывает только его. Приёмка вырезанием: убрал уникальный ключ в
миграции — тест покраснел ровно там, где должен; вернул — зелёный.
2. AdWalletUnderRealRoleTest. В промте: «откат противопоказан, он ходит
настоящей ролью базы». Оказалось — не противопоказан, смена роли идёт по
ТОМУ ЖЕ соединению и незакоммиченные записи видны. Тут была реальная
опасность, что откат обезвредит самого сторожа, поэтому вырезание делал
отдельно: убрал у службы установку контекста клиента — оба теста
покраснели. Значит сторож сторожит по-прежнему.
Журнал вебхука оставлял 4 строки: он пишется ВТОРЫМ соединением к базе, куда
обычный откат не дотягивается. Двум файлам добавлена общая связка соединений
(SharesSupplierPdo) — папка вебхука теперь оставляет ноль.
Прежняя оценка исправлена. В промте стояло «~20 файлов оставляют по одной-две
записи». На деле после уборки восьми главных грязь оставляли ДВА файла из всей
папки: 2 и 3 записи, сумма сошлась с наблюдаемыми 5 ровно. «Нет отката» и
«гадит в базу» — разные вещи: папка Autopodbor, где без отката 16 файлов, не
оставляет ни одной записи. Мерить надо остатком, а не поиском по тексту.
Боевой код не тронут: git diff по app/app/ пуст после каждой правки.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
72 lines
3.3 KiB
PHP
72 lines
3.3 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
use App\Models\SystemSetting;
|
||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||
use Tests\Concerns\SharesSupplierPdo;
|
||
|
||
// SharesSupplierPdo — не ради видимости, а ради ОТКАТА: журнал вебхука пишется
|
||
// вторым соединением (pgsql_supplier), и без общего соединения откат его не
|
||
// достаёт — строки журнала оставались в базе после прогона.
|
||
uses(DatabaseTransactions::class, SharesSupplierPdo::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');
|
||
});
|