diff options
Diffstat (limited to 'tests/phpunit/includes/json/FormatJsonTest.php')
-rw-r--r-- | tests/phpunit/includes/json/FormatJsonTest.php | 279 |
1 files changed, 279 insertions, 0 deletions
diff --git a/tests/phpunit/includes/json/FormatJsonTest.php b/tests/phpunit/includes/json/FormatJsonTest.php new file mode 100644 index 00000000..af68ab03 --- /dev/null +++ b/tests/phpunit/includes/json/FormatJsonTest.php @@ -0,0 +1,279 @@ +<?php + +/** + * @covers FormatJson + */ +class FormatJsonTest extends MediaWikiTestCase { + + public static function provideEncoderPrettyPrinting() { + return array( + // Four spaces + array( true, ' ' ), + array( ' ', ' ' ), + // Two spaces + array( ' ', ' ' ), + // One tab + array( "\t", "\t" ), + ); + } + + /** + * @dataProvider provideEncoderPrettyPrinting + */ + public function testEncoderPrettyPrinting( $pretty, $expectedIndent ) { + $obj = array( + 'emptyObject' => new stdClass, + 'emptyArray' => array(), + 'string' => 'foobar\\', + 'filledArray' => array( + array( + 123, + 456, + ), + // Nested json works without problems + '"7":["8",{"9":"10"}]', + // Whitespace clean up doesn't touch strings that look alike + "{\n\t\"emptyObject\": {\n\t},\n\t\"emptyArray\": [ ]\n}", + ), + ); + + // No trailing whitespace, no trailing linefeed + $json = '{ + "emptyObject": {}, + "emptyArray": [], + "string": "foobar\\\\", + "filledArray": [ + [ + 123, + 456 + ], + "\"7\":[\"8\",{\"9\":\"10\"}]", + "{\n\t\"emptyObject\": {\n\t},\n\t\"emptyArray\": [ ]\n}" + ] +}'; + + $json = str_replace( "\r", '', $json ); // Windows compat + $json = str_replace( "\t", $expectedIndent, $json ); + $this->assertSame( $json, FormatJson::encode( $obj, $pretty ) ); + } + + public static function provideEncodeDefault() { + return self::getEncodeTestCases( array() ); + } + + /** + * @dataProvider provideEncodeDefault + */ + public function testEncodeDefault( $from, $to ) { + $this->assertSame( $to, FormatJson::encode( $from ) ); + } + + public static function provideEncodeUtf8() { + return self::getEncodeTestCases( array( 'unicode' ) ); + } + + /** + * @dataProvider provideEncodeUtf8 + */ + public function testEncodeUtf8( $from, $to ) { + $this->assertSame( $to, FormatJson::encode( $from, false, FormatJson::UTF8_OK ) ); + } + + public static function provideEncodeXmlMeta() { + return self::getEncodeTestCases( array( 'xmlmeta' ) ); + } + + /** + * @dataProvider provideEncodeXmlMeta + */ + public function testEncodeXmlMeta( $from, $to ) { + $this->assertSame( $to, FormatJson::encode( $from, false, FormatJson::XMLMETA_OK ) ); + } + + public static function provideEncodeAllOk() { + return self::getEncodeTestCases( array( 'unicode', 'xmlmeta' ) ); + } + + /** + * @dataProvider provideEncodeAllOk + */ + public function testEncodeAllOk( $from, $to ) { + $this->assertSame( $to, FormatJson::encode( $from, false, FormatJson::ALL_OK ) ); + } + + public function testEncodePhpBug46944() { + $this->assertNotEquals( + '\ud840\udc00', + strtolower( FormatJson::encode( "\xf0\xa0\x80\x80" ) ), + 'Test encoding an broken json_encode character (U+20000)' + ); + } + + public function testDecodeReturnType() { + $this->assertInternalType( + 'object', + FormatJson::decode( '{"Name": "Cheeso", "Rank": 7}' ), + 'Default to object' + ); + + $this->assertInternalType( + 'array', + FormatJson::decode( '{"Name": "Cheeso", "Rank": 7}', true ), + 'Optional array' + ); + } + + public static function provideParse() { + return array( + array( null ), + array( true ), + array( false ), + array( 0 ), + array( 1 ), + array( 1.2 ), + array( '' ), + array( 'str' ), + array( array( 0, 1, 2 ) ), + array( array( 'a' => 'b' ) ), + array( array( 'a' => 'b' ) ), + array( array( 'a' => 'b', 'x' => array( 'c' => 'd' ) ) ), + ); + } + + /** + * Recursively convert arrays into stdClass + * @param array|string|bool|int|float|null $value + * @return stdClass|string|bool|int|float|null + */ + public static function toObject( $value ) { + return !is_array( $value ) ? $value : (object) array_map( __METHOD__, $value ); + } + + /** + * @dataProvider provideParse + * @param mixed $value + */ + public function testParse( $value ) { + $expected = self::toObject( $value ); + $json = FormatJson::encode( $expected, false, FormatJson::ALL_OK ); + $this->assertJson( $json ); + + $st = FormatJson::parse( $json ); + $this->assertType( 'Status', $st ); + $this->assertTrue( $st->isGood() ); + $this->assertEquals( $expected, $st->getValue() ); + + $st = FormatJson::parse( $json, FormatJson::FORCE_ASSOC ); + $this->assertType( 'Status', $st ); + $this->assertTrue( $st->isGood() ); + $this->assertEquals( $value, $st->getValue() ); + } + + public static function provideParseTryFixing() { + return array( + array( "[,]", '[]' ), + array( "[ , ]", '[]' ), + array( "[ , }", false ), + array( '[1],', false ), + array( "[1,]", '[1]' ), + array( "[1\n,]", '[1]' ), + array( "[1,\n]", '[1]' ), + array( "[1,]\n", '[1]' ), + array( "[1\n,\n]\n", '[1]' ), + array( '["a,",]', '["a,"]' ), + array( "[[1,]\n,[2,\n],[3\n,]]", '[[1],[2],[3]]' ), + array( '[[1,],[2,],[3,]]', false ), // I wish we could parse this, but would need quote parsing + array( '[1,,]', false ), + ); + } + + /** + * @dataProvider provideParseTryFixing + * @param string $value + * @param string|bool $expected + */ + public function testParseTryFixing( $value, $expected ) { + $st = FormatJson::parse( $value, FormatJson::TRY_FIXING ); + $this->assertType( 'Status', $st ); + if ( $expected === false ) { + $this->assertFalse( $st->isOK() ); + } else { + $this->assertFalse( $st->isGood() ); + $this->assertTrue( $st->isOK() ); + $val = FormatJson::encode( $st->getValue(), false, FormatJson::ALL_OK ); + $this->assertEquals( $expected, $val ); + } + } + + public static function provideParseErrors() { + return array( + array( 'aaa' ), + array( '{"j": 1 ] }' ), + ); + } + + /** + * @dataProvider provideParseErrors + * @param mixed $value + */ + public function testParseErrors( $value ) { + $st = FormatJson::parse( $value ); + $this->assertType( 'Status', $st ); + $this->assertFalse( $st->isOK() ); + } + + /** + * Generate a set of test cases for a particular combination of encoder options. + * + * @param array $unescapedGroups List of character groups to leave unescaped + * @return array Arrays of unencoded strings and corresponding encoded strings + */ + private static function getEncodeTestCases( array $unescapedGroups ) { + $groups = array( + 'always' => array( + // Forward slash (always unescaped) + '/' => '/', + + // Control characters + "\0" => '\u0000', + "\x08" => '\b', + "\t" => '\t', + "\n" => '\n', + "\r" => '\r', + "\f" => '\f', + "\x1f" => '\u001f', // representative example + + // Double quotes + '"' => '\"', + + // Backslashes + '\\' => '\\\\', + '\\\\' => '\\\\\\\\', + '\\u00e9' => '\\\u00e9', // security check for Unicode unescaping + + // Line terminators + "\xe2\x80\xa8" => '\u2028', + "\xe2\x80\xa9" => '\u2029', + ), + 'unicode' => array( + "\xc3\xa9" => '\u00e9', + "\xf0\x9d\x92\x9e" => '\ud835\udc9e', // U+1D49E, outside the BMP + ), + 'xmlmeta' => array( + '<' => '\u003C', // JSON_HEX_TAG uses uppercase hex digits + '>' => '\u003E', + '&' => '\u0026', + ), + ); + + $cases = array(); + foreach ( $groups as $name => $rules ) { + $leaveUnescaped = in_array( $name, $unescapedGroups ); + foreach ( $rules as $from => $to ) { + $cases[] = array( $from, '"' . ( $leaveUnescaped ? $from : $to ) . '"' ); + } + } + + return $cases; + } +} |