First unit tests, misc fixes
This commit is contained in:
parent
b3476881e1
commit
8be2e81054
@ -53,7 +53,7 @@ if (isset($opts['C'])) {
|
|||||||
key: foo.key
|
key: foo.key
|
||||||
|
|
||||||
publish:
|
publish:
|
||||||
overwrite_id: false
|
overwrite_ids: false
|
||||||
reject_duplicates: true
|
reject_duplicates: true
|
||||||
|
|
||||||
subscribe:
|
subscribe:
|
||||||
|
@ -21,7 +21,7 @@ server:
|
|||||||
|
|
||||||
publish:
|
publish:
|
||||||
# Assign a UUID to published messages even if one is already set in the message
|
# Assign a UUID to published messages even if one is already set in the message
|
||||||
overwrite_id: false
|
overwrite_ids: false
|
||||||
# Reject messages with previously seen IDs
|
# Reject messages with previously seen IDs
|
||||||
reject_duplicates: true
|
reject_duplicates: true
|
||||||
|
|
||||||
|
@ -23,11 +23,11 @@ class Message
|
|||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
array $topic,
|
array $topic,
|
||||||
?string $type,
|
?string $data=null,
|
||||||
?string $data,
|
?string $type=null,
|
||||||
?bool $private,
|
?bool $private=null,
|
||||||
?string $id,
|
?string $id=null,
|
||||||
?int $retry
|
?int $retry=null
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
$this->topic = $topic;
|
$this->topic = $topic;
|
||||||
@ -60,7 +60,7 @@ class Message
|
|||||||
topic: (array)$data['topic'],
|
topic: (array)$data['topic'],
|
||||||
type: $data['type']??null,
|
type: $data['type']??null,
|
||||||
data: $data['data']??null,
|
data: $data['data']??null,
|
||||||
private: match ($data['private']??null) { "on" => true, null => null, default => false },
|
private: match ($data['private']??null) { "on" => true, true => true, null => null, default => false },
|
||||||
id: $data['id']??null,
|
id: $data['id']??null,
|
||||||
retry: $data['retry']??null,
|
retry: $data['retry']??null,
|
||||||
);
|
);
|
||||||
|
@ -7,4 +7,6 @@ interface SubscriberInterface
|
|||||||
public function deliver(Message $message): void;
|
public function deliver(Message $message): void;
|
||||||
|
|
||||||
public function isAuthorized(): bool;
|
public function isAuthorized(): bool;
|
||||||
|
|
||||||
|
public function getPayload(): ?array;
|
||||||
}
|
}
|
@ -108,5 +108,19 @@ class Configuration
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getOverwriteMessageIds(): bool
|
||||||
|
{
|
||||||
|
return $this->config['publish.overwrite_ids']??true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRejectDuplicateMessages(): bool
|
||||||
|
{
|
||||||
|
return $this->config['publish.reject_duplicates']??true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDuplicateIdHistorySize(): int
|
||||||
|
{
|
||||||
|
return 50;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace NoccyLabs\Mercureact\Http\Middleware;
|
namespace NoccyLabs\Mercureact\Http\Middleware;
|
||||||
|
|
||||||
|
use LDAP\Result;
|
||||||
use NoccyLabs\Mercureact\Broker\Message;
|
use NoccyLabs\Mercureact\Broker\Message;
|
||||||
use NoccyLabs\Mercureact\Broker\SseSubscriber;
|
use NoccyLabs\Mercureact\Broker\SseSubscriber;
|
||||||
use NoccyLabs\Mercureact\Broker\TopicManager;
|
use NoccyLabs\Mercureact\Broker\TopicManager;
|
||||||
@ -24,6 +25,10 @@ class MercureHandler
|
|||||||
{
|
{
|
||||||
private LoopInterface $loop;
|
private LoopInterface $loop;
|
||||||
|
|
||||||
|
private array $seenMessageIds = [];
|
||||||
|
|
||||||
|
private int $seenIdHistorySize = 100;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private Configuration $config,
|
private Configuration $config,
|
||||||
private TopicManager $topicManager,
|
private TopicManager $topicManager,
|
||||||
@ -31,6 +36,7 @@ class MercureHandler
|
|||||||
)
|
)
|
||||||
{
|
{
|
||||||
$this->loop = $loop ?? Loop::get();
|
$this->loop = $loop ?? Loop::get();
|
||||||
|
$this->seenIdHistorySize = $this->config->getDuplicateIdHistorySize();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -166,9 +172,16 @@ class MercureHandler
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->config->getRejectDuplicateMessages() && !empty($data['id'])) {
|
||||||
|
if (in_array($data['id'], $this->seenMessageIds)) {
|
||||||
|
return Response::plaintext("Duplicate message id")->withStatus(Response::STATUS_BAD_REQUEST);
|
||||||
|
}
|
||||||
|
array_push($this->seenMessageIds, $data['id']);
|
||||||
|
$this->seenMessageIds = array_slice($this->seenMessageIds, -100, 100);
|
||||||
|
}
|
||||||
|
|
||||||
// Put an id in there if none already
|
// Put an id in there if none already
|
||||||
// TODO add a configurable for this
|
if (empty($data['id']) || $this->config->getOverwriteMessageIds()) {
|
||||||
if (!isset($data['id'])) {
|
|
||||||
$data['id'] = (string)Uuid::v7();
|
$data['id'] = (string)Uuid::v7();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,7 +189,7 @@ class MercureHandler
|
|||||||
$message = Message::fromData($data);
|
$message = Message::fromData($data);
|
||||||
|
|
||||||
$this->loop->futureTick(function () use ($message) {
|
$this->loop->futureTick(function () use ($message) {
|
||||||
$this->publishMercureMessage($message);
|
$this->topicManager->publish($message);
|
||||||
});
|
});
|
||||||
|
|
||||||
return Response::plaintext("urn:uuid:".$message->id."\n");
|
return Response::plaintext("urn:uuid:".$message->id."\n");
|
||||||
@ -201,15 +214,4 @@ class MercureHandler
|
|||||||
return ($matched == count($topic));
|
return ($matched == count($topic));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param Message $message
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
private function publishMercureMessage(Message $message): void
|
|
||||||
{
|
|
||||||
$this->topicManager->publish($message);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
70
tests/Broker/MessageTest.php
Normal file
70
tests/Broker/MessageTest.php
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\Mercureact\Broker;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\Attributes\CoversClass;
|
||||||
|
|
||||||
|
#[CoversClass(Message::class)]
|
||||||
|
class MessageTest extends \PHPUnit\Framework\TestCase
|
||||||
|
{
|
||||||
|
|
||||||
|
public function testCreatingMessageFromData()
|
||||||
|
{
|
||||||
|
$message = Message::fromData([
|
||||||
|
'id' => '1a',
|
||||||
|
'topic' => '2b',
|
||||||
|
'data' => '3c',
|
||||||
|
'type' => '4d',
|
||||||
|
'retry' => 42,
|
||||||
|
'private' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertEquals("1a", $message->id);
|
||||||
|
$this->assertEquals(["2b"], $message->topic);
|
||||||
|
$this->assertEquals("3c", $message->data);
|
||||||
|
$this->assertEquals("4d", $message->type);
|
||||||
|
$this->assertEquals(42, $message->retry);
|
||||||
|
$this->assertEquals(true, $message->private);
|
||||||
|
|
||||||
|
$message = Message::fromData([
|
||||||
|
'id' => '1a',
|
||||||
|
'topic' => ['2b'],
|
||||||
|
'data' => '3c',
|
||||||
|
'type' => '4d',
|
||||||
|
'retry' => 42,
|
||||||
|
'private' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertEquals("1a", $message->id);
|
||||||
|
$this->assertEquals(["2b"], $message->topic);
|
||||||
|
$this->assertEquals("3c", $message->data);
|
||||||
|
$this->assertEquals("4d", $message->type);
|
||||||
|
$this->assertEquals(42, $message->retry);
|
||||||
|
$this->assertEquals(true, $message->private);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCreatingSseFromMessage()
|
||||||
|
{
|
||||||
|
$message = Message::fromData([
|
||||||
|
'id' => '1a',
|
||||||
|
'topic' => '2b',
|
||||||
|
'data' => '3c',
|
||||||
|
'type' => '4d',
|
||||||
|
'retry' => 42,
|
||||||
|
'private' => true,
|
||||||
|
]);
|
||||||
|
$sse = $message->toString();
|
||||||
|
|
||||||
|
$this->assertEquals(<<<EXPECT
|
||||||
|
event: 4d
|
||||||
|
retry: 42
|
||||||
|
id: 1a
|
||||||
|
data: 3c
|
||||||
|
|
||||||
|
|
||||||
|
EXPECT, $sse);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
63
tests/Broker/TopicTest.php
Normal file
63
tests/Broker/TopicTest.php
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace NoccyLabs\Mercureact\Broker;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\Attributes\CoversClass;
|
||||||
|
|
||||||
|
#[CoversClass(Topic::class)]
|
||||||
|
class TopicTest extends \PHPUnit\Framework\TestCase
|
||||||
|
{
|
||||||
|
|
||||||
|
public function testPublicMessagesAreDeliveredToAllSubscribers()
|
||||||
|
{
|
||||||
|
$authorizedSubscriber = new class implements SubscriberInterface {
|
||||||
|
public array $messages = [];
|
||||||
|
public function isAuthorized():bool { return true; }
|
||||||
|
public function deliver(Message $message):void { $this->messages[] = $message; }
|
||||||
|
public function getPayload(): ?array { return null; }
|
||||||
|
};
|
||||||
|
$unauthorizedSubscriber = new class implements SubscriberInterface {
|
||||||
|
public array $messages = [];
|
||||||
|
public function isAuthorized():bool { return false; }
|
||||||
|
public function deliver(Message $message):void { $this->messages[] = $message; }
|
||||||
|
public function getPayload(): ?array { return null; }
|
||||||
|
};
|
||||||
|
|
||||||
|
$topic = new Topic("foo");
|
||||||
|
$topic->addSubscriber($authorizedSubscriber);
|
||||||
|
$topic->addSubscriber($unauthorizedSubscriber);
|
||||||
|
|
||||||
|
$message = new Message(topic:["foo"], data:"test", private:false);
|
||||||
|
$topic->publish($message);
|
||||||
|
|
||||||
|
$this->assertEquals(1, count($authorizedSubscriber->messages));
|
||||||
|
$this->assertEquals(1, count($unauthorizedSubscriber->messages));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPrivateMessagesAreNotDeliveredToUnauthorizedSubscribers()
|
||||||
|
{
|
||||||
|
$authorizedSubscriber = new class implements SubscriberInterface {
|
||||||
|
public array $messages = [];
|
||||||
|
public function isAuthorized():bool { return true; }
|
||||||
|
public function deliver(Message $message):void { $this->messages[] = $message; }
|
||||||
|
public function getPayload(): ?array { return null; }
|
||||||
|
};
|
||||||
|
$unauthorizedSubscriber = new class implements SubscriberInterface {
|
||||||
|
public array $messages = [];
|
||||||
|
public function isAuthorized():bool { return false; }
|
||||||
|
public function deliver(Message $message):void { $this->messages[] = $message; }
|
||||||
|
public function getPayload(): ?array { return null; }
|
||||||
|
};
|
||||||
|
|
||||||
|
$topic = new Topic("foo");
|
||||||
|
$topic->addSubscriber($authorizedSubscriber);
|
||||||
|
$topic->addSubscriber($unauthorizedSubscriber);
|
||||||
|
|
||||||
|
$message = new Message(topic:["foo"], data:"test", private:true);
|
||||||
|
$topic->publish($message);
|
||||||
|
|
||||||
|
$this->assertEquals(1, count($authorizedSubscriber->messages));
|
||||||
|
$this->assertEquals(0, count($unauthorizedSubscriber->messages));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user