aboutsummaryrefslogtreecommitdiffstats
path: root/tests/phpunit/unit/includes/Notification/MiddlewareChainTest.php
diff options
context:
space:
mode:
authorPiotr Miazga <pmiazga@wikimedia.org>2025-03-06 14:48:12 +0100
committerPiotr Miazga <pmiazga@wikimedia.org>2025-03-21 10:50:14 +0100
commitf1e88be974500910c79ad0dd9a2cf4a454d24b4d (patch)
tree681a41dd81136fb3609b071d2ccce8ad5a436af7 /tests/phpunit/unit/includes/Notification/MiddlewareChainTest.php
parent35bbe148a98cb4f119524cbbf52dd0e96ec0f4dc (diff)
downloadmediawikicore-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.php170
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() );
+ }
+
+}