diff --git a/src/Api.php b/src/Api.php index 781477a..b2d7d71 100644 --- a/src/Api.php +++ b/src/Api.php @@ -10,15 +10,19 @@ use Phptg\BotApi\ParseResult\ResultFactory; use Phptg\BotApi\ParseResult\TelegramParseResultException; use Phptg\BotApi\Transport\ApiResponse; +use Phptg\BotApi\Transport\HttpMethod; use Phptg\BotApi\Transport\TransportInterface; +use Phptg\BotApi\Type\InputFile; use Phptg\BotApi\Type\ResponseParameters; use function array_key_exists; use function is_array; use function is_bool; use function is_int; +use function is_scalar; use function is_string; use function json_decode; +use function strlen; /** * @internal @@ -50,11 +54,11 @@ public function call(MethodInterface $method, ?LoggerInterface $logger): mixed LogContextFactory::sendRequest($method), ); - $response = $this->transport->send( - $this->makeUrlPath($method->getApiMethod()), - $method->getData(), - $method->getHttpMethod(), - ); + $url = $this->baseUrl . '/bot' . $this->token . '/' . $method->getApiMethod(); + $response = match ($method->getHttpMethod()) { + HttpMethod::GET => $this->sendGetRequest($url, $method->getData()), + HttpMethod::POST => $this->sendPostRequest($url, $method->getData()), + }; try { $decodedBody = json_decode($response->body, true, flags: JSON_THROW_ON_ERROR); @@ -106,9 +110,54 @@ public function call(MethodInterface $method, ?LoggerInterface $logger): mixed return $result; } - private function makeUrlPath(string $apiMethod): string + /** + * @psalm-param array $data + */ + private function sendGetRequest(string $url, array $data): ApiResponse { - return $this->baseUrl . '/bot' . $this->token . '/' . $apiMethod; + $queryParameters = array_map( + static fn($value) => is_scalar($value) ? $value : json_encode($value, JSON_THROW_ON_ERROR), + $data, + ); + + if (!empty($queryParameters)) { + $url .= '?' . http_build_query($queryParameters); + } + + return $this->transport->get($url); + } + + /** + * @psalm-param array $data + */ + private function sendPostRequest(string $url, array $data): ApiResponse + { + $files = []; + foreach ($data as $key => $value) { + if ($value instanceof InputFile) { + $files[$key] = $value; + unset($data[$key]); + } + } + + if (empty($files)) { + $content = json_encode($data, JSON_THROW_ON_ERROR); + return $this->transport->post( + $url, + $content, + [ + 'Content-Length' => (string) strlen($content), + 'Content-Type' => 'application/json; charset=utf-8', + ], + ); + } + + $data = array_map( + static fn(mixed $value) => is_scalar($value) ? $value : json_encode($value, JSON_THROW_ON_ERROR), + $data, + ); + + return $this->transport->postWithFiles($url, $data, $files); } /** diff --git a/src/Transport/CurlTransport.php b/src/Transport/CurlTransport.php index 6e6ca32..3a219d6 100644 --- a/src/Transport/CurlTransport.php +++ b/src/Transport/CurlTransport.php @@ -9,11 +9,8 @@ use Phptg\BotApi\Curl\Curl; use Phptg\BotApi\Curl\CurlException; use Phptg\BotApi\Curl\CurlInterface; -use Phptg\BotApi\Type\InputFile; use function is_int; -use function is_scalar; -use function json_encode; /** * @api @@ -28,37 +25,46 @@ public function __construct( $this->curlShareHandle = $this->createCurlShareHandle(); } - /** - * @psalm-param array $data - */ - public function send(string $urlPath, array $data = [], HttpMethod $httpMethod = HttpMethod::POST): ApiResponse + public function get(string $url): ApiResponse { - $options = match ($httpMethod) { - HttpMethod::GET => $this->createGetOptions($urlPath, $data), - HttpMethod::POST => $this->createPostOptions($urlPath, $data), - }; - $options[CURLOPT_RETURNTRANSFER] = true; - $options[CURLOPT_SHARE] = $this->curlShareHandle; - - $curl = $this->curl->init(); + $options = [ + CURLOPT_HTTPGET => true, + CURLOPT_URL => $url, + ]; + return $this->send($options); + } - try { - $this->curl->setopt_array($curl, $options); + public function post(string $url, string $body, array $headers): ApiResponse + { + $header = []; + foreach ($headers as $name => $value) { + $header[] = $name . ': ' . $value; + } - /** - * @var string $body `curl_exec` returns string because `CURLOPT_RETURNTRANSFER` is set to `true`. - */ - $body = $this->curl->exec($curl); + $options = [ + CURLOPT_POST => true, + CURLOPT_URL => $url, + CURLOPT_POSTFIELDS => $body, + CURLOPT_HTTPHEADER => $header, + ]; + return $this->send($options); + } - $statusCode = $this->curl->getinfo($curl, CURLINFO_HTTP_CODE); - if (!is_int($statusCode)) { - $statusCode = 0; - } - } finally { - $this->curl->close($curl); + public function postWithFiles(string $url, array $data, array $files): ApiResponse + { + foreach ($files as $key => $file) { + $data[$key] = new CURLStringFile( + FileHelper::read($file), + $file->filename ?? '', + ); } - return new ApiResponse($statusCode, $body); + $options = [ + CURLOPT_POST => true, + CURLOPT_URL => $url, + CURLOPT_POSTFIELDS => $data, + ]; + return $this->send($options); } public function downloadFile(string $url): string @@ -128,55 +134,30 @@ static function (int $errorNumber, string $errorString): bool { } } - /** - * @psalm-param array $data - */ - private function createPostOptions(string $urlPath, array $data): array + private function send(array $options): ApiResponse { - $postFields = []; - foreach ($data as $key => $value) { - if (is_scalar($value)) { - $postFields[$key] = $value; - continue; - } - - if ($value instanceof InputFile) { - $postFields[$key] = new CURLStringFile( - FileHelper::read($value), - $value->filename ?? '', - ); - continue; - } + $options[CURLOPT_RETURNTRANSFER] = true; + $options[CURLOPT_SHARE] = $this->curlShareHandle; - $postFields[$key] = json_encode($value, JSON_THROW_ON_ERROR); - } + $curl = $this->curl->init(); - return [ - CURLOPT_POST => true, - CURLOPT_URL => $urlPath, - CURLOPT_POSTFIELDS => $postFields, - ]; - } + try { + $this->curl->setopt_array($curl, $options); - /** - * @psalm-param array $data - */ - private function createGetOptions(string $urlPath, array $data): array - { - $queryParameters = []; - foreach ($data as $key => $value) { - $queryParameters[$key] = is_scalar($value) ? $value : json_encode($value, JSON_THROW_ON_ERROR); - } + /** + * @var string $body `curl_exec` returns string because `CURLOPT_RETURNTRANSFER` is set to `true`. + */ + $body = $this->curl->exec($curl); - $url = $urlPath; - if (!empty($queryParameters)) { - $url .= '?' . http_build_query($queryParameters); + $statusCode = $this->curl->getinfo($curl, CURLINFO_HTTP_CODE); + if (!is_int($statusCode)) { + $statusCode = 0; + } + } finally { + $this->curl->close($curl); } - return [ - CURLOPT_HTTPGET => true, - CURLOPT_URL => $url, - ]; + return new ApiResponse($statusCode, $body); } private function createCurlShareHandle(): CurlShareHandle diff --git a/src/Transport/NativeTransport.php b/src/Transport/NativeTransport.php index 4256740..0f6e8b8 100644 --- a/src/Transport/NativeTransport.php +++ b/src/Transport/NativeTransport.php @@ -9,7 +9,6 @@ use Phptg\BotApi\Transport\MimeTypeResolver\MimeTypeResolverInterface; use Phptg\BotApi\Type\InputFile; -use function is_scalar; use function is_string; use function json_encode; @@ -29,40 +28,44 @@ public function __construct( $this->mimeTypeResolver = $mimeTypeResolver ?? new ApacheMimeTypeResolver(); } - public function send(string $urlPath, array $data = [], HttpMethod $httpMethod = HttpMethod::POST): ApiResponse + public function get(string $url): ApiResponse { - global $http_response_header; - - [$url, $options] = match ($httpMethod) { - HttpMethod::GET => $this->createGetRequest($urlPath, $data), - HttpMethod::POST => $this->createPostRequest($urlPath, $data), - }; - $options['ignore_errors'] = true; + return $this->send( + $url, + ['method' => 'GET'], + ); + } - $context = stream_context_create(['http' => $options]); + public function post(string $url, string $body, array $headers): ApiResponse + { + $header = []; + foreach ($headers as $name => $value) { + $header[] = $name . ': ' . $value; + } - set_error_handler( - static function (int $errorNumber, string $errorString): bool { - throw new RuntimeException($errorString); - }, + return $this->send( + $url, + [ + 'method' => 'POST', + 'header' => $header, + 'content' => $body, + ], ); - try { - /** - * @var string $body We throw exception on error, so `file_get_contents()` returns string. - */ - $body = file_get_contents($url, context: $context); - } finally { - restore_error_handler(); - } + } - /** - * @psalm-var non-empty-list $http_response_header - * @see https://www.php.net/manual/reserved.variables.httpresponseheader.php - */ + public function postWithFiles(string $url, array $data, array $files): ApiResponse + { + $boundary = uniqid('', true); + $content = $this->buildMultipartFormData($data, $files, $boundary); + $contentType = 'multipart/form-data; boundary=' . $boundary . '; charset=utf-8'; - return new ApiResponse( - $this->parseStatusCode($http_response_header), - $body, + return $this->send( + $url, + [ + 'method' => 'POST', + 'header' => 'Content-type: ' . $contentType, + 'content' => $content, + ], ); } @@ -99,63 +102,37 @@ static function (int $errorNumber, string $errorString): bool { } } - /** - * @psalm-param array $data - * @psalm-return list{string, array} - */ - private function createGetRequest(string $urlPath, array $data): array + private function send(string $url, array $options): ApiResponse { - $queryParameters = array_map( - static fn($value) => is_scalar($value) ? $value : json_encode($value, JSON_THROW_ON_ERROR), - $data, - ); + global $http_response_header; - $url = $urlPath; - if (!empty($queryParameters)) { - $url .= '?' . http_build_query($queryParameters); - } + $options['ignore_errors'] = true; - return [ - $url, - ['method' => 'GET'], - ]; - } + $context = stream_context_create(['http' => $options]); - /** - * @psalm-param array $data - * @psalm-return list{string, array} - */ - private function createPostRequest(string $urlPath, array $data): array - { - $files = []; - foreach ($data as $key => $value) { - if ($value instanceof InputFile) { - $files[$key] = $value; - unset($data[$key]); - } + set_error_handler( + static function (int $errorNumber, string $errorString): bool { + throw new RuntimeException($errorString); + }, + ); + try { + /** + * @var string $body We throw an exception on error, so `file_get_contents()` returns the string. + */ + $body = file_get_contents($url, context: $context); + } finally { + restore_error_handler(); } - if (empty($files)) { - $fields = array_map( - static fn($value) => is_scalar($value) ? $value : json_encode($value, JSON_THROW_ON_ERROR), - $data, - ); - $content = http_build_query($fields); - $contentType = 'application/x-www-form-urlencoded'; - } else { - $boundary = uniqid('', true); - $content = $this->buildMultipartFormData($data, $files, $boundary); - $contentType = 'multipart/form-data; boundary=' . $boundary . '; charset=utf-8'; - } + /** + * @psalm-var non-empty-list $http_response_header + * @see https://www.php.net/manual/reserved.variables.httpresponseheader.php + */ - return [ - $urlPath, - [ - 'method' => 'POST', - 'header' => 'Content-type: ' . $contentType, - 'content' => $content, - ], - ]; + return new ApiResponse( + $this->parseStatusCode($http_response_header), + $body, + ); } /** diff --git a/src/Transport/PsrTransport.php b/src/Transport/PsrTransport.php index 6e12a9a..825d007 100644 --- a/src/Transport/PsrTransport.php +++ b/src/Transport/PsrTransport.php @@ -11,11 +11,6 @@ use Psr\Http\Message\RequestInterface; use Psr\Http\Message\StreamFactoryInterface; use Psr\Http\Message\StreamInterface; -use Phptg\BotApi\Type\InputFile; - -use function is_scalar; -use function is_string; -use function json_encode; /** * @api @@ -28,27 +23,50 @@ public function __construct( private StreamFactoryInterface $streamFactory, ) {} - public function send( - string $urlPath, - array $data = [], - HttpMethod $httpMethod = HttpMethod::POST, - ): ApiResponse { - $response = $this->client->sendRequest( - match ($httpMethod) { - HttpMethod::GET => $this->createGetRequest($urlPath, $data), - HttpMethod::POST => $this->createPostRequest($urlPath, $data), - }, + public function get(string $url): ApiResponse + { + return $this->send( + $this->requestFactory->createRequest('GET', $url), ); + } - $body = $response->getBody(); - if ($body->isSeekable()) { - $body->rewind(); + public function post(string $url, string $body, array $headers): ApiResponse + { + $request = $this->requestFactory->createRequest('POST', $url); + + $stream = $this->streamFactory->createStream($body); + $request = $request->withBody($stream); + + foreach ($headers as $name => $value) { + $request = $request->withHeader($name, $value); } - return new ApiResponse( - $response->getStatusCode(), - $body->getContents(), - ); + return $this->send($request); + } + + public function postWithFiles(string $url, array $data, array $files): ApiResponse + { + $streamBuilder = new MultipartStreamBuilder($this->streamFactory); + foreach ($data as $key => $value) { + $streamBuilder->addResource($key, (string) $value); + } + foreach ($files as $key => $file) { + $streamBuilder->addResource( + $key, + $file->resource, + $file->filename === null ? [] : ['filename' => $file->filename], + ); + } + $body = $streamBuilder->build(); + $contentType = 'multipart/form-data; boundary=' . $streamBuilder->getBoundary() . '; charset=utf-8'; + + $request = $this->requestFactory + ->createRequest('POST', $url) + ->withHeader('Content-Length', (string) $body->getSize()) + ->withHeader('Content-Type', $contentType) + ->withBody($body); + + return $this->send($request); } public function downloadFile(string $url): string @@ -75,6 +93,21 @@ static function (int $errorNumber, string $errorString): bool { } } + private function send(RequestInterface $request): ApiResponse + { + $response = $this->client->sendRequest($request); + + $body = $response->getBody(); + if ($body->isSeekable()) { + $body->rewind(); + } + + return new ApiResponse( + $response->getStatusCode(), + $body->getContents(), + ); + } + /** * @throws DownloadFileException */ @@ -95,69 +128,4 @@ private function internalDownload(string $url): StreamInterface return $body; } - - /** - * @psalm-param array $data - */ - private function createPostRequest(string $urlPath, array $data): RequestInterface - { - $request = $this->requestFactory->createRequest('POST', $urlPath); - - $files = []; - foreach ($data as $key => $value) { - if ($value instanceof InputFile) { - $files[$key] = $value; - unset($data[$key]); - } - } - - if (empty($data) && empty($files)) { - return $request; - } - if (empty($files)) { - $content = json_encode($data, JSON_THROW_ON_ERROR); - $body = $this->streamFactory->createStream($content); - $contentType = 'application/json; charset=utf-8'; - } else { - $streamBuilder = new MultipartStreamBuilder($this->streamFactory); - foreach ($data as $key => $value) { - $streamBuilder->addResource( - $key, - is_string($value) ? $value : json_encode($value, JSON_THROW_ON_ERROR), - ); - } - foreach ($files as $key => $file) { - $streamBuilder->addResource( - $key, - $file->resource, - $file->filename === null ? [] : ['filename' => $file->filename], - ); - } - $body = $streamBuilder->build(); - $contentType = 'multipart/form-data; boundary=' . $streamBuilder->getBoundary() . '; charset=utf-8'; - } - - return $request - ->withHeader('Content-Length', (string) $body->getSize()) - ->withHeader('Content-Type', $contentType) - ->withBody($body); - } - - /** - * @psalm-param array $data - */ - private function createGetRequest(string $urlPath, array $data): RequestInterface - { - $queryParameters = []; - foreach ($data as $key => $value) { - $queryParameters[$key] = is_scalar($value) ? $value : json_encode($value, JSON_THROW_ON_ERROR); - } - - $url = $urlPath; - if (!empty($queryParameters)) { - $url .= '?' . http_build_query($queryParameters); - } - - return $this->requestFactory->createRequest('GET', $url); - } } diff --git a/src/Transport/TransportInterface.php b/src/Transport/TransportInterface.php index ae4cc11..bd4b5fd 100644 --- a/src/Transport/TransportInterface.php +++ b/src/Transport/TransportInterface.php @@ -4,19 +4,25 @@ namespace Phptg\BotApi\Transport; +use Phptg\BotApi\Type\InputFile; + /** * @api */ interface TransportInterface { + public function get(string $url): ApiResponse; + + /** + * @psalm-param array $headers + */ + public function post(string $url, string $body, array $headers): ApiResponse; + /** - * @psalm-param array $data + * @psalm-param array $data + * @psalm-param array $files */ - public function send( - string $urlPath, - array $data = [], - HttpMethod $httpMethod = HttpMethod::POST, - ): ApiResponse; + public function postWithFiles(string $url, array $data, array $files): ApiResponse; /** * Downloads a file by URL. diff --git a/tests/Support/TransportMock.php b/tests/Support/TransportMock.php index 1377d9c..c0b7939 100644 --- a/tests/Support/TransportMock.php +++ b/tests/Support/TransportMock.php @@ -4,30 +4,48 @@ namespace Phptg\BotApi\Tests\Support; -use Phptg\BotApi\Transport\HttpMethod; use Phptg\BotApi\Transport\TransportInterface; use Phptg\BotApi\Transport\ApiResponse; final class TransportMock implements TransportInterface { - private ?string $urlPath = null; + private ?string $url = null; /** - * @var list + * @psalm-var list */ private array $savedFiles = []; public function __construct( - private readonly ?ApiResponse $response = null, + private readonly ApiResponse $response = new ApiResponse(200, '{"ok":true,"result":true}'), ) {} - public function send( - string $urlPath, - array $data = [], - HttpMethod $httpMethod = HttpMethod::POST, - ): ApiResponse { - $this->urlPath = $urlPath; - return $this->response ?? new ApiResponse(200, '{"ok":true,"result":true}'); + public static function successResult(mixed $result): self + { + return new self( + new ApiResponse( + 200, + json_encode(['ok' => true, 'result' => $result], JSON_THROW_ON_ERROR), + ), + ); + } + + public function get(string $url): ApiResponse + { + $this->url = $url; + return $this->response; + } + + public function post(string $url, string $body, array $headers): ApiResponse + { + $this->url = $url; + return $this->response; + } + + public function postWithFiles(string $url, array $data, array $files): ApiResponse + { + $this->url = $url; + return $this->response; } public function downloadFile(string $url): string @@ -41,15 +59,15 @@ public function downloadFileTo(string $url, string $savePath): void } /** - * @return list + * @psalm-return list */ public function savedFiles(): array { return $this->savedFiles; } - public function urlPath(): ?string + public function url(): ?string { - return $this->urlPath; + return $this->url; } } diff --git a/tests/TelegramBotApiTest.php b/tests/TelegramBotApiTest.php index a7d6677..6fbea6e 100644 --- a/tests/TelegramBotApiTest.php +++ b/tests/TelegramBotApiTest.php @@ -346,7 +346,7 @@ public function testMakeUrlPath(): void $api->logout(); - assertSame('https://api.telegram.org/botstub-token/logOut', $transport->urlPath()); + assertSame('https://api.telegram.org/botstub-token/logOut', $transport->url()); } public static function dataMakeFileUrl(): iterable @@ -1328,6 +1328,19 @@ public function testGetUpdates(): void assertSame(2, $result[1]->updateId); } + public function testGetMethodWithParams(): void + { + $transport = TransportMock::successResult([]); + $api = new TelegramBotApi('stub-token', transport: $transport); + + $api->getUpdates(offset: 5, allowedUpdates: ['message', 'edited_message', 'channel_post']); + + assertSame( + 'https://api.telegram.org/botstub-token/getUpdates?offset=5&allowed_updates=%5B%22message%22%2C%22edited_message%22%2C%22channel_post%22%5D', + $transport->url(), + ); + } + public function testGetUserChatBoosts(): void { $api = TestHelper::createSuccessStubApi([ diff --git a/tests/Transport/CurlTransport/CurlTransportTest.php b/tests/Transport/CurlTransport/CurlTransportTest.php index 7a3745f..07db744 100644 --- a/tests/Transport/CurlTransport/CurlTransportTest.php +++ b/tests/Transport/CurlTransport/CurlTransportTest.php @@ -9,11 +9,9 @@ use HttpSoft\Message\StreamFactory; use PHPUnit\Framework\TestCase; use RuntimeException; -use stdClass; use Throwable; use Phptg\BotApi\Tests\Curl\CurlMock; use Phptg\BotApi\Transport\CurlTransport; -use Phptg\BotApi\Transport\HttpMethod; use Phptg\BotApi\Type\InputFile; use function PHPUnit\Framework\assertCount; @@ -32,14 +30,7 @@ public function testGet(): void ); $transport = new CurlTransport($curl); - $response = $transport->send( - '//url/getMe', - [ - 'key' => 'value', - 'array' => [1, 'test'], - ], - HttpMethod::GET, - ); + $response = $transport->get('//url/getMe?key=value&array=%5B1%2C%22test%22%5D'); assertSame(200, $response->statusCode); assertSame('{"ok":true,"result":[]}', $response->body); @@ -60,45 +51,29 @@ public function testPost(): void ); $transport = new CurlTransport($curl); - $response = $transport->send('//url/logOut'); + $response = $transport->post( + '//url/logOut', + '', + [ + 'Content-Length' => '0', + 'Content-Type' => 'application/json; charset=utf-8', + ], + ); assertSame(200, $response->statusCode); assertSame('{"ok":true,"result":[]}', $response->body); $options = $curl->getOptions(); - assertCount(5, $options); + assertCount(6, $options); assertTrue($options[CURLOPT_POST]); assertSame('//url/logOut', $options[CURLOPT_URL]); - assertSame([], $options[CURLOPT_POSTFIELDS]); - assertTrue($options[CURLOPT_RETURNTRANSFER]); - assertInstanceOf(CurlShareHandle::class, $options[CURLOPT_SHARE]); - } - - public function testPostWithParams(): void - { - $curl = new CurlMock( - execResult: '{"ok":true,"result":[]}', - getinfoResult: [CURLINFO_HTTP_CODE => 200], - ); - $transport = new CurlTransport($curl); - - $transport->send('//url/setChatTitle', [ - 'chat_id' => 123, - 'title' => 'test', - 'object' => new stdClass(), - ]); - - $options = $curl->getOptions(); - assertCount(5, $options); - assertTrue($options[CURLOPT_POST]); - assertSame('//url/setChatTitle', $options[CURLOPT_URL]); + assertSame('', $options[CURLOPT_POSTFIELDS]); assertSame( [ - 'chat_id' => 123, - 'title' => 'test', - 'object' => '{}', + 'Content-Length: 0', + 'Content-Type: application/json; charset=utf-8', ], - $options[CURLOPT_POSTFIELDS], + $options[CURLOPT_HTTPHEADER], ); assertTrue($options[CURLOPT_RETURNTRANSFER]); assertInstanceOf(CurlShareHandle::class, $options[CURLOPT_SHARE]); @@ -112,7 +87,7 @@ public function testWithoutCode(): void ), ); - $response = $transport->send('logOut'); + $response = $transport->get('getMe'); assertSame(0, $response->statusCode); } @@ -125,10 +100,14 @@ public function testPostWithLocalFiles(): void ); $transport = new CurlTransport($curl); - $response = $transport->send('//url/sendPhoto', [ - 'photo1' => InputFile::fromLocalFile(__DIR__ . '/photo.png'), - 'photo2' => InputFile::fromLocalFile(__DIR__ . '/photo.png', 'photo.png'), - ]); + $response = $transport->postWithFiles( + '//url/sendPhoto', + [], + [ + 'photo1' => InputFile::fromLocalFile(__DIR__ . '/photo.png'), + 'photo2' => InputFile::fromLocalFile(__DIR__ . '/photo.png', 'photo.png'), + ], + ); assertSame(200, $response->statusCode); assertSame('{"ok":true,"result":[]}', $response->body); @@ -162,15 +141,19 @@ public function testPostWithStreamFile(): void ); $transport = new CurlTransport($curl); - $transport->send('sendPhoto', [ - 'photo1' => new InputFile( - (new StreamFactory())->createStream('test1'), - ), - 'photo2' => new InputFile( - (new StreamFactory())->createStream('test2'), - 'test.jpg', - ), - ]); + $transport->postWithFiles( + 'sendPhoto', + [], + [ + 'photo1' => new InputFile( + (new StreamFactory())->createStream('test1'), + ), + 'photo2' => new InputFile( + (new StreamFactory())->createStream('test2'), + 'test.jpg', + ), + ], + ); assertEquals( [ @@ -188,9 +171,13 @@ public function testSeekableStream(): void $stream = (new StreamFactory())->createStream('test1'); $stream->getContents(); - $transport->send('sendPhoto', [ - 'photo' => new InputFile($stream), - ]); + $transport->postWithFiles( + 'sendPhoto', + [], + [ + 'photo' => new InputFile($stream), + ], + ); assertEquals( [ @@ -207,9 +194,13 @@ public function testSeekableResource(): void $resource = fopen(__DIR__ . '/photo.png', 'r'); stream_get_contents($resource); - $transport->send('sendPhoto', [ - 'photo' => new InputFile($resource), - ]); + $transport->postWithFiles( + 'sendPhoto', + [], + [ + 'photo' => new InputFile($resource), + ], + ); assertEquals( [ @@ -228,7 +219,7 @@ public function testCloseOnException(): void $transport = new CurlTransport($curl); try { - $transport->send('getMe'); + $transport->get('getMe'); } catch (Throwable) { } @@ -242,7 +233,7 @@ public function testShareOptions(): void getinfoResult: [CURLINFO_HTTP_CODE => 200], ); - (new CurlTransport($curl))->send('getMe'); + (new CurlTransport($curl))->get('getMe'); assertSame( [ diff --git a/tests/Transport/NativeTransport/NativeTransportTest.php b/tests/Transport/NativeTransport/NativeTransportTest.php index b17b9fe..ef1a8f3 100644 --- a/tests/Transport/NativeTransport/NativeTransportTest.php +++ b/tests/Transport/NativeTransport/NativeTransportTest.php @@ -7,9 +7,7 @@ use HttpSoft\Message\StreamFactory; use PHPUnit\Framework\TestCase; use RuntimeException; -use stdClass; use Phptg\BotApi\Tests\Transport\NativeTransport\StreamMock\StreamMock; -use Phptg\BotApi\Transport\HttpMethod; use Phptg\BotApi\Transport\MimeTypeResolver\MimeTypeResolverInterface; use Phptg\BotApi\Transport\NativeTransport; use Phptg\BotApi\Type\InputFile; @@ -35,14 +33,7 @@ public function testGet(): void responseBody: '{"ok":true,"result":[]}', ); - $response = $transport->send( - 'http://url/getMe', - [ - 'key' => 'value', - 'array' => [1, 'test'], - ], - HttpMethod::GET, - ); + $response = $transport->get('http://url/getMe?key=value&array=%5B1%2C%22test%22%5D'); $request = StreamMock::disable(); @@ -74,7 +65,14 @@ public function testPost(): void responseBody: '{"ok":true,"result":[]}', ); - $response = $transport->send('http://url/logOut'); + $response = $transport->post( + 'http://url/logOut', + '', + [ + 'Content-Length' => '0', + 'Content-Type' => 'application/json; charset=utf-8', + ], + ); $request = StreamMock::disable(); @@ -86,7 +84,10 @@ public function testPost(): void 'options' => [ 'http' => [ 'method' => 'POST', - 'header' => 'Content-type: application/x-www-form-urlencoded', + 'header' => [ + 'Content-Length: 0', + 'Content-Type: application/json; charset=utf-8', + ], 'content' => '', 'ignore_errors' => true, ], @@ -96,7 +97,7 @@ public function testPost(): void ); } - public function testPostWithParams(): void + public function testPostWithLocalFiles(): void { $transport = new NativeTransport(); @@ -108,50 +109,18 @@ public function testPostWithParams(): void responseBody: '{"ok":true,"result":[]}', ); - $response = $transport->send('http://url/setChatTitle', [ - 'chat_id' => 123, - 'title' => 'test', - 'object' => new stdClass(), - ]); - - $request = StreamMock::disable(); - - assertSame(200, $response->statusCode); - assertSame('{"ok":true,"result":[]}', $response->body); - assertSame( + $response = $transport->postWithFiles( + 'http://url/sendPhoto', [ - 'path' => 'http://url/setChatTitle', - 'options' => [ - 'http' => [ - 'method' => 'POST', - 'header' => 'Content-type: application/x-www-form-urlencoded', - 'content' => 'chat_id=123&title=test&object=%7B%7D', - 'ignore_errors' => true, - ], - ], - ], - $request, - ); - } - public function testPostWithLocalFiles(): void - { - $transport = new NativeTransport(); - - StreamMock::enable( - responseHeaders: [ - 'HTTP/1.1 200 OK', - 'Content-Type: text/json', + 'age' => 19, + ], + [ + 'photo1' => InputFile::fromLocalFile(__DIR__ . '/photo.png'), + 'photo2' => InputFile::fromLocalFile(__DIR__ . '/photo.png', 'face.png'), ], - responseBody: '{"ok":true,"result":[]}', ); - $response = $transport->send('http://url/sendPhoto', [ - 'age' => 19, - 'photo1' => InputFile::fromLocalFile(__DIR__ . '/photo.png'), - 'photo2' => InputFile::fromLocalFile(__DIR__ . '/photo.png', 'face.png'), - ]); - $request = StreamMock::disable(); assertSame(200, $response->statusCode); @@ -188,15 +157,19 @@ public function testPostWithStreamFile(): void responseBody: '{"ok":true,"result":[]}', ); - $response = $transport->send('http://url/sendPhoto', [ - 'file1' => new InputFile( - (new StreamFactory())->createStream('test1'), - ), - 'file2' => new InputFile( - (new StreamFactory())->createStream('test2'), - 'test.txt', - ), - ]); + $response = $transport->postWithFiles( + 'http://url/sendPhoto', + [], + [ + 'file1' => new InputFile( + (new StreamFactory())->createStream('test1'), + ), + 'file2' => new InputFile( + (new StreamFactory())->createStream('test2'), + 'test.txt', + ), + ], + ); $request = StreamMock::disable(); @@ -225,7 +198,7 @@ public function testPostWithStreamFile(): void assertTrue($request['options']['http']['ignore_errors']); } - public function testPostWithFileAndArray(): void + public function testPostWithFiles(): void { $transport = new NativeTransport(); @@ -237,12 +210,17 @@ public function testPostWithFileAndArray(): void responseBody: '{"ok":true,"result":[]}', ); - $transport->send('http://url/method', [ - 'file1' => new InputFile( - (new StreamFactory())->createStream('test1'), - ), - 'ages' => [23, 45], - ]); + $transport->postWithFiles( + 'http://url/method', + [ + 'ages' => [23, 45], + ], + [ + 'file1' => new InputFile( + (new StreamFactory())->createStream('test1'), + ), + ], + ); $request = StreamMock::disable(); @@ -280,11 +258,15 @@ public function resolve(InputFile $file): ?string responseBody: '{"ok":true,"result":[]}', ); - $transport->send('http://url/method', [ - 'file1' => new InputFile( - (new StreamFactory())->createStream('test1'), - ), - ]); + $transport->postWithFiles( + 'http://url/method', + [], + [ + 'file1' => new InputFile( + (new StreamFactory())->createStream('test1'), + ), + ], + ); $request = StreamMock::disable(); @@ -304,7 +286,7 @@ public function testErrorOnSend(): void $this->expectException(RuntimeException::class); $this->expectExceptionMessage('file_get_contents(): Unable to find the wrapper "example"'); - $transport->send('example://url/logOut'); + $transport->get('example://url/getMe'); } public function testWithoutCode(): void @@ -318,7 +300,7 @@ public function testWithoutCode(): void responseBody: '{"ok":true,"result":[]}', ); - $response = $transport->send('http://url/logOut'); + $response = $transport->get('http://url/getMe'); StreamMock::disable(); diff --git a/tests/Transport/PsrTransport/PsrTransportTest.php b/tests/Transport/PsrTransport/PsrTransportTest.php index 3d4c987..2662da8 100644 --- a/tests/Transport/PsrTransport/PsrTransportTest.php +++ b/tests/Transport/PsrTransport/PsrTransportTest.php @@ -12,7 +12,6 @@ use Psr\Http\Client\ClientInterface; use Psr\Http\Message\RequestFactoryInterface; use Phptg\BotApi\Transport\PsrTransport; -use Phptg\BotApi\Transport\HttpMethod; use Phptg\BotApi\Type\InputFile; use function PHPUnit\Framework\assertInstanceOf; @@ -45,14 +44,7 @@ public function testGet(): void new StreamFactory(), ); - $response = $transport->send( - '//url/getMe', - [ - 'key' => 'value', - 'array' => [1, 'test'], - ], - HttpMethod::GET, - ); + $response = $transport->get('//url/getMe?key=value&array=%5B1%2C%22test%22%5D'); assertSame(201, $response->statusCode); } @@ -65,7 +57,20 @@ public function testPost(): void $client ->expects($this->once()) ->method('sendRequest') - ->with($httpRequest) + ->with( + new Callback(function ($request): bool { + assertInstanceOf(Request::class, $request); + assertSame( + [ + 'Content-Length' => ['0'], + 'Content-Type' => ['application/json; charset=utf-8'], + ], + $request->getHeaders(), + ); + assertSame('', $request->getBody()->getContents()); + return true; + }), + ) ->willReturn(new Response(201)); $requestFactory = $this->createMock(RequestFactoryInterface::class); @@ -81,7 +86,14 @@ public function testPost(): void new StreamFactory(), ); - $response = $transport->send('//url/logOut'); + $response = $transport->post( + '//url/logOut', + '', + [ + 'Content-Length' => '0', + 'Content-Type' => 'application/json; charset=utf-8', + ], + ); assertSame(201, $response->statusCode); } @@ -97,7 +109,6 @@ public function testPostWithData(): void ->with( new Callback(function ($request): bool { assertInstanceOf(Request::class, $request); - /** @var Request $request */ assertSame( [ 'Content-Length' => ['29'], @@ -124,12 +135,19 @@ public function testPostWithData(): void new StreamFactory(), ); - $response = $transport->send('//url/sendMessage', ['chat_id' => 123, 'text' => 'test']); + $response = $transport->post( + '//url/sendMessage', + '{"chat_id":123,"text":"test"}', + [ + 'Content-Length' => '29', + 'Content-Type' => 'application/json; charset=utf-8', + ], + ); assertSame(201, $response->statusCode); } - public function testPostWithDataAndFiles(): void + public function testPostWithFiles(): void { $httpRequest = new Request(); @@ -190,11 +208,13 @@ public function testPostWithDataAndFiles(): void new StreamFactory(), ); - $response = $transport->send( + $response = $transport->postWithFiles( '//url/sendPhoto', [ 'chat_id' => 123, 'caption' => 'hello', + ], + [ 'photo' => new InputFile( (new StreamFactory())->createStream('test-file-body'), 'face.png', @@ -223,7 +243,7 @@ public function testRewind(): void $streamFactory, ); - $response = $transport->send('getMe'); + $response = $transport->get('getMe'); assertSame(201, $response->statusCode); assertSame('hello', $response->body); diff --git a/tests/Transport/PsrTransport/StrictTypeRequest/StrictTypeRequestTest.php b/tests/Transport/PsrTransport/StrictTypeRequest/StrictTypeRequestTest.php index 6377dd2..1fc2025 100644 --- a/tests/Transport/PsrTransport/StrictTypeRequest/StrictTypeRequestTest.php +++ b/tests/Transport/PsrTransport/StrictTypeRequest/StrictTypeRequestTest.php @@ -29,7 +29,7 @@ public function testWithHeader(): void $streamFactory, ); - $response = $transport->send('getMyName', ['language_code' => 'ru']); + $response = $transport->get('getMyName'); assertSame(201, $response->statusCode); assertSame('hello', $response->body);