apiMock = $this->createMock(Api::class); $this->configMock = $this->createMock(Config::class); $this->container->instance(Api::class, $this->apiMock); $this->container->instance(Config::class, $this->configMock); $this->command = new WebhookSubscribeCommand(); $this->command->setLaravel($this->container); $application = new ConsoleApplication(); $application->add($this->command); $commandInApp = $application->find('maxbot:webhook:subscribe'); $this->tester = new CommandTester($commandInApp); } #[Test] public function handleSuccessfullySubscribesWithAllOptions(): void { $url = 'https://example.com/webhook'; $secret = 'my-super-secret'; $types = ['message_created', 'bot_started']; $expectedUpdateTypes = [UpdateType::MessageCreated, UpdateType::BotStarted]; $this->apiMock ->expects($this->once()) ->method('subscribe') ->with($url, $secret, $expectedUpdateTypes) ->willReturn(new Result(true, null)); $this->tester->execute([ 'url' => $url, '--secret' => $secret, '--types' => $types, ]); $this->tester->assertCommandIsSuccessful(); $output = $this->tester->getDisplay(); $this->assertStringContainsString('✅ Successfully subscribed to webhook!', $output); $this->assertStringContainsString("URL: $url", $output); $this->assertStringContainsString("Secret: ***************", $output); $this->assertStringContainsString("Update types: message_created, bot_started", $output); } #[Test] public function handleUsesSecretFromConfigWhenOptionIsNotProvided(): void { $url = 'https://example.com/webhook'; $configSecret = 'secret-from-config'; $this->configMock ->expects($this->once()) ->method('get') ->with('maxbot.webhook_secret') ->willReturn($configSecret); $this->apiMock ->expects($this->once()) ->method('subscribe') ->with($url, $configSecret, null) ->willReturn(new Result(true, null)); $this->tester->execute(['url' => $url]); $this->tester->assertCommandIsSuccessful(); } #[Test] public function handleFailsForInvalidUrl(): void { $this->apiMock->expects($this->never())->method('subscribe'); $statusCode = $this->tester->execute(['url' => 'not-a-valid-url']); $this->assertSame(1, $statusCode); $this->assertStringContainsString('Invalid URL provided.', $this->tester->getDisplay()); } #[Test] public function handleFailsForInvalidUpdateType(): void { $this->apiMock->expects($this->never())->method('subscribe'); $statusCode = $this->tester->execute([ 'url' => 'https://example.com', '--types' => ['message_created', 'invalid_type'], ]); $this->assertSame(1, $statusCode); $this->assertStringContainsString('Invalid update type: invalid_type', $this->tester->getDisplay()); } #[Test] public function handleDisplaysApiErrorMessageOnFailure(): void { $url = 'https://example.com/webhook'; $apiErrorMessage = 'URL is already subscribed'; $this->apiMock ->expects($this->once()) ->method('subscribe') ->willReturn(new Result(false, $apiErrorMessage)); $statusCode = $this->tester->execute(['url' => $url]); $this->assertSame(1, $statusCode); $output = $this->tester->getDisplay(); $this->assertStringContainsString('❌ Failed to subscribe to webhook.', $output); $this->assertStringContainsString("Response: $apiErrorMessage", $output); } #[Test] public function handleCatchesExceptionAndLogsError(): void { $url = 'https://example.com/webhook'; $exceptionMessage = 'Network error'; $exception = new \RuntimeException($exceptionMessage); $this->apiMock ->expects($this->once()) ->method('subscribe') ->willThrowException($exception); Log::shouldReceive('error') ->once() ->with("Webhook subscription error: $exceptionMessage", ['exception' => $exception]); $statusCode = $this->tester->execute(['url' => $url]); $this->assertSame(1, $statusCode); $output = $this->tester->getDisplay(); $this->assertStringContainsString("❌ Webhook subscription error: $exceptionMessage", $output); } }