disallowDBAccess(); $this->disallowHttpAccess(); } final protected static function getExtensionJson(): array { // Temporary get the path depending of the property state - T393065 if ( !isset( self::$cacheExtensionJsonPath[static::class] ) ) { $reflectionProperty = new ReflectionProperty( static::class, 'extensionJsonPath' ); $reflectionProperty->setAccessible( true ); $invokeObject = null; if ( !$reflectionProperty->isStatic() ) { $invokeObject = new static(); } self::$cacheExtensionJsonPath[static::class] = $reflectionProperty->getValue( $invokeObject ); } $extensionJsonPath = self::$cacheExtensionJsonPath[static::class]; if ( !array_key_exists( $extensionJsonPath, self::$extensionJsonCache ) ) { self::$extensionJsonCache[$extensionJsonPath] = json_decode( file_get_contents( $extensionJsonPath ), true, 512, JSON_THROW_ON_ERROR ); } return self::$extensionJsonCache[$extensionJsonPath]; } /** @dataProvider provideHookHandlerNamesStatically */ public function testHookHandler( string $hookHandlerName ): void { $specification = self::getExtensionJson()['HookHandlers'][$hookHandlerName]; $objectFactory = MediaWikiServices::getInstance()->getObjectFactory(); $objectFactory->createObject( $specification, [ 'allowClassName' => true, ] ); $this->assertTrue( true ); } // public static function provideHookHandlerNames(): iterable private static function provideHookHandlerNamesBase(): iterable { foreach ( self::getExtensionJson()['HookHandlers'] ?? [] as $hookHandlerName => $specification ) { yield [ $hookHandlerName ]; } } /** * Temporary override to make provideHookHandlerNames static. * See T393065. */ final public static function provideHookHandlerNamesStatically(): iterable { if ( method_exists( static::class, 'provideHookHandlerNames' ) ) { // override exists, check if changed to static or non-static $reflectionMethod = new ReflectionMethod( static::class, 'provideHookHandlerNames' ); $invokeObject = null; if ( !$reflectionMethod->isStatic() ) { // Non-static data provider are soft-deprecated $invokeObject = new static(); } return $reflectionMethod->invoke( $invokeObject ); } // The base implementation temporary has own name to avoid php fatal for non-static functions return self::provideHookHandlerNamesBase(); } public function __call( $name, $args ) { // Called as parent::provideHookHandlerNames() if ( $name === 'provideHookHandlerNames' ) { return self::provideHookHandlerNamesBase(); } throw new \RuntimeException( "Call to undefined method $name()" ); } public static function __callStatic( $name, $args ) { // Called as parent::provideHookHandlerNames() if ( $name === 'provideHookHandlerNames' ) { return self::provideHookHandlerNamesBase(); } throw new \RuntimeException( "Call to undefined method $name()" ); } /** @dataProvider provideContentModelIDs */ public function testContentHandler( string $contentModelID ): void { $specification = self::getExtensionJson()['ContentHandlers'][$contentModelID]; $objectFactory = MediaWikiServices::getInstance()->getObjectFactory(); $objectFactory->createObject( $specification, [ 'assertClass' => ContentHandler::class, 'allowCallable' => true, 'allowClassName' => true, 'extraArgs' => [ $contentModelID ], ] ); $this->assertTrue( true ); } public static function provideContentModelIDs(): iterable { foreach ( self::getExtensionJson()['ContentHandlers'] ?? [] as $contentModelID => $specification ) { yield [ $contentModelID ]; } } /** @dataProvider provideApiModuleNames */ public function testApiModule( string $moduleName ): void { $specification = self::getExtensionJson()['APIModules'][$moduleName]; $objectFactory = MediaWikiServices::getInstance()->getObjectFactory(); $objectFactory->createObject( $specification, [ 'allowClassName' => true, 'extraArgs' => [ $this->mockApiMain(), 'modulename' ], ] ); $this->assertTrue( true ); } public static function provideApiModuleNames(): iterable { foreach ( self::getExtensionJson()['APIModules'] ?? [] as $moduleName => $specification ) { yield [ $moduleName ]; } } /** @dataProvider provideApiQueryModuleListsAndNames */ public function testApiQueryModule( string $moduleList, string $moduleName ): void { $specification = self::getExtensionJson()[$moduleList][$moduleName]; $objectFactory = MediaWikiServices::getInstance()->getObjectFactory(); $objectFactory->createObject( $specification, [ 'allowClassName' => true, 'extraArgs' => [ $this->mockApiQuery(), 'query' ], ] ); $this->assertTrue( true ); } public static function provideApiQueryModuleListsAndNames(): iterable { $extensionJson = self::getExtensionJson(); foreach ( [ 'APIListModules', 'APIMetaModules', 'APIPropModules' ] as $moduleList ) { foreach ( $extensionJson[$moduleList] ?? [] as $moduleName => $specification ) { yield [ $moduleList, $moduleName ]; } } } /** @dataProvider provideSpecialPageNames */ public function testSpecialPage( string $specialPageName ): void { $specification = self::getExtensionJson()['SpecialPages'][$specialPageName]; $objectFactory = MediaWikiServices::getInstance()->getObjectFactory(); $objectFactory->createObject( $specification, [ 'allowClassName' => true, ] ); $this->assertTrue( true ); } public static function provideSpecialPageNames(): iterable { foreach ( self::getExtensionJson()['SpecialPages'] ?? [] as $specialPageName => $specification ) { yield [ $specialPageName ]; } } /** @dataProvider provideAuthenticationProviders */ public function testAuthenticationProviders( string $providerType, string $providerName, string $providerClass ): void { $specification = self::getExtensionJson()['AuthManagerAutoConfig'][$providerType][$providerName]; $objectFactory = MediaWikiServices::getInstance()->getObjectFactory(); $objectFactory->createObject( $specification, [ 'assertClass' => $providerClass, ] ); $this->assertTrue( true ); } public static function provideAuthenticationProviders(): iterable { $config = self::getExtensionJson()['AuthManagerAutoConfig'] ?? []; $types = [ 'preauth' => PreAuthenticationProvider::class, 'primaryauth' => PrimaryAuthenticationProvider::class, 'secondaryauth' => SecondaryAuthenticationProvider::class, ]; foreach ( $types as $providerType => $providerClass ) { foreach ( $config[$providerType] ?? [] as $providerName => $specification ) { yield [ $providerType, $providerName, $providerClass ]; } } } /** @dataProvider provideSessionProviders */ public function testSessionProviders( string $providerName ): void { $specification = self::getExtensionJson()['SessionProviders'][$providerName]; $objectFactory = MediaWikiServices::getInstance()->getObjectFactory(); $objectFactory->createObject( $specification ); $this->assertTrue( true ); } public static function provideSessionProviders(): iterable { foreach ( self::getExtensionJson()['SessionProviders'] ?? [] as $providerName => $specification ) { yield [ $providerName ]; } } /** @dataProvider provideServicesLists */ public function testServicesSorted( array $services ): void { if ( $this->serviceNamePrefix === null ) { // do not test sorting $this->assertTrue( true ); return; } $sortedServices = $services; usort( $sortedServices, function ( $serviceA, $serviceB ) { $isExtensionServiceA = str_starts_with( $serviceA, $this->serviceNamePrefix ); $isExtensionServiceB = str_starts_with( $serviceB, $this->serviceNamePrefix ); if ( $isExtensionServiceA !== $isExtensionServiceB ) { return $isExtensionServiceA ? 1 : -1; } return strcmp( $serviceA, $serviceB ); } ); $this->assertSame( $sortedServices, $services, 'Services should be sorted: first all MediaWiki services, ' . "then all {$this->serviceNamePrefix}* ones." ); } public static function provideServicesLists(): iterable { foreach ( self::provideSpecifications() as $name => $specification ) { if ( is_array( $specification ) && array_key_exists( 'services', $specification ) ) { yield $name => [ $specification['services'] ]; } } } public static function provideSpecifications(): iterable { $extensionJson = self::getExtensionJson(); foreach ( self::provideHookHandlerNamesStatically() as [ $hookHandlerName ] ) { yield "HookHandlers/$hookHandlerName" => $extensionJson['HookHandlers'][$hookHandlerName]; } foreach ( self::provideContentModelIDs() as [ $contentModelID ] ) { yield "ContentHandlers/$contentModelID" => $extensionJson['ContentHandlers'][$contentModelID]; } foreach ( self::provideApiModuleNames() as [ $moduleName ] ) { yield "APIModules/$moduleName" => $extensionJson['APIModules'][$moduleName]; } foreach ( self::provideApiQueryModuleListsAndNames() as [ $moduleList, $moduleName ] ) { yield "$moduleList/$moduleName" => $extensionJson[$moduleList][$moduleName]; } foreach ( self::provideSpecialPageNames() as [ $specialPageName ] ) { yield "SpecialPages/$specialPageName" => $extensionJson['SpecialPages'][$specialPageName]; } foreach ( self::provideAuthenticationProviders() as [ $providerType, $providerName, $providerClass ] ) { yield "AuthManagerAutoConfig/$providerType/$providerName" => $extensionJson['AuthManagerAutoConfig'][$providerType][$providerName]; } foreach ( self::provideSessionProviders() as [ $providerName ] ) { yield "SessionProviders/$providerName" => $extensionJson['SessionProviders'][$providerName]; } } private function disallowDBAccess() { $this->setService( 'DBLoadBalancerFactory', function () { $lb = $this->createMock( ILoadBalancer::class ); $lb->expects( $this->never() ) ->method( 'getMaintenanceConnectionRef' ); $lb->method( 'getLocalDomainID' ) ->willReturn( 'banana' ); // This IDatabase will fail when actually trying to do database actions $db = $this->createNoOpMock( IDatabase::class ); $lb->method( 'getConnection' ) ->willReturn( $db ); $lbFactory = $this->createMock( LBFactory::class ); $lbFactory->method( 'getMainLB' ) ->willReturn( $lb ); $lbFactory->method( 'getLocalDomainID' ) ->willReturn( 'banana' ); return $lbFactory; } ); } private function disallowHttpAccess() { $this->setService( 'HttpRequestFactory', function () { $factory = $this->createMock( HttpRequestFactory::class ); $factory->expects( $this->never() ) ->method( 'create' ); $factory->expects( $this->never() ) ->method( 'request' ); $factory->expects( $this->never() ) ->method( 'get' ); $factory->expects( $this->never() ) ->method( 'post' ); $factory->method( 'createMultiClient' ) ->willReturn( $this->createMock( MultiHttpClient::class ) ); return $factory; } ); } private function mockApiMain(): ApiMain { $request = new FauxRequest(); $ctx = new ApiTestContext(); $ctx = $ctx->newTestContext( $request ); return new ApiMain( $ctx ); } private function mockApiQuery(): ApiQuery { return $this->mockApiMain()->getModuleManager()->getModule( 'query' ); } }