First unit tests, misc fixes
This commit is contained in:
		@@ -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));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user