459 lines
18 KiB
PHP
459 lines
18 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
namespace Esun3d\LanguageUrlStoreResolver\Test\Unit\Plugin;
|
|
|
|
use Magento\Framework\App\FrontControllerInterface;
|
|
use Magento\Framework\App\Request\Http as HttpRequest;
|
|
use Magento\Framework\App\Response\Http as HttpResponse;
|
|
use Magento\Store\Model\Store;
|
|
use Magento\Store\Model\StoreManagerInterface;
|
|
use Magento\Store\Model\Website;
|
|
use PHPUnit\Framework\TestCase;
|
|
use Psr\Log\LoggerInterface;
|
|
use Esun3d\LanguageUrlStoreResolver\Model\Config;
|
|
use Esun3d\LanguageUrlStoreResolver\Model\LanguageResolver;
|
|
use Esun3d\LanguageUrlStoreResolver\Model\MarketCookie;
|
|
use Esun3d\LanguageUrlStoreResolver\Model\MarketResolver;
|
|
use Esun3d\LanguageUrlStoreResolver\Model\RequestPathProcessor;
|
|
use Esun3d\LanguageUrlStoreResolver\Model\StoreCodeMapper;
|
|
use Esun3d\LanguageUrlStoreResolver\Model\StoreViewRegistry;
|
|
use Esun3d\LanguageUrlStoreResolver\Plugin\LanguageUrlFrontControllerPlugin;
|
|
|
|
class LanguageUrlFrontControllerPluginTest extends TestCase
|
|
{
|
|
/**
|
|
* @dataProvider productPathStoreProvider
|
|
*/
|
|
public function testSetsStoreAndStripsLanguageForProductPage(
|
|
string $pathInfo,
|
|
?string $market,
|
|
string $expectedStoreCode,
|
|
string $expectedPath
|
|
): void {
|
|
$config = $this->createConfig();
|
|
$request = $this->createRequest($pathInfo, null);
|
|
$request->expects(self::once())->method('setPathInfo')->with($expectedPath);
|
|
$request->expects(self::exactly(3))->method('setParam');
|
|
$request->expects(self::exactly(3))->method('setAlias');
|
|
|
|
$storeManager = $this->createMock(StoreManagerInterface::class);
|
|
$storeManager->expects(self::once())->method('setCurrentStore')->with($expectedStoreCode);
|
|
|
|
$plugin = $this->createPlugin($config, $market, $storeManager);
|
|
$proceedCalled = false;
|
|
|
|
$plugin->aroundDispatch(
|
|
$this->createMock(FrontControllerInterface::class),
|
|
function (HttpRequest $passedRequest) use (&$proceedCalled, $request) {
|
|
$proceedCalled = true;
|
|
self::assertSame($request, $passedRequest);
|
|
|
|
return 'proceeded';
|
|
},
|
|
$request
|
|
);
|
|
|
|
self::assertTrue($proceedCalled);
|
|
}
|
|
|
|
/**
|
|
* @return mixed[]
|
|
*/
|
|
public static function productPathStoreProvider(): array
|
|
{
|
|
return [
|
|
'english product path without cookie uses language default' => ['/en/product-a', null, 'en_US', '/product-a'],
|
|
'english product path in DE market cookie' => ['/en/product-a', 'en_DE', 'en_DE', '/product-a'],
|
|
'german product path without cookie uses language default' => ['/de/product-a', null, 'de_US', '/product-a'],
|
|
'german product path in DE market cookie' => ['/de/product-a', 'de_DE', 'de_DE', '/product-a'],
|
|
'german customer path uses the same language resolver' => [
|
|
'/de/customer/account/login',
|
|
null,
|
|
'de_US',
|
|
'/customer/account/login',
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @dataProvider realStoreCodeRedirectProvider
|
|
*/
|
|
public function testRedirectsRealStoreCodeUrl(string $pathInfo, string $expectedUrl): void
|
|
{
|
|
$config = $this->createConfig();
|
|
$request = $this->createRequest($pathInfo, null);
|
|
$request->method('getRequestUri')->willReturn($pathInfo);
|
|
$request->method('getBaseUrl')->willReturn('');
|
|
|
|
$response = $this->createResponse($expectedUrl, 301);
|
|
$marketCookie = $this->createMock(MarketCookie::class);
|
|
$marketCookie->expects(self::once())->method('set');
|
|
$storeManager = $this->createMock(StoreManagerInterface::class);
|
|
$storeManager->expects(self::never())->method('setCurrentStore');
|
|
|
|
$plugin = $this->createPlugin($config, null, $storeManager, $marketCookie, $response);
|
|
$proceedCalled = false;
|
|
$result = $plugin->aroundDispatch(
|
|
$this->createMock(FrontControllerInterface::class),
|
|
function () use (&$proceedCalled) {
|
|
$proceedCalled = true;
|
|
},
|
|
$request
|
|
);
|
|
|
|
self::assertSame($response, $result);
|
|
self::assertFalse($proceedCalled);
|
|
}
|
|
|
|
/**
|
|
* @return mixed[]
|
|
*/
|
|
public static function realStoreCodeRedirectProvider(): array
|
|
{
|
|
return [
|
|
'english DE store code' => ['/en_DE/product-a', '/en/product-a'],
|
|
'german DE store code' => ['/de_DE/product-a', '/de/product-a'],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @dataProvider marketRedirectProvider
|
|
*/
|
|
public function testRedirectsMarketSwitchUrl(string $pathInfo, string $market, string $expectedUrl): void
|
|
{
|
|
$config = $this->createConfig();
|
|
$request = $this->createRequest($pathInfo, $market);
|
|
$request->method('getRequestUri')->willReturn($pathInfo . '?market=' . $market);
|
|
$request->method('getBaseUrl')->willReturn('');
|
|
|
|
$response = $this->createResponse($expectedUrl, 302);
|
|
$marketCookie = $this->createMock(MarketCookie::class);
|
|
$marketCookie->expects(self::once())->method('set')->with($market);
|
|
$storeManager = $this->createMock(StoreManagerInterface::class);
|
|
$storeManager->expects(self::never())->method('setCurrentStore');
|
|
|
|
$plugin = $this->createPlugin($config, null, $storeManager, $marketCookie, $response);
|
|
$proceedCalled = false;
|
|
$result = $plugin->aroundDispatch(
|
|
$this->createMock(FrontControllerInterface::class),
|
|
function () use (&$proceedCalled) {
|
|
$proceedCalled = true;
|
|
},
|
|
$request
|
|
);
|
|
|
|
self::assertSame($response, $result);
|
|
self::assertFalse($proceedCalled);
|
|
}
|
|
|
|
/**
|
|
* @return mixed[]
|
|
*/
|
|
public static function marketRedirectProvider(): array
|
|
{
|
|
return [
|
|
'english URL switches to german DE store' => ['/en/', 'de_DE', '/de/'],
|
|
'english URL stays english DE store' => ['/en/', 'en_DE', '/en/'],
|
|
'german URL stays german US store' => ['/de/', 'de_US', '/de/'],
|
|
'root URL switches to german DE store' => ['/', 'de_DE', '/de/'],
|
|
'root URL switches to english DE store' => ['/', 'en_DE', '/en/'],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @dataProvider skippedPathProvider
|
|
*/
|
|
public function testSkippedPathsAreNotProcessed(string $pathInfo): void
|
|
{
|
|
$config = $this->createConfig();
|
|
$request = $this->createRequest($pathInfo, null);
|
|
$storeManager = $this->createMock(StoreManagerInterface::class);
|
|
$storeManager->expects(self::never())->method('setCurrentStore');
|
|
|
|
$plugin = $this->createPlugin($config, null, $storeManager);
|
|
$proceedCalled = false;
|
|
$result = $plugin->aroundDispatch(
|
|
$this->createMock(FrontControllerInterface::class),
|
|
function () use (&$proceedCalled) {
|
|
$proceedCalled = true;
|
|
|
|
return 'skipped';
|
|
},
|
|
$request
|
|
);
|
|
|
|
self::assertSame('skipped', $result);
|
|
self::assertTrue($proceedCalled);
|
|
}
|
|
|
|
public function testInvalidMarketParamDoesNotSetCurrentStore(): void
|
|
{
|
|
$config = $this->createConfig();
|
|
$request = $this->createRequest('/en/product-a', 'fr_FR');
|
|
$storeManager = $this->createMock(StoreManagerInterface::class);
|
|
$storeManager->expects(self::never())->method('setCurrentStore');
|
|
|
|
$plugin = $this->createPlugin($config, null, $storeManager);
|
|
$result = $plugin->aroundDispatch(
|
|
$this->createMock(FrontControllerInterface::class),
|
|
static function () {
|
|
return 'invalid-market-proceeded';
|
|
},
|
|
$request
|
|
);
|
|
|
|
self::assertSame('invalid-market-proceeded', $result);
|
|
}
|
|
|
|
public function testRootPathForcesWebsiteDefaultStore(): void
|
|
{
|
|
$config = $this->createConfig();
|
|
$request = $this->createRequest('/', null);
|
|
$request->expects(self::exactly(2))->method('setParam');
|
|
$request->expects(self::exactly(2))->method('setAlias');
|
|
|
|
$defaultStore = $this->createMock(Store::class);
|
|
$defaultStore->method('isActive')->willReturn(true);
|
|
$defaultStore->method('getCode')->willReturn('en_US');
|
|
|
|
$website = $this->createMock(Website::class);
|
|
$website->method('getDefaultStore')->willReturn($defaultStore);
|
|
|
|
$storeManager = $this->createMock(StoreManagerInterface::class);
|
|
$storeManager->method('getWebsite')->willReturn($website);
|
|
$storeManager->expects(self::once())->method('setCurrentStore')->with('en_US');
|
|
|
|
$marketCookie = $this->createMock(MarketCookie::class);
|
|
$marketCookie->expects(self::once())->method('set')->with('en_US');
|
|
|
|
$plugin = $this->createPlugin($config, 'ja_JP', $storeManager, $marketCookie);
|
|
$result = $plugin->aroundDispatch(
|
|
$this->createMock(FrontControllerInterface::class),
|
|
static function () {
|
|
return 'root-proceeded';
|
|
},
|
|
$request
|
|
);
|
|
|
|
self::assertSame('root-proceeded', $result);
|
|
}
|
|
|
|
public function testUnprefixedContentPathForcesWebsiteDefaultStore(): void
|
|
{
|
|
$config = $this->createConfig();
|
|
$request = $this->createRequest('/product-a.html', null);
|
|
$request->expects(self::exactly(2))->method('setParam');
|
|
$request->expects(self::exactly(2))->method('setAlias');
|
|
|
|
$defaultStore = $this->createMock(Store::class);
|
|
$defaultStore->method('isActive')->willReturn(true);
|
|
$defaultStore->method('getCode')->willReturn('en_US');
|
|
|
|
$website = $this->createMock(Website::class);
|
|
$website->method('getDefaultStore')->willReturn($defaultStore);
|
|
|
|
$storeManager = $this->createMock(StoreManagerInterface::class);
|
|
$storeManager->method('getWebsite')->willReturn($website);
|
|
$storeManager->expects(self::once())->method('setCurrentStore')->with('en_US');
|
|
|
|
$marketCookie = $this->createMock(MarketCookie::class);
|
|
$marketCookie->expects(self::once())->method('set')->with('en_US');
|
|
|
|
$plugin = $this->createPlugin($config, 'de_US', $storeManager, $marketCookie);
|
|
$result = $plugin->aroundDispatch(
|
|
$this->createMock(FrontControllerInterface::class),
|
|
static function () {
|
|
return 'unprefixed-product-proceeded';
|
|
},
|
|
$request
|
|
);
|
|
|
|
self::assertSame('unprefixed-product-proceeded', $result);
|
|
}
|
|
|
|
public function testCustomerAccountPathForcesWebsiteDefaultStoreWithoutLanguageContext(): void
|
|
{
|
|
$config = $this->createConfig();
|
|
$request = $this->createRequest('/customer/account/login', null);
|
|
$request->expects(self::exactly(2))->method('setParam');
|
|
$request->expects(self::exactly(2))->method('setAlias');
|
|
|
|
$defaultStore = $this->createMock(Store::class);
|
|
$defaultStore->method('isActive')->willReturn(true);
|
|
$defaultStore->method('getCode')->willReturn('en_US');
|
|
|
|
$website = $this->createMock(Website::class);
|
|
$website->method('getDefaultStore')->willReturn($defaultStore);
|
|
|
|
$storeManager = $this->createMock(StoreManagerInterface::class);
|
|
$storeManager->method('getWebsite')->willReturn($website);
|
|
$storeManager->expects(self::once())->method('setCurrentStore')->with('en_US');
|
|
|
|
$marketCookie = $this->createMock(MarketCookie::class);
|
|
$marketCookie->expects(self::once())->method('set')->with('en_US');
|
|
|
|
$plugin = $this->createPlugin($config, null, $storeManager, $marketCookie);
|
|
$result = $plugin->aroundDispatch(
|
|
$this->createMock(FrontControllerInterface::class),
|
|
static function () {
|
|
return 'customer-login-proceeded';
|
|
},
|
|
$request
|
|
);
|
|
|
|
self::assertSame('customer-login-proceeded', $result);
|
|
}
|
|
|
|
/**
|
|
* @return mixed[]
|
|
*/
|
|
public static function skippedPathProvider(): array
|
|
{
|
|
return [
|
|
'static' => ['/static/version123/frontend/file.css'],
|
|
'media' => ['/media/catalog/product/a.jpg'],
|
|
'rest' => ['/rest/V1/products'],
|
|
'graphql' => ['/graphql'],
|
|
];
|
|
}
|
|
|
|
private function createPlugin(
|
|
Config $config,
|
|
?string $cookieMarket,
|
|
StoreManagerInterface $storeManager,
|
|
?MarketCookie $marketCookie = null,
|
|
?HttpResponse $response = null
|
|
): LanguageUrlFrontControllerPlugin {
|
|
$registry = $this->createRegistry();
|
|
$scopeStoreManager = $this->createScopeStoreManager();
|
|
$languageResolver = new LanguageResolver($registry, $scopeStoreManager);
|
|
$marketCookie = $marketCookie ?: $this->createMock(MarketCookie::class);
|
|
$marketCookie->method('get')->willReturn($cookieMarket);
|
|
$storeCodeMapper = new StoreCodeMapper($registry, $scopeStoreManager);
|
|
|
|
return new LanguageUrlFrontControllerPlugin(
|
|
$config,
|
|
$languageResolver,
|
|
new MarketResolver($marketCookie, $storeCodeMapper),
|
|
$storeCodeMapper,
|
|
new RequestPathProcessor($languageResolver),
|
|
$storeManager,
|
|
$marketCookie,
|
|
$response ?: $this->createMock(HttpResponse::class),
|
|
$this->createMock(LoggerInterface::class),
|
|
$this->createMock(\Magento\Framework\Locale\ResolverInterface::class),
|
|
$this->createMock(\Magento\Framework\App\Config\ScopeConfigInterface::class)
|
|
);
|
|
}
|
|
|
|
private function createConfig(): Config
|
|
{
|
|
$scopeConfig = $this->createMock(\Magento\Framework\App\Config\ScopeConfigInterface::class);
|
|
$scopeConfig->method('isSetFlag')->willReturn(true);
|
|
$scopeConfig->method('getValue')->willReturnCallback(static function (string $path, string $scope) {
|
|
self::assertSame(\Magento\Store\Model\ScopeInterface::SCOPE_STORE, $scope);
|
|
|
|
$values = [
|
|
'language_url_store_resolver/general/market_cookie_name' => 'market',
|
|
'language_url_store_resolver/general/market_cookie_lifetime' => '2592000',
|
|
];
|
|
|
|
return $values[$path] ?? null;
|
|
});
|
|
|
|
return new Config($scopeConfig);
|
|
}
|
|
|
|
private function createRegistry(): StoreViewRegistry
|
|
{
|
|
$registry = $this->createMock(StoreViewRegistry::class);
|
|
$registry->method('isKnownLanguage')->willReturnCallback(static function (string $language): bool {
|
|
return in_array($language, ['en', 'de'], true);
|
|
});
|
|
$registry->method('isKnownStoreCode')->willReturnCallback(static function (string $storeCode): bool {
|
|
return in_array($storeCode, ['en_US', 'en_DE', 'de_US', 'de_DE'], true);
|
|
});
|
|
$registry->method('getLanguageByStoreCode')->willReturnCallback(static function (string $storeCode): ?string {
|
|
$map = [
|
|
'en_US' => 'en',
|
|
'en_DE' => 'en',
|
|
'de_US' => 'de',
|
|
'de_DE' => 'de',
|
|
];
|
|
|
|
return $map[$storeCode] ?? null;
|
|
});
|
|
$registry->method('getDefaultStoreCodeForLanguage')->willReturnCallback(
|
|
static function (string $language): ?string {
|
|
$map = [
|
|
'en' => 'en_US',
|
|
'de' => 'de_US',
|
|
];
|
|
|
|
return $map[$language] ?? null;
|
|
}
|
|
);
|
|
$registry->method('isLanguageInWebsite')->willReturnCallback(
|
|
static fn(string $language, int $websiteId): bool =>
|
|
$websiteId === 1 && in_array($language, ['en', 'de'], true)
|
|
);
|
|
$registry->method('isStoreCodeInWebsite')->willReturnCallback(
|
|
static fn(string $storeCode, int $websiteId): bool =>
|
|
$websiteId === 1 && in_array($storeCode, ['en_US', 'en_DE', 'de_US', 'de_DE'], true)
|
|
);
|
|
$registry->method('getStoreCodeForLanguageInWebsite')->willReturnCallback(
|
|
static function (string $language, int $websiteId): ?string {
|
|
if ($websiteId !== 1) {
|
|
return null;
|
|
}
|
|
|
|
return ['en' => 'en_US', 'de' => 'de_US'][$language] ?? null;
|
|
}
|
|
);
|
|
|
|
return $registry;
|
|
}
|
|
|
|
private function createScopeStoreManager(): StoreManagerInterface
|
|
{
|
|
$defaultStore = $this->createMock(Store::class);
|
|
$defaultStore->method('getCode')->willReturn('default_store');
|
|
$website = $this->createMock(Website::class);
|
|
$website->method('getId')->willReturn(1);
|
|
$website->method('getDefaultStore')->willReturn($defaultStore);
|
|
$storeManager = $this->createMock(StoreManagerInterface::class);
|
|
$storeManager->method('getWebsite')->willReturn($website);
|
|
|
|
return $storeManager;
|
|
}
|
|
|
|
private function createRequest(string $pathInfo, ?string $market): HttpRequest
|
|
{
|
|
$request = $this->getMockBuilder(HttpRequest::class)
|
|
->disableOriginalConstructor()
|
|
->onlyMethods(['getPathInfo', 'getRequestUri', 'getBaseUrl', 'getParam', 'setPathInfo', 'setParam', 'setAlias'])
|
|
->getMock();
|
|
$request->method('getPathInfo')->willReturn($pathInfo);
|
|
$request->method('getRequestUri')->willReturn($pathInfo);
|
|
$request->method('getBaseUrl')->willReturn('');
|
|
$request->method('getParam')->with('market')->willReturn($market);
|
|
|
|
return $request;
|
|
}
|
|
|
|
private function createResponse(string $expectedUrl, int $expectedStatusCode): HttpResponse
|
|
{
|
|
$response = $this->getMockBuilder(HttpResponse::class)
|
|
->disableOriginalConstructor()
|
|
->onlyMethods(['setRedirect'])
|
|
->getMock();
|
|
$response->expects(self::once())
|
|
->method('setRedirect')
|
|
->with($expectedUrl, $expectedStatusCode)
|
|
->willReturnSelf();
|
|
|
|
return $response;
|
|
}
|
|
}
|