Skip to content

Commit 9d25131

Browse files
authored
Fix API parameter handling and add unit tests for API classes (#2)
1 parent 17c10fd commit 9d25131

12 files changed

Lines changed: 971 additions & 32 deletions

README.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,29 @@ $botEventListener->onCommand(
121121
);
122122

123123
// Start long polling (must be called after all handlers are registered)
124-
$botEventListener->listen(pollTime: 30);
124+
$botEventListener->listen(
125+
pollTime: 30,
126+
onException: function (
127+
\Exception $exception,
128+
\BelkaTech\VkTeamsBot\Event\EventDto $event
129+
): void {
130+
// Log the error
131+
$this->logger->error('Some text', [
132+
'event_id' => $event->eventId,
133+
'event_type' => $event->type,
134+
'event_payload' => $event->payload,
135+
'exception' => $exception,
136+
]);
137+
error_log($exception->getMessage());
138+
139+
// Or catch exception to an error reporting system
140+
$this->sentry->captureException($exception);
141+
142+
// On exception loop continues,
143+
// you can re-throw the exception to force stop the loop
144+
throw $exception;
145+
},
146+
);
125147

126148
// Stop the listener programmatically (e.g. from a handler)
127149
$botEventListener->stop();

src/Api/ChatsApi.php

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -149,11 +149,16 @@ public function getAdmins(
149149
*/
150150
public function getMembers(
151151
string $chatId,
152+
?string $cursor = null,
152153
): array {
154+
$params = ['chatId' => $chatId];
155+
156+
if ($cursor !== null) {
157+
$params['cursor'] = $cursor;
158+
}
159+
153160
/** @phpstan-ignore return.type */
154-
return $this->httpClient->get('/v1/chats/getMembers', [
155-
'chatId' => $chatId,
156-
]);
161+
return $this->httpClient->get('/v1/chats/getMembers', $params);
157162
}
158163

159164
/**
@@ -283,7 +288,7 @@ public function setAvatar(
283288
string $imagePath,
284289
): array {
285290
/** @phpstan-ignore return.type */
286-
return $this->httpClient->post(
291+
return $this->httpClient->postMultipart(
287292
'/v1/chats/avatar/set',
288293
['chatId' => $chatId],
289294
$imagePath,

src/Api/MessagesApi.php

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ public function sendText(
4747
'text' => $text,
4848
'replyMsgId' => $replyMsgId,
4949
'forwardChatId' => $forwardChatId,
50-
'forwardMsgId' => json_encode($forwardMsgId, flags: JSON_THROW_ON_ERROR),
51-
'inlineKeyboardMarkup' => json_encode($inlineKeyboardMarkup, flags: JSON_THROW_ON_ERROR),
50+
'forwardMsgId' => $forwardMsgId !== null ? json_encode($forwardMsgId, flags: JSON_THROW_ON_ERROR) : null,
51+
'inlineKeyboardMarkup' => $inlineKeyboardMarkup !== null ? json_encode($inlineKeyboardMarkup, flags: JSON_THROW_ON_ERROR) : null,
5252
'format' => $format,
5353
'parseMode' => ($parseMode ?? $this->parseMode)->value,
5454
]);
@@ -127,15 +127,15 @@ public function sendVoice(
127127
'fileId' => $fileId,
128128
'replyMsgId' => $replyMsgId,
129129
'forwardChatId' => $forwardChatId,
130-
'forwardMsgId' => json_encode($forwardMsgId, flags: JSON_THROW_ON_ERROR),
131-
'inlineKeyboardMarkup' => json_encode($inlineKeyboardMarkup, flags: JSON_THROW_ON_ERROR),
130+
'forwardMsgId' => $forwardMsgId !== null ? json_encode($forwardMsgId, flags: JSON_THROW_ON_ERROR) : null,
131+
'inlineKeyboardMarkup' => $inlineKeyboardMarkup !== null ? json_encode($inlineKeyboardMarkup, flags: JSON_THROW_ON_ERROR) : null,
132132
];
133133

134134
if ($filePath !== null) {
135135
unset($params['fileId']);
136136

137137
/** @phpstan-ignore return.type */
138-
return $this->httpClient->post(
138+
return $this->httpClient->postMultipart(
139139
'/v1/messages/sendVoice',
140140
$params,
141141
$filePath,
@@ -170,7 +170,7 @@ public function editText(
170170
'chatId' => $chatId,
171171
'msgId' => $msgId,
172172
'text' => $text,
173-
'inlineKeyboardMarkup' => json_encode($inlineKeyboardMarkup, flags: JSON_THROW_ON_ERROR),
173+
'inlineKeyboardMarkup' => $inlineKeyboardMarkup !== null ? json_encode($inlineKeyboardMarkup, flags: JSON_THROW_ON_ERROR) : null,
174174
'format' => $format,
175175
'parseMode' => ($parseMode ?? $this->parseMode)->value,
176176
]);
@@ -299,15 +299,15 @@ private function sendMedia(
299299
'caption' => $caption,
300300
'replyMsgId' => $replyMsgId,
301301
'forwardChatId' => $forwardChatId,
302-
'forwardMsgId' => json_encode($forwardMsgId, flags: JSON_THROW_ON_ERROR),
303-
'inlineKeyboardMarkup' => json_encode($inlineKeyboardMarkup, flags: JSON_THROW_ON_ERROR),
302+
'forwardMsgId' => $forwardMsgId !== null ? json_encode($forwardMsgId, flags: JSON_THROW_ON_ERROR) : null,
303+
'inlineKeyboardMarkup' => $inlineKeyboardMarkup !== null ? json_encode($inlineKeyboardMarkup, flags: JSON_THROW_ON_ERROR) : null,
304304
'format' => $format,
305305
'parseMode' => ($parseMode ?? $this->parseMode)->value,
306306
];
307307

308308
if ($filePath !== null) {
309309
/** @phpstan-ignore return.type */
310-
return $this->httpClient->post(
310+
return $this->httpClient->postMultipart(
311311
$endpoint,
312312
$params,
313313
$filePath,

src/BotEventListener.php

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ public function onCommand(
3131
string $command,
3232
\Closure $handler,
3333
): void {
34+
if (isset($this->commandHandlers[$command])) {
35+
throw new InvalidArgumentException(
36+
"Command handler for '{$command}' is already registered",
37+
);
38+
}
39+
3440
$this->commandHandlers[$command] = $handler;
3541
}
3642

@@ -113,9 +119,11 @@ public function stop(): void
113119

114120
/**
115121
* @param int $pollTime Maximum polling request duration (1-60 sec)
122+
* @param ?\Closure(\Exception, EventDto): void $onException
116123
*/
117124
public function listen(
118125
int $pollTime,
126+
?\Closure $onException = null,
119127
): void {
120128
if ($pollTime < 1 || $pollTime > 60) {
121129
throw new InvalidArgumentException(
@@ -144,23 +152,36 @@ public function listen(
144152
payload: $event['payload'],
145153
);
146154

147-
if ($eventType === EventTypeEnum::MessageNew) {
148-
$text = isset($event['payload']['text']) && is_string($event['payload']['text'])
149-
? $event['payload']['text']
150-
: '';
151-
foreach ($this->commandHandlers as $command => $handler) {
152-
if (str_starts_with($text, $command)) {
153-
$handler($this->bot, $eventDto);
154-
155-
continue 2;
155+
try {
156+
if ($eventType === EventTypeEnum::MessageNew) {
157+
$text = isset($event['payload']['text']) && is_string($event['payload']['text'])
158+
? $event['payload']['text']
159+
: '';
160+
161+
foreach ($this->commandHandlers as $command => $handler) {
162+
if (
163+
$text === $command
164+
|| str_starts_with($text, $command . ' ')
165+
|| str_starts_with($text, $command . '@')
166+
) {
167+
$handler($this->bot, $eventDto);
168+
169+
continue 2;
170+
}
156171
}
157172
}
158-
}
159173

160-
if (array_key_exists($eventType->value, $this->handlers)) {
161-
foreach ($this->handlers[$eventType->value] as $handler) {
162-
$handler($this->bot, $eventDto);
174+
if (array_key_exists($eventType->value, $this->handlers)) {
175+
foreach ($this->handlers[$eventType->value] as $handler) {
176+
$handler($this->bot, $eventDto);
177+
}
163178
}
179+
} catch (\Exception $e) {
180+
if ($onException === null) {
181+
throw $e;
182+
}
183+
184+
$onException($e, $eventDto);
164185
}
165186
}
166187
}

src/Http/HttpClient.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public function get(
5353
*
5454
* @throws ClientExceptionInterface
5555
*/
56-
public function post(
56+
public function postMultipart(
5757
string $path,
5858
array $params,
5959
string $filePath,
@@ -110,7 +110,7 @@ private function filterParams(
110110
): array {
111111
return array_filter(
112112
$params,
113-
static fn(mixed $value): bool => $value !== null && $value !== 'null',
113+
static fn(mixed $value): bool => $value !== null,
114114
);
115115
}
116116
}

src/Keyboard/Keyboard.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
final class Keyboard implements \JsonSerializable
88
{
99
/** @var list<list<Button>> */
10-
public array $rows = [];
10+
private array $rows = [];
1111

1212
/**
1313
* @param list<Button> $row

test/Spy/HttpClientSpy.php

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace BelkaTech\VkTeamsBot\Test\Spy;
6+
7+
use BelkaTech\VkTeamsBot\Http\HttpClient;
8+
9+
final class HttpClientSpy extends HttpClient
10+
{
11+
/**
12+
* @var list<array{
13+
* string,
14+
* string,
15+
* array<string, mixed>,
16+
* }|array{
17+
* string,
18+
* string,
19+
* array<string, mixed>,
20+
* string,
21+
* }>
22+
*/
23+
public array $calls = [];
24+
25+
/** @var array<string, mixed> */
26+
private array $getResponse;
27+
28+
/** @var array<string, mixed> */
29+
private array $postMultipartResponse;
30+
31+
/**
32+
* @param array<string, mixed> $getResponse
33+
* @param array<string, mixed> $postMultipartResponse
34+
*/
35+
public function __construct(
36+
array $getResponse = ['ok' => true],
37+
array $postMultipartResponse = ['ok' => true],
38+
) {
39+
$this->getResponse = $getResponse;
40+
$this->postMultipartResponse = $postMultipartResponse;
41+
}
42+
43+
public function get(
44+
string $path,
45+
array $params = [],
46+
): array {
47+
$this->calls[] = ['get', $path, $params];
48+
49+
return $this->getResponse;
50+
}
51+
52+
public function postMultipart(
53+
string $path,
54+
array $params,
55+
string $filePath,
56+
): array {
57+
$this->calls[] = ['postMultipart', $path, $params, $filePath];
58+
59+
return $this->postMultipartResponse;
60+
}
61+
}

0 commit comments

Comments
 (0)