aboutsummaryrefslogtreecommitdiffstats
path: root/tests/phpunit/includes/logging/LogFormatterTestCase.php
blob: d09c662e314e16190c9ba14ad37d537badc36818 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
<?php

use MediaWiki\Cache\GenderCache;
use MediaWiki\Cache\LinkCache;
use MediaWiki\Config\ServiceOptions;
use MediaWiki\Context\RequestContext;
use MediaWiki\Linker\LinkRenderer;
use MediaWiki\Linker\LinkTarget;
use MediaWiki\Logging\LogEntryBase;
use MediaWiki\Logging\LogPage;
use MediaWiki\Page\ExistingPageRecord;
use MediaWiki\Page\PageStore;
use MediaWiki\Tests\Unit\Permissions\MockAuthorityTrait;
use MediaWiki\User\UserFactory;
use Wikimedia\IPUtils;
use Wikimedia\Rdbms\ILoadBalancer;
use Wikimedia\Stats\StatsFactory;
use Wikimedia\TestingAccessWrapper;

/**
 * @since 1.26
 */
abstract class LogFormatterTestCase extends MediaWikiLangTestCase {
	use MockAuthorityTrait;

	public function doTestLogFormatter( $row, $extra, $userGroups = [] ) {
		RequestContext::resetMain();
		$row = $this->expandDatabaseRow( $row, $this->isLegacy( $extra ) );

		$services = $this->getServiceContainer();
		$userGroups = (array)$userGroups;
		$userRights = $services->getGroupPermissionsLookup()->getGroupPermissions( $userGroups );
		$context = new RequestContext();
		$authority = $this->mockRegisteredAuthorityWithPermissions( $userRights );
		$context->setAuthority( $authority );
		$context->setLanguage( 'en' );

		$formatter = $services->getLogFormatterFactory()->newFromRow( $row );
		$formatter->setContext( $context );

		// Create a LinkRenderer without LinkCache to avoid DB access
		$realLinkRenderer = new LinkRenderer(
			$services->getTitleFormatter(),
			$this->createMock( LinkCache::class ),
			$services->getSpecialPageFactory(),
			$services->getHookContainer(),
			new ServiceOptions(
				LinkRenderer::CONSTRUCTOR_OPTIONS,
				$services->getMainConfig(),
				[ 'renderForComment' => false ]
			)
		);
		// Then create a mock LinkRenderer that proxies makeLink calls to the original LinkRenderer, but assumes
		// that all links are known to bypass DB access in Title::exists().
		$linkRenderer = $this->createMock( LinkRenderer::class );
		$linkRenderer->method( 'makeLink' )
			->willReturnCallback(
				static function ( $target, $text = null, $extra = [], $query = [] ) use ( $realLinkRenderer ) {
					return $realLinkRenderer->makeKnownLink( $target, $text, $extra, $query );
				}
			);
		$formatter->setLinkRenderer( $linkRenderer );
		$this->setService( 'LinkRenderer', $linkRenderer );

		// Create a mock PageStore where all pages are existing, in case any calls to Title::exists are not
		// caught by the mocks above.
		$pageStore = $this->getMockBuilder( PageStore::class )
			->onlyMethods( [ 'getPageByName' ] )
			->setConstructorArgs( [
				new ServiceOptions( PageStore::CONSTRUCTOR_OPTIONS, $services->getMainConfig() ),
				$this->createNoOpMock( ILoadBalancer::class ),
				$services->getNamespaceInfo(),
				$services->getTitleParser(),
				null,
				StatsFactory::newNull()
			] )
			->getMock();
		$pageStore->method( 'getPageByName' )
			->willReturn( $this->createMock( ExistingPageRecord::class ) );
		$this->setService( 'PageStore', $pageStore );

		// Create a mock UserFactory where all registered users are created with ID and name and where loading of
		// other fields is prevented, to avoid DB access.
		$origUserFactory = $services->getUserFactory();
		$userFactory = $this->createMock( UserFactory::class );
		$userFactory->method( 'newFromName' )
			->willReturnCallback( static function ( $name, $validation ) use ( $origUserFactory ) {
				$ret = $origUserFactory->newFromName( $name, $validation );
				if ( !$ret ) {
					return $ret;
				}
				$userID = IPUtils::isIPAddress( $name ) ? 0 : 42;
				$ret = TestingAccessWrapper::newFromObject( $ret );
				$ret->mId = $userID;
				$ret->mLoadedItems = true;
				return $ret->object;
			} );
		$userFactory->method( 'newFromId' )->willReturnCallback( [ $origUserFactory, 'newFromId' ] );
		$userFactory->method( 'newAnonymous' )->willReturnCallback( [ $origUserFactory, 'newAnonymous' ] );
		$userFactory->method( 'newFromUserIdentity' )
			->willReturnCallback( [ $origUserFactory, 'newFromUserIdentity' ] );
		$this->setService( 'UserFactory', $userFactory );

		// Replace gender cache to avoid gender DB lookups
		$genderCache = $this->createMock( GenderCache::class );
		$genderCache->method( 'getGenderOf' )->willReturn( 'unknown' );
		$this->setService( 'GenderCache', $genderCache );

		$this->assertEquals(
			$extra['text'],
			self::removeSomeHtml( $formatter->getActionText() ),
			'Action text is equal to expected text'
		);

		$this->assertSame( // ensure types and array key order
			$extra['api'],
			self::removeApiMetaData( $formatter->formatParametersForApi() ),
			'Api log params is equal to expected array'
		);

		if ( isset( $extra['preload'] ) ) {
			$this->assertArrayEquals(
				$this->getLinkTargetsAsStrings( $extra['preload'] ),
				$this->getLinkTargetsAsStrings(
					$formatter->getPreloadTitles()
				)
			);
		}
	}

	private function getLinkTargetsAsStrings( array $linkTargets ) {
		return array_map( static function ( LinkTarget $t ) {
			return $t->getInterwiki() . ':' . $t->getNamespace() . ':'
				. $t->getDBkey() . '#' . $t->getFragment();
		}, $linkTargets );
	}

	protected function isLegacy( $extra ) {
		return isset( $extra['legacy'] ) && $extra['legacy'];
	}

	protected function expandDatabaseRow( $data, $legacy ) {
		return [
			// no log_id because no insert in database
			'log_type' => $data['type'],
			'log_action' => $data['action'],
			'log_timestamp' => $data['timestamp'] ?? wfTimestampNow(),
			'log_user' => $data['user'] ?? 42,
			'log_user_text' => $data['user_text'] ?? 'User',
			'log_actor' => $data['actor'] ?? 24,
			'log_namespace' => $data['namespace'] ?? NS_MAIN,
			'log_title' => $data['title'] ?? 'Main_Page',
			'log_page' => $data['page'] ?? 0,
			'log_comment_text' => $data['comment'] ?? '',
			'log_comment_data' => null,
			'log_params' => $legacy
				? LogPage::makeParamBlob( $data['params'] )
				: LogEntryBase::makeParamBlob( $data['params'] ),
			'log_deleted' => $data['deleted'] ?? 0,
		];
	}

	protected static function removeSomeHtml( $html ) {
		$html = str_replace( '&quot;', '"', $html );
		$html = preg_replace( '/\xE2\x80[\x8E\x8F]/', '', $html ); // Strip lrm/rlm
		return trim( strip_tags( $html ) );
	}

	protected static function removeApiMetaData( $val ) {
		if ( is_array( $val ) ) {
			unset( $val['_element'] );
			unset( $val['_type'] );
			foreach ( $val as $key => $value ) {
				$val[$key] = self::removeApiMetaData( $value );
			}
		}
		return $val;
	}
}