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'); });