Files
cf7-spam-to-telegram/tests/MessageFormatterTest.php
T
Vladimir Bryzgalov 2de8f0be57 fix(formatter+telegram): drop tel: inline button, mark all 4xx as permanent
Pilot on washanyanya.ru exposed that Telegram Bot API rejects tel:
URLs in inline_keyboard with HTTP 400 "Wrong port number specified
in the URL". Queue was stuck on 5 items retrying the same failure
4 times each.

- MessageFormatter: always returns reply_markup = null. The phone
  number is already rendered in the '📱 ...' line, and mobile
  Telegram clients linkify it automatically.
- TelegramClient::is_permanent: treat all 4xx except 429 as permanent
  client-side errors, so future payload-shape bugs surface in the
  dashboard widget immediately instead of burning five retry cycles.
- MessageFormatterTest updated: the old test asserting tel: in
  reply_markup is replaced with one asserting reply_markup is null
  and the phone still appears in the text body.
- Spec §9 + §19 updated, §19's open question closed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 23:55:06 +05:00

155 lines
6.5 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace Cf7stg\Tests;
use Cf7stg\MessageFormatter;
use Cf7stg\Classifier;
use PHPUnit\Framework\TestCase;
final class MessageFormatterTest extends TestCase
{
public function test_full_client_payload(): void
{
$fields = [
'your-name' => 'Анна Петровна',
'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' => '<script>alert(1)</script>', '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('<script>', $r['text']);
self::assertStringContainsString('&lt;script&gt;', $r['text']);
}
public function test_long_message_is_truncated(): void
{
$long = str_repeat('А', 5000);
$fields = ['your-message' => $long, '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::assertLessThanOrEqual(4096, mb_strlen($r['text']));
self::assertStringContainsString('…[обрезано]', $r['text']);
}
public function test_retro_prefix_added(): void
{
$fields = ['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' => true,
]);
self::assertStringContainsString('📂 Ретроспектива', $r['text']);
}
public function test_spam_label_emoji(): void
{
$fields = [
'your-name' => 'Melissa',
'your-message' => 'SEO promotion, visit http://spam.net',
];
$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::assertStringContainsString('🔴 похоже спам', $r['text']);
}
public function test_unclear_label_emoji(): void
{
$fields = ['your-name' => 'Олег', 'your-message' => 'Здравствуйте'];
$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::assertStringContainsString('🟡 неясно', $r['text']);
}
}