2020-12-19 02:46:40 +00:00
|
|
|
<?php
|
|
|
|
|
|
2023-09-20 07:54:42 +00:00
|
|
|
use MediaWiki\Config\HashConfig;
|
2020-12-19 02:46:40 +00:00
|
|
|
use MediaWiki\Http\HttpRequestFactory;
|
2023-06-02 18:23:50 +00:00
|
|
|
use MediaWiki\Installer\Pingback;
|
2022-08-17 20:33:06 +00:00
|
|
|
use MediaWiki\MainConfigNames;
|
2023-07-17 11:26:03 +00:00
|
|
|
use MediaWiki\Status\Status;
|
2020-12-19 02:46:40 +00:00
|
|
|
use Psr\Log\NullLogger;
|
2024-07-09 13:37:44 +00:00
|
|
|
use Wikimedia\ObjectCache\BagOStuff;
|
|
|
|
|
use Wikimedia\ObjectCache\HashBagOStuff;
|
2023-05-05 11:33:11 +00:00
|
|
|
use Wikimedia\Rdbms\IConnectionProvider;
|
2024-05-02 20:51:01 +00:00
|
|
|
use Wikimedia\Rdbms\IDatabase;
|
2023-09-05 19:21:03 +00:00
|
|
|
use Wikimedia\Rdbms\InsertQueryBuilder;
|
2023-02-03 12:09:05 +00:00
|
|
|
use Wikimedia\Rdbms\SelectQueryBuilder;
|
2020-12-19 02:46:40 +00:00
|
|
|
use Wikimedia\Timestamp\ConvertibleTimestamp;
|
|
|
|
|
|
|
|
|
|
/**
|
2024-01-27 00:11:07 +00:00
|
|
|
* @covers \MediaWiki\Installer\Pingback
|
2020-12-19 02:46:40 +00:00
|
|
|
*/
|
|
|
|
|
class PingbackTest extends MediaWikiUnitTestCase {
|
|
|
|
|
|
2022-05-17 12:11:22 +00:00
|
|
|
protected function setUp(): void {
|
2020-12-19 02:46:40 +00:00
|
|
|
parent::setUp();
|
|
|
|
|
ConvertibleTimestamp::setFakeTime( '20110401090000' );
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-20 09:29:31 +00:00
|
|
|
/**
|
2024-05-02 20:51:01 +00:00
|
|
|
* @param IDatabase $database
|
2021-07-20 09:29:31 +00:00
|
|
|
* @param HttpRequestFactory $httpRequestFactory
|
|
|
|
|
* @param bool $enablePingback
|
|
|
|
|
* @param BagOStuff|null $cache
|
2023-06-06 18:40:58 +00:00
|
|
|
* @return Pingback
|
2021-07-20 09:29:31 +00:00
|
|
|
*/
|
2023-06-06 18:40:58 +00:00
|
|
|
private function makePingback(
|
2024-05-02 20:51:01 +00:00
|
|
|
IDatabase $database,
|
2021-07-20 09:29:31 +00:00
|
|
|
$httpRequestFactory,
|
|
|
|
|
bool $enablePingback = true,
|
|
|
|
|
$cache = null
|
|
|
|
|
) {
|
2023-05-05 11:33:11 +00:00
|
|
|
$dbProvider = $this->createNoOpMock( IConnectionProvider::class, [ 'getPrimaryDatabase', 'getReplicaDatabase' ] );
|
|
|
|
|
$dbProvider->method( 'getPrimaryDatabase' )->willReturn( $database );
|
|
|
|
|
$dbProvider->method( 'getReplicaDatabase' )->willReturn( $database );
|
2021-07-20 09:29:31 +00:00
|
|
|
|
2023-06-06 18:40:58 +00:00
|
|
|
return new MockPingback(
|
2022-08-17 20:33:06 +00:00
|
|
|
new HashConfig( [ MainConfigNames::Pingback => $enablePingback ] ),
|
2023-05-05 11:33:11 +00:00
|
|
|
$dbProvider,
|
2021-07-20 09:29:31 +00:00
|
|
|
$cache ?? new HashBagOStuff(),
|
|
|
|
|
$httpRequestFactory,
|
|
|
|
|
new NullLogger()
|
|
|
|
|
);
|
|
|
|
|
}
|
2020-12-19 02:46:40 +00:00
|
|
|
|
2021-07-20 09:29:31 +00:00
|
|
|
public function testDisabled() {
|
2020-12-19 02:46:40 +00:00
|
|
|
// Scenario: Disabled
|
2023-06-06 18:40:58 +00:00
|
|
|
$pingback = $this->makePingback(
|
2024-05-02 20:51:01 +00:00
|
|
|
$this->createNoOpMock( IDatabase::class ),
|
2021-07-20 09:29:31 +00:00
|
|
|
$this->createNoOpMock( HttpRequestFactory::class ),
|
|
|
|
|
false /* $enablePingback */
|
|
|
|
|
);
|
2023-06-06 18:40:58 +00:00
|
|
|
// Expect:
|
|
|
|
|
// - no db select, lock, or upsert (asserted via no-op mock)
|
|
|
|
|
// - no HTTP request (asserted via no-op mock)
|
|
|
|
|
$pingback->run();
|
2020-12-19 02:46:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testCacheBusy() {
|
|
|
|
|
// Scenario:
|
|
|
|
|
// - enabled
|
|
|
|
|
// - table is empty
|
|
|
|
|
// - cache lock is unavailable
|
2024-05-02 20:51:01 +00:00
|
|
|
$database = $this->createNoOpMock( IDatabase::class, [ 'selectField', 'newSelectQueryBuilder' ] );
|
2021-07-20 09:29:31 +00:00
|
|
|
$database->expects( $this->once() )->method( 'selectField' )->willReturn( false );
|
2023-09-27 13:22:44 +00:00
|
|
|
$database->method( 'newSelectQueryBuilder' )->willReturnCallback( static fn () => new SelectQueryBuilder( $database ) );
|
2023-02-03 12:09:05 +00:00
|
|
|
|
2021-07-20 09:29:31 +00:00
|
|
|
$cache = $this->createMock( BagOStuff::class );
|
|
|
|
|
$cache->method( 'add' )->willReturn( false );
|
2020-12-19 02:46:40 +00:00
|
|
|
|
2023-06-06 18:40:58 +00:00
|
|
|
$pingback = $this->makePingback(
|
2021-07-20 09:29:31 +00:00
|
|
|
$database,
|
|
|
|
|
$this->createNoOpMock( HttpRequestFactory::class ),
|
|
|
|
|
true, /* $enablePingback */
|
|
|
|
|
$cache
|
|
|
|
|
);
|
2023-06-06 18:40:58 +00:00
|
|
|
|
|
|
|
|
// Expect:
|
|
|
|
|
// - no db lock
|
|
|
|
|
// - no HTTP request
|
|
|
|
|
// - no db upsert for timestamp
|
|
|
|
|
$pingback->run();
|
2020-12-19 02:46:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testDbBusy() {
|
|
|
|
|
// Scenario:
|
|
|
|
|
// - enabled
|
|
|
|
|
// - table is empty
|
|
|
|
|
// - cache lock is available
|
|
|
|
|
// - db lock is unavailable
|
2024-05-02 20:51:01 +00:00
|
|
|
$database = $this->createNoOpMock( IDatabase::class, [ 'selectField', 'lock', 'newSelectQueryBuilder' ] );
|
2021-07-20 09:29:31 +00:00
|
|
|
$database->expects( $this->once() )->method( 'selectField' )->willReturn( false );
|
|
|
|
|
$database->expects( $this->once() )->method( 'lock' )->willReturn( false );
|
2023-09-27 13:22:44 +00:00
|
|
|
$database->method( 'newSelectQueryBuilder' )->willReturnCallback( static fn () => new SelectQueryBuilder( $database ) );
|
2020-12-19 02:46:40 +00:00
|
|
|
|
2023-06-06 18:40:58 +00:00
|
|
|
$pingback = $this->makePingback(
|
2021-07-20 09:29:31 +00:00
|
|
|
$database,
|
|
|
|
|
$this->createNoOpMock( HttpRequestFactory::class )
|
|
|
|
|
);
|
2023-06-06 18:40:58 +00:00
|
|
|
// Expect:
|
|
|
|
|
// - no HTTP request
|
|
|
|
|
// - no db upsert for timestamp
|
|
|
|
|
$pingback->run();
|
2020-12-19 02:46:40 +00:00
|
|
|
}
|
|
|
|
|
|
2021-07-20 09:29:31 +00:00
|
|
|
/**
|
|
|
|
|
* @dataProvider provideMakePing
|
|
|
|
|
*/
|
|
|
|
|
public function testMakePing( $priorPing ) {
|
2020-12-19 02:46:40 +00:00
|
|
|
// Scenario:
|
|
|
|
|
// - enabled
|
2021-07-20 09:29:31 +00:00
|
|
|
// - table is either
|
|
|
|
|
// - empty ($priorPing is false)
|
|
|
|
|
// - has a ping from over a month ago ($piorPing)
|
2020-12-19 02:46:40 +00:00
|
|
|
// - cache lock and db lock are available
|
2023-09-05 19:21:03 +00:00
|
|
|
$database = $this->createNoOpMock(
|
2024-05-02 20:51:01 +00:00
|
|
|
IDatabase::class,
|
2023-09-05 19:21:03 +00:00
|
|
|
[ 'selectField', 'lock', 'upsert', 'newSelectQueryBuilder', 'newInsertQueryBuilder' ]
|
|
|
|
|
);
|
2023-07-17 11:26:03 +00:00
|
|
|
$httpRequestFactory = $this->createNoOpMock( HttpRequestFactory::class, [ 'create' ] );
|
|
|
|
|
$httpRequest = $this->createNoOpMock( MWHttpRequest::class, [ 'setHeader', 'execute' ] );
|
2021-07-20 09:29:31 +00:00
|
|
|
|
|
|
|
|
$database->expects( $this->once() )->method( 'selectField' )->willReturn( $priorPing );
|
|
|
|
|
$database->expects( $this->once() )->method( 'lock' )->willReturn( true );
|
2023-07-17 11:26:03 +00:00
|
|
|
|
2021-07-20 09:29:31 +00:00
|
|
|
$httpRequestFactory->expects( $this->once() )
|
2023-07-17 11:26:03 +00:00
|
|
|
->method( 'create' )
|
|
|
|
|
->with(
|
|
|
|
|
'https://intake-analytics.wikimedia.org/v1/events?hasty=true',
|
|
|
|
|
[
|
|
|
|
|
'method' => 'POST',
|
|
|
|
|
'postData' => '{"some":"stuff"}',
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
->willReturn( $httpRequest );
|
|
|
|
|
|
|
|
|
|
$httpRequest->expects( $this->once() )
|
|
|
|
|
->method( 'execute' )
|
|
|
|
|
->willReturn( Status::newGood() );
|
|
|
|
|
|
2021-07-20 09:29:31 +00:00
|
|
|
$database->expects( $this->once() )->method( 'upsert' );
|
2023-09-27 13:22:44 +00:00
|
|
|
$database->method( 'newSelectQueryBuilder' )->willReturnCallback( static fn () => new SelectQueryBuilder( $database ) );
|
|
|
|
|
$database->method( 'newInsertQueryBuilder' )->willReturnCallback( static fn () => new InsertQueryBuilder( $database ) );
|
2020-12-19 02:46:40 +00:00
|
|
|
|
2023-06-06 18:40:58 +00:00
|
|
|
$pingback = $this->makePingback(
|
2021-07-20 09:29:31 +00:00
|
|
|
$database,
|
|
|
|
|
$httpRequestFactory
|
|
|
|
|
);
|
2023-06-06 18:40:58 +00:00
|
|
|
// Expect:
|
|
|
|
|
// - db lock acquired
|
|
|
|
|
// - HTTP POST request
|
|
|
|
|
// - db upsert for timestamp
|
|
|
|
|
$pingback->run();
|
2020-12-19 02:46:40 +00:00
|
|
|
}
|
|
|
|
|
|
2023-03-23 11:36:19 +00:00
|
|
|
public static function provideMakePing() {
|
2021-07-20 09:29:31 +00:00
|
|
|
yield 'No prior ping' => [ false ];
|
|
|
|
|
yield 'Prior ping from over a month ago' => [
|
|
|
|
|
ConvertibleTimestamp::convert( TS_UNIX, '20110301080000' )
|
|
|
|
|
];
|
|
|
|
|
}
|
2020-12-19 02:46:40 +00:00
|
|
|
|
2021-07-20 09:29:31 +00:00
|
|
|
public function testAfterRecentPing() {
|
2020-12-19 02:46:40 +00:00
|
|
|
// Scenario:
|
|
|
|
|
// - enabled
|
|
|
|
|
// - table contains a ping from one hour ago
|
|
|
|
|
// - cache lock and db lock are available
|
2024-05-02 20:51:01 +00:00
|
|
|
$database = $this->createNoOpMock( IDatabase::class, [ 'selectField', 'newSelectQueryBuilder' ] );
|
2021-07-20 09:29:31 +00:00
|
|
|
$database->expects( $this->once() )->method( 'selectField' )->willReturn(
|
|
|
|
|
ConvertibleTimestamp::convert( TS_UNIX, '20110401080000' )
|
2020-12-19 02:46:40 +00:00
|
|
|
);
|
2023-09-27 13:22:44 +00:00
|
|
|
$database->method( 'newSelectQueryBuilder' )->willReturnCallback( static fn () => new SelectQueryBuilder( $database ) );
|
2020-12-19 02:46:40 +00:00
|
|
|
|
2023-06-06 18:40:58 +00:00
|
|
|
$pingback = $this->makePingback(
|
2021-07-20 09:29:31 +00:00
|
|
|
$database,
|
|
|
|
|
$this->createNoOpMock( HttpRequestFactory::class )
|
|
|
|
|
);
|
2023-06-06 18:40:58 +00:00
|
|
|
// Expect:
|
|
|
|
|
// - no db lock
|
|
|
|
|
// - no HTTP request
|
|
|
|
|
// - no db upsert for timestamp
|
|
|
|
|
$pingback->run();
|
2020-12-19 02:46:40 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class MockPingback extends Pingback {
|
2021-07-22 03:11:47 +00:00
|
|
|
protected function getData(): array {
|
2020-12-19 02:46:40 +00:00
|
|
|
return [ 'some' => 'stuff' ];
|
|
|
|
|
}
|
|
|
|
|
}
|