ConsumerTest.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. <?php declare(strict_types=1);
  2. namespace mikemadisonweb\rabbitmq\tests\components;
  3. use mikemadisonweb\rabbitmq\components\{
  4. Consumer, ConsumerInterface, Logger, Routing
  5. };
  6. use mikemadisonweb\rabbitmq\Configuration;
  7. use mikemadisonweb\rabbitmq\events\RabbitMQConsumerEvent;
  8. use mikemadisonweb\rabbitmq\tests\TestCase;
  9. use PhpAmqpLib\Channel\AMQPChannel;
  10. use PhpAmqpLib\Connection\AMQPLazyConnection;
  11. use PhpAmqpLib\Message\AMQPMessage;
  12. use PhpAmqpLib\Wire\AMQPTable;
  13. use yii\console\Controller;
  14. class ConsumerTest extends TestCase
  15. {
  16. /**
  17. * @dataProvider checkConsume
  18. * @param $queues
  19. * @param $consumeCount
  20. */
  21. public function testConsume($queues, $consumeCount)
  22. {
  23. $connection = $this->getMockBuilder(AMQPLazyConnection::class)
  24. ->disableOriginalConstructor()
  25. ->setMethods(['channel'])
  26. ->getMock();
  27. $channel = $this->getMockBuilder(AMQPChannel::class)
  28. ->disableOriginalConstructor()
  29. ->getMock();
  30. $connection->method('channel')
  31. ->willReturn($channel);
  32. $routing = $this->createMock(Routing::class);
  33. $routing->expects($this->once())
  34. ->method('declareAll');
  35. $logger = \Yii::$container->get(Configuration::LOGGER_SERVICE_NAME);
  36. $consumer = new Consumer($connection, $routing, $logger, true);
  37. if (!empty($queues)) {
  38. $consumer->setQueues($queues);
  39. }
  40. $channel
  41. ->expects($consumeCount)
  42. ->method('basic_consume');
  43. $this->assertSame(Controller::EXIT_CODE_NORMAL, $consumer->consume());
  44. }
  45. public function checkConsume() : array
  46. {
  47. return [
  48. [[], $this->never()],
  49. [['queue' => 'callback'], $this->once()],
  50. [['queue1' => 'callback1', 'queue2' => 'callback2', 'queue3' => 'callback3'], $this->exactly(3)],
  51. ];
  52. }
  53. public function testNoAutoDeclare()
  54. {
  55. $connection = $this->getMockBuilder(AMQPLazyConnection::class)
  56. ->disableOriginalConstructor()
  57. ->setMethods(['channel'])
  58. ->getMock();
  59. $channel = $this->getMockBuilder(AMQPChannel::class)
  60. ->disableOriginalConstructor()
  61. ->getMock();
  62. $connection->method('channel')
  63. ->willReturn($channel);
  64. $routing = $this->createMock(Routing::class);
  65. $routing->expects($this->never())
  66. ->method('declareAll');
  67. $logger = \Yii::$container->get(Configuration::LOGGER_SERVICE_NAME);
  68. $consumer = new Consumer($connection, $routing, $logger, false);
  69. $consumer->setQos(['prefetch_size' => 0, 'prefetch_count' => 0, 'global' => false]);
  70. $this->assertSame(Controller::EXIT_CODE_NORMAL, $consumer->consume());
  71. }
  72. public function testConsumeEvents()
  73. {
  74. $queue = 'test-queue';
  75. $msgBody = 'Test message!';
  76. $consumerName = 'test';
  77. $callbackName = 'MockCallback';
  78. $callback = $this->getMockBuilder(ConsumerInterface::class)
  79. ->setMockClassName($callbackName)
  80. ->getMock();
  81. $this->loadExtension([
  82. 'components' => [
  83. 'rabbitmq' => [
  84. 'class' => Configuration::class,
  85. 'connections' => [
  86. [
  87. 'host' => 'unreal',
  88. ],
  89. ],
  90. 'queues' => [
  91. [
  92. 'name' => $queue,
  93. ],
  94. ],
  95. 'consumers' => [
  96. [
  97. 'name' => $consumerName,
  98. 'callbacks' => [$queue => $callbackName],
  99. ]
  100. ],
  101. 'on before_consume' => function ($event) use ($msgBody) {
  102. $this->assertInstanceOf(RabbitMQConsumerEvent::class, $event);
  103. $this->assertSame($msgBody, $event->message->getBody());
  104. },
  105. 'on after_consume' => function ($event) use ($msgBody) {
  106. $this->assertInstanceOf(RabbitMQConsumerEvent::class, $event);
  107. $this->assertSame($msgBody, $event->message->getBody());
  108. },
  109. ],
  110. ],
  111. ]);
  112. $connection = $this->getMockBuilder(AMQPLazyConnection::class)
  113. ->disableOriginalConstructor()
  114. ->setMethods(['channel'])
  115. ->getMock();
  116. $channel = $this->getMockBuilder(AMQPChannel::class)
  117. ->disableOriginalConstructor()
  118. ->getMock();
  119. $connection->method('channel')
  120. ->willReturn($channel);
  121. $logger = $this->createMock(Logger::class);
  122. $routing = $this->createMock(Routing::class);
  123. $consumer = \Yii::$app->rabbitmq->getConsumer($consumerName);
  124. $this->setInaccessibleProperty($consumer, 'routing', $routing);
  125. $this->setInaccessibleProperty($consumer, 'conn', $connection);
  126. $this->setInaccessibleProperty($consumer, 'logger', $logger);
  127. $msg = new AMQPMessage($msgBody);
  128. $this->invokeMethod($consumer, 'onReceive', [$msg, $queue, [$callback, 'execute']]);
  129. }
  130. public function testOnReceive()
  131. {
  132. $queue = 'test-queue';
  133. $this->loadExtension([
  134. 'components' => [
  135. 'rabbitmq' => [
  136. 'class' => Configuration::class,
  137. 'connections' => [
  138. [
  139. 'host' => 'unreal',
  140. ],
  141. ],
  142. ],
  143. ],
  144. ]);
  145. $callback = $this->createMock(ConsumerInterface::class);
  146. $connection = $this->getMockBuilder(AMQPLazyConnection::class)
  147. ->disableOriginalConstructor()
  148. ->setMethods(['channel'])
  149. ->getMock();
  150. $channel = $this->getMockBuilder(AMQPChannel::class)
  151. ->disableOriginalConstructor()
  152. ->getMock();
  153. $connection->method('channel')
  154. ->willReturn($channel);
  155. $routing = $this->createMock(Routing::class);
  156. $routing->expects($this->never())
  157. ->method('declareAll');
  158. $logger = $this->createMock(Logger::class);
  159. $consumer = $this->getMockBuilder(Consumer::class)
  160. ->setConstructorArgs([$connection, $routing, $logger, false])
  161. ->setMethods(['sendResult'])
  162. ->getMock();
  163. $msgBody = 'Test message';
  164. $consumer->method('sendResult')
  165. ->willThrowException(new \Exception($msgBody));
  166. $msg = new AMQPMessage($msgBody);
  167. // No exception should be thrown
  168. $consumer->setProceedOnException(true);
  169. $before = $consumer->getConsumed();
  170. $this->assertTrue($this->invokeMethod($consumer, 'onReceive', [$msg, $queue, [$callback, 'execute']]));
  171. $this->assertSame($before + 1, $consumer->getConsumed());
  172. // Exception should be thrown
  173. $consumer->setProceedOnException(false);
  174. $this->expectExceptionMessage($msgBody);
  175. $callback->expects($this->once())
  176. ->method('execute');
  177. $logger->expects($this->once())
  178. ->method('logError');
  179. $this->invokeMethod($consumer, 'onReceive', [$msg, $queue, [$callback, 'execute']]);
  180. }
  181. /**
  182. * @dataProvider checkMsgTypes
  183. * @param $userData
  184. */
  185. public function testOnReceiveDifferentTypes($userData)
  186. {
  187. $queue = 'test-queue';
  188. $this->loadExtension([
  189. 'components' => [
  190. 'rabbitmq' => [
  191. 'class' => Configuration::class,
  192. 'connections' => [
  193. [
  194. 'host' => 'unreal',
  195. ],
  196. ],
  197. ],
  198. ],
  199. ]);
  200. $callback = $this->createMock(ConsumerInterface::class);
  201. $connection = $this->getMockBuilder(AMQPLazyConnection::class)
  202. ->disableOriginalConstructor()
  203. ->setMethods(['channel'])
  204. ->getMock();
  205. $channel = $this->getMockBuilder(AMQPChannel::class)
  206. ->disableOriginalConstructor()
  207. ->getMock();
  208. $connection->method('channel')
  209. ->willReturn($channel);
  210. $routing = $this->createMock(Routing::class);
  211. $routing->expects($this->never())
  212. ->method('declareAll');
  213. $logger = $this->createMock(Logger::class);
  214. $consumer = $this->getMockBuilder(Consumer::class)
  215. ->setConstructorArgs([$connection, $routing, $logger, false])
  216. ->setMethods(['sendResult'])
  217. ->getMock();
  218. $consumer->setDeserializer('json_decode');
  219. $msgBody = json_encode($userData);
  220. $msg = new AMQPMessage($msgBody);
  221. $headers['rabbitmq.serialized'] = 1;
  222. $headersTable = new AMQPTable($headers);
  223. $msg->set('application_headers', $headersTable);
  224. $this->invokeMethod($consumer, 'onReceive', [$msg, $queue, [$callback, 'execute']]);
  225. $this->assertEquals($userData, $msg->getBody());
  226. }
  227. public function checkMsgTypes() : array
  228. {
  229. return [
  230. ['String!'],
  231. [['array']],
  232. [1],
  233. [1.1],
  234. [null],
  235. [new \StdClass()],
  236. ];
  237. }
  238. public function testForceStop()
  239. {
  240. $connection = $this->getMockBuilder(AMQPLazyConnection::class)
  241. ->disableOriginalConstructor()
  242. ->setMethods(['channel'])
  243. ->getMock();
  244. $channel = $this->getMockBuilder(AMQPChannel::class)
  245. ->disableOriginalConstructor()
  246. ->getMock();
  247. $channel->expects($this->once())
  248. ->method('basic_cancel');
  249. $connection->method('channel')
  250. ->willReturn($channel);
  251. $routing = $this->createMock(Routing::class);
  252. $routing->expects($this->never())
  253. ->method('declareAll');
  254. $logger = $this->createMock(Logger::class);
  255. $consumer = $this->getMockBuilder(Consumer::class)
  256. ->setConstructorArgs([$connection, $routing, $logger, false])
  257. ->setMethods(['maybeStopConsumer'])
  258. ->getMock();
  259. $consumer->setQueues(['queue' => 'callback']);
  260. $consumer->stopDaemon();
  261. }
  262. public function testForceRestart()
  263. {
  264. $connection = $this->getMockBuilder(AMQPLazyConnection::class)
  265. ->disableOriginalConstructor()
  266. ->setMethods(['channel'])
  267. ->getMock();
  268. $channel = $this->getMockBuilder(AMQPChannel::class)
  269. ->disableOriginalConstructor()
  270. ->getMock();
  271. $connection->method('channel')
  272. ->willReturn($channel);
  273. $routing = $this->createMock(Routing::class);
  274. $logger = $this->createMock(Logger::class);
  275. $consumer = $this->getMockBuilder(Consumer::class)
  276. ->setConstructorArgs([$connection, $routing, $logger, false])
  277. ->setMethods(['stopConsuming', 'renew', 'setup'])
  278. ->getMock();
  279. $consumer->expects($this->once())
  280. ->method('stopConsuming');
  281. $consumer->expects($this->once())
  282. ->method('renew');
  283. $consumer->expects($this->once())
  284. ->method('setup');
  285. $consumer->setQueues(['queue' => 'callback']);
  286. $consumer->restartDaemon();
  287. }
  288. }