aboutsummaryrefslogtreecommitdiffstats
path: root/tests/phpunit/integration/includes/Html/TemplateParserIntegrationTest.php
diff options
context:
space:
mode:
authorUmherirrender <umherirrender_de.wp@web.de>2023-09-12 20:28:21 +0200
committerUmherirrender <umherirrender_de.wp@web.de>2023-09-13 00:09:05 +0200
commit790ae736c165bc6b2e2cc893c1b0be2cbd2628b3 (patch)
treeb420e7567fe0e701fabef992d16b6bc370b3f4e5 /tests/phpunit/integration/includes/Html/TemplateParserIntegrationTest.php
parent707450bc74891a125f8826b0a7c004ea3c79c48c (diff)
downloadmediawikicore-790ae736c165bc6b2e2cc893c1b0be2cbd2628b3.tar.gz
mediawikicore-790ae736c165bc6b2e2cc893c1b0be2cbd2628b3.zip
tests: Move test cases from /includes/ into sub folder
Follow move of the tested class Most moves are part of T321882 Change-Id: I74ab45d6a5331dcb2ff0b65dc2cc7c6315146646
Diffstat (limited to 'tests/phpunit/integration/includes/Html/TemplateParserIntegrationTest.php')
-rw-r--r--tests/phpunit/integration/includes/Html/TemplateParserIntegrationTest.php353
1 files changed, 353 insertions, 0 deletions
diff --git a/tests/phpunit/integration/includes/Html/TemplateParserIntegrationTest.php b/tests/phpunit/integration/includes/Html/TemplateParserIntegrationTest.php
new file mode 100644
index 000000000000..6964af96415e
--- /dev/null
+++ b/tests/phpunit/integration/includes/Html/TemplateParserIntegrationTest.php
@@ -0,0 +1,353 @@
+<?php
+
+use MediaWiki\Html\TemplateParser;
+use MediaWiki\MainConfigNames;
+
+/**
+ * @group Templates
+ * @coversDefaultClass MediaWiki\Html\TemplateParser
+ */
+class TemplateParserIntegrationTest extends MediaWikiIntegrationTestCase {
+
+ private const NAME = 'foobar';
+ private const RESULT = "hello world!\n";
+ private const DIR = __DIR__ . '/../../../data/templates';
+ private const SECRET_KEY = 'foo';
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->overrideConfigValue( MainConfigNames::SecretKey, self::SECRET_KEY );
+ }
+
+ /**
+ * @covers ::getTemplate
+ */
+ public function testGetTemplateNeverCacheWithoutSecretKey() {
+ $this->overrideConfigValue( MainConfigNames::SecretKey, false );
+
+ // Expect no cache interaction
+ $cache = $this->createMock( BagOStuff::class );
+ $cache->expects( $this->never() )->method( 'get' );
+ $cache->expects( $this->never() )->method( 'set' );
+
+ $tp = new TemplateParser( self::DIR, $cache );
+ $this->assertEquals( self::RESULT, $tp->processTemplate( self::NAME, [] ) );
+ }
+
+ /**
+ * @covers ::getTemplate
+ */
+ public function testGetTemplateCachesCompilationResult() {
+ $store = null;
+
+ // 1. Expect a cache miss, compile, and cache set
+ $cache1 = $this->createMock( BagOStuff::class );
+ $cache1->expects( $this->once() )->method( 'get' )->willReturn( false );
+ $cache1->expects( $this->once() )->method( 'set' )
+ ->willReturnCallback( static function ( $key, $val ) use ( &$store ) {
+ $store = [ 'key' => $key, 'val' => $val ];
+ } );
+
+ $tp1 = new TemplateParser( self::DIR, $cache1 );
+ $this->assertEquals( self::RESULT, $tp1->processTemplate( self::NAME, [] ) );
+
+ // Inspect cache
+ $this->assertEquals(
+ [
+ 'phpCode',
+ 'files',
+ 'filesHash',
+ 'integrityHash',
+ ],
+ array_keys( $store['val'] ),
+ 'keys of the cached array'
+ );
+ $this->assertEquals(
+ FileContentsHasher::getFileContentsHash( [
+ self::DIR . '/' . self::NAME . '.mustache'
+ ] ),
+ $store['val']['filesHash'],
+ 'content hash for the compiled template'
+ );
+ $this->assertEquals(
+ hash_hmac( 'sha256', $store['val']['phpCode'], self::SECRET_KEY ),
+ $store['val']['integrityHash'],
+ 'integrity hash for the compiled template'
+ );
+
+ // 2. Expect a cache hit that passes validation checks, and no compilation
+ $cache2 = $this->createMock( BagOStuff::class );
+ $cache2->expects( $this->once() )->method( 'get' )
+ ->willReturnCallback( static function ( $key ) use ( &$store ) {
+ return $key === $store['key'] ? $store['val'] : false;
+ } );
+ $cache2->expects( $this->never() )->method( 'set' );
+
+ $tp2 = $this->getMockBuilder( TemplateParser::class )
+ ->setConstructorArgs( [ self::DIR, $cache2 ] )
+ ->onlyMethods( [ 'compile' ] )
+ ->getMock();
+ $tp2->expects( $this->never() )->method( 'compile' );
+
+ $this->assertEquals( self::RESULT, $tp2->processTemplate( self::NAME, [] ) );
+ }
+
+ /**
+ * @covers ::getTemplate
+ */
+ public function testGetTemplateInvalidatesCacheWhenFilesHashIsInvalid() {
+ $store = null;
+
+ // 1. Expect a cache miss, compile, and cache set
+ $cache1 = $this->createMock( BagOStuff::class );
+ $cache1->expects( $this->once() )->method( 'get' )->willReturn( false );
+ $cache1->expects( $this->once() )->method( 'set' )
+ ->willReturnCallback( static function ( $key, $val ) use ( &$store ) {
+ $store = [ 'key' => $key, 'val' => $val ];
+ } );
+
+ $tp1 = new TemplateParser( self::DIR, $cache1 );
+ $this->assertEquals( self::RESULT, $tp1->processTemplate( self::NAME, [] ) );
+
+ // Invalidate file hash
+ $store['val']['filesHash'] = 'baz';
+
+ // 2. Expect a cache hit that fails validation, and a re-compilation
+ $cache2 = $this->createMock( BagOStuff::class );
+ $cache2->expects( $this->once() )->method( 'get' )
+ ->willReturnCallback( static function ( $key ) use ( &$store ) {
+ return $key === $store['key'] ? $store['val'] : false;
+ } );
+ $cache2->expects( $this->once() )->method( 'set' );
+
+ $tp2 = $this->getMockBuilder( TemplateParser::class )
+ ->setConstructorArgs( [ self::DIR, $cache2 ] )
+ ->onlyMethods( [ 'compile' ] )
+ ->getMock();
+ $tp2->expects( $this->once() )->method( 'compile' )
+ ->willReturn( $store['val'] );
+
+ $this->assertEquals( self::RESULT, $tp2->processTemplate( self::NAME, [] ) );
+ }
+
+ /**
+ * @covers ::getTemplate
+ */
+ public function testGetTemplateInvalidatesCacheWhenIntegrityHashIsInvalid() {
+ $store = null;
+
+ // 1. Cache miss, expect a compile and cache set
+ $cache1 = $this->createMock( BagOStuff::class );
+ $cache1->expects( $this->once() )->method( 'get' )->willReturn( false );
+ $cache1->expects( $this->once() )->method( 'set' )
+ ->willReturnCallback( static function ( $key, $val ) use ( &$store ) {
+ $store = [ 'key' => $key, 'val' => $val ];
+ } );
+
+ $tp1 = new TemplateParser( self::DIR, $cache1 );
+ $this->assertEquals( self::RESULT, $tp1->processTemplate( self::NAME, [] ) );
+
+ // Invalidate integrity hash
+ $store['val']['integrityHash'] = 'foo';
+
+ // 2. Expect a cache hit that fails validation, and a re-compilation
+ $cache2 = $this->createMock( BagOStuff::class );
+ $cache2->expects( $this->once() )->method( 'get' )
+ ->willReturnCallback( static function ( $key ) use ( &$store ) {
+ return $key === $store['key'] ? $store['val'] : false;
+ } );
+ $cache2->expects( $this->once() )->method( 'set' );
+
+ $tp2 = $this->getMockBuilder( TemplateParser::class )
+ ->setConstructorArgs( [ self::DIR, $cache2 ] )
+ ->onlyMethods( [ 'compile' ] )
+ ->getMock();
+ $tp2->expects( $this->once() )->method( 'compile' )
+ ->willReturn( $store['val'] );
+
+ $this->assertEquals( self::RESULT, $tp2->processTemplate( self::NAME, [] ) );
+ }
+
+ /**
+ * @dataProvider provideProcessTemplate
+ * @covers MediaWiki\Html\TemplateParser
+ */
+ public function testProcessTemplate( $name, $args, $result, $exception = false ) {
+ $tp = new TemplateParser( self::DIR, new EmptyBagOStuff );
+ if ( $exception ) {
+ $this->expectException( $exception );
+ }
+ $this->assertEquals( $result, $tp->processTemplate( $name, $args ) );
+ }
+
+ public static function provideProcessTemplate() {
+ return [
+ [
+ 'foobar',
+ [],
+ "hello world!\n"
+ ],
+ [
+ 'foobar_args',
+ [
+ 'planet' => 'world',
+ ],
+ self::RESULT,
+ ],
+ [
+ '../foobar',
+ [],
+ false,
+ UnexpectedValueException::class
+ ],
+ [
+ "\000../foobar",
+ [],
+ false,
+ UnexpectedValueException::class
+ ],
+ [
+ '/',
+ [],
+ false,
+ UnexpectedValueException::class
+ ],
+ [
+ // Allegedly this can strip ext in windows.
+ 'baz<',
+ [],
+ false,
+ UnexpectedValueException::class
+ ],
+ [
+ '\\foo',
+ [],
+ false,
+ UnexpectedValueException::class
+ ],
+ [
+ 'C:\bar',
+ [],
+ false,
+ UnexpectedValueException::class
+ ],
+ [
+ "foo\000bar",
+ [],
+ false,
+ UnexpectedValueException::class
+ ],
+ [
+ 'nonexistenttemplate',
+ [],
+ false,
+ RuntimeException::class,
+ ],
+ [
+ 'has_partial',
+ [
+ 'planet' => 'world',
+ ],
+ "Partial hello world!\n in here\n",
+ ],
+ [
+ 'bad_partial',
+ [],
+ false,
+ Exception::class,
+ ],
+ [
+ 'invalid_syntax',
+ [],
+ false,
+ Exception::class
+ ],
+ [
+ 'parentvars',
+ [
+ 'foo' => 'f',
+ 'bar' => [
+ [ 'baz' => 'x' ],
+ [ 'baz' => 'y' ]
+ ]
+ ],
+ "f\n\tf x\n\tf y\n"
+ ]
+ ];
+ }
+
+ /**
+ * @covers ::enableRecursivePartials
+ */
+ public function testEnableRecursivePartials() {
+ $tp = new TemplateParser( self::DIR, new EmptyBagOStuff );
+ $data = [ 'r' => [ 'r' => [ 'r' => [] ] ] ];
+
+ $tp->enableRecursivePartials( true );
+ $this->assertEquals( 'rrr', $tp->processTemplate( 'recurse', $data ) );
+
+ $tp->enableRecursivePartials( false );
+ $this->expectException( Exception::class );
+ $tp->processTemplate( 'recurse', $data );
+ }
+
+ /**
+ * @covers MediaWiki\Html\TemplateParser::compile
+ */
+ public function testCompileReturnsPHPCodeAndMetadata() {
+ $store = null;
+
+ // 1. Expect a compile and cache set
+ $cache = $this->createMock( BagOStuff::class );
+ $cache->expects( $this->once() )->method( 'get' )->willReturn( false );
+ $cache->expects( $this->once() )->method( 'set' )
+ ->willReturnCallback( static function ( $key, $val ) use ( &$store ) {
+ $store = [ 'key' => $key, 'val' => $val ];
+ } );
+ $tp = new TemplateParser( self::DIR, $cache );
+ $tp->processTemplate( 'has_partial', [] );
+
+ // 2. Inspect cache
+ $expectedFiles = [
+ self::DIR . '/has_partial.mustache',
+ self::DIR . '/foobar_args.mustache',
+ ];
+ $this->assertEquals(
+ $expectedFiles,
+ $store['val']['files'],
+ 'track all files read during the compilation'
+ );
+ $this->assertEquals(
+ FileContentsHasher::getFileContentsHash( $expectedFiles ),
+ $store['val'][ 'filesHash' ],
+ 'hash of all files read during the compilation'
+ );
+ }
+
+ /**
+ * @covers ::getTemplate
+ */
+ public function testGetTemplateCachingHandlesRecursivePartials() {
+ $store = null;
+
+ $cache = $this->createMock( BagOStuff::class );
+ $cache->expects( $this->once() )->method( 'get' )->willReturn( false );
+ $cache->expects( $this->once() )->method( 'set' )
+ ->willReturnCallback( static function ( $key, $val ) use ( &$store ) {
+ $store = [ 'key' => $key, 'val' => $val ];
+ } );
+
+ $tp = new TemplateParser( self::DIR, $cache );
+ $tp->enableRecursivePartials( true );
+
+ $data = [ 'r' => [ 'r' => [ 'r' => [] ] ] ];
+ $tp->processTemplate( 'recurse', $data );
+
+ $this->assertArrayEquals(
+ [ self::DIR . '/recurse.mustache' ],
+ $store['val']['files'],
+ 'The hash is computed from unique template files.'
+ );
+ }
+}