diff --git a/README.md b/README.md
index 897e9d8..7ac988a 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# Max Bot API Client library for PHP
+# Max Messenger Bot API Client library for PHP
[](https://github.com/BushlanovDev/max-bot-api-client-php/actions)
[](https://github.com/BushlanovDev/max-bot-api-client-php/actions)
diff --git a/docs/README.md b/docs/README.md
index f025196..6077c77 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -332,7 +332,8 @@ $updateList = $api->getUpdates(
```php
$uploadEndpoint = $api->getUploadUrl(UploadType::Video);
-// Далее вы можете загрузить файл по полученному URL самостоятельно или воспользоваться методом Client::upload()
+// Далее вы можете загрузить файл по полученному URL самостоятельно
+// или воспользоваться методами Client::multipartUpload(), Client::resumableUpload(), Api::uploadFile()
```
### Загрузка файла
diff --git a/src/Api.php b/src/Api.php
index 8d7e242..39f1358 100644
--- a/src/Api.php
+++ b/src/Api.php
@@ -446,6 +446,8 @@ class Api
}
/**
+ * Uploads a file to the specified URL.
+ *
* @param string $uploadUrl The target URL for the upload.
* @param resource $fileHandle A stream resource pointing to the file.
* @param string $fileName The desired file name for the upload.
diff --git a/src/Client.php b/src/Client.php
index d7edadd..5d48fa7 100644
--- a/src/Client.php
+++ b/src/Client.php
@@ -181,7 +181,9 @@ final readonly class Client implements ClientApiInterface
while (!feof($fileResource)) {
$chunk = fread($fileResource, $chunkSize);
if ($chunk === false) {
+ // @codeCoverageIgnoreStart
throw new RuntimeException('Failed to read chunk from file stream.');
+ // @codeCoverageIgnoreEnd
}
$chunkLength = strlen($chunk);
diff --git a/tests/ClientTest.php b/tests/ClientTest.php
index 00b901d..323179b 100644
--- a/tests/ClientTest.php
+++ b/tests/ClientTest.php
@@ -451,4 +451,89 @@ final class ClientTest extends TestCase
$this->client->resumableUpload('http://a.b', $fileResource, 'file.txt', 9);
fclose($fileResource);
}
+
+ #[Test]
+ public function resumableUploadSuccessfullyUploadsSingleChunk(): void
+ {
+ $fileContents = 'test-data';
+ $fileResource = fopen('php://memory', 'w+');
+ fwrite($fileResource, $fileContents);
+ rewind($fileResource);
+
+ $uploadUrl = 'http://a.b';
+ $fileName = 'file.txt';
+ $fileSize = strlen($fileContents);
+
+ $this->requestFactoryMock->method('createRequest')->willReturn($this->requestMock);
+ $this->requestMock->method('withBody')->willReturnSelf();
+ $this->requestMock->method('withHeader')->willReturnSelf();
+
+ $this->httpClientMock
+ ->expects($this->once())
+ ->method('sendRequest')
+ ->with($this->requestMock)
+ ->willReturn($this->responseMock);
+
+ $this->responseMock->method('getStatusCode')->willReturn(200);
+ $this->streamMock->method('__toString')->willReturn('1');
+
+ $result = $this->client->resumableUpload($uploadUrl, $fileResource, $fileName, $fileSize);
+
+ $this->assertSame('1', $result);
+ fclose($fileResource);
+ }
+
+ #[Test]
+ public function resumableUploadSuccessfullyUploadsMultipleChunks(): void
+ {
+ $fileContents = str_repeat('A', 3 * 1024 * 1024); // 3 MB
+ $fileResource = fopen('php://memory', 'w+');
+ fwrite($fileResource, $fileContents);
+ rewind($fileResource);
+
+ $uploadUrl = 'http://a.b';
+ $fileName = 'bigfile.bin';
+ $fileSize = strlen($fileContents);
+
+ $this->requestFactoryMock->method('createRequest')->willReturn($this->requestMock);
+ $this->requestMock->method('withBody')->willReturnSelf();
+ $this->requestMock->method('withHeader')->willReturnSelf();
+
+ $this->httpClientMock
+ ->expects($this->exactly(3))
+ ->method('sendRequest')
+ ->willReturnOnConsecutiveCalls(
+ $this->responseMock,
+ $this->responseMock,
+ $this->responseMock,
+ );
+
+ $this->responseMock->method('getStatusCode')->willReturn(200);
+ $this->streamMock
+ ->method('__toString')
+ ->willReturnOnConsecutiveCalls('', '', '1');
+
+ $result = $this->client->resumableUpload($uploadUrl, $fileResource, $fileName, $fileSize, 1024 * 1024);
+
+ $this->assertSame('1', $result);
+ fclose($fileResource);
+ }
+
+ #[Test]
+ public function resumableUploadStopsOnEmptyChunk(): void
+ {
+ $fileResource = fopen('php://memory', 'w+');
+ rewind($fileResource);
+
+ $uploadUrl = 'http://a.b';
+ $fileName = 'empty.txt';
+
+ $this->requestFactoryMock->expects($this->never())->method('createRequest');
+ $this->httpClientMock->expects($this->never())->method('sendRequest');
+
+ $result = $this->client->resumableUpload($uploadUrl, $fileResource, $fileName, 100);
+
+ $this->assertSame('', $result);
+ fclose($fileResource);
+ }
}