mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/Echo
synced 2024-12-02 11:26:26 +00:00
8da75c3a50
Currently echo attempts to find a signature by looking for a series of strings starting with what it thinks are the current aliases of NS_USER and NS_USER_TALK. This has shown to be error prone, see the linked bug for how a change to ru.wikipedia.org/wiki/Mediawiki:Signature broke mention notifications. Patch switches things arround to pull wikilinks out of the text and run them through the Title class. The results of this parsing are checked for NS_USER and NS_USER_TALK, giving a much stronger guarantee of finding translated namespaces. Bug: 71353 Change-Id: Ib0d0f4e068339d2fd28761087c05f5a1acb3c1fc
655 lines
14 KiB
PHP
655 lines
14 KiB
PHP
<?php
|
|
|
|
/**
|
|
* @group Echo
|
|
*/
|
|
class EchoDiscussionParserTest extends MediaWikiTestCase {
|
|
// TODO test cases for:
|
|
// - generateEventsForRevision
|
|
// - stripHeader
|
|
// - stripIndents
|
|
// - stripSignature
|
|
// - getNotifiedUsersForComment
|
|
|
|
public function testDiscussionParserAcceptsInternalDiff() {
|
|
global $wgDiff;
|
|
|
|
$origWgDiff = $wgDiff;
|
|
$wgDiff = '/does/not/exist/or/at/least/we/hope/not';
|
|
try {
|
|
$res = EchoDiscussionParser::getMachineReadableDiff(
|
|
<<<TEXT
|
|
line 1
|
|
line 2
|
|
line 3
|
|
line 4
|
|
TEXT
|
|
,
|
|
<<<TEXT
|
|
line 1
|
|
line c
|
|
line 4
|
|
TEXT
|
|
);
|
|
} catch ( MWException $e ) {
|
|
$wgDiff = $origWgDiff;
|
|
throw $e;
|
|
}
|
|
$wgDiff = $origWgDiff;
|
|
|
|
// Test failure occurs when MWException is thrown due to parsing failure
|
|
$this->assertTrue( true );
|
|
}
|
|
|
|
public function testTimestampRegex() {
|
|
$exemplarTimestamp = self::getExemplarTimestamp();
|
|
$timestampRegex = EchoDiscussionParser::getTimestampRegex();
|
|
|
|
$match = preg_match( '/' . $timestampRegex . '/u', $exemplarTimestamp );
|
|
$this->assertEquals( 1, $match );
|
|
}
|
|
|
|
public function testGetTimestampPosition() {
|
|
$line = 'Hello World. '. self::getExemplarTimestamp();
|
|
$pos = EchoDiscussionParser::getTimestampPosition( $line );
|
|
$this->assertEquals( 13, $pos );
|
|
}
|
|
|
|
/**
|
|
* @dataProvider signingDetectionData
|
|
* FIXME some of the app logic is in the test...
|
|
*/
|
|
public function testSigningDetection( $line, $expectedUser ) {
|
|
if ( !EchoDiscussionParser::isSignedComment( $line ) ) {
|
|
$this->assertEquals( $expectedUser, false );
|
|
return;
|
|
}
|
|
|
|
$tsPos = EchoDiscussionParser::getTimestampPosition( $line );
|
|
$output = EchoDiscussionParser::getUserFromLine( $line, $tsPos );
|
|
|
|
if ( $output === false ) {
|
|
$this->assertEquals( false, $expectedUser );
|
|
} elseif ( is_array( $expectedUser ) ) {
|
|
// Sometimes testing for correct user detection,
|
|
// sometimes testing for offset detection
|
|
$this->assertEquals( $expectedUser, $output );
|
|
} else {
|
|
$this->assertEquals( $expectedUser, $output[1] );
|
|
}
|
|
}
|
|
|
|
public function signingDetectionData() {
|
|
$ts = self::getExemplarTimestamp();
|
|
return array(
|
|
// Basic
|
|
array(
|
|
"I like this. [[User:Werdna]] ([[User talk:Werdna|talk]]) $ts",
|
|
array(
|
|
13,
|
|
'Werdna'
|
|
),
|
|
),
|
|
// Confounding
|
|
array(
|
|
"[[User:Jorm]] is a meanie. --[[User:Werdna|Andrew]] $ts",
|
|
array(
|
|
29,
|
|
"Werdna"
|
|
),
|
|
),
|
|
// Talk page link only
|
|
array(
|
|
"[[User:Swalling|Steve]] is the best person I have ever met. --[[User talk:Werdna|Andrew]] $ts",
|
|
array(
|
|
62,
|
|
'Werdna'
|
|
),
|
|
),
|
|
// Anonymous user
|
|
array(
|
|
"I am anonymous because I like my IP address. --[[Special:Contributions/127.0.0.1]] $ts",
|
|
array(
|
|
47,
|
|
'127.0.0.1'
|
|
),
|
|
),
|
|
// No signature
|
|
array(
|
|
"Well, \nI do think that [[User:Newyorkbrad]] is pretty cool, but what do I know?",
|
|
false
|
|
),
|
|
// Hash symbols in usernames
|
|
array(
|
|
"What do you think? [[User talk:We buried our secrets in the garden#top|wbositg]] $ts",
|
|
array(
|
|
19,
|
|
'We buried our secrets in the garden'
|
|
),
|
|
),
|
|
// Title that gets normalized different than it is provided in the wikitext
|
|
array(
|
|
"Beep boop [[User:I_Heart_Spaces]] ([[User_talk:I_Heart_Spaces]]) $ts",
|
|
array(
|
|
10,
|
|
'I Heart Spaces'
|
|
),
|
|
),
|
|
// Accepts ] in the pipe
|
|
array(
|
|
"Shake n Bake --[[User:Werdna|wer]dna]] $ts",
|
|
array(
|
|
15,
|
|
'Werdna',
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
/** @dataProvider diffData */
|
|
public function testDiff( $oldText, $newText, $expected ) {
|
|
$actual = EchoDiscussionParser::getMachineReadableDiff( $oldText, $newText );
|
|
unset( $actual['_info'] );
|
|
unset( $expected['_info'] );
|
|
|
|
$this->assertEquals( $expected, $actual );
|
|
}
|
|
|
|
public function diffData() {
|
|
return array(
|
|
array(
|
|
<<<TEXT
|
|
line 1
|
|
line 2
|
|
line 3
|
|
line 4
|
|
TEXT
|
|
,<<<TEXT
|
|
line 1
|
|
line 3
|
|
line 4
|
|
TEXT
|
|
,
|
|
array( array(
|
|
'action' => 'subtract',
|
|
'content' => 'line 2',
|
|
'left-pos' => 2,
|
|
'right-pos' => 2,
|
|
) )
|
|
),
|
|
array(
|
|
<<<TEXT
|
|
line 1
|
|
line 2
|
|
line 3
|
|
line 4
|
|
TEXT
|
|
,<<<TEXT
|
|
line 1
|
|
line 2
|
|
line 2.5
|
|
line 3
|
|
line 4
|
|
TEXT
|
|
,
|
|
array( array(
|
|
'action' => 'add',
|
|
'content' => 'line 2.5',
|
|
'left-pos' => 3,
|
|
'right-pos' => 3,
|
|
) )
|
|
),
|
|
array(
|
|
<<<TEXT
|
|
line 1
|
|
line 2
|
|
line 3
|
|
line 4
|
|
TEXT
|
|
,<<<TEXT
|
|
line 1
|
|
line b
|
|
line 3
|
|
line 4
|
|
TEXT
|
|
,
|
|
array( array(
|
|
'action' => 'change',
|
|
'old_content' => 'line 2',
|
|
'new_content' => 'line b',
|
|
'left-pos' => 2,
|
|
'right-pos' => 2,
|
|
) )
|
|
),
|
|
array(
|
|
<<<TEXT
|
|
line 1
|
|
line 2
|
|
line 3
|
|
line 4
|
|
TEXT
|
|
,<<<TEXT
|
|
line 1
|
|
line b
|
|
line c
|
|
line d
|
|
line 3
|
|
line 4
|
|
TEXT
|
|
,
|
|
array(
|
|
array(
|
|
'action' => 'change',
|
|
'old_content' => 'line 2',
|
|
'new_content' => 'line b',
|
|
'left-pos' => 2,
|
|
'right-pos' => 2,
|
|
),
|
|
array(
|
|
'action' => 'add',
|
|
'content' => 'line c
|
|
line d',
|
|
'left-pos' => 3,
|
|
'right-pos' => 3,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
/** @dataProvider annotationData */
|
|
public function testAnnotation( $message, $diff, $user, $expectedAnnotation ) {
|
|
$actual = EchoDiscussionParser::interpretDiff( $diff, $user );
|
|
$this->assertEquals( $expectedAnnotation, $actual, $message );
|
|
}
|
|
|
|
public function annotationData() {
|
|
$ts = self::getExemplarTimestamp();
|
|
|
|
return array(
|
|
|
|
array(
|
|
'Must detect added comments',
|
|
// Diff
|
|
array(
|
|
// Action
|
|
array(
|
|
'action' => 'add',
|
|
'content' => ":What do you think? [[User:Werdna]] $ts",
|
|
'left-pos' => 3,
|
|
'right-pos' => 3,
|
|
),
|
|
'_info' => array(
|
|
'lhs' => array(
|
|
'== Section 1 ==',
|
|
"I do not like you. [[User:Jorm|Jorm]] $ts",
|
|
),
|
|
'rhs' => array(
|
|
'== Section 1 ==',
|
|
"I do not like you. [[User:Jorm|Jorm]] $ts",
|
|
":What do you think? [[User:Werdna]] $ts",
|
|
),
|
|
),
|
|
),
|
|
// User
|
|
'Werdna',
|
|
// Expected annotation
|
|
array(
|
|
array(
|
|
'type' => 'add-comment',
|
|
'content' => ":What do you think? [[User:Werdna]] $ts",
|
|
'full-section' => <<<TEXT
|
|
== Section 1 ==
|
|
I do not like you. [[User:Jorm|Jorm]] $ts
|
|
:What do you think? [[User:Werdna]] $ts
|
|
TEXT
|
|
),
|
|
),
|
|
),
|
|
|
|
array(
|
|
'Full Section must not include the following pre-existing section',
|
|
// Diff
|
|
array(
|
|
// Action
|
|
array(
|
|
'action' => 'add',
|
|
'content' => ":What do you think? [[User:Werdna]] $ts",
|
|
'left-pos' => 3,
|
|
'right-pos' => 3,
|
|
),
|
|
'_info' => array(
|
|
'lhs' => array(
|
|
'== Section 1 ==',
|
|
"I do not like you. [[User:Jorm|Jorm]] $ts",
|
|
'== Section 2 ==',
|
|
"Well well well. [[User:DarTar]] $ts",
|
|
),
|
|
'rhs' => array(
|
|
'== Section 1 ==',
|
|
"I do not like you. [[User:Jorm|Jorm]] $ts",
|
|
":What do you think? [[User:Werdna]] $ts",
|
|
'== Section 2 ==',
|
|
"Well well well. [[User:DarTar]] $ts",
|
|
),
|
|
),
|
|
),
|
|
// User
|
|
'Werdna',
|
|
// Expected annotation
|
|
array(
|
|
array(
|
|
'type' => 'add-comment',
|
|
'content' => ":What do you think? [[User:Werdna]] $ts",
|
|
'full-section' => <<<TEXT
|
|
== Section 1 ==
|
|
I do not like you. [[User:Jorm|Jorm]] $ts
|
|
:What do you think? [[User:Werdna]] $ts
|
|
TEXT
|
|
),
|
|
),
|
|
),
|
|
|
|
array(
|
|
'Must detect new-section-with-comment when a new section is added',
|
|
// Diff
|
|
array(
|
|
// Action
|
|
array(
|
|
'action' => 'add',
|
|
'content' => <<<TEXT
|
|
== Section 1a ==
|
|
Hmmm? [[User:Jdforrester]] $ts
|
|
TEXT
|
|
,
|
|
'left-pos' => 4,
|
|
'right-pos' => 4,
|
|
),
|
|
'_info' => array(
|
|
'lhs' => array(
|
|
'== Section 1 ==',
|
|
"I do not like you. [[User:Jorm|Jorm]] $ts",
|
|
":What do you think? [[User:Werdna]] $ts",
|
|
'== Section 2 ==',
|
|
"Well well well. [[User:DarTar]] $ts",
|
|
),
|
|
'rhs' => array(
|
|
'== Section 1 ==',
|
|
"I do not like you. [[User:Jorm|Jorm]] $ts",
|
|
":What do you think? [[User:Werdna]] $ts",
|
|
'== Section 1a ==',
|
|
'Hmmm? [[User:Jdforrester]] $ts',
|
|
'== Section 2 ==',
|
|
"Well well well. [[User:DarTar]] $ts",
|
|
),
|
|
),
|
|
),
|
|
// User
|
|
'Jdforrester',
|
|
// Expected annotation
|
|
array(
|
|
array(
|
|
'type' => 'new-section-with-comment',
|
|
'content' => <<<TEXT
|
|
== Section 1a ==
|
|
Hmmm? [[User:Jdforrester]] $ts
|
|
TEXT
|
|
,
|
|
),
|
|
),
|
|
),
|
|
|
|
array(
|
|
'Must detect multiple added comments when multiple sections are edited',
|
|
EchoDiscussionParser::getMachineReadableDiff(
|
|
<<<TEXT
|
|
== Section 1 ==
|
|
I do not like you. [[User:Jorm|Jorm]] $ts
|
|
:What do you think? [[User:Werdna]] $ts
|
|
== Section 2 ==
|
|
Well well well. [[User:DarTar]] $ts
|
|
== Section 3 ==
|
|
Hai [[User:Bsitu]] $ts
|
|
TEXT
|
|
,
|
|
<<<TEXT
|
|
== Section 1 ==
|
|
I do not like you. [[User:Jorm|Jorm]] $ts
|
|
:What do you think? [[User:Werdna]] $ts
|
|
:New Comment [[User:JarJar]] $ts
|
|
== Section 2 ==
|
|
Well well well. [[User:DarTar]] $ts
|
|
== Section 3 ==
|
|
Hai [[User:Bsitu]] $ts
|
|
:Other New Comment [[User:JarJar]] $ts
|
|
TEXT
|
|
),
|
|
// User
|
|
'JarJar',
|
|
// Expected annotation
|
|
array(
|
|
array(
|
|
'type' => 'add-comment',
|
|
'content' => ":New Comment [[User:JarJar]] $ts",
|
|
'full-section' => <<<TEXT
|
|
== Section 1 ==
|
|
I do not like you. [[User:Jorm|Jorm]] $ts
|
|
:What do you think? [[User:Werdna]] $ts
|
|
:New Comment [[User:JarJar]] $ts
|
|
TEXT
|
|
),
|
|
array(
|
|
'type' => 'add-comment',
|
|
'content' => ":Other New Comment [[User:JarJar]] $ts",
|
|
'full-section' => <<<TEXT
|
|
== Section 3 ==
|
|
Hai [[User:Bsitu]] $ts
|
|
:Other New Comment [[User:JarJar]] $ts
|
|
TEXT
|
|
),
|
|
),
|
|
|
|
),
|
|
);
|
|
}
|
|
|
|
public static function getExemplarTimestamp() {
|
|
$title = Title::newMainPage();
|
|
$user = User::newFromName( 'Test' );
|
|
$options = new ParserOptions;
|
|
|
|
global $wgParser;
|
|
$exemplarTimestamp =
|
|
$wgParser->preSaveTransform( '~~~~~', $title, $user, $options );
|
|
|
|
return $exemplarTimestamp;
|
|
}
|
|
|
|
static public function provider_detectSectionTitleAndText() {
|
|
$name = 'TestUser';
|
|
$comment = self::signedMessage( $name );
|
|
|
|
return array(
|
|
array(
|
|
'Must detect first sub heading when inserting in the middle of two sub headings',
|
|
// expected header content
|
|
'Sub Heading 1',
|
|
// test content format
|
|
"
|
|
== Heading ==
|
|
$comment
|
|
|
|
== Sub Heading 1 ==
|
|
$comment
|
|
%s
|
|
|
|
== Sub Heading 2 ==
|
|
$comment
|
|
",
|
|
// user signing new comment
|
|
$name
|
|
),
|
|
|
|
array(
|
|
'Must detect second sub heading when inserting in the end of two sub headings',
|
|
// expected header content
|
|
'Sub Heading 2',
|
|
// test content format
|
|
"
|
|
== Heading ==
|
|
$comment
|
|
|
|
== Sub Heading 1 ==
|
|
$comment
|
|
|
|
== Sub Heading 2 ==
|
|
$comment
|
|
%s
|
|
",
|
|
// user signing new comment
|
|
$name
|
|
),
|
|
|
|
array(
|
|
'Commenting in multiple sub-headings must result in no section link',
|
|
// expected header content
|
|
'',
|
|
// test content format
|
|
"
|
|
== Heading ==
|
|
$comment
|
|
|
|
== Sub Heading 1 ==
|
|
$comment
|
|
%s
|
|
|
|
== Sub Heading 2 ==
|
|
$comment
|
|
%s
|
|
|
|
",
|
|
// user signing new comment
|
|
$name
|
|
),
|
|
|
|
array(
|
|
'Must accept headings without a space between the = and the section name',
|
|
// expected header content
|
|
'Heading',
|
|
// test content format
|
|
"
|
|
==Heading==
|
|
$comment
|
|
%s
|
|
",
|
|
// user signing new comment
|
|
$name
|
|
),
|
|
|
|
array(
|
|
'Must not accept invalid headings split with a return',
|
|
// expected header content
|
|
'',
|
|
// test content format
|
|
"
|
|
==Some
|
|
Heading==
|
|
$comment
|
|
%s
|
|
",
|
|
// user signing new comment
|
|
$name
|
|
),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @dataProvider provider_detectSectionTitleAndText
|
|
*/
|
|
public function testDetectSectionTitleAndText( $message, $expect, $format, $name ) {
|
|
// str_replace because we want to replace multiple instances of '%s' with the same valueA
|
|
$before = str_replace( '%s', '', $format );
|
|
$after = str_replace( '%s', self::signedMessage( $name ), $format );
|
|
|
|
$diff = EchoDiscussionParser::getMachineReadableDiff( $before, $after );
|
|
$interp = EchoDiscussionParser::interpretDiff( $diff, $name );
|
|
|
|
// There should be a section-text only if there is section-title
|
|
$expectText = $expect ? self::message( $name ) : '';
|
|
$this->assertEquals(
|
|
array( 'section-title' => $expect, 'section-text' => $expectText ),
|
|
EchoDiscussionParser::detectSectionTitleAndText( $interp, $message )
|
|
);
|
|
}
|
|
|
|
protected static function signedMessage( $name ) {
|
|
return ": " . self::message() . " [[User:$name|$name]] ([[User talk:$name|talk]]) 00:17, 7 May 2013 (UTC)";
|
|
}
|
|
|
|
protected static function message() {
|
|
return 'foo';
|
|
}
|
|
|
|
static public function provider_getFullSection() {
|
|
$tests = array(
|
|
array(
|
|
'Extracts full section',
|
|
// Full document content
|
|
<<<TEXT
|
|
==Header 1==
|
|
foo
|
|
===Header 2===
|
|
bar
|
|
==Header 3==
|
|
baz
|
|
TEXT
|
|
,
|
|
// Map of Line numbers to expanded section content
|
|
array(
|
|
1 => "==Header 1==\nfoo",
|
|
2 => "==Header 1==\nfoo",
|
|
3 => "===Header 2===\nbar",
|
|
4 => "===Header 2===\nbar",
|
|
5 => "==Header 3==\nbaz",
|
|
6 => "==Header 3==\nbaz",
|
|
),
|
|
),
|
|
);
|
|
|
|
// Allow for setting an array of line numbers to expand from rather than
|
|
// just a single line number
|
|
$retval = array();
|
|
foreach ( $tests as $test ) {
|
|
foreach ( $test[2] as $lineNum => $expected ) {
|
|
$retval[] = array(
|
|
$test[0],
|
|
$expected,
|
|
$test[1],
|
|
$lineNum,
|
|
);
|
|
}
|
|
}
|
|
|
|
return $retval;
|
|
}
|
|
|
|
/**
|
|
* @dataProvider provider_getFullSection
|
|
*/
|
|
public function testGetFullSection( $message, $expect, $lines, $startLineNum ) {
|
|
$section = EchoDiscussionParser::getFullSection( explode( "\n", $lines ), $startLineNum );
|
|
$this->assertEquals( $expect, $section, $message );
|
|
}
|
|
|
|
public function testGetSectionCount() {
|
|
$one = "==Zomg==\nfoobar\n";
|
|
$two = "===SubZomg===\nHi there\n";
|
|
$three = "==Header==\nOh Hai!\n";
|
|
|
|
$this->assertEquals( 1, EchoDiscussionParser::getSectionCount( $one ) );
|
|
$this->assertEquals( 2, EchoDiscussionParser::getSectionCount( $one . $two ) );
|
|
$this->assertEquals( 2, EchoDiscussionParser::getSectionCount( $one . $three ) );
|
|
$this->assertEquals( 3, EchoDiscussionParser::getSectionCount( $one . $two . $three ) );
|
|
}
|
|
}
|