First unit tests, misc fixes
This commit is contained in:
		@@ -53,7 +53,7 @@ if (isset($opts['C'])) {
 | 
			
		||||
        key: foo.key
 | 
			
		||||
 | 
			
		||||
    publish:
 | 
			
		||||
      overwrite_id: false
 | 
			
		||||
      overwrite_ids: false
 | 
			
		||||
      reject_duplicates: true
 | 
			
		||||
 | 
			
		||||
    subscribe:
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@ server:
 | 
			
		||||
 | 
			
		||||
publish:
 | 
			
		||||
  # 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_duplicates: true
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -23,11 +23,11 @@ class Message
 | 
			
		||||
 | 
			
		||||
    public function __construct(
 | 
			
		||||
        array $topic,
 | 
			
		||||
        ?string $type,
 | 
			
		||||
        ?string $data,
 | 
			
		||||
        ?bool $private,
 | 
			
		||||
        ?string $id,
 | 
			
		||||
        ?int $retry
 | 
			
		||||
        ?string $data=null,
 | 
			
		||||
        ?string $type=null,
 | 
			
		||||
        ?bool $private=null,
 | 
			
		||||
        ?string $id=null,
 | 
			
		||||
        ?int $retry=null
 | 
			
		||||
    )
 | 
			
		||||
    {
 | 
			
		||||
        $this->topic = $topic;
 | 
			
		||||
@@ -60,7 +60,7 @@ class Message
 | 
			
		||||
            topic: (array)$data['topic'],
 | 
			
		||||
            type: $data['type']??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,
 | 
			
		||||
            retry: $data['retry']??null,
 | 
			
		||||
        );
 | 
			
		||||
 
 | 
			
		||||
@@ -7,4 +7,6 @@ interface SubscriberInterface
 | 
			
		||||
    public function deliver(Message $message): void;
 | 
			
		||||
 | 
			
		||||
    public function isAuthorized(): bool;
 | 
			
		||||
 | 
			
		||||
    public function getPayload(): ?array;
 | 
			
		||||
}
 | 
			
		||||
@@ -108,5 +108,19 @@ class Configuration
 | 
			
		||||
        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;
 | 
			
		||||
 | 
			
		||||
use LDAP\Result;
 | 
			
		||||
use NoccyLabs\Mercureact\Broker\Message;
 | 
			
		||||
use NoccyLabs\Mercureact\Broker\SseSubscriber;
 | 
			
		||||
use NoccyLabs\Mercureact\Broker\TopicManager;
 | 
			
		||||
@@ -24,6 +25,10 @@ class MercureHandler
 | 
			
		||||
{
 | 
			
		||||
    private LoopInterface $loop;
 | 
			
		||||
 | 
			
		||||
    private array $seenMessageIds = [];
 | 
			
		||||
 | 
			
		||||
    private int $seenIdHistorySize = 100;
 | 
			
		||||
 | 
			
		||||
    public function __construct(
 | 
			
		||||
        private Configuration $config,
 | 
			
		||||
        private TopicManager $topicManager,
 | 
			
		||||
@@ -31,6 +36,7 @@ class MercureHandler
 | 
			
		||||
    )
 | 
			
		||||
    {
 | 
			
		||||
        $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
 | 
			
		||||
        // TODO add a configurable for this
 | 
			
		||||
        if (!isset($data['id'])) {
 | 
			
		||||
        if (empty($data['id']) || $this->config->getOverwriteMessageIds()) {
 | 
			
		||||
            $data['id'] = (string)Uuid::v7();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -176,7 +189,7 @@ class MercureHandler
 | 
			
		||||
        $message = Message::fromData($data);
 | 
			
		||||
 | 
			
		||||
        $this->loop->futureTick(function () use ($message) {
 | 
			
		||||
            $this->publishMercureMessage($message);
 | 
			
		||||
            $this->topicManager->publish($message);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        return Response::plaintext("urn:uuid:".$message->id."\n");
 | 
			
		||||
@@ -201,15 +214,4 @@ class MercureHandler
 | 
			
		||||
        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));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user