trypost/tests/Feature/Social/TelegramConnectionTest.php
Paulo Castellano a4cf8aa4ce Add Telegram connection flow (controller + webhook)
Connect a channel by issuing a one-time code the user posts as /connect <code>
in their channel. A secret-token-guarded webhook matches the code, links the
channel as a SocialAccount (chat_id in meta), and records it on the request so
the connect endpoint can poll for completion. Adds the TelegramConnectRequest
model + migration, the connect/status endpoints, the public webhook route (CSRF
exempt), a ConnectionVerifier branch (getChat liveness), and a telegram:set-webhook
command. Tests cover the code issue, webhook link, secret rejection, expired/
unknown codes, status polling, and the command.
2026-06-13 21:39:03 -03:00

143 lines
4.9 KiB
PHP

<?php
declare(strict_types=1);
use App\Enums\SocialAccount\Platform;
use App\Enums\UserWorkspace\Role;
use App\Models\SocialAccount;
use App\Models\TelegramConnectRequest;
use App\Models\User;
use App\Models\Workspace;
use Illuminate\Support\Facades\Http;
beforeEach(function () {
config([
'trypost.platforms.telegram.bot_token' => 'TESTTOKEN',
'trypost.platforms.telegram.bot_username' => 'TryPostBot',
'trypost.platforms.telegram.webhook_secret' => 'shh-secret',
]);
$this->workspace = Workspace::factory()->create();
$this->user = User::factory()->create([
'current_workspace_id' => $this->workspace->id,
'account_id' => $this->workspace->account_id,
]);
$this->workspace->members()->attach($this->user->id, ['role' => Role::Admin->value]);
$this->user->refresh();
});
function telegramUpdate(string $code, array $chat = []): array
{
return [
'channel_post' => [
'message_id' => 5,
'chat' => array_merge([
'id' => -1001234567890,
'title' => 'My Channel',
'username' => 'mychannel',
'type' => 'channel',
], $chat),
'text' => "/connect {$code}",
],
];
}
it('issues a connect code', function () {
$response = $this->actingAs($this->user)
->postJson(route('app.social.telegram.connect'))
->assertOk()
->assertJsonStructure(['code', 'bot_username', 'expires_at']);
expect($response->json('bot_username'))->toBe('TryPostBot');
$this->assertDatabaseHas('telegram_connect_requests', [
'workspace_id' => $this->workspace->id,
'code' => $response->json('code'),
'social_account_id' => null,
]);
});
it('links the channel when the webhook receives a matching /connect', function () {
$request = TelegramConnectRequest::create([
'workspace_id' => $this->workspace->id,
'user_id' => $this->user->id,
'code' => 'abc123code',
'expires_at' => now()->addMinutes(15),
]);
$this->withHeader('X-Telegram-Bot-Api-Secret-Token', 'shh-secret')
->postJson(route('telegram.webhook'), telegramUpdate('abc123code'))
->assertNoContent();
$account = SocialAccount::where('workspace_id', $this->workspace->id)
->where('platform', Platform::Telegram)
->first();
expect($account)->not->toBeNull();
expect($account->platform_user_id)->toBe('-1001234567890');
expect($account->display_name)->toBe('My Channel');
expect($account->username)->toBe('mychannel');
expect(data_get($account->meta, 'chat_id'))->toBe('-1001234567890');
expect($request->fresh()->social_account_id)->toBe($account->id);
});
it('rejects the webhook without the secret token', function () {
$this->postJson(route('telegram.webhook'), telegramUpdate('whatever'))
->assertForbidden();
});
it('ignores the webhook for an unknown or expired code', function () {
TelegramConnectRequest::create([
'workspace_id' => $this->workspace->id,
'user_id' => $this->user->id,
'code' => 'expiredcode',
'expires_at' => now()->subMinute(),
]);
$this->withHeader('X-Telegram-Bot-Api-Secret-Token', 'shh-secret')
->postJson(route('telegram.webhook'), telegramUpdate('expiredcode'))
->assertNoContent();
$this->withHeader('X-Telegram-Bot-Api-Secret-Token', 'shh-secret')
->postJson(route('telegram.webhook'), telegramUpdate('does-not-exist'))
->assertNoContent();
expect(SocialAccount::where('platform', Platform::Telegram)->count())->toBe(0);
});
it('reports connection status while pending and once connected', function () {
$request = TelegramConnectRequest::create([
'workspace_id' => $this->workspace->id,
'user_id' => $this->user->id,
'code' => 'statuscode',
'expires_at' => now()->addMinutes(15),
]);
$this->actingAs($this->user)
->getJson(route('app.social.telegram.status', ['code' => 'statuscode']))
->assertOk()
->assertJson(['status' => 'pending']);
$account = SocialAccount::factory()->telegram()->create(['workspace_id' => $this->workspace->id]);
$request->update(['social_account_id' => $account->id]);
$this->actingAs($this->user)
->getJson(route('app.social.telegram.status', ['code' => 'statuscode']))
->assertOk()
->assertJson(['status' => 'connected']);
});
it('registers the webhook via the artisan command', function () {
Http::fake([
'*/botTESTTOKEN/setWebhook' => Http::response(['ok' => true, 'result' => true], 200),
]);
$this->artisan('telegram:set-webhook')->assertSuccessful();
Http::assertSent(function ($request) {
return str_contains($request->url(), '/setWebhook')
&& $request['secret_token'] === 'shh-secret'
&& str_contains($request['url'], 'telegram/webhook');
});
});