'Анна Петровна', 'your-tel' => '+79031234567', 'your-email' => 'anna@mail.ru', 'your-message' => 'Нужна сиделка для бабушки', ]; $classification = Classifier::classify($fields, ['reason' => 'Akismet']); $result = MessageFormatter::build([ 'fields' => $fields, 'classification' => $classification, 'site_title' => 'вашаняня.рф', 'form_title' => 'Обратная связь', 'submitted_at' => '17.04.2026 19:45', 'reason' => 'Akismet', 'ip' => '185.15.56.12', 'user_agent' => 'Mozilla/5.0', 'is_retro' => false, ]); self::assertIsArray($result); self::assertArrayHasKey('text', $result); self::assertArrayHasKey('reply_markup', $result); self::assertStringContainsString('🟢 похоже клиент', $result['text']); self::assertStringContainsString('вашаняня.рф', $result['text']); self::assertStringContainsString('Анна Петровна', $result['text']); self::assertStringContainsString('+7 (903) 123-45-67', $result['text']); self::assertStringContainsString('anna@mail.ru', $result['text']); self::assertStringContainsString('Akismet', $result['text']); } public function test_reply_markup_is_null_because_tg_rejects_tel_urls(): void { // Telegram Bot API rejects tel: URLs in inline_keyboard with // HTTP 400 "Wrong port number specified in the URL". We no longer // emit any inline button; the phone number is rendered in the // message body instead and mobile TG clients auto-linkify it. $fields = ['your-tel' => '+79031234567', 'your-name' => 'Анна']; $classification = Classifier::classify($fields, ['reason' => 'Akismet']); $r = MessageFormatter::build([ 'fields' => $fields, 'classification' => $classification, 'site_title' => 's.ru', 'form_title' => 'f', 'submitted_at' => '17.04.2026', 'reason' => 'Akismet', 'ip' => '', 'user_agent' => '', 'is_retro' => false, ]); self::assertNull($r['reply_markup']); // Phone must still be present in the text, formatted as Russian pretty. self::assertStringContainsString('+7 (903) 123-45-67', $r['text']); } public function test_no_tel_button_when_no_phone(): void { $fields = ['your-email' => 'a@b.com']; $classification = Classifier::classify($fields, ['reason' => 'Akismet']); $r = MessageFormatter::build([ 'fields' => $fields, 'classification' => $classification, 'site_title' => 's.ru', 'form_title' => 'f', 'submitted_at' => '17.04.2026', 'reason' => 'Akismet', 'ip' => '', 'user_agent' => '', 'is_retro' => false, ]); self::assertNull($r['reply_markup']); } public function test_html_injection_in_name_is_escaped(): void { $fields = ['your-name' => '', 'your-tel' => '+79031234567']; $classification = Classifier::classify($fields, ['reason' => 'Akismet']); $r = MessageFormatter::build([ 'fields' => $fields, 'classification' => $classification, 'site_title' => 's.ru', 'form_title' => 'f', 'submitted_at' => '17.04.2026', 'reason' => 'Akismet', 'ip' => '', 'user_agent' => '', 'is_retro' => false, ]); self::assertStringNotContainsString('