Files
esun3d/app/code/Esun3d/LanguageUrlStoreResolver/Test/Unit/Plugin/LanguageUrlFrontControllerPluginTest.php
2026-09-15 10:35:56 +08:00

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;
}
}