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