mirror of
https://github.com/BushlanovDev/max-bot-api-client-php.git
synced 2026-08-30 03:57:41 +00:00
Added PhotoAttachment
This commit is contained in:
+73
@@ -6,18 +6,23 @@ namespace BushlanovDev\MaxMessengerBot;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\MessageFormat;
|
||||
use BushlanovDev\MaxMessengerBot\Enums\UpdateType;
|
||||
use BushlanovDev\MaxMessengerBot\Enums\UploadType;
|
||||
use BushlanovDev\MaxMessengerBot\Exceptions\ClientApiException;
|
||||
use BushlanovDev\MaxMessengerBot\Exceptions\NetworkException;
|
||||
use BushlanovDev\MaxMessengerBot\Exceptions\SerializationException;
|
||||
use BushlanovDev\MaxMessengerBot\Models\AbstractModel;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Requests\AbstractAttachmentRequest;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Requests\PhotoAttachmentRequest;
|
||||
use BushlanovDev\MaxMessengerBot\Models\BotInfo;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Message;
|
||||
use BushlanovDev\MaxMessengerBot\Models\MessageLink;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Result;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Subscription;
|
||||
use BushlanovDev\MaxMessengerBot\Models\UploadEndpoint;
|
||||
use InvalidArgumentException;
|
||||
use LogicException;
|
||||
use ReflectionException;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* The main entry point for interacting with the Max Bot API.
|
||||
@@ -37,6 +42,7 @@ class Api
|
||||
private const string ACTION_ME = '/me';
|
||||
private const string ACTION_SUBSCRIPTIONS = '/subscriptions';
|
||||
private const string ACTION_MESSAGES = '/messages';
|
||||
private const string ACTION_UPLOADS = '/uploads';
|
||||
|
||||
private readonly ClientApiInterface $client;
|
||||
|
||||
@@ -204,4 +210,71 @@ class Api
|
||||
|
||||
return $this->modelFactory->createMessage($response['message']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URL for the subsequent file upload.
|
||||
*
|
||||
* @param UploadType $type Uploaded file type.
|
||||
*
|
||||
* @return UploadEndpoint Endpoint you should upload to your binaries.
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
public function getUploadUrl(UploadType $type): UploadEndpoint
|
||||
{
|
||||
return $this->modelFactory->createUploadEndpoint(
|
||||
$this->client->request(
|
||||
self::METHOD_POST,
|
||||
self::ACTION_UPLOADS,
|
||||
['type' => $type->value],
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* A simplified method for uploading a file and getting the resulting attachment object.
|
||||
*
|
||||
* @param UploadType $type Uploaded file type.
|
||||
* @param string $filePath Path to the file on the local disk.
|
||||
*
|
||||
* @return AbstractAttachmentRequest
|
||||
* @throws ReflectionException
|
||||
* @throws SerializationException
|
||||
* @throws InvalidArgumentException
|
||||
* @throws RuntimeException
|
||||
* @throws LogicException
|
||||
* @throws NetworkException
|
||||
* @throws ClientApiException
|
||||
*/
|
||||
public function uploadAttachment(UploadType $type, string $filePath): AbstractAttachmentRequest
|
||||
{
|
||||
if (!file_exists($filePath) || !is_readable($filePath)) {
|
||||
throw new InvalidArgumentException("File not found or not readable: $filePath");
|
||||
}
|
||||
|
||||
$fileHandle = fopen($filePath, 'r');
|
||||
if ($fileHandle === false) {
|
||||
throw new RuntimeException("Could not open file for reading: $filePath");
|
||||
}
|
||||
|
||||
$uploadEndpoint = $this->getUploadUrl($type);
|
||||
|
||||
$uploadResult = $this->client->upload(
|
||||
$uploadEndpoint->url,
|
||||
$fileHandle,
|
||||
basename($filePath),
|
||||
);
|
||||
|
||||
fclose($fileHandle);
|
||||
|
||||
if (!isset($uploadResult['token'])) {
|
||||
throw new SerializationException('Could not find "token" in upload server response.');
|
||||
}
|
||||
|
||||
return match ($type) {
|
||||
UploadType::Image => PhotoAttachmentRequest::fromToken($uploadResult['token']),
|
||||
default => throw new LogicException(
|
||||
"Attachment creation for type '$type->value' is not yet implemented."
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,6 +100,48 @@ final class Client implements ClientApiInterface
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function upload(string $uri, mixed $fileContents, string $fileName): array
|
||||
{
|
||||
$boundary = '--------------------------' . microtime(true);
|
||||
$bodyStream = $this->streamFactory->createStream();
|
||||
|
||||
$bodyStream->write("--$boundary\r\n");
|
||||
$bodyStream->write("Content-Disposition: form-data; name=\"data\"; filename=\"{$fileName}\"\r\n");
|
||||
$bodyStream->write("Content-Type: application/octet-stream\r\n\r\n");
|
||||
|
||||
if (is_resource($fileContents)) {
|
||||
$bodyStream->write((string)stream_get_contents($fileContents));
|
||||
} else {
|
||||
$bodyStream->write((string)$fileContents);
|
||||
}
|
||||
$bodyStream->write("\r\n");
|
||||
$bodyStream->write("--$boundary--\r\n");
|
||||
|
||||
$request = $this->requestFactory
|
||||
->createRequest('POST', $uri)
|
||||
->withHeader('Content-Type', 'multipart/form-data; boundary=' . $boundary)
|
||||
->withBody($bodyStream);
|
||||
|
||||
try {
|
||||
$response = $this->httpClient->sendRequest($request);
|
||||
} catch (ClientExceptionInterface $e) {
|
||||
throw new NetworkException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
|
||||
$this->handleErrorResponse($response);
|
||||
|
||||
$responseBody = (string)$response->getBody();
|
||||
|
||||
try {
|
||||
return json_decode($responseBody, true, 512, JSON_THROW_ON_ERROR);
|
||||
} catch (JsonException $e) {
|
||||
throw new SerializationException('Failed to decode upload server response JSON.', 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the response for an error status code and throws a corresponding typed exception.
|
||||
*
|
||||
|
||||
@@ -24,4 +24,18 @@ interface ClientApiInterface
|
||||
* @throws SerializationException for JSON encoding/decoding failures.
|
||||
*/
|
||||
public function request(string $method, string $uri, array $queryParams = [], array $body = []): array;
|
||||
|
||||
/**
|
||||
* Performs a file download at the specified URL.
|
||||
*
|
||||
* @param string $uri URL received from the download API.
|
||||
* @param resource|string $fileContents File content (stream resource or string).
|
||||
* @param string $fileName The name of the file that will be sent to the server.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
* @throws ClientApiException
|
||||
* @throws NetworkException
|
||||
* @throws SerializationException
|
||||
*/
|
||||
public function upload(string $uri, mixed $fileContents, string $fileName): array;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Enums;
|
||||
|
||||
enum UploadType: string
|
||||
{
|
||||
case Image = 'image';
|
||||
case Video = 'video';
|
||||
case Audio = 'audio';
|
||||
case File = 'file';
|
||||
}
|
||||
@@ -8,6 +8,7 @@ use BushlanovDev\MaxMessengerBot\Models\BotInfo;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Message;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Result;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Subscription;
|
||||
use BushlanovDev\MaxMessengerBot\Models\UploadEndpoint;
|
||||
use ReflectionException;
|
||||
|
||||
/**
|
||||
@@ -81,4 +82,17 @@ class ModelFactory
|
||||
{
|
||||
return Message::fromArray($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Endpoint you should upload to your binaries.
|
||||
*
|
||||
* @param array<string, mixed> $data
|
||||
*
|
||||
* @return UploadEndpoint
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
public function createUploadEndpoint(array $data): UploadEndpoint
|
||||
{
|
||||
return UploadEndpoint::fromArray($data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Attributes\ArrayOf;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Request to attach image. All fields are mutually exclusive.
|
||||
*/
|
||||
final readonly class PhotoAttachmentPayload extends AbstractAttachmentPayload
|
||||
{
|
||||
/**
|
||||
* @param string|null $url Any external image URL you want to attach.
|
||||
* @param string|null $token Token of any existing attachment.
|
||||
* @param PhotoToken[]|null $photos Tokens were obtained after uploading images.
|
||||
*/
|
||||
public function __construct(
|
||||
public ?string $url = null,
|
||||
public ?string $token = null,
|
||||
#[ArrayOf(PhotoToken::class)]
|
||||
public ?array $photos = null,
|
||||
) {
|
||||
if (count(array_filter([$this->url, $this->token, $this->photos])) !== 1) {
|
||||
throw new InvalidArgumentException(
|
||||
'Provide exactly one of "url", "token", or "photos" for PhotoAttachmentPayload.'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Models\AbstractModel;
|
||||
|
||||
/**
|
||||
* Encoded information of uploaded image
|
||||
*/
|
||||
final readonly class PhotoToken extends AbstractModel
|
||||
{
|
||||
/**
|
||||
* @param string $token Encoded information of uploaded image.
|
||||
*/
|
||||
public function __construct(
|
||||
public string $token,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models\Attachments\Requests;
|
||||
|
||||
use BushlanovDev\MaxMessengerBot\Enums\AttachmentType;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\PhotoAttachmentPayload;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\PhotoToken;
|
||||
|
||||
/**
|
||||
* Request to attach some data to message.
|
||||
*/
|
||||
final readonly class PhotoAttachmentRequest extends AbstractAttachmentRequest
|
||||
{
|
||||
/**
|
||||
* Creates a request to attach an image by URL.
|
||||
*
|
||||
* @param string $url
|
||||
*
|
||||
* @return PhotoAttachmentRequest
|
||||
*/
|
||||
public static function fromUrl(string $url): self
|
||||
{
|
||||
return new self(new PhotoAttachmentPayload(url: $url));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a request to attach an image using the token received after uploading.
|
||||
*
|
||||
* @param string $token
|
||||
*
|
||||
* @return PhotoAttachmentRequest
|
||||
*/
|
||||
public static function fromToken(string $token): self
|
||||
{
|
||||
return new self(new PhotoAttachmentPayload(token: $token));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a request to attach an image using the tokens received after uploading.
|
||||
*
|
||||
* @param PhotoToken[] $photos
|
||||
*
|
||||
* @return PhotoAttachmentRequest
|
||||
*/
|
||||
public static function fromPhotos(array $photos): self
|
||||
{
|
||||
return new self(new PhotoAttachmentPayload(photos: $photos));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param PhotoAttachmentPayload $payload Request to attach image.
|
||||
*/
|
||||
private function __construct(PhotoAttachmentPayload $payload)
|
||||
{
|
||||
parent::__construct(AttachmentType::Image, $payload);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BushlanovDev\MaxMessengerBot\Models;
|
||||
|
||||
/**
|
||||
* Endpoint you should upload to your binaries
|
||||
*/
|
||||
final readonly class UploadEndpoint extends AbstractModel
|
||||
{
|
||||
/**
|
||||
* @param string $url URL to upload.
|
||||
* @param string|null $token Video or audio token for send message.
|
||||
*/
|
||||
public function __construct(
|
||||
public string $url,
|
||||
public ?string $token = null,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -11,10 +11,14 @@ use BushlanovDev\MaxMessengerBot\Enums\AttachmentType;
|
||||
use BushlanovDev\MaxMessengerBot\Enums\ButtonType;
|
||||
use BushlanovDev\MaxMessengerBot\Enums\MessageFormat;
|
||||
use BushlanovDev\MaxMessengerBot\Enums\UpdateType;
|
||||
use BushlanovDev\MaxMessengerBot\Enums\UploadType;
|
||||
use BushlanovDev\MaxMessengerBot\ModelFactory;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Buttons\CallbackButton;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\InlineKeyboardPayload;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\PhotoAttachmentPayload;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Payloads\PhotoToken;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Requests\InlineKeyboardAttachmentRequest;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Attachments\Requests\PhotoAttachmentRequest;
|
||||
use BushlanovDev\MaxMessengerBot\Models\BotInfo;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Message;
|
||||
use BushlanovDev\MaxMessengerBot\Models\MessageBody;
|
||||
@@ -22,6 +26,8 @@ use BushlanovDev\MaxMessengerBot\Models\Recipient;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Result;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Sender;
|
||||
use BushlanovDev\MaxMessengerBot\Models\Subscription;
|
||||
use BushlanovDev\MaxMessengerBot\Models\UploadEndpoint;
|
||||
use InvalidArgumentException;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
@@ -42,6 +48,10 @@ use ReflectionClass;
|
||||
#[UsesClass(CallbackButton::class)]
|
||||
#[UsesClass(InlineKeyboardPayload::class)]
|
||||
#[UsesClass(InlineKeyboardAttachmentRequest::class)]
|
||||
#[UsesClass(PhotoToken::class)]
|
||||
#[UsesClass(PhotoAttachmentRequest::class)]
|
||||
#[UsesClass(PhotoAttachmentPayload::class)]
|
||||
#[UsesClass(UploadEndpoint::class)]
|
||||
final class ApiTest extends TestCase
|
||||
{
|
||||
private MockObject&ClientApiInterface $clientMock;
|
||||
@@ -368,4 +378,76 @@ final class ApiTest extends TestCase
|
||||
|
||||
$this->assertSame($expectedMessageObject, $result);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function uploadAttachmentSuccessfullyUploadsImageAndReturnsAttachment(): void
|
||||
{
|
||||
$filePath = tempnam(sys_get_temp_dir(), 'test_upload_');
|
||||
file_put_contents($filePath, 'fake-image-content');
|
||||
|
||||
$uploadType = UploadType::Image;
|
||||
$uploadUrl = 'https://upload.server/gohere';
|
||||
$uploadToken = 'FINAL_TOKEN_123';
|
||||
|
||||
$getUploadUrlResponse = ['url' => $uploadUrl];
|
||||
$uploadResponse = ['token' => $uploadToken];
|
||||
$expectedEndpoint = new UploadEndpoint($uploadUrl);
|
||||
$expectedAttachment = PhotoAttachmentRequest::fromToken($uploadToken);
|
||||
|
||||
$this->clientMock
|
||||
->expects($this->once())
|
||||
->method('request')
|
||||
->with('POST', '/uploads', ['type' => $uploadType->value])
|
||||
->willReturn($getUploadUrlResponse);
|
||||
|
||||
$this->modelFactoryMock
|
||||
->expects($this->once())
|
||||
->method('createUploadEndpoint')
|
||||
->with($getUploadUrlResponse)
|
||||
->willReturn($expectedEndpoint);
|
||||
|
||||
$this->clientMock
|
||||
->expects($this->once())
|
||||
->method('upload')
|
||||
->with($uploadUrl, $this->isResource(), basename($filePath))
|
||||
->willReturn($uploadResponse);
|
||||
|
||||
$result = $this->api->uploadAttachment($uploadType, $filePath);
|
||||
|
||||
$this->assertEquals($expectedAttachment, $result);
|
||||
|
||||
unlink($filePath);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function uploadAttachmentForMultiplePhotosReturnsCorrectAttachment(): void
|
||||
{
|
||||
$filePath = tempnam(sys_get_temp_dir(), 'test_');
|
||||
file_put_contents($filePath, 'content');
|
||||
|
||||
$getUploadUrlResponse = ['url' => 'http://upload.server'];
|
||||
$expectedEndpoint = new UploadEndpoint('http://upload.server');
|
||||
|
||||
$uploadResponse = ['token' => 'token'];
|
||||
|
||||
$expectedAttachment = PhotoAttachmentRequest::fromToken('token');
|
||||
|
||||
$this->clientMock->method('request')->willReturn($getUploadUrlResponse);
|
||||
$this->modelFactoryMock->method('createUploadEndpoint')->willReturn($expectedEndpoint);
|
||||
$this->clientMock->method('upload')->willReturn($uploadResponse);
|
||||
|
||||
$result = $this->api->uploadAttachment(UploadType::Image, $filePath);
|
||||
|
||||
$this->assertEquals($expectedAttachment, $result);
|
||||
|
||||
unlink($filePath);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function uploadAttachmentThrowsExceptionForNonExistentFile(): void
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
$this->expectExceptionMessageMatches('/File not found or not readable/');
|
||||
$this->api->uploadAttachment(UploadType::Image, '/path/to/non/existent/file.jpg');
|
||||
}
|
||||
}
|
||||
|
||||
+48
-14
@@ -13,6 +13,7 @@ use BushlanovDev\MaxMessengerBot\Exceptions\NotFoundException;
|
||||
use BushlanovDev\MaxMessengerBot\Exceptions\RateLimitExceededException;
|
||||
use BushlanovDev\MaxMessengerBot\Exceptions\SerializationException;
|
||||
use BushlanovDev\MaxMessengerBot\Exceptions\UnauthorizedException;
|
||||
use GuzzleHttp\Psr7\HttpFactory;
|
||||
use InvalidArgumentException;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
@@ -37,7 +38,7 @@ final class ClientTest extends TestCase
|
||||
|
||||
private MockObject&ClientInterface $httpClientMock;
|
||||
private MockObject&RequestFactoryInterface $requestFactoryMock;
|
||||
private MockObject&StreamFactoryInterface $streamFactoryMock;
|
||||
private StreamFactoryInterface $streamFactory;
|
||||
private MockObject&RequestInterface $requestMock;
|
||||
private MockObject&ResponseInterface $responseMock;
|
||||
private MockObject&StreamInterface $streamMock;
|
||||
@@ -56,7 +57,7 @@ final class ClientTest extends TestCase
|
||||
// Create mocks for all PSR interfaces
|
||||
$this->httpClientMock = $this->createMock(ClientInterface::class);
|
||||
$this->requestFactoryMock = $this->createMock(RequestFactoryInterface::class);
|
||||
$this->streamFactoryMock = $this->createMock(StreamFactoryInterface::class);
|
||||
$this->streamFactory = new HttpFactory();
|
||||
$this->requestMock = $this->createMock(RequestInterface::class);
|
||||
$this->responseMock = $this->createMock(ResponseInterface::class);
|
||||
$this->streamMock = $this->createMock(StreamInterface::class);
|
||||
@@ -65,15 +66,13 @@ final class ClientTest extends TestCase
|
||||
$this->requestFactoryMock->method('createRequest')->willReturn($this->requestMock);
|
||||
$this->responseMock->method('getBody')->willReturn($this->streamMock);
|
||||
$this->httpClientMock->method('sendRequest')->willReturn($this->responseMock);
|
||||
$this->requestMock->method('withBody')->willReturn($this->requestMock);
|
||||
$this->requestMock->method('withHeader')->willReturn($this->requestMock);
|
||||
|
||||
// Instantiate the System Under Test (SUT)
|
||||
$this->client = new Client(
|
||||
self::FAKE_TOKEN,
|
||||
$this->httpClientMock,
|
||||
$this->requestFactoryMock,
|
||||
$this->streamFactoryMock,
|
||||
$this->streamFactory,
|
||||
self::API_BASE_URL,
|
||||
self::API_VERSION,
|
||||
);
|
||||
@@ -85,7 +84,7 @@ final class ClientTest extends TestCase
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
$this->expectExceptionMessage('Access token cannot be empty.');
|
||||
|
||||
new Client('', $this->httpClientMock, $this->requestFactoryMock, $this->streamFactoryMock, '', '');
|
||||
new Client('', $this->httpClientMock, $this->requestFactoryMock, $this->streamFactory, '', '');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
@@ -134,7 +133,6 @@ final class ClientTest extends TestCase
|
||||
],
|
||||
];
|
||||
$responsePayload = ['success' => true];
|
||||
|
||||
$expectedUrl = self::API_BASE_URL . $uri . '?' . http_build_query([
|
||||
'access_token' => self::FAKE_TOKEN,
|
||||
'v' => self::API_VERSION
|
||||
@@ -146,16 +144,13 @@ final class ClientTest extends TestCase
|
||||
->with('POST', $expectedUrl)
|
||||
->willReturn($this->requestMock);
|
||||
|
||||
$this->streamFactoryMock
|
||||
->expects($this->once())
|
||||
->method('createStream')
|
||||
->with(json_encode($requestBody))
|
||||
->willReturn($this->streamMock);
|
||||
|
||||
$this->requestMock
|
||||
->expects($this->once())
|
||||
->method('withBody')
|
||||
->with($this->streamMock)
|
||||
->with($this->callback(function (StreamInterface $stream) use ($requestBody) {
|
||||
$this->assertSame(json_encode($requestBody), $stream->getContents());
|
||||
return true;
|
||||
}))
|
||||
->willReturn($this->requestMock);
|
||||
|
||||
$this->requestMock
|
||||
@@ -273,4 +268,43 @@ final class ClientTest extends TestCase
|
||||
throw $e; // Re-throw for PHPUnit to catch the expected exception type
|
||||
}
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function uploadMethodSendsCorrectMultipartRequest(): void
|
||||
{
|
||||
$uploadUrl = 'https://upload.server/path';
|
||||
$fileContents = 'fake-image-binary-data';
|
||||
$fileName = 'test.jpg';
|
||||
$responsePayload = ['token' => 'upload_successful_token'];
|
||||
|
||||
$this->requestMock
|
||||
->expects($this->once())
|
||||
->method('withBody')
|
||||
->with($this->callback(function (StreamInterface $stream) use ($fileContents, $fileName) {
|
||||
$stream->rewind();
|
||||
$body = $stream->getContents();
|
||||
$this->assertStringContainsString('Content-Disposition: form-data; name="data"; filename="' . $fileName . '"', $body);
|
||||
$this->assertStringContainsString($fileContents, $body);
|
||||
return true;
|
||||
}))
|
||||
->willReturn($this->requestMock);
|
||||
|
||||
$this->requestMock
|
||||
->expects($this->once())
|
||||
->method('withHeader')
|
||||
->with($this->stringStartsWith('Content-Type'), $this->stringStartsWith('multipart/form-data'))
|
||||
->willReturn($this->requestMock);
|
||||
|
||||
$this->requestFactoryMock
|
||||
->expects($this->once())
|
||||
->method('createRequest')
|
||||
->with('POST', $uploadUrl)
|
||||
->willReturn($this->requestMock);
|
||||
|
||||
$this->responseMock->method('getStatusCode')->willReturn(200);
|
||||
$this->streamMock->method('__toString')->willReturn(json_encode($responsePayload));
|
||||
|
||||
$result = $this->client->upload($uploadUrl, $fileContents, $fileName);
|
||||
$this->assertSame($responsePayload, $result);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user