diff options
author | Piotr Miazga <pmiazga@wikimedia.org> | 2025-03-06 14:48:12 +0100 |
---|---|---|
committer | Piotr Miazga <pmiazga@wikimedia.org> | 2025-03-21 10:50:14 +0100 |
commit | f1e88be974500910c79ad0dd9a2cf4a454d24b4d (patch) | |
tree | 681a41dd81136fb3609b071d2ccce8ad5a436af7 /tests/phpunit/unit/includes/Notification/MiddlewareChainTest.php | |
parent | 35bbe148a98cb4f119524cbbf52dd0e96ec0f4dc (diff) | |
download | mediawikicore-f1e88be974500910c79ad0dd9a2cf4a454d24b4d.tar.gz mediawikicore-f1e88be974500910c79ad0dd9a2cf4a454d24b4d.zip |
notifications: Introduce Notification Middleware and NotificationEnvelope
To allow ourselves esier processing/modifying Notifications lets
introduce an idea of NotificationsEnvelope which represents a
Notification being sent and list of recipients.
The middleware approach will allow us to modify the Notification
behaviour by letting extensions to inject/modify the Notifications.
Each Middleware will retrieve a list of Envelopes MediaWiki wants to
send. Middlewares should iterate over envelopes and decide if those
want to add/remove/replace Notifications and/or Recipients.
Bug: T387996
Change-Id: Ib3ee35c75b2f4dcfdc516b9259a852dc73c4a778
Diffstat (limited to 'tests/phpunit/unit/includes/Notification/MiddlewareChainTest.php')
-rw-r--r-- | tests/phpunit/unit/includes/Notification/MiddlewareChainTest.php | 170 |
1 files changed, 170 insertions, 0 deletions
diff --git a/tests/phpunit/unit/includes/Notification/MiddlewareChainTest.php b/tests/phpunit/unit/includes/Notification/MiddlewareChainTest.php new file mode 100644 index 000000000000..c133727d84cb --- /dev/null +++ b/tests/phpunit/unit/includes/Notification/MiddlewareChainTest.php @@ -0,0 +1,170 @@ +<?php + +namespace MediaWiki\Tests\Notification; + +use MediaWiki\Notification\MiddlewareChain; +use MediaWiki\Notification\Notification; +use MediaWiki\Notification\NotificationEnvelope; +use MediaWiki\Notification\NotificationMiddlewareInterface; +use MediaWiki\Notification\NotificationsBatch; +use MediaWiki\Notification\RecipientSet; +use MediaWiki\User\UserIdentity; +use MediaWikiUnitTestCase; + +/** + * @covers \MediaWiki\Notification\MiddlewareChain + */ +class MiddlewareChainTest extends MediaWikiUnitTestCase { + + private function getSut( array $middlewares ): MiddlewareChain { + $specs = []; + foreach ( $middlewares as $middleware ) { + $specs[] = [ 'factory' => static fn () => $middleware ]; + } + return new MiddlewareChain( $this->createSimpleObjectFactory(), $specs ); + } + + public function testEmptyMiddlewareChainReturnsOriginalBatch() { + $userIdentity = $this->createMock( UserIdentity::class ); + $notificationToSend = new Notification( 'test', [] ); + $recipients = new RecipientSet( [ $userIdentity ] ); + + $envelopes = new NotificationsBatch( + new NotificationEnvelope( $notificationToSend, $recipients ) + ); + + $sut = $this->getSut( [] ); + $this->assertSame( $envelopes, $sut->process( $envelopes ) ); + } + + public function testExecutesInOrderAndModifiesBatch() { + $userIdentity = $this->createMock( UserIdentity::class ); + $notificationToSend = new Notification( 'test', [] ); + $recipients = new RecipientSet( [ $userIdentity ] ); + + $noOpMiddleware = $this->createMock( NotificationMiddlewareInterface::class ); + $noOpMiddleware->expects( $this->exactly( 2 ) ) + ->method( 'handle' ) + ->willReturnCallback( function ( NotificationsBatch $batch, callable $next ) { + $this->assertCount( 1, $batch ); + $next(); + } ); + $emptyMiddleware = $this->createMock( NotificationMiddlewareInterface::class ); + $emptyMiddleware->expects( $this->once() ) + ->method( 'handle' ) + ->willReturnCallback( static function ( NotificationsBatch $batch, callable $next ) { + foreach ( $batch as $envelope ) { + $batch->remove( $envelope ); + } + $next(); + } ); + $makeSureBatchIsEmptyMiddleware = $this->createMock( NotificationMiddlewareInterface::class ); + $makeSureBatchIsEmptyMiddleware->expects( $this->once() ) + ->method( 'handle' ) + ->willReturnCallback( function ( NotificationsBatch $batch, callable $next ) { + $this->assertCount( 0, $batch ); + $next(); + } ); + + $middlewareChain = $this->getSut( [ + $noOpMiddleware, $noOpMiddleware, $emptyMiddleware, $makeSureBatchIsEmptyMiddleware, + ] ); + + $result = $middlewareChain->process( new NotificationsBatch( + new NotificationEnvelope( $notificationToSend, $recipients ) + ) ); + $this->assertCount( 0, $result ); + } + + public function testMiddlewareDoesntCallNext() { + $userIdentity = $this->createMock( UserIdentity::class ); + $keptNotification = new Notification( 'test', [] ); + $recipients = new RecipientSet( [ $userIdentity ] ); + + $suppressWelcomeMiddleware = $this->createMock( NotificationMiddlewareInterface::class ); + $suppressWelcomeMiddleware->expects( $this->once() ) + ->method( 'handle' ) + ->willReturnCallback( static function ( NotificationsBatch $batch, callable $next ) { + // no op, but also don't call next + } ); + + $middlewareChain = $this->getSut( [ + $suppressWelcomeMiddleware, + ] ); + + $batch = $middlewareChain->process( + new NotificationsBatch( + new NotificationEnvelope( $keptNotification, $recipients ) + ) + ); + $this->assertCount( 1, $batch ); + $envelopes = iterator_to_array( $batch ); + $this->assertSame( $keptNotification, $envelopes[0]->getNotification() ); + } + + public function testSupressNotification() { + $userIdentity = $this->createMock( UserIdentity::class ); + $keptNotification = new Notification( 'test', [] ); + $removedNotification = new Notification( 'welcome', [] ); + $recipients = new RecipientSet( [ $userIdentity ] ); + + $suppressWelcomeMiddleware = $this->createMock( NotificationMiddlewareInterface::class ); + $suppressWelcomeMiddleware->expects( $this->once() ) + ->method( 'handle' ) + ->willReturnCallback( static function ( NotificationsBatch $batch, callable $next ) { + foreach ( $batch as $envelope ) { + if ( $envelope->getNotification()->getType() === 'welcome' ) { + $batch->remove( $envelope ); + } + } + $next(); + } ); + + $middlewareChain = $this->getSut( [ + $suppressWelcomeMiddleware, + ] ); + + $batch = $middlewareChain->process( new NotificationsBatch( + new NotificationEnvelope( $keptNotification, $recipients ), + new NotificationEnvelope( $removedNotification, $recipients ) + ) ); + $this->assertCount( 1, $batch ); + foreach ( $batch as $envelope ) { + $this->assertSame( $keptNotification, $envelope->getNotification() ); + } + } + + /** + * Test case when Middleware calls NotificationService::notify() to inject new notification + * This can cause endless loops where Middleware triggers a notification, that triggers the + * middleware again. + * + * @return void + */ + public function testBadMiddlewareTriesToSendNotificationInsteadOfInjectingToBatch() { + $userIdentity = $this->createMock( UserIdentity::class ); + $keptNotification = new Notification( 'test', [] ); + $recipients = new RecipientSet( [ $userIdentity ] ); + + $suppressWelcomeMiddleware = $this->createMock( NotificationMiddlewareInterface::class ); + $suppressWelcomeMiddleware->expects( $this->once() ) + ->method( 'handle' ) + ->willReturnCallback( static function ( NotificationsBatch $batch, callable $next ) { + // no op, but also don't call next + } ); + + $middlewareChain = $this->getSut( [ + $suppressWelcomeMiddleware, + ] ); + + $batch = $middlewareChain->process( + new NotificationsBatch( + new NotificationEnvelope( $keptNotification, $recipients ) + ) + ); + $this->assertCount( 1, $batch ); + $envelopes = iterator_to_array( $batch ); + $this->assertSame( $keptNotification, $envelopes[0]->getNotification() ); + } + +} |