Skip to content

Add Pest tests for Laravel integration using Orchestra Testbench #16

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ composer.lock
vendor
phpunit.xml
.phpunit.result.cache
workbench/
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,17 @@ Head over to [Awesome Telegram Bots][link-awesome-telegram-bots] to share, disco

Thank you for considering contributing to the project. Please read [the contributing guide][link-contributing] before creating an issue or sending in a pull request.

## Local Development

This package uses [orchestral/workbench](https://github.com/orchestral/workbench) for local testing.

To set up the workbench environment:

```bash
composer install
php artisan workbench:install
```

## Code of Conduct

Please read our [Code of Conduct][link-code-of-conduct] before contributing or engaging in discussions.
Expand Down
9 changes: 7 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@
},
"require-dev": {
"irazasyed/docgen": "^0.2",
"pestphp/pest": "^2.0",
"orchestra/pest-plugin-testbench": "^3.2",
"orchestra/testbench": "^10.3",
"pestphp/pest": "^3.8",
"php-parallel-lint/php-parallel-lint": "^1.3",
"rector/rector": "^2.0"
},
Expand All @@ -48,7 +50,10 @@
},
"autoload-dev": {
"psr-4": {
"Telegram\\Bot\\Laravel\\Tests\\": "tests/"
"Telegram\\Bot\\Laravel\\Tests\\": "tests/",
"Workbench\\App\\": "workbench/app/",
"Workbench\\Database\\Factories\\": "workbench/database/factories/",
"Workbench\\Database\\Seeders\\": "workbench/database/seeders/"
}
},
"config": {
Expand Down
4 changes: 1 addition & 3 deletions src/Events/WebhookFailed.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,5 @@ class WebhookFailed
/**
* Create a new event instance.
*/
public function __construct(public string $botname, public ResponseObject $update, public Throwable $exception)
{
}
public function __construct(public string $botname, public ResponseObject $update, public Throwable $exception) {}
}
2 changes: 1 addition & 1 deletion src/Facades/Telegram.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

use Illuminate\Support\Facades\Facade;
use Telegram\Bot\BotManager;
use Telegram\Bot\Testing\BotFake;
use Telegram\Bot\Testing\Fakes\BotFake;

/**
* @see \Telegram\Bot\BotManager
Expand Down
11 changes: 1 addition & 10 deletions src/TelegramServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

namespace Telegram\Bot\Laravel;

use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\ServiceProvider;
use Telegram\Bot\Api;
Expand All @@ -19,7 +18,7 @@
/**
* Class TelegramServiceProvider.
*/
class TelegramServiceProvider extends ServiceProvider implements DeferrableProvider
class TelegramServiceProvider extends ServiceProvider
{
/**
* Register the service provider.
Expand Down Expand Up @@ -111,12 +110,4 @@ protected function registerCommands(): void
WebhookSetupCommand::class,
]);
}

/**
* Get the services provided by the provider.
*/
public function provides(): array
{
return [BotManager::class, Bot::class, Api::class, 'telegram', 'telegram.bot', 'telegram.api'];
}
}
5 changes: 1 addition & 4 deletions stubs/app/Providers/TelegramServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,5 @@ public function register()
*
* @return void
*/
public function boot()
{

}
public function boot() {}
}
25 changes: 25 additions & 0 deletions tests/Pest.php
Original file line number Diff line number Diff line change
@@ -1 +1,26 @@
<?php

use Orchestra\Testbench\TestCase;
use Telegram\Bot\Api;
use Telegram\Bot\Bot;
use Telegram\Bot\BotManager;
use Telegram\Bot\Laravel\TelegramServiceProvider;

/*
|--------------------------------------------------------------------------
| Test Case
|--------------------------------------------------------------------------
|
| The tests in this file belong to the Testbench test case.
|
*/

uses(TestCase::class)
->beforeEach(function () {
// Automatically load the package service provider for all tests in 'Unit' and 'Feature'
$this->app->register(TelegramServiceProvider::class);
$this->app->alias(BotManager::class, 'telegram');
$this->app->alias(Bot::class, 'telegram.bot');
$this->app->alias(Api::class, 'telegram.api');
})
->in('Unit', 'Feature'); // Apply to tests in 'Unit' and 'Feature' directories
39 changes: 39 additions & 0 deletions tests/Unit/ConfigTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

use Telegram\Bot\Commands\HelpCommand;
use Telegram\Bot\Laravel\Http\Controllers\WebhookController;
use Telegram\Bot\Laravel\TelegramServiceProvider;

// This will apply to all tests in this file
beforeEach(function () {
$this->app->register(TelegramServiceProvider::class);
});

test('it loads the default bot configuration', function () {
$config = $this->app['config']->get('telegram.bots.default');
expect($config)->toBeArray();
expect($config)->toHaveKey('token');
expect($config['token'])->toBe('YOUR-BOT-TOKEN');
});

test('it loads the second bot configuration', function () {
$config = $this->app['config']->get('telegram.bots.second');
expect($config)->toBeArray();
expect($config)->toHaveKey('token');
expect($config['token'])->toBe('123456:abc');
});

test('it loads the webhook configuration', function () {
$config = $this->app['config']->get('telegram.webhook');
expect($config)->toBeArray();
expect($config)->toHaveKey('path');
expect($config['path'])->toBe('telegram');
expect($config['controller'])->toBe(WebhookController::class);
});

test('it loads the global commands', function () {
$commands = $this->app['config']->get('telegram.commands');
expect($commands)->toBeArray();
expect($commands)->toHaveKey('help');
expect($commands['help'])->toBe(HelpCommand::class);
});
110 changes: 110 additions & 0 deletions tests/Unit/RoutesTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<?php

use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Route;
use Telegram\Bot\Bot;
use Telegram\Bot\BotManager;
use Telegram\Bot\Laravel\Facades\Telegram;
use Telegram\Bot\Laravel\Http\Controllers\WebhookController;
use Telegram\Bot\Laravel\Http\Middleware\ValidateWebhook;
use Telegram\Bot\Objects\ResponseObject;

// Test that the routes are registered and middleware is attached
it('registers telegram webhook route with middleware', function () {
$routes = collect(Route::getRoutes()->get() ?: []);
$webhookRoute = $routes->firstWhere('uri', 'telegram/{bot}/webhook');

expect($webhookRoute)->not->toBeNull()
->and($webhookRoute->methods())->toContain('POST')
->and(collect($webhookRoute->middleware())->contains(ValidateWebhook::class))->toBeTrue();
});

// Test webhook controller handles a request
it('webhook controller listen handles update', function () {
$botName = 'default';
$manager = $this->app->make(BotManager::class);
$controller = new WebhookController;

// Simulate lifecycle termination callback invocation
$invoked = false;
App::terminating(function () use (&$invoked, $manager, $botName) {
$invoked = true;
$bot = $manager->bot($botName);
expect($bot)->toBeInstanceOf(Bot::class);
});

$response = $controller->__invoke($manager, $botName);

expect($response->getStatusCode())->toBe(204); // no content
expect($invoked)->toBeTrue();
})
->todo('To work on');

// Console Command: telegram:webhook:setup - success scenario
it('executes telegram:webhook:setup command successfully', function () {
$this->artisan('telegram:webhook:setup')->assertExitCode(0)->expectsOutputToContain('Setting webhook for');
})
->todo('Making an actual outbound request. Needs to be mocked');

// Console Command: telegram:webhook:remove - confirmation rejected scenario
it('does not remove webhook if confirmation rejected', function () {
$command = $this->artisan('telegram:webhook:remove');
$command->expectsQuestion('Are you sure you want to remove the webhook for [default] bot?', false);
$command->assertExitCode(0);
});

// Console Command: telegram:command:register - success scenario
it('executes telegram:command:register command successfully', function () {
$this->artisan('telegram:command:register')->assertExitCode(0)->expectsOutputToContain('Commands Registered Successfully!');
})
->todo('Making an actual outbound request. Needs to be mocked');

// Console Command: telegram:command:list - lists commands
it('executes telegram:command:list command successfully and lists commands', function () {
$this->artisan('telegram:command:list')->assertExitCode(0);
})
->todo('needs to add expect table');

// Facade Telegram fake test
it('telegram facade fake returns predefined response', function () {
$tgFake = Telegram::fake([
new ResponseObject(['id' => 1, 'first_name' => 'Test', 'username' => 'testbot']),
]);

$response = Telegram::sendMessage([
'chat_id' => 123456789,
'text' => 'Hello Pest Test',
]);

expect($response->id)->toBe(1);
expect($response->first_name)->toBe('Test');
$tgFake->assertSent('sendMessage');
});

// Middleware test for ValidateWebhook
it('validate webhook middleware rejects invalid secret token', function () {
// Create a test route that uses the middleware
Route::post('/test-webhook/{bot}', fn () => response('OK'))->middleware(ValidateWebhook::class);

// Make a request with invalid secret token
$response = $this->postJson('/test-webhook/default', [], [
'X-Telegram-Bot-Api-Secret-Token' => 'invalid-token',
]);

// Should get 403 forbidden
$response->assertStatus(403);
});

it('validate webhook middleware allows valid secret token', function () {
Route::post('/test-webhook/{bot}', fn () => response('OK'))->middleware(ValidateWebhook::class);

$fullBotToken = '123456:secretpart';
config(['telegram.bots.default.token' => $fullBotToken]);

$response = $this->postJson('/test-webhook/default', [], [
'X-Telegram-Bot-Api-Secret-Token' => Str::after($fullBotToken, ':'),
]);

$response->assertStatus(200);
$response->assertSeeText('OK');
});
25 changes: 25 additions & 0 deletions tests/Unit/ServiceProviderTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

use Telegram\Bot\Api;
use Telegram\Bot\Bot;
use Telegram\Bot\BotManager;
use Telegram\Bot\Laravel\Facades\Telegram;

test('it binds bot manager to the container', function () {
expect($this->app->make(BotManager::class))->toBeInstanceOf(BotManager::class);
expect($this->app->make('telegram'))->toBeInstanceOf(BotManager::class);
});

test('it binds default bot to the container', function () {
expect($this->app->make(Bot::class))->toBeInstanceOf(Bot::class);
expect($this->app->make('telegram.bot'))->toBeInstanceOf(Bot::class);
});

test('it binds api to the container', function () {
expect($this->app->make(Api::class))->toBeInstanceOf(Api::class);
expect($this->app->make('telegram.api'))->toBeInstanceOf(Api::class);
});

test('the telegram facade resolves to bot manager', function () {
expect(Telegram::getFacadeRoot())->toBeInstanceOf(BotManager::class);
});