where('key', 'supplier_webhook_secret')->update(['value' => 'test-secret-32chars-aaaaaaaaaaaaaa']); SystemSetting::query()->where('key', 'supplier_ip_allowlist')->update(['value' => '[]']); }); it('returns 404 for invalid secret', function () { $response = $this->postJson('/api/webhook/supplier/wrong-secret', [ 'vid' => 1, 'project' => 'B1_test.ru', 'phone' => '79991234567', 'time' => time(), ]); $response->assertStatus(404); }); it('returns 404 if IP not in allowlist (when allowlist non-empty)', function () { SystemSetting::query()->where('key', 'supplier_ip_allowlist') ->update(['value' => '["1.2.3.4", "10.0.0.0/24"]']); $response = $this->withServerVariables(['REMOTE_ADDR' => '5.6.7.8']) ->postJson('/api/webhook/supplier/test-secret-32chars-aaaaaaaaaaaaaa', [ 'vid' => 1, 'project' => 'B1_test.ru', 'phone' => '79991234567', 'time' => time(), ]); $response->assertStatus(404); }); it('passes IP allowlist when IP matches CIDR', function () { SystemSetting::query()->where('key', 'supplier_ip_allowlist') ->update(['value' => '["10.0.0.0/24"]']); Bus::fake(); $response = $this->withServerVariables(['REMOTE_ADDR' => '10.0.0.50']) ->postJson('/api/webhook/supplier/test-secret-32chars-aaaaaaaaaaaaaa', [ 'vid' => 1, 'project' => 'B1_test.ru', 'phone' => '79991234567', 'time' => time(), ]); $response->assertStatus(202); }); it('inserts supplier_lead row + dispatches RouteSupplierLeadJob', function () { Bus::fake(); $response = $this->postJson('/api/webhook/supplier/test-secret-32chars-aaaaaaaaaaaaaa', [ 'vid' => 432176649, 'project' => 'B1_vashinvestor.ru', 'tag' => 'Ваш инвестор', 'phone' => '79991234567', 'phones' => ['79991234567'], 'time' => 1703781939, ]); $response->assertStatus(202); expect(SupplierLead::where('vid', 432176649)->exists())->toBeTrue(); Bus::assertDispatched(RouteSupplierLeadJob::class); }); it('returns 200 OK on duplicate vid (idempotency)', function () { SupplierLead::factory()->create(['vid' => 12345]); Bus::fake(); $response = $this->postJson('/api/webhook/supplier/test-secret-32chars-aaaaaaaaaaaaaa', [ 'vid' => 12345, 'project' => 'B1_test.ru', 'phone' => '79991234567', 'time' => time(), ]); $response->assertStatus(200); expect(SupplierLead::where('vid', 12345)->count())->toBe(1); Bus::assertNotDispatched(RouteSupplierLeadJob::class); }); it('rejects invalid payload (missing vid) with 422', function () { $response = $this->postJson('/api/webhook/supplier/test-secret-32chars-aaaaaaaaaaaaaa', [ 'project' => 'B1_test.ru', 'phone' => '79991234567', 'time' => time(), ]); $response->assertStatus(422)->assertJsonValidationErrors('vid'); }); it('rejects invalid phone format (not 7XXXXXXXXXX) with 422', function () { $response = $this->postJson('/api/webhook/supplier/test-secret-32chars-aaaaaaaaaaaaaa', [ 'vid' => 1, 'project' => 'B1_test.ru', 'phone' => '89991234567', 'time' => time(), ]); $response->assertStatus(422); }); it('rejects invalid project format (no B[123]_ prefix) with 422', function () { $response = $this->postJson('/api/webhook/supplier/test-secret-32chars-aaaaaaaaaaaaaa', [ 'vid' => 1, 'project' => 'invalid_format', 'phone' => '79991234567', 'time' => time(), ]); $response->assertStatus(422); });