1<?php 2/** 3 * This program is free software; you can redistribute it and/or modify 4 * it under the terms of the GNU General Public License as published by 5 * the Free Software Foundation; either version 2 of the License, or 6 * (at your option) any later version. 7 * 8 * This program is distributed in the hope that it will be useful, 9 * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 * GNU General Public License for more details. 12 * 13 * You should have received a copy of the GNU General Public License along 14 * with this program; if not, write to the Free Software Foundation, Inc., 15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 16 * http://www.gnu.org/copyleft/gpl.html 17 * 18 * @file 19 */ 20 21namespace MediaWiki\Logger\Monolog; 22 23use Kafka\Exception; 24use Monolog\Logger; 25 26/** 27 * @covers \MediaWiki\Logger\Monolog\KafkaHandler 28 */ 29class KafkaHandlerTest extends \MediaWikiUnitTestCase { 30 31 protected function setUp(): void { 32 parent::setUp(); 33 if ( !class_exists( \Monolog\Handler\AbstractProcessingHandler::class ) 34 || !class_exists( \Kafka\Produce::class ) 35 ) { 36 $this->markTestSkipped( 'Monolog and Kafka are required for the KafkaHandlerTest' ); 37 } 38 } 39 40 public function topicNamingProvider() { 41 return [ 42 [ [], 'monolog_foo' ], 43 [ [ 'alias' => [ 'foo' => 'bar' ] ], 'bar' ] 44 ]; 45 } 46 47 /** 48 * @dataProvider topicNamingProvider 49 */ 50 public function testTopicNaming( $options, $expect ) { 51 $produce = $this->getMockBuilder( \Kafka\Produce::class ) 52 ->disableOriginalConstructor() 53 ->getMock(); 54 $produce->method( 'getAvailablePartitions' ) 55 ->willReturn( [ 'A' ] ); 56 $produce->expects( $this->once() ) 57 ->method( 'setMessages' ) 58 ->with( $expect, $this->anything(), $this->anything() ); 59 $produce->method( 'send' ) 60 ->willReturn( true ); 61 62 $handler = new KafkaHandler( $produce, $options ); 63 $handler->handle( [ 64 'channel' => 'foo', 65 'level' => Logger::EMERGENCY, 66 'extra' => [], 67 'context' => [], 68 ] ); 69 } 70 71 public function swallowsExceptionsWhenRequested() { 72 return [ 73 // defaults to false 74 [ [], true ], 75 // also try false explicitly 76 [ [ 'swallowExceptions' => false ], true ], 77 // turn it on 78 [ [ 'swallowExceptions' => true ], false ], 79 ]; 80 } 81 82 /** 83 * @dataProvider swallowsExceptionsWhenRequested 84 */ 85 public function testGetAvailablePartitionsException( $options, $expectException ) { 86 $produce = $this->getMockBuilder( \Kafka\Produce::class ) 87 ->disableOriginalConstructor() 88 ->getMock(); 89 $produce->method( 'getAvailablePartitions' ) 90 ->will( $this->throwException( new Exception ) ); 91 $produce->method( 'send' ) 92 ->willReturn( true ); 93 94 if ( $expectException ) { 95 $this->expectException( Exception::class ); 96 } 97 98 $handler = new KafkaHandler( $produce, $options ); 99 $handler->handle( [ 100 'channel' => 'foo', 101 'level' => Logger::EMERGENCY, 102 'extra' => [], 103 'context' => [], 104 ] ); 105 106 if ( !$expectException ) { 107 $this->assertTrue( true, 'no exception was thrown' ); 108 } 109 } 110 111 /** 112 * @dataProvider swallowsExceptionsWhenRequested 113 */ 114 public function testSendException( $options, $expectException ) { 115 $produce = $this->getMockBuilder( \Kafka\Produce::class ) 116 ->disableOriginalConstructor() 117 ->getMock(); 118 $produce->method( 'getAvailablePartitions' ) 119 ->willReturn( [ 'A' ] ); 120 $produce->method( 'send' ) 121 ->will( $this->throwException( new Exception ) ); 122 123 if ( $expectException ) { 124 $this->expectException( Exception::class ); 125 } 126 127 $handler = new KafkaHandler( $produce, $options ); 128 $handler->handle( [ 129 'channel' => 'foo', 130 'level' => Logger::EMERGENCY, 131 'extra' => [], 132 'context' => [], 133 ] ); 134 135 if ( !$expectException ) { 136 $this->assertTrue( true, 'no exception was thrown' ); 137 } 138 } 139 140 public function testHandlesNullFormatterResult() { 141 $produce = $this->getMockBuilder( \Kafka\Produce::class ) 142 ->disableOriginalConstructor() 143 ->getMock(); 144 $produce->method( 'getAvailablePartitions' ) 145 ->willReturn( [ 'A' ] ); 146 $produce->expects( $this->exactly( 2 ) ) 147 ->method( 'setMessages' ) 148 ->will( $this->onConsecutiveCalls( 149 [ $this->anything(), $this->anything(), [ 'words' ] ], 150 [ $this->anything(), $this->anything(), [ 'lines' ] ] 151 ) ); 152 $produce->method( 'send' ) 153 ->willReturn( true ); 154 155 $formatter = $this->createMock( \Monolog\Formatter\FormatterInterface::class ); 156 $formatter->method( 'format' ) 157 ->will( $this->onConsecutiveCalls( 'words', null, 'lines' ) ); 158 159 $handler = new KafkaHandler( $produce, [] ); 160 $handler->setFormatter( $formatter ); 161 for ( $i = 0; $i < 3; ++$i ) { 162 $handler->handle( [ 163 'channel' => 'foo', 164 'level' => Logger::EMERGENCY, 165 'extra' => [], 166 'context' => [], 167 ] ); 168 } 169 } 170 171 public function testBatchHandlesNullFormatterResult() { 172 $produce = $this->getMockBuilder( \Kafka\Produce::class ) 173 ->disableOriginalConstructor() 174 ->getMock(); 175 $produce->method( 'getAvailablePartitions' ) 176 ->willReturn( [ 'A' ] ); 177 $produce->expects( $this->once() ) 178 ->method( 'setMessages' ) 179 ->with( $this->anything(), $this->anything(), [ 'words', 'lines' ] ); 180 $produce->method( 'send' ) 181 ->willReturn( true ); 182 183 $formatter = $this->createMock( \Monolog\Formatter\FormatterInterface::class ); 184 $formatter->method( 'format' ) 185 ->will( $this->onConsecutiveCalls( 'words', null, 'lines' ) ); 186 187 $handler = new KafkaHandler( $produce, [] ); 188 $handler->setFormatter( $formatter ); 189 $handler->handleBatch( [ 190 [ 191 'channel' => 'foo', 192 'level' => Logger::EMERGENCY, 193 'extra' => [], 194 'context' => [], 195 ], 196 [ 197 'channel' => 'foo', 198 'level' => Logger::EMERGENCY, 199 'extra' => [], 200 'context' => [], 201 ], 202 [ 203 'channel' => 'foo', 204 'level' => Logger::EMERGENCY, 205 'extra' => [], 206 'context' => [], 207 ], 208 ] ); 209 } 210} 211