diff --git a/API.php b/API.php index 9311c6e..7f7ecdb 100644 --- a/API.php +++ b/API.php @@ -12,6 +12,7 @@ use Piwik\Piwik; use Piwik\Plugin\Manager; use Piwik\Plugins\OpenApiDocs\Specs\SpecGenerator; +use Piwik\Plugins\OpenApiDocs\Specs\PathResolver; /** * API for plugin OpenApiDocs @@ -67,9 +68,7 @@ public function getOpenApiSpec(string $pluginName, string $format = 'json'): arr protected function getSpecFilePath(string $pluginName): string { - $currentPluginDir = Manager::getInstance()::getPluginDirectory('OpenApiDocs'); - - return $currentPluginDir . OpenApiDocs::GENERATED_SPECS_PATH . $pluginName . '_openapi_spec_v' . OpenApiDocs::DEFAULT_SPEC_VERSION . '.json'; + return $this->getSpecPathResolver()->getSpecFilePath($pluginName); } protected function isSpecFileReadable(string $filePath): bool @@ -98,6 +97,11 @@ protected function validateJsonFormat(string $format): void } } + protected function getSpecPathResolver(): PathResolver + { + return new PathResolver(); + } + /** * Get the generated API documentation data for the specified plugin. * diff --git a/Annotations/AnnotationGenerator.php b/Annotations/AnnotationGenerator.php index 96773c9..0a699aa 100644 --- a/Annotations/AnnotationGenerator.php +++ b/Annotations/AnnotationGenerator.php @@ -23,7 +23,9 @@ use Piwik\Http; use Piwik\Piwik; use Piwik\Plugin\Manager; +use Piwik\Plugins\OpenApiDocs\Artifact\ArtifactWriter; use Piwik\Plugins\OpenApiDocs\OpenApiDocs; +use Piwik\Plugins\OpenApiDocs\Specs\PathResolver; use Piwik\SettingsPiwik; use Piwik\Url; use Piwik\UrlHelper; @@ -72,6 +74,16 @@ class AnnotationGenerator */ protected $generator; + /** + * @var PathResolver + */ + protected $pathResolver; + + /** + * @var ArtifactWriter + */ + protected $artifactWriter; + /** * @var array[] */ @@ -82,9 +94,14 @@ class AnnotationGenerator */ protected $missingImportantDataWarnings; - public function __construct(DocumentationGenerator $generator) - { + public function __construct( + DocumentationGenerator $generator, + ?PathResolver $pathResolver = null, + ?ArtifactWriter $artifactWriter = null + ) { $this->generator = $generator; + $this->pathResolver = $pathResolver ?? new PathResolver(); + $this->artifactWriter = $artifactWriter ?? new ArtifactWriter(); $this->missingImportantDataWarnings = []; $this->currentPluginDir = Manager::getInstance()::getPluginDirectory('OpenApiDocs'); } @@ -115,8 +132,7 @@ public function generatePluginApiAnnotations(string $pluginName, bool $writeToFi } $rules = require $this->currentPluginDir . '/Annotations/config.php'; - $pluginAnnotationDir = $this->currentPluginDir . OpenApiDocs::GENERATED_ANNOTATIONS_PATH; - $pluginAnnotationPath = $pluginAnnotationDir . "/{$pluginName}GeneratedAnnotations.php"; + $pluginAnnotationPath = $this->pathResolver->getAnnotationFilePath($pluginName); $className = Request::getClassNameAPI($pluginName); @@ -231,8 +247,7 @@ public function getContentForGeneratedAnnotationsFile(array $annotations, string */ protected function writeAnnotationsToFile(array $annotations, string $filePath, string $pluginName) { - // Create or overwrite the annotations file - return file_put_contents($filePath, $this->getContentForGeneratedAnnotationsFile($annotations, $pluginName)); + return $this->writeFile($filePath, $this->getContentForGeneratedAnnotationsFile($annotations, $pluginName)); } /** @@ -903,11 +918,11 @@ protected function getExampleIfAvailable(string $url, bool $useLocalToken = fals } $method = $queryParams['method']; $format = strtolower($queryParams['format']); - $exampleFilePath = $this->currentPluginDir . OpenApiDocs::EXAMPLE_RESPONSES_PATH . $method . '.' . $format; + [$pluginName, $methodName] = explode('.', $method); + $exampleFilePath = $this->pathResolver->getExampleResponseFilePath($pluginName, $methodName, $format); // If there's already a file, use that instead of making a new server call. Ignore the file when the flag is set. if (!$ignoreCached) { // If an example file is found, return its contents instead of making the server call. - [$pluginName, $methodName] = explode('.', $method); $exampleContents = $this->getCachedExampleResponseFile($pluginName, $methodName, $format); if (!empty($exampleContents)) { return $exampleContents; @@ -962,7 +977,7 @@ protected function getExampleIfAvailable(string $url, bool $useLocalToken = fals $body = $response['data']; // Write the example response to file as a cache and reference. - file_put_contents($exampleFilePath, $body); + $this->writeFile($exampleFilePath, $body); // Convert the XML responses into a JSON object and then encode it into a string. This is helpful for building schemas. if ($format === 'xml') { @@ -994,7 +1009,7 @@ protected function getExampleIfAvailable(string $url, bool $useLocalToken = fals */ protected function getCachedExampleResponseFile(string $pluginName, string $methodName, string $format, bool $rawResult = false, bool $applyMaxLength = true): string { - $exampleFilePath = $this->currentPluginDir . OpenApiDocs::EXAMPLE_RESPONSES_PATH . $pluginName . '.' . $methodName . '.' . $format; + $exampleFilePath = $this->pathResolver->getExampleResponseFilePath($pluginName, $methodName, $format); // Simply return an empty string if the file doesn't exist yet. if (!file_exists($exampleFilePath)) { return ''; @@ -1021,6 +1036,11 @@ protected function getCachedExampleResponseFile(string $pluginName, string $meth return $exampleContents; } + protected function writeFile(string $filePath, string $contents) + { + return $this->artifactWriter->writeFile($filePath, $contents); + } + /** * Try to build an example URL for a specific API method using report metadata. This queries the demo server for * report metadata to get examples of existing reports which can be used as example URLS. If no metadata matches the diff --git a/Annotations/ApiMethodInfoExtractor.php b/Annotations/ApiMethodInfoExtractor.php index 23e41a2..aead720 100644 --- a/Annotations/ApiMethodInfoExtractor.php +++ b/Annotations/ApiMethodInfoExtractor.php @@ -15,12 +15,29 @@ use Piwik\API\Proxy; use Piwik\API\Request; use Piwik\Plugin\Manager; -use Piwik\Plugins\OpenApiDocs\OpenApiDocs; +use Piwik\Plugins\OpenApiDocs\Artifact\ArtifactWriter; +use Piwik\Plugins\OpenApiDocs\Specs\PathResolver; use Piwik\Validators\BaseValidator; use Piwik\Validators\NotEmpty; class ApiMethodInfoExtractor { + /** + * @var PathResolver + */ + private $pathResolver; + + /** + * @var ArtifactWriter + */ + private $artifactWriter; + + public function __construct(?PathResolver $pathResolver = null, ?ArtifactWriter $artifactWriter = null) + { + $this->pathResolver = $pathResolver ?? new PathResolver(); + $this->artifactWriter = $artifactWriter ?? new ArtifactWriter(); + } + /** * Look up the Matomo Reporting API methods for the specified plugin(s) and output the basic information for each. * This includes the comment block, parameter information, and things like that. This can then be fed to a secure @@ -38,8 +55,6 @@ public function extractMethodInfo(string $pluginName, bool $writeToFile = false) $pluginNames = explode(',', $pluginName); BaseValidator::check('pluginNames', $pluginNames, [new NotEmpty()]); - $currentPluginDir = Manager::getInstance()::getPluginDirectory('OpenApiDocs'); - $methodInfoArray = []; foreach ($pluginNames as $plugin) { BaseValidator::check('pluginName', $plugin, [new NotEmpty()]); @@ -63,8 +78,8 @@ public function extractMethodInfo(string $pluginName, bool $writeToFile = false) } if ($writeToFile) { - $pluginSpecPath = $currentPluginDir . OpenApiDocs::GENERATED_ANNOTATIONS_PATH . $fileBaseName . '_api_method_info.json'; - file_put_contents($pluginSpecPath, $methodInfoString); + $pluginSpecPath = $this->pathResolver->getApiMethodInfoFilePath($fileBaseName); + $this->artifactWriter->writeFile($pluginSpecPath, $methodInfoString); } return $methodInfoString; diff --git a/Artifact/ArtifactWriter.php b/Artifact/ArtifactWriter.php new file mode 100644 index 0000000..1ff0712 --- /dev/null +++ b/Artifact/ArtifactWriter.php @@ -0,0 +1,27 @@ +extractMethodInfo($plugin, $notDryRun); if ($notDryRun) { - $output->writeln('Results written to plugins/OpenApiDocs/tmp/annotations directory.'); + $output->writeln('Results written to ' . StaticContainer::get(PathResolver::class)->getAnnotationsDirectory() . ''); return $result ? self::SUCCESS : self::FAILURE; } diff --git a/Commands/GenerateAnnotations.php b/Commands/GenerateAnnotations.php index d5faa45..1f43a30 100644 --- a/Commands/GenerateAnnotations.php +++ b/Commands/GenerateAnnotations.php @@ -12,6 +12,7 @@ use Piwik\Container\StaticContainer; use Piwik\Plugin\ConsoleCommand; use Piwik\Plugins\OpenApiDocs\Annotations\AnnotationGenerator; +use Piwik\Plugins\OpenApiDocs\Specs\PathResolver; /** * This class lets you define a new command. To read more about commands have a look at our Matomo Console guide on @@ -83,7 +84,7 @@ protected function doExecute(): int $result = (StaticContainer::get(AnnotationGenerator::class))->generatePluginApiAnnotations($plugin, $notDryRun); if ($notDryRun) { - $output->writeln('Results written to plugins/OpenApiDocs/tmp/annotations/ directory.'); + $output->writeln('Results written to ' . StaticContainer::get(PathResolver::class)->getAnnotationsDirectory() . ''); return $result ? self::SUCCESS : self::FAILURE; } diff --git a/Commands/GenerateSpecFile.php b/Commands/GenerateSpecFile.php index c593de5..c688fe9 100644 --- a/Commands/GenerateSpecFile.php +++ b/Commands/GenerateSpecFile.php @@ -13,6 +13,7 @@ use Piwik\Plugin\ConsoleCommand; use Piwik\Plugins\OpenApiDocs\Generation\SpecGenerationService; use Piwik\Plugins\OpenApiDocs\OpenApiDocs; +use Piwik\Plugins\OpenApiDocs\Specs\PathResolver; /** * This class lets you define a new command. To read more about commands have a look at our Matomo Console guide on @@ -28,9 +29,15 @@ class GenerateSpecFile extends ConsoleCommand */ private $specGenerationService; - public function __construct(?SpecGenerationService $specGenerationService = null) + /** + * @var PathResolver + */ + private $specPathResolver; + + public function __construct(?SpecGenerationService $specGenerationService = null, ?PathResolver $specPathResolver = null) { $this->specGenerationService = $specGenerationService ?: StaticContainer::get(SpecGenerationService::class); + $this->specPathResolver = $specPathResolver ?: StaticContainer::get(PathResolver::class); parent::__construct(); } @@ -116,12 +123,12 @@ protected function doExecute(): int if ($addAnnotations) { foreach ($pluginNames as $pluginName) { - $output->writeln('Created Annotations for ' . $pluginName . ' and wrote results to plugins/OpenApiDocs/tmp/annotations.'); + $output->writeln('Created Annotations for ' . $pluginName . ' and wrote results to ' . $this->specPathResolver->getAnnotationsDirectory() . ''); } } if ($notDryRun) { - $output->writeln('Results written to plugins/OpenApiDocs/tmp/specs/ directory.'); + $output->writeln('Results written to ' . $this->specPathResolver->getSpecDirectory() . ''); return self::SUCCESS; } diff --git a/Specs/PathResolver.php b/Specs/PathResolver.php new file mode 100644 index 0000000..1a012d2 --- /dev/null +++ b/Specs/PathResolver.php @@ -0,0 +1,121 @@ +pluginDirectory = $pluginDirectory ?? Manager::getInstance()::getPluginDirectory('OpenApiDocs'); + $this->isCloudActivated = $isCloudActivated ?? Manager::getInstance()->isPluginActivated('Cloud'); + $this->container = $container ?? $this->getStaticContainer(); + } + + public function getSpecDirectory(): string + { + return $this->getArtifactDirectory(self::SHARED_SPECS_SUBDIRECTORY, OpenApiDocs::GENERATED_SPECS_PATH); + } + + public function getSpecFilePath( + string $specFileBaseName, + string $version = OpenApiDocs::DEFAULT_SPEC_VERSION, + string $format = 'json' + ): string { + return $this->getSpecDirectory() . $specFileBaseName . '_openapi_spec_v' . $version . '.' . strtolower($format); + } + + public function getAnnotationsDirectory(): string + { + return $this->getArtifactDirectory(self::SHARED_ANNOTATIONS_SUBDIRECTORY, OpenApiDocs::GENERATED_ANNOTATIONS_PATH); + } + + public function getAnnotationFilePath(string $pluginName): string + { + return $this->getAnnotationsDirectory() . $pluginName . 'GeneratedAnnotations.php'; + } + + public function getApiMethodInfoFilePath(string $fileBaseName): string + { + return $this->getAnnotationsDirectory() . $fileBaseName . '_api_method_info.json'; + } + + public function getResponsesDirectory(): string + { + return $this->getArtifactDirectory(self::SHARED_RESPONSES_SUBDIRECTORY, OpenApiDocs::EXAMPLE_RESPONSES_PATH); + } + + public function getExampleResponseFilePath(string $pluginName, string $methodName, string $format): string + { + return $this->getResponsesDirectory() . $pluginName . '.' . $methodName . '.' . strtolower($format); + } + + private function getArtifactDirectory(string $sharedSubdirectory, string $fallbackPath): string + { + $sharedPath = $this->getSharedArtifactDirectory($sharedSubdirectory); + if ($sharedPath !== null) { + return $sharedPath; + } + + return $this->pluginDirectory . $fallbackPath; + } + + private function getSharedArtifactDirectory(string $sharedSubdirectory): ?string + { + if (!$this->isCloudActivated || $this->container === null || !$this->container->has('CloudDistributedCachePath')) { + return null; + } + + $sharedBasePath = trim((string) $this->container->get('CloudDistributedCachePath')); + if ($sharedBasePath === '') { + return null; + } + + if (!$this->isUsableSharedBasePath($sharedBasePath)) { + return null; + } + + return rtrim($sharedBasePath, '/\\') . self::SHARED_BASE_SUBDIRECTORY . ltrim(substr($sharedSubdirectory, strlen(self::SHARED_BASE_SUBDIRECTORY)), '/\\'); + } + + protected function isUsableSharedBasePath(string $sharedBasePath): bool + { + return is_dir($sharedBasePath) && is_writable($sharedBasePath); + } + + private function getStaticContainer(): ?Container + { + try { + return StaticContainer::getContainer(); + } catch (\Throwable $e) { + return null; + } + } +} diff --git a/Specs/SpecGenerator.php b/Specs/SpecGenerator.php index c7f8bfb..e7dc959 100644 --- a/Specs/SpecGenerator.php +++ b/Specs/SpecGenerator.php @@ -16,6 +16,7 @@ use Piwik\Log\LoggerInterface; use Piwik\Log\NullLogger; use Piwik\Plugin\Manager; +use Piwik\Plugins\OpenApiDocs\Artifact\ArtifactWriter; use Piwik\Plugins\OpenApiDocs\OpenApiDocs; use Piwik\SettingsPiwik; use Piwik\Validators\BaseValidator; @@ -23,8 +24,21 @@ class SpecGenerator { - public function __construct() + /** + * @var PathResolver + */ + private $specPathResolver; + + /** + * @var ArtifactWriter + */ + private $artifactWriter; + + public function __construct(?PathResolver $specPathResolver = null, ?ArtifactWriter $artifactWriter = null) { + $this->specPathResolver = $specPathResolver ?? new PathResolver(); + $this->artifactWriter = $artifactWriter ?? new ArtifactWriter(); + // Set the constant for the current instance's URL if (!defined('LOCAL_MATOMO_SERVER_URL')) { define('LOCAL_MATOMO_SERVER_URL', SettingsPiwik::getPiwikUrl()); @@ -79,7 +93,7 @@ public function generateSpec(array $pluginNames, string $format = 'json', string throw new PluginNotFoundException($pluginName); } - $pluginAnnotationsSource = $currentPluginDir . '/tmp/annotations/' . $pluginName . 'GeneratedAnnotations.php'; + $pluginAnnotationsSource = $this->specPathResolver->getAnnotationFilePath($pluginName); try { $openapi = (new Generator(StaticContainer::get(NullLogger::class)))->generate([ $pluginAnnotationsSource, @@ -117,8 +131,8 @@ public function generateSpec(array $pluginNames, string $format = 'json', string $lowercaseFormat = strtolower($format); $specContents = $lowercaseFormat === 'yaml' ? $openapi->toYaml() : $openapi->toJson(); if ($writeToFile) { - $pluginSpecPath = $currentPluginDir . OpenApiDocs::GENERATED_SPECS_PATH . $specFileBaseName . '_openapi_spec_v' . $version . '.' . $lowercaseFormat; - file_put_contents($pluginSpecPath, $specContents); + $pluginSpecPath = $this->specPathResolver->getSpecFilePath($specFileBaseName, $version, $lowercaseFormat); + $this->artifactWriter->writeFile($pluginSpecPath, $specContents); } return $specContents; diff --git a/tests/Unit/APITest.php b/tests/Unit/APITest.php index bcc49ce..124b6b4 100644 --- a/tests/Unit/APITest.php +++ b/tests/Unit/APITest.php @@ -17,6 +17,7 @@ use Piwik\Access; use Piwik\Container\StaticContainer; use Piwik\Plugins\OpenApiDocs\API; +use Piwik\Plugins\OpenApiDocs\Specs\PathResolver; use Piwik\Tests\Framework\Mock\FakeAccess; /** @@ -100,12 +101,21 @@ public function testGetOpenApiSpecThrowsExceptionWhenSpecIsNotAValidPlugin() $api->getOpenApiSpec('DefinitelyNotARealPlugin'); } - public function testGetSpecFilePathUsesPluginSpecificFileName() + public function testGetSpecFilePathDelegatesToPathResolver() { - $api = new API(); + $pathResolver = $this->createMock(PathResolver::class); + $pathResolver->expects($this->once()) + ->method('getSpecFilePath') + ->with('CustomAlerts') + ->willReturn('/shared/specs/CustomAlerts_openapi_spec_v1.0.0.json'); + + $api = $this->getMockBuilder(API::class) + ->onlyMethods(['getSpecPathResolver']) + ->getMock(); + $api->method('getSpecPathResolver')->willReturn($pathResolver); $this->assertSame( - PIWIK_INCLUDE_PATH . '/plugins/OpenApiDocs/tmp/specs/CustomAlerts_openapi_spec_v1.0.0.json', + '/shared/specs/CustomAlerts_openapi_spec_v1.0.0.json', $this->callProtectedMethod($api, 'getSpecFilePath', ['CustomAlerts']) ); } diff --git a/tests/Unit/Artifact/ArtifactWriterTest.php b/tests/Unit/Artifact/ArtifactWriterTest.php new file mode 100644 index 0000000..fb969a7 --- /dev/null +++ b/tests/Unit/Artifact/ArtifactWriterTest.php @@ -0,0 +1,66 @@ +temporaryDirectory = sys_get_temp_dir() . '/openapidocs_artifact_writer_' . uniqid('', true); + } + + protected function tearDown(): void + { + if (is_dir($this->temporaryDirectory)) { + $files = scandir($this->temporaryDirectory); + if (is_array($files)) { + foreach ($files as $file) { + if ($file === '.' || $file === '..') { + continue; + } + + @unlink($this->temporaryDirectory . '/' . $file); + } + } + + @rmdir($this->temporaryDirectory . '/nested'); + @rmdir($this->temporaryDirectory); + } + + parent::tearDown(); + } + + public function testWriteFileCreatesDirectoryAndWritesContents(): void + { + $writer = new ArtifactWriter(); + $filePath = $this->temporaryDirectory . '/nested/example.json'; + + $result = $writer->writeFile($filePath, '{"status":"ok"}'); + + $this->assertIsInt($result); + $this->assertSame('{"status":"ok"}', file_get_contents($filePath)); + } +} diff --git a/tests/Unit/Specs/PathResolverTest.php b/tests/Unit/Specs/PathResolverTest.php new file mode 100644 index 0000000..45d983a --- /dev/null +++ b/tests/Unit/Specs/PathResolverTest.php @@ -0,0 +1,121 @@ +assertSame('/plugins/OpenApiDocs/tmp/specs/', $resolver->getSpecDirectory()); + $this->assertSame('/plugins/OpenApiDocs/tmp/annotations/', $resolver->getAnnotationsDirectory()); + $this->assertSame('/plugins/OpenApiDocs/tmp/responses/', $resolver->getResponsesDirectory()); + } + + public function testReturnsSharedPathsWhenCloudIsEnabledAndDistributedCachePathExists(): void + { + $resolver = $this->buildPathResolverWithSharedPathValidationResult(true, '/cache/distributed', true); + + $this->assertSame('/cache/distributed/OpenApiDocs/specs/', $resolver->getSpecDirectory()); + $this->assertSame('/cache/distributed/OpenApiDocs/annotations/', $resolver->getAnnotationsDirectory()); + $this->assertSame('/cache/distributed/OpenApiDocs/responses/', $resolver->getResponsesDirectory()); + } + + public function testFallsBackToPluginLocalPathsWhenCloudCachePathIsMissing(): void + { + $resolver = new PathResolver('/plugins/OpenApiDocs', true, $this->buildContainerStub(false)); + + $this->assertSame('/plugins/OpenApiDocs/tmp/specs/', $resolver->getSpecDirectory()); + $this->assertSame('/plugins/OpenApiDocs/tmp/annotations/', $resolver->getAnnotationsDirectory()); + $this->assertSame('/plugins/OpenApiDocs/tmp/responses/', $resolver->getResponsesDirectory()); + } + + public function testFallsBackToPluginLocalPathsWhenCloudCachePathIsEmpty(): void + { + $resolver = new PathResolver('/plugins/OpenApiDocs', true, $this->buildContainerStub(true, ' ')); + + $this->assertSame('/plugins/OpenApiDocs/tmp/specs/', $resolver->getSpecDirectory()); + $this->assertSame('/plugins/OpenApiDocs/tmp/annotations/', $resolver->getAnnotationsDirectory()); + $this->assertSame('/plugins/OpenApiDocs/tmp/responses/', $resolver->getResponsesDirectory()); + } + + public function testBuildsFilePathsUsingExpectedNamingConventions(): void + { + $resolver = $this->buildPathResolverWithSharedPathValidationResult(true, '/cache/distributed/', true); + + $this->assertSame( + '/cache/distributed/OpenApiDocs/specs/CustomAlerts_openapi_spec_v2.0.0.yaml', + $resolver->getSpecFilePath('CustomAlerts', '2.0.0', 'YAML') + ); + $this->assertSame( + '/cache/distributed/OpenApiDocs/annotations/CustomAlertsGeneratedAnnotations.php', + $resolver->getAnnotationFilePath('CustomAlerts') + ); + $this->assertSame( + '/cache/distributed/OpenApiDocs/annotations/matomo_api_method_info.json', + $resolver->getApiMethodInfoFilePath('matomo') + ); + $this->assertSame( + '/cache/distributed/OpenApiDocs/responses/CustomAlerts.getAlerts.json', + $resolver->getExampleResponseFilePath('CustomAlerts', 'getAlerts', 'JSON') + ); + } + + private function buildContainerStub(bool $hasDistributedCachePath, string $distributedCachePath = ''): Container + { + $container = $this->getMockBuilder(Container::class) + ->disableOriginalConstructor() + ->onlyMethods(['has', 'get']) + ->getMock(); + + $container->method('has') + ->with('CloudDistributedCachePath') + ->willReturn($hasDistributedCachePath); + + if ($hasDistributedCachePath) { + $container->method('get') + ->with('CloudDistributedCachePath') + ->willReturn($distributedCachePath); + } + + return $container; + } + + private function buildPathResolverWithSharedPathValidationResult( + bool $hasDistributedCachePath, + string $distributedCachePath, + bool $isUsableSharedBasePath + ): PathResolver { + $resolver = $this->getMockBuilder(PathResolver::class) + ->setConstructorArgs(['/plugins/OpenApiDocs', true, $this->buildContainerStub($hasDistributedCachePath, $distributedCachePath)]) + ->onlyMethods(['isUsableSharedBasePath']) + ->getMock(); + + $resolver->method('isUsableSharedBasePath') + ->with(trim($distributedCachePath)) + ->willReturn($isUsableSharedBasePath); + + return $resolver; + } +}