summaryrefslogtreecommitdiff
path: root/www/wiki/extensions/Translate/tests/phpunit/TTMServerMessageUpdateJobTest.php
diff options
context:
space:
mode:
Diffstat (limited to 'www/wiki/extensions/Translate/tests/phpunit/TTMServerMessageUpdateJobTest.php')
-rw-r--r--www/wiki/extensions/Translate/tests/phpunit/TTMServerMessageUpdateJobTest.php438
1 files changed, 438 insertions, 0 deletions
diff --git a/www/wiki/extensions/Translate/tests/phpunit/TTMServerMessageUpdateJobTest.php b/www/wiki/extensions/Translate/tests/phpunit/TTMServerMessageUpdateJobTest.php
new file mode 100644
index 00000000..1509c5b8
--- /dev/null
+++ b/www/wiki/extensions/Translate/tests/phpunit/TTMServerMessageUpdateJobTest.php
@@ -0,0 +1,438 @@
+<?php
+/**
+ * @file
+ * @author David Causse
+ * @license GPL-2.0-or-later
+ */
+
+/**
+ * Mostly test mirroring and failure modes.
+ */
+class TTMServerMessageUpdateJobTest extends MediaWikiTestCase {
+ /**
+ * @var WritableTTMServer[] used to link our mocks with TestableTTMServer built by the
+ * factory
+ */
+ public static $mockups = [];
+
+ public function setUp() {
+ parent::setUp();
+ self::$mockups = [];
+ $this->setMwGlobals( [
+ 'wgTranslateTranslationServices' => [
+ 'primary' => [
+ 'class' => TestableTTMServer::class,
+ // will be used as the key in static::$mockups to attach the
+ // mock to the newly created TestableTTMServer instance
+ 'name' => 'primary',
+ 'mirrors' => [ 'secondary' ],
+ ],
+ 'secondary' => [
+ 'class' => TestableTTMServer::class,
+ 'name' => 'secondary',
+ ]
+ ],
+ 'wgTranslateTranslationDefaultService' => 'primary'
+ ] );
+ }
+
+ public function tearDown() {
+ parent::tearDown();
+ self::$mockups = [];
+ }
+
+ /**
+ * Normal mode, we ensure that update is called on primary and its mirror without any resent
+ * jobs
+ */
+ public function testReplication() {
+ $mock = $this->getMockBuilder( WritableTTMServer::class )
+ ->getMock();
+ $mock->expects( $this->atLeastOnce() )
+ ->method( 'update' );
+ static::$mockups['primary'] = $mock;
+ $mock = $this->getMockBuilder( WritableTTMServer::class )
+ ->getMock();
+ $mock->expects( $this->atLeastOnce() )
+ ->method( 'update' );
+ static::$mockups['secondary'] = $mock;
+
+ $job = new TestableTTMServerMessageUpdateJob(
+ Title::makeTitle( NS_MAIN, 'Main Page' ),
+ [ 'command' => 'refresh' ],
+ $this->getMockBuilder( MessageHandle::class )
+ ->disableOriginalConstructor()
+ ->getMock()
+ );
+ $job->run();
+ $this->assertEmpty( $job->getResentJobs() );
+ }
+
+ /**
+ * The mirror failed, we ensure that we resend a job
+ * with the appropriate params.
+ */
+ public function testReplicationError() {
+ $mock = $this->getMockBuilder( WritableTTMServer::class )
+ ->getMock();
+ $mock->expects( $this->atLeastOnce() )
+ ->method( 'update' );
+ static::$mockups['primary'] = $mock;
+ $mock = $this->getMockBuilder( WritableTTMServer::class )
+ ->getMock();
+ $mock->expects( $this->atLeastOnce() )
+ ->method( 'update' )
+ ->will( $this->throwException( new TTMServerException ) );
+ static::$mockups['secondary'] = $mock;
+
+ $job = new TestableTTMServerMessageUpdateJob(
+ Title::makeTitle( NS_MAIN, 'Main Page' ),
+ [ 'command' => 'refresh' ],
+ $this->getMockBuilder( MessageHandle::class )
+ ->disableOriginalConstructor()
+ ->getMock()
+ );
+ $job->run();
+ $this->assertEquals( 1, count( $job->getResentJobs() ) );
+ $expectedParams = [
+ 'errorCount' => 1,
+ 'service' => 'secondary',
+ 'command' => 'refresh'
+ ];
+ $actualParams = array_intersect_key(
+ $job->getResentJobs()[0]->getParams(),
+ $expectedParams
+ );
+ $this->assertEquals( $expectedParams, $actualParams );
+ }
+
+ /**
+ * All services failed, we ensure that we resend 2 jobs for
+ * each services
+ */
+ public function testAllServicesInError() {
+ $mock = $this->getMockBuilder( WritableTTMServer::class )
+ ->getMock();
+ $mock->expects( $this->atLeastOnce() )
+ ->method( 'update' )
+ ->will( $this->throwException( new TTMServerException ) );
+ static::$mockups['primary'] = $mock;
+ $mock = $this->getMockBuilder( WritableTTMServer::class )
+ ->getMock();
+ $mock->expects( $this->atLeastOnce() )
+ ->method( 'update' )
+ ->will( $this->throwException( new TTMServerException ) );
+ static::$mockups['secondary'] = $mock;
+
+ $job = new TestableTTMServerMessageUpdateJob(
+ Title::makeTitle( NS_MAIN, 'Main Page' ),
+ [ 'command' => 'refresh' ],
+ $this->getMockBuilder( MessageHandle::class )
+ ->disableOriginalConstructor()
+ ->getMock()
+ );
+ $job->run();
+ $this->assertEquals( 2, count( $job->getResentJobs() ) );
+ $expectedParams = [
+ 'errorCount' => 1,
+ 'service' => 'primary',
+ 'command' => 'refresh'
+ ];
+ $actualParams = array_intersect_key(
+ $job->getResentJobs()[0]->getParams(),
+ $expectedParams
+ );
+ $this->assertEquals( $expectedParams, $actualParams );
+
+ $expectedParams = [
+ 'errorCount' => 1,
+ 'service' => 'secondary',
+ 'command' => 'refresh'
+ ];
+ $actualParams = array_intersect_key(
+ $job->getResentJobs()[1]->getParams(),
+ $expectedParams
+ );
+ $this->assertEquals( $expectedParams, $actualParams );
+ }
+
+ /**
+ * We simulate a resent job after a failure, this job is directed to a specific service, we
+ * ensure that we do not replicate the write to its mirror
+ */
+ public function testJobOnSingleService() {
+ $mock = $this->getMockBuilder( WritableTTMServer::class )
+ ->getMock();
+ $mock->expects( $this->atLeastOnce() )
+ ->method( 'update' );
+ static::$mockups['primary'] = $mock;
+ $mock = $this->getMockBuilder( WritableTTMServer::class )
+ ->getMock();
+ $mock->expects( $this->never() )
+ ->method( 'update' );
+ static::$mockups['secondary'] = $mock;
+
+ $job = new TestableTTMServerMessageUpdateJob(
+ Title::makeTitle( NS_MAIN, 'Main Page' ),
+ [
+ 'errorCount' => 1,
+ 'service' => 'primary',
+ 'command' => 'refresh'
+ ],
+ $this->getMockBuilder( MessageHandle::class )
+ ->disableOriginalConstructor()
+ ->getMock()
+ );
+ $job->run();
+ $this->assertEmpty( $job->getResentJobs() );
+ }
+
+ /**
+ * We simulate a job that failed multiple times and we fail again, we encure that we adandon
+ * the job by not resending it to queue
+ */
+ public function testAbandonedJob() {
+ $mock = $this->getMockBuilder( WritableTTMServer::class )
+ ->getMock();
+ $mock->expects( $this->atLeastOnce() )
+ ->method( 'update' )
+ ->will( $this->throwException( new TTMServerException ) );
+ static::$mockups['primary'] = $mock;
+ $mock = $this->getMockBuilder( WritableTTMServer::class )
+ ->getMock();
+ $mock->expects( $this->never() )
+ ->method( 'update' );
+ static::$mockups['secondary'] = $mock;
+
+ $job = new TestableTTMServerMessageUpdateJob(
+ Title::makeTitle( NS_MAIN, 'Main Page' ),
+ [
+ 'errorCount' => 4,
+ 'service' => 'primary',
+ 'command' => 'refresh'
+ ],
+ $this->getMockBuilder( MessageHandle::class )
+ ->disableOriginalConstructor()
+ ->getMock()
+ );
+ $job->run();
+ $this->assertEmpty( $job->getResentJobs() );
+ }
+
+ /**
+ * One service is frozen
+ */
+ public function testOneServiceFrozen() {
+ $mock = $this->getMockBuilder( WritableTTMServer::class )
+ ->getMock();
+ $mock->expects( $this->atLeastOnce() )
+ ->method( 'update' );
+ static::$mockups['primary'] = $mock;
+ $mock = $this->getMockBuilder( WritableTTMServer::class )
+ ->getMock();
+ $mock->expects( $this->never() )
+ ->method( 'update' );
+ $mock->expects( $this->atLeastOnce() )
+ ->method( 'isFrozen' )
+ ->willReturn( true );
+ static::$mockups['secondary'] = $mock;
+
+ $now = time();
+ $job = new TestableTTMServerMessageUpdateJob(
+ Title::makeTitle( NS_MAIN, 'Main Page' ),
+ [
+ 'command' => 'refresh',
+ 'createdAt' => $now
+ ],
+ $this->getMockBuilder( MessageHandle::class )
+ ->disableOriginalConstructor()
+ ->getMock()
+ );
+ $job->run();
+ $this->assertEquals( 1, count( $job->getResentJobs() ) );
+ $expectedParams = [
+ 'errorCount' => 0,
+ 'retryCount' => 1,
+ 'createdAt' => $now,
+ 'service' => 'secondary',
+ 'command' => 'refresh'
+ ];
+ $actualParams = array_intersect_key(
+ $job->getResentJobs()[0]->getParams(),
+ $expectedParams
+ );
+ $this->assertEquals( $expectedParams, $actualParams );
+ }
+
+ /**
+ * One is broken
+ * One is frozen
+ */
+ public function testOneBrokenOneFrozen() {
+ $mock = $this->getMockBuilder( WritableTTMServer::class )
+ ->getMock();
+ $mock->expects( $this->atLeastOnce() )
+ ->method( 'update' )
+ ->will( $this->throwException( new TTMServerException ) );
+ static::$mockups['primary'] = $mock;
+ $mock = $this->getMockBuilder( WritableTTMServer::class )
+ ->getMock();
+ $mock->expects( $this->never() )
+ ->method( 'update' );
+ $mock->expects( $this->atLeastOnce() )
+ ->method( 'isFrozen' )
+ ->willReturn( true );
+ static::$mockups['secondary'] = $mock;
+
+ $now = time();
+ $job = new TestableTTMServerMessageUpdateJob(
+ Title::makeTitle( NS_MAIN, 'Main Page' ),
+ [
+ 'command' => 'refresh',
+ 'createdAt' => $now
+ ],
+ $this->getMockBuilder( MessageHandle::class )
+ ->disableOriginalConstructor()
+ ->getMock()
+ );
+ $job->run();
+ $this->assertEquals( 2, count( $job->getResentJobs() ) );
+ $expectedParams = [
+ 'errorCount' => 1,
+ 'retryCount' => 0,
+ 'createdAt' => $now,
+ 'service' => 'primary',
+ 'command' => 'refresh'
+ ];
+ $actualParams = array_intersect_key(
+ $job->getResentJobs()[0]->getParams(),
+ $expectedParams
+ );
+ $this->assertEquals( $expectedParams, $actualParams );
+
+ $expectedParams = [
+ 'errorCount' => 0,
+ 'retryCount' => 1,
+ 'createdAt' => $now,
+ 'service' => 'secondary',
+ 'command' => 'refresh'
+ ];
+ $actualParams = array_intersect_key(
+ $job->getResentJobs()[1]->getParams(),
+ $expectedParams
+ );
+ $this->assertEquals( $expectedParams, $actualParams );
+ }
+
+ /**
+ * Old jobs are abandoned
+ */
+ public function testAbandonedOldJob() {
+ $mock = $this->getMockBuilder( WritableTTMServer::class )
+ ->getMock();
+ $mock->expects( $this->never() )
+ ->method( 'update' );
+ $mock->expects( $this->never() )
+ ->method( 'isFrozen' );
+ static::$mockups['primary'] = $mock;
+ $mock = $this->getMockBuilder( WritableTTMServer::class )
+ ->getMock();
+ $mock->expects( $this->never() )
+ ->method( 'update' );
+ $mock->expects( $this->atLeastOnce() )
+ ->method( 'isFrozen' )
+ ->willReturn( true );
+ static::$mockups['secondary'] = $mock;
+
+ $job = new TestableTTMServerMessageUpdateJob(
+ Title::makeTitle( NS_MAIN, 'Main Page' ),
+ [
+ 'command' => 'refresh',
+ 'retryCount' => 10,
+ 'service' => 'secondary',
+ 'createdAt' => time() - TTMServerMessageUpdateJob::DROP_DELAYED_JOBS_AFTER - 1,
+ ],
+ $this->getMockBuilder( MessageHandle::class )
+ ->disableOriginalConstructor()
+ ->getMock()
+ );
+ $job->run();
+ $this->assertEquals( 0, count( $job->getResentJobs() ) );
+ }
+}
+
+/**
+ * Test subclass to override methods that we are not able to mock
+ * easily.
+ * For the context of the test we can only test the 'refresh' command
+ * because other ones would need to have a more complex context to prepare
+ */
+class TestableTTMServerMessageUpdateJob extends TTMServerMessageUpdateJob {
+ private $resentJobs = [];
+ private $handleMock;
+ public function __construct( Title $title, $params, $handleMock ) {
+ parent::__construct( $title, $params );
+ $this->handleMock = $handleMock;
+ }
+ public function resend( TTMServerMessageUpdateJob $job ) {
+ $this->resentJobs[] = $job;
+ }
+
+ protected function getHandle() {
+ return $this->handleMock;
+ }
+
+ protected function getTranslation( MessageHandle $handle ) {
+ return 'random text';
+ }
+
+ public function getResentJobs() {
+ return $this->resentJobs;
+ }
+}
+
+/**
+ * This "testable" TTMServer implementation allows to:
+ * - test TTMServer specific methods
+ * - attach our mocks to the Test static context, this is needed because
+ * the factory always creates a new instance of the service
+ */
+class TestableTTMServer extends TTMServer implements WritableTTMServer {
+ private $delegate;
+ public function __construct( array $config ) {
+ parent::__construct( $config );
+ $this->delegate = TTMServerMessageUpdateJobTest::$mockups[$config['name']];
+ }
+
+ public function update( MessageHandle $handle, $targetText ) {
+ $this->delegate->update( $handle, $targetText );
+ }
+
+ public function beginBootstrap() {
+ $this->delegate->beginBootstrap();
+ }
+
+ public function beginBatch() {
+ $this->delegate->beginBatch();
+ }
+
+ public function batchInsertDefinitions( array $batch ) {
+ $this->delegate->batchInsertDefinitions( $batch );
+ }
+
+ public function batchInsertTranslations( array $batch ) {
+ $this->delegate->batchInsertTranslations( $batch );
+ }
+
+ public function endBatch() {
+ $this->delegate->endBatch();
+ }
+
+ public function endBootstrap() {
+ $this->delegate->endBootstrap();
+ }
+
+ public function isFrozen() {
+ return $this->delegate->isFrozen();
+ }
+}