@@ -20,22 +20,32 @@ class PublishSubscribeTest extends TestCase
2020{
2121 public function publishSubscribeData (): array
2222 {
23- return [
24- [false , 'test/ foo/bar/baz ' , 'test/ foo/bar/baz ' , 'hello world ' , []],
25- [false , 'test/ foo/bar/+ ' , 'test/ foo/bar/baz ' , 'hello world ' , ['baz ' ]],
26- [false , 'test/ foo/+/baz ' , 'test/ foo/bar/baz ' , 'hello world ' , ['bar ' ]],
27- [false , 'test/ foo/# ' , 'test/ foo/bar/baz ' , 'hello world ' , ['bar/baz ' ]],
28- [false , 'test/ foo/+/bar/# ' , 'test/ foo/my/bar/baz ' , 'hello world ' , ['my ' , 'baz ' ]],
29- [false , 'test/ foo/+/bar/# ' , 'test/ foo/my/bar/baz/blub ' , 'hello world ' , ['my ' , 'baz/blub ' ]],
30- [false , 'test/ foo/bar/baz ' , 'test/ foo/bar/baz ' , random_bytes (2 * 1024 * 1024 ), []], // 2MB message
31- [true , 'test/ foo/bar/baz ' , 'test/ foo/bar/baz ' , 'hello world ' , []],
32- [true , 'test/ foo/bar/+ ' , 'test/ foo/bar/baz ' , 'hello world ' , ['baz ' ]],
33- [true , 'test/ foo/+/baz ' , 'test/ foo/bar/baz ' , 'hello world ' , ['bar ' ]],
34- [true , 'test/ foo/# ' , 'test/ foo/bar/baz ' , 'hello world ' , ['bar/baz ' ]],
35- [true , 'test/ foo/+/bar/# ' , 'test/ foo/my/bar/baz ' , 'hello world ' , ['my ' , 'baz ' ]],
36- [true , 'test/ foo/+/bar/# ' , 'test/ foo/my/bar/baz/blub ' , 'hello world ' , ['my ' , 'baz/blub ' ]],
37- [true , 'test/ foo/bar/baz ' , 'test/ foo/bar/baz ' , random_bytes (2 * 1024 * 1024 ), []], // 2MB message
23+ $ data = [
24+ [false , 'foo/bar/baz ' , 'foo/bar/baz ' , 'hello world ' , []],
25+ [false , 'foo/bar/+ ' , 'foo/bar/baz ' , 'hello world ' , ['baz ' ]],
26+ [false , 'foo/+/baz ' , 'foo/bar/baz ' , 'hello world ' , ['bar ' ]],
27+ [false , 'foo/# ' , 'foo/bar/baz ' , 'hello world ' , ['bar/baz ' ]],
28+ [false , 'foo/+/bar/# ' , 'foo/my/bar/baz ' , 'hello world ' , ['my ' , 'baz ' ]],
29+ [false , 'foo/+/bar/# ' , 'foo/my/bar/baz/blub ' , 'hello world ' , ['my ' , 'baz/blub ' ]],
30+ [false , 'foo/bar/baz ' , 'foo/bar/baz ' , random_bytes (2 * 1024 * 1024 ), []], // 2MB message
31+ [true , 'foo/bar/baz ' , 'foo/bar/baz ' , 'hello world ' , []],
32+ [true , 'foo/bar/+ ' , 'foo/bar/baz ' , 'hello world ' , ['baz ' ]],
33+ [true , 'foo/+/baz ' , 'foo/bar/baz ' , 'hello world ' , ['bar ' ]],
34+ [true , 'foo/# ' , 'foo/bar/baz ' , 'hello world ' , ['bar/baz ' ]],
35+ [true , 'foo/+/bar/# ' , 'foo/my/bar/baz ' , 'hello world ' , ['my ' , 'baz ' ]],
36+ [true , 'foo/+/bar/# ' , 'foo/my/bar/baz/blub ' , 'hello world ' , ['my ' , 'baz/blub ' ]],
37+ [true , 'foo/bar/baz ' , 'foo/bar/baz ' , random_bytes (2 * 1024 * 1024 ), []], // 2MB message
3838 ];
39+
40+ // Because our tests are run against a real MQTT broker and some messages are retained,
41+ // we need to prevent false-positives by giving each test case its own 'test space' using a random prefix.
42+ for ($ i = 0 ; $ i < count ($ data ); $ i ++) {
43+ $ prefix = 'test/ ' . uniqid ('' , true ) . '/ ' ;
44+ $ data [$ i ][1 ] = $ prefix . $ data [$ i ][1 ];
45+ $ data [$ i ][2 ] = $ prefix . $ data [$ i ][2 ];
46+ }
47+
48+ return $ data ;
3949 }
4050
4151 /**
@@ -84,6 +94,57 @@ function (string $topic, string $message, bool $retained, array $wildcards) use
8494 $ subscriber ->disconnect ();
8595 }
8696
97+ /**
98+ * @dataProvider publishSubscribeData
99+ */
100+ public function test_publishing_and_subscribing_using_quality_of_service_0_with_message_retention_works_as_intended (
101+ bool $ useBlockingSocket ,
102+ string $ subscriptionTopicFilter ,
103+ string $ publishTopic ,
104+ string $ publishMessage ,
105+ array $ matchedTopicWildcards
106+ ): void
107+ {
108+ // We publish a message from the first client, which disconnects before the other client even subscribes.
109+ $ publisher = new MqttClient ($ this ->mqttBrokerHost , $ this ->mqttBrokerPort , 'publisher ' );
110+ $ publisher ->connect (null , true );
111+
112+ $ publisher ->publish ($ publishTopic , $ publishMessage , 0 , true );
113+
114+ $ publisher ->disconnect ();
115+
116+ // Because we need to make sure the message reached the broker, we delay the execution for a short period (100ms) intentionally.
117+ // With higher QoS, this is replaced by awaiting delivery of the message.
118+ usleep (100_000 );
119+
120+ // We connect and subscribe to a topic using the second client.
121+ $ connectionSettings = (new ConnectionSettings ())
122+ ->useBlockingSocket ($ useBlockingSocket );
123+
124+ $ subscriber = new MqttClient ($ this ->mqttBrokerHost , $ this ->mqttBrokerPort , 'subscriber ' );
125+ $ subscriber ->connect ($ connectionSettings , true );
126+
127+ $ subscriber ->subscribe (
128+ $ subscriptionTopicFilter ,
129+ function (string $ topic , string $ message , bool $ retained , array $ wildcards ) use ($ subscriber , $ publishTopic , $ publishMessage , $ matchedTopicWildcards ) {
130+ // By asserting something here, we will avoid a no-assertions-in-test warning, making the test pass.
131+ $ this ->assertEquals ($ publishTopic , $ topic );
132+ $ this ->assertEquals ($ publishMessage , $ message );
133+ $ this ->assertTrue ($ retained );
134+ $ this ->assertEquals ($ matchedTopicWildcards , $ wildcards );
135+
136+ $ subscriber ->interrupt (); // This allows us to exit the test as soon as possible.
137+ },
138+ 0
139+ );
140+
141+ // Then we loop on the subscriber to (hopefully) receive the published message.
142+ $ subscriber ->loop (true );
143+
144+ // Finally, we disconnect for a graceful shutdown on the broker side.
145+ $ subscriber ->disconnect ();
146+ }
147+
87148 /**
88149 * @dataProvider publishSubscribeData
89150 */
@@ -130,6 +191,54 @@ function (string $topic, string $message, bool $retained, array $wildcards) use
130191 $ subscriber ->disconnect ();
131192 }
132193
194+ /**
195+ * @dataProvider publishSubscribeData
196+ */
197+ public function test_publishing_and_subscribing_using_quality_of_service_1_with_message_retention_works_as_intended (
198+ bool $ useBlockingSocket ,
199+ string $ subscriptionTopicFilter ,
200+ string $ publishTopic ,
201+ string $ publishMessage ,
202+ array $ matchedTopicWildcards
203+ ): void
204+ {
205+ // We publish a message from the first client, which disconnects before the other client even subscribes.
206+ $ publisher = new MqttClient ($ this ->mqttBrokerHost , $ this ->mqttBrokerPort , 'publisher ' );
207+ $ publisher ->connect (null , true );
208+
209+ $ publisher ->publish ($ publishTopic , $ publishMessage , 1 , true );
210+ $ publisher ->loop (true , true );
211+
212+ $ publisher ->disconnect ();
213+
214+ // We connect and subscribe to a topic using the second client.
215+ $ connectionSettings = (new ConnectionSettings ())
216+ ->useBlockingSocket ($ useBlockingSocket );
217+
218+ $ subscriber = new MqttClient ($ this ->mqttBrokerHost , $ this ->mqttBrokerPort , 'subscriber ' );
219+ $ subscriber ->connect ($ connectionSettings , true );
220+
221+ $ subscriber ->subscribe (
222+ $ subscriptionTopicFilter ,
223+ function (string $ topic , string $ message , bool $ retained , array $ wildcards ) use ($ subscriber , $ publishTopic , $ publishMessage , $ matchedTopicWildcards ) {
224+ // By asserting something here, we will avoid a no-assertions-in-test warning, making the test pass.
225+ $ this ->assertEquals ($ publishTopic , $ topic );
226+ $ this ->assertEquals ($ publishMessage , $ message );
227+ $ this ->assertTrue ($ retained );
228+ $ this ->assertEquals ($ matchedTopicWildcards , $ wildcards );
229+
230+ $ subscriber ->interrupt (); // This allows us to exit the test as soon as possible.
231+ },
232+ 1
233+ );
234+
235+ // Then we loop on the subscriber to (hopefully) receive the published message.
236+ $ subscriber ->loop (true );
237+
238+ // Finally, we disconnect for a graceful shutdown on the broker side.
239+ $ subscriber ->disconnect ();
240+ }
241+
133242 /**
134243 * @dataProvider publishSubscribeData
135244 */
@@ -176,6 +285,53 @@ public function test_publishing_and_subscribing_using_quality_of_service_2_works
176285 $ subscriber ->disconnect ();
177286 }
178287
288+ /**
289+ * @dataProvider publishSubscribeData
290+ */
291+ public function test_publishing_and_subscribing_using_quality_of_service_2_with_message_retention_works_as_intended (
292+ bool $ useBlockingSocket ,
293+ string $ subscriptionTopicFilter ,
294+ string $ publishTopic ,
295+ string $ publishMessage ,
296+ array $ matchedTopicWildcards
297+ ): void
298+ {
299+ // We publish a message from the first client. The loop is called until all QoS 2 handshakes are done.
300+ $ publisher = new MqttClient ($ this ->mqttBrokerHost , $ this ->mqttBrokerPort , 'publisher ' );
301+ $ publisher ->connect (null , true );
302+
303+ $ publisher ->publish ($ publishTopic , $ publishMessage , 2 , true );
304+ $ publisher ->loop (true , true );
305+
306+ $ publisher ->disconnect ();
307+
308+ // We connect and subscribe to a topic using the second client.
309+ $ connectionSettings = (new ConnectionSettings ())
310+ ->useBlockingSocket ($ useBlockingSocket );
311+
312+ $ subscriber = new MqttClient ($ this ->mqttBrokerHost , $ this ->mqttBrokerPort , 'subscriber ' );
313+ $ subscriber ->connect ($ connectionSettings , true );
314+
315+ $ subscription = function (string $ topic , string $ message , bool $ retained , array $ wildcards ) use ($ subscriber , $ subscriptionTopicFilter , $ publishTopic , $ publishMessage , $ matchedTopicWildcards ) {
316+ // By asserting something here, we will avoid a no-assertions-in-test warning, making the test pass.
317+ $ this ->assertEquals ($ publishTopic , $ topic );
318+ $ this ->assertEquals ($ publishMessage , $ message );
319+ $ this ->assertTrue ($ retained );
320+ $ this ->assertEquals ($ matchedTopicWildcards , $ wildcards );
321+
322+ $ subscriber ->unsubscribe ($ subscriptionTopicFilter );
323+ $ subscriber ->interrupt (); // This allows us to exit the test as soon as possible.
324+ };
325+
326+ $ subscriber ->subscribe ($ subscriptionTopicFilter , $ subscription , 2 );
327+
328+ // Then we loop on the subscriber to (hopefully) receive the published message until the receive handshake is done.
329+ $ subscriber ->loop (true , true );
330+
331+ // Finally, we disconnect for a graceful shutdown on the broker side.
332+ $ subscriber ->disconnect ();
333+ }
334+
179335 public function test_unsubscribe_stops_receiving_messages_on_topic (): void
180336 {
181337 // We connect and subscribe to a topic using the first client.
0 commit comments