diff --git a/app/Exceptions/TelegramException.php b/app/Exceptions/TelegramException.php new file mode 100644 index 000000000..dd8ef20d9 --- /dev/null +++ b/app/Exceptions/TelegramException.php @@ -0,0 +1,11 @@ +update(['processed' => true]); if (!App::runningUnitTests() && TelegramService::isAdminActive()) { - TelegramService::admin()->sendMessage( - strtr("Event suggestion accepted" . PHP_EOL . - "Title: :name" . PHP_EOL - . "Accepting user: :username" . PHP_EOL, [ - ':name' => $eventSuggestion->name, - ':username' => auth()->user()->username, - ]) - ); + try { + TelegramService::admin()->sendMessage( + strtr("Event suggestion accepted" . PHP_EOL . + "Title: :name" . PHP_EOL + . "Accepting user: :username" . PHP_EOL, [ + ':name' => $eventSuggestion->name, + ':username' => auth()->user()->username, + ]) + ); + } catch (TelegramException $exception) { + report($exception); + } } $eventSuggestion->user->notify(new EventSuggestionProcessed($eventSuggestion, $event)); diff --git a/app/Observers/ReportObserver.php b/app/Observers/ReportObserver.php index 27a507a03..16abea825 100644 --- a/app/Observers/ReportObserver.php +++ b/app/Observers/ReportObserver.php @@ -5,6 +5,7 @@ namespace App\Observers; use App\Enum\Report\ReportStatus; +use App\Exceptions\TelegramException; use App\Models\Report; use App\Services\TelegramService; use Illuminate\Support\Facades\App; @@ -15,11 +16,15 @@ public function created(Report $report): void { if (App::runningUnitTests() || !TelegramService::isAdminActive()) { return; } - $telegramMessageId = TelegramService::admin()->sendMessage("🚨 New Report for " . $report->subject_type . "" . PHP_EOL - . "Reason: " . $report->reason?->value . PHP_EOL - . "Description: " . ($report->description ?? 'None') . PHP_EOL - . "View Report: " . config('app.url') . "/admin/reports/" . $report->id . PHP_EOL); - $report->update(['admin_notification_id' => $telegramMessageId]); + try { + $telegramMessageId = TelegramService::admin()->sendMessage("🚨 New Report for " . $report->subject_type . "" . PHP_EOL + . "Reason: " . $report->reason?->value . PHP_EOL + . "Description: " . ($report->description ?? 'None') . PHP_EOL + . "View Report: " . config('app.url') . "/admin/reports/" . $report->id . PHP_EOL); + $report->update(['admin_notification_id' => $telegramMessageId]); + } catch (TelegramException $exception) { + report($exception); + } } public function updated(Report $report): void { diff --git a/app/Services/TelegramService.php b/app/Services/TelegramService.php index 5c9cbd5f5..0e3f9ba16 100644 --- a/app/Services/TelegramService.php +++ b/app/Services/TelegramService.php @@ -2,15 +2,16 @@ namespace App\Services; +use App\Exceptions\TelegramException; use Illuminate\Support\Facades\Http; -class TelegramService +class TelegramService { const TELEGRAM_API_URL = 'https://api.telegram.org/bot'; - private string $chatId; - private string $token; + public readonly string $chatId; + private readonly string $token; public function __construct(string $chatId, string $token) { $this->chatId = $chatId; @@ -26,7 +27,11 @@ public static function admin(): self { } /** + * @param string $text + * @param string $parseMode + * * @return int Telegram Message ID + * @throws TelegramException */ public function sendMessage(string $text, string $parseMode = 'HTML'): int { $response = Http::post(self::TELEGRAM_API_URL . $this->token . '/sendMessage', [ @@ -34,13 +39,17 @@ public function sendMessage(string $text, string $parseMode = 'HTML'): int { 'text' => $text, 'parse_mode' => $parseMode, ]); + if (!$response->ok()) { + throw new TelegramException('Telegram API error: ' . $response->body()); + } return $response->json('result.message_id'); } - public function deleteMessage(int $messageId): void { - Http::post(self::TELEGRAM_API_URL . $this->token . '/deleteMessage', [ + public function deleteMessage(int $messageId): bool { + $response = Http::post(self::TELEGRAM_API_URL . $this->token . '/deleteMessage', [ 'chat_id' => $this->chatId, 'message_id' => $messageId, ]); + return $response->ok(); } } diff --git a/tests/Feature/TelegramServiceTest.php b/tests/Feature/TelegramServiceTest.php new file mode 100644 index 000000000..33c04b2f1 --- /dev/null +++ b/tests/Feature/TelegramServiceTest.php @@ -0,0 +1,54 @@ + Http::response(['result' => ['message_id' => 123]])]); + $telegramService = new TelegramService(self::CHAT_ID, self::TOKEN); + $messageId = $telegramService->sendMessage('Hello, World!'); + $this->assertIsInt($messageId); + } + + public function testSendTelegramMessageWithWrongCredentials(): void { + Http::fake([ + 'https://api.telegram.org/bot' . self::TOKEN . '/sendMessage' => Http::response( + body: [ + 'ok' => false, + 'error_code' => 401, + 'description' => 'Unauthorized', + ], + status: 401 + ) + ]); + $telegramService = new TelegramService(self::CHAT_ID, self::TOKEN); + $this->expectException(TelegramException::class); + $telegramService->sendMessage('Hello, World!'); + } + + public function testDeleteTelegramMessage(): void { + Http::fake(['https://api.telegram.org/bot' . self::TOKEN . '/deleteMessage' => Http::response()]); + $telegramService = new TelegramService(self::CHAT_ID, self::TOKEN); + $this->assertTrue($telegramService->deleteMessage(123)); + } + + public function testAdminChatSelector(): void { + config(['services.telegram.admin.active' => true]); + $this->assertTrue(TelegramService::isAdminActive()); + + config(['services.telegram.admin.chat_id' => self::CHAT_ID]); + config(['services.telegram.admin.token' => self::TOKEN]); + $telegramService = TelegramService::admin(); + $this->assertEquals(self::CHAT_ID, $telegramService->chatId); + } +} diff --git a/tests/FeatureTestCase.php b/tests/FeatureTestCase.php index c71a08444..3d7f12203 100644 --- a/tests/FeatureTestCase.php +++ b/tests/FeatureTestCase.php @@ -136,6 +136,12 @@ abstract class FeatureTestCase extends BaseTestCase protected function setUp(): void { parent::setUp(); + + if (!in_array('RefreshDatabase', class_uses($this), true)) { + //if class doesn't use RefreshDatabase trait, skip the migration and seeding + return; + } + $this->artisan('db:seed --class=Database\\\\Seeders\\\\Constants\\\\PermissionSeeder'); $this->artisan('db:seed --class=Database\\\\Seeders\\\\PrivacyAgreementSeeder'); } @@ -171,7 +177,7 @@ public function createOAuthClient(User $user, bool $confidential): OAuthClient { } public function createWebhook(User $user, OAuthClient $client, array $events): Webhook { - $events = array_map(function ($event) { + $events = array_map(function($event) { return $event->value; }, $events); $request = WebhookController::createWebhookRequest($user, $client, 'stub', "https://example.com", $events);