From b92d9b3bfceb3db66333a73207f5ebfda439a21e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Mon, 25 May 2026 16:29:01 +0300 Subject: [PATCH] test(supplier-webhook): assert JSON 422 for non-JSON Accept clients (failing) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reproduces 302-redirect bug observed on prod 2026-05-25 — when supplier crm.bp-gr.ru POSTs without Accept: application/json, Laravel renders ValidationException as redirect to /, losing body. Test calls webhook without Accept header and asserts JSON 422 response. Will fail until bootstrap/app.php has render(ValidationException) for api/webhook/supplier/*. --- .../SupplierWebhookValidationFormatTest.php | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 app/tests/Feature/Http/Webhook/SupplierWebhookValidationFormatTest.php diff --git a/app/tests/Feature/Http/Webhook/SupplierWebhookValidationFormatTest.php b/app/tests/Feature/Http/Webhook/SupplierWebhookValidationFormatTest.php new file mode 100644 index 00000000..a2486934 --- /dev/null +++ b/app/tests/Feature/Http/Webhook/SupplierWebhookValidationFormatTest.php @@ -0,0 +1,65 @@ +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([ + 'vid' => 1, + 'project' => 'invalid_no_b_prefix', + 'phone' => '79991234567', + 'time' => time(), + ]) + ); + + $response->assertStatus(422); + expect($response->headers->get('Content-Type'))->toContain('application/json'); + $response->assertJsonStructure(['message', 'errors' => ['project']]); +}); + +it('still works correctly for postJson clients (regression)', function () { + $response = $this->postJson('/api/webhook/supplier/test-secret-32chars-aaaaaaaaaaaaaa', [ + 'vid' => 1, + 'project' => 'invalid_no_b_prefix', + 'phone' => '79991234567', + 'time' => time(), + ]); + + $response->assertStatus(422)->assertJsonValidationErrors('project'); +}); + +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'); +});