wiki.techinc.nl/tests/phpunit/includes/json/FormatJsonTest.php

338 lines
8.7 KiB
PHP
Raw Normal View History

<?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() );
}
public function provideStripComments() {
return array(
array( '{"a":"b"}', '{"a":"b"}' ),
array( "{\"a\":\"b\"}\n", "{\"a\":\"b\"}\n" ),
array( '/*c*/{"c":"b"}', '{"c":"b"}' ),
array( '{"a":"c"}/*c*/', '{"a":"c"}' ),
array( '/*c//d*/{"c":"b"}', '{"c":"b"}' ),
array( '{/*c*/"c":"b"}', '{"c":"b"}' ),
array( "/*\nc\r\n*/{\"c\":\"b\"}", '{"c":"b"}' ),
array( "//c\n{\"c\":\"b\"}", '{"c":"b"}' ),
array( "//c\r\n{\"c\":\"b\"}", '{"c":"b"}' ),
array( '{"a":"c"}//c', '{"a":"c"}' ),
array( "{\"a-c\"://c\n\"b\"}", '{"a-c":"b"}' ),
array( '{"/*a":"b"}', '{"/*a":"b"}' ),
array( '{"a":"//b"}', '{"a":"//b"}' ),
array( '{"a":"b/*c*/"}', '{"a":"b/*c*/"}' ),
array( "{\"\\\"/*a\":\"b\"}", "{\"\\\"/*a\":\"b\"}" ),
array( '', '' ),
array( '/*c', '' ),
array( '//c', '' ),
array( '"http://example.com"', '"http://example.com"' ),
array( "\0", "\0" ),
array( '"Blåbærsyltetøy"', '"Blåbærsyltetøy"' ),
);
}
/**
* @covers FormatJson::stripComments
* @dataProvider provideStripComments
* @param string $json
* @param string $expect
*/
public function testStripComments( $json, $expect ) {
$this->assertSame( $expect, FormatJson::stripComments( $json ) );
}
public function provideParseStripComments() {
return array(
array( '/* blah */true', true ),
array( "// blah \ntrue", true ),
array( '[ "a" , /* blah */ "b" ]', array( 'a', 'b' ) ),
);
}
/**
* @covers FormatJson::parse
* @covers FormatJson::stripComments
* @dataProvider provideParseStripComments
* @param string $json
* @param mixed $expect
*/
public function testParseStripComments( $json, $expect ) {
$st = FormatJson::parse( $json, FormatJson::STRIP_COMMENTS );
$this->assertType( 'Status', $st );
$this->assertTrue( $st->isGood() );
$this->assertEquals( $expect, $st->getValue() );
}
/**
* 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;
}
}