[TASK] Add more tests to site resolving
[Packages/TYPO3.CMS.git] / typo3 / sysext / frontend / Tests / Unit / Middleware / SiteResolverTest.php
1 <?php
2 declare(strict_types = 1);
3
4 namespace TYPO3\CMS\Frontend\Tests\Unit\Middleware;
5
6 /*
7 * This file is part of the TYPO3 CMS project.
8 *
9 * It is free software; you can redistribute it and/or modify it under
10 * the terms of the GNU General Public License, either version 2
11 * of the License, or any later version.
12 *
13 * For the full copyright and license information, please read the
14 * LICENSE.txt file that was distributed with this source code.
15 *
16 * The TYPO3 project - inspiring people to share!
17 */
18
19 use Psr\Http\Message\ResponseInterface;
20 use Psr\Http\Message\ServerRequestInterface;
21 use Psr\Http\Server\RequestHandlerInterface;
22 use TYPO3\CMS\Core\Http\JsonResponse;
23 use TYPO3\CMS\Core\Http\NullResponse;
24 use TYPO3\CMS\Core\Http\ServerRequest;
25 use TYPO3\CMS\Core\Site\Entity\Site;
26 use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
27 use TYPO3\CMS\Core\Site\SiteFinder;
28 use TYPO3\CMS\Frontend\Middleware\SiteResolver;
29 use TYPO3\TestingFramework\Core\AccessibleObjectInterface;
30 use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
31
32 class SiteResolverTest extends UnitTestCase
33 {
34 /**
35 * @var bool Reset singletons created by subject
36 */
37 protected $resetSingletonInstances = true;
38
39 /**
40 * @var SiteFinder|AccessibleObjectInterface
41 */
42 protected $siteFinder;
43
44 protected $siteFoundRequestHandler;
45
46 /**
47 * Set up
48 */
49 protected function setUp(): void
50 {
51 // Make global object available, however it is not actively used
52 $GLOBALS['TSFE'] = new \stdClass();
53 $this->siteFinder = $this->getAccessibleMock(SiteFinder::class, ['dummy'], [], '', false);
54
55 // A request handler which expects a site to be found.
56 $this->siteFoundRequestHandler = new class implements RequestHandlerInterface {
57 public function handle(ServerRequestInterface $request): ResponseInterface
58 {
59 /** @var Site $site */
60 /** @var SiteLanguage $language */
61 $site = $request->getAttribute('site', false);
62 $language = $request->getAttribute('language', false);
63 if ($site && $language) {
64 return new JsonResponse(
65 [
66 'site' => $site->getIdentifier(),
67 'language-id' => $language->getLanguageId(),
68 'language-base' => $language->getBase(),
69 'rootpage' => $GLOBALS['TSFE']->domainStartPage
70 ]
71 );
72 }
73 return new NullResponse();
74 }
75 };
76 }
77
78 /**
79 * Expect a URL handed in, as a request. This URL does not have a GET parameter "id"
80 * Then the site handling gets triggered, and the URL is taken to resolve a site.
81 *
82 * This case tests against a site with no domain or scheme, and successfully finds it.
83 *
84 * @test
85 */
86 public function detectASingleSiteWhenProperRequestIsGiven()
87 {
88 $incomingUrl = 'https://a-random-domain.com/mysite/';
89 $siteIdentifier = 'full-site';
90 $this->siteFinder->_set('sites', [
91 $siteIdentifier => new Site($siteIdentifier, 13, [
92 'base' => '/mysite/',
93 'languages' => [
94 0 => [
95 'languageId' => 0,
96 'locale' => 'fr_FR.UTF-8',
97 'base' => '/'
98 ]
99 ]
100 ])
101 ]);
102
103 $request = new ServerRequest($incomingUrl, 'GET');
104 $subject = new SiteResolver($this->siteFinder);
105 $response = $subject->process($request, $this->siteFoundRequestHandler);
106 if ($response instanceof NullResponse) {
107 $this->fail('No site configuration found in URL ' . $incomingUrl . '.');
108 } else {
109 $result = $response->getBody()->getContents();
110 $result = json_decode($result, true);
111 $this->assertEquals($siteIdentifier, $result['site']);
112 $this->assertEquals(0, $result['language-id']);
113 $this->assertEquals('/mysite/', $result['language-base']);
114 }
115 }
116
117 /**
118 * Scenario with two sites
119 * Site 1: /
120 * Site 2: /mysubsite/
121 *
122 * The result should be that site 2 is resolved by the router when calling
123 *
124 * www.random-result.com/mysubsite/you-know-why/
125 *
126 * @test
127 */
128 public function detectSubsiteInsideNestedUrlStructure()
129 {
130 $incomingUrl = 'https://www.random-result.com/mysubsite/you-know-why/';
131 $this->siteFinder->_set('sites', [
132 'outside-site' => new Site('outside-site', 13, [
133 'base' => '/',
134 'languages' => [
135 0 => [
136 'languageId' => 0,
137 'locale' => 'fr_FR.UTF-8',
138 'base' => '/'
139 ]
140 ]
141 ]),
142 'sub-site' => new Site('sub-site', 15, [
143 'base' => '/mysubsite/',
144 'languages' => [
145 0 => [
146 'languageId' => 0,
147 'locale' => 'fr_FR.UTF-8',
148 'base' => '/'
149 ]
150 ]
151 ]),
152 ]);
153
154 $request = new ServerRequest($incomingUrl, 'GET');
155 $subject = new SiteResolver($this->siteFinder);
156 $response = $subject->process($request, $this->siteFoundRequestHandler);
157 if ($response instanceof NullResponse) {
158 $this->fail('No site configuration found in URL ' . $incomingUrl . '.');
159 } else {
160 $result = $response->getBody()->getContents();
161 $result = json_decode($result, true);
162 $this->assertEquals('sub-site', $result['site']);
163 $this->assertEquals(0, $result['language-id']);
164 $this->assertEquals('/mysubsite/', $result['language-base']);
165 }
166 }
167
168 public function detectSubSubsiteInsideNestedUrlStructureDataProvider()
169 {
170 return [
171 'matches second site' => [
172 'https://www.random-result.com/mysubsite/you-know-why/',
173 'sub-site',
174 14,
175 '/mysubsite/'
176 ],
177 'matches third site' => [
178 'https://www.random-result.com/mysubsite/micro-site/oh-yes-you-do/',
179 'subsub-site',
180 15,
181 '/mysubsite/micro-site/'
182 ],
183 'matches a subsite in first site' => [
184 'https://www.random-result.com/products/pampers/',
185 'outside-site',
186 13,
187 '/'
188 ],
189 ];
190 }
191
192 /**
193 * Scenario with three sites
194 * Site 1: /
195 * Site 2: /mysubsite/
196 * Site 3: /mysubsite/micro-site/
197 *
198 * The result should be that site 2 is resolved by the router when calling
199 *
200 * www.random-result.com/mysubsite/you-know-why/
201 *
202 * and site 3 when calling
203 * www.random-result.com/mysubsite/micro-site/oh-yes-you-do/
204 *
205 * @test
206 * @dataProvider detectSubSubsiteInsideNestedUrlStructureDataProvider
207 */
208 public function detectSubSubsiteInsideNestedUrlStructure($incomingUrl, $expectedSiteIdentifier, $expectedRootPageId, $expectedBase)
209 {
210 $this->siteFinder->_set('sites', [
211 'outside-site' => new Site('outside-site', 13, [
212 'base' => '/',
213 'languages' => [
214 0 => [
215 'languageId' => 0,
216 'locale' => 'fr_FR.UTF-8',
217 'base' => '/'
218 ]
219 ]
220 ]),
221 'sub-site' => new Site('sub-site', 14, [
222 'base' => '/mysubsite/',
223 'languages' => [
224 0 => [
225 'languageId' => 0,
226 'locale' => 'fr_FR.UTF-8',
227 'base' => '/'
228 ]
229 ]
230 ]),
231 'subsub-site' => new Site('subsub-site', 15, [
232 'base' => '/mysubsite/micro-site/',
233 'languages' => [
234 0 => [
235 'languageId' => 0,
236 'locale' => 'fr_FR.UTF-8',
237 'base' => '/'
238 ]
239 ]
240 ]),
241 ]);
242
243 $request = new ServerRequest($incomingUrl, 'GET');
244 $subject = new SiteResolver($this->siteFinder);
245 $response = $subject->process($request, $this->siteFoundRequestHandler);
246 if ($response instanceof NullResponse) {
247 $this->fail('No site configuration found in URL ' . $incomingUrl . '.');
248 } else {
249 $result = $response->getBody()->getContents();
250 $result = json_decode($result, true);
251 $this->assertEquals($expectedSiteIdentifier, $result['site']);
252 $this->assertEquals($expectedRootPageId, $result['rootpage']);
253 $this->assertEquals($expectedBase, $result['language-base']);
254 }
255 }
256
257 public function detectProperLanguageByIncomingUrlDataProvider()
258 {
259 return [
260 'matches second site' => [
261 'https://www.random-result.com/mysubsite/you-know-why/',
262 'sub-site',
263 14,
264 2,
265 '/mysubsite/'
266 ],
267 'matches second site in other language' => [
268 'https://www.random-result.com/mysubsite/it/you-know-why/',
269 'sub-site',
270 14,
271 2,
272 '/mysubsite/'
273 ],
274 'matches second site because third site language prefix did not match' => [
275 'https://www.random-result.com/mysubsite/micro-site/oh-yes-you-do/',
276 'sub-site',
277 14,
278 2,
279 '/mysubsite/'
280 ],
281 'matches third site' => [
282 'https://www.random-result.com/mysubsite/micro-site/ru/oh-yes-you-do/',
283 'subsub-site',
284 15,
285 13,
286 '/mysubsite/micro-site/ru/'
287 ],
288 /**
289 * This case does not work, as no language prefix is defined.
290 'matches a subsite in first site' => [
291 'https://www.random-result.com/products/pampers/',
292 'outside-site',
293 13,
294 0,
295 '/'
296 ],
297 */
298 'matches a subsite with translation in first site' => [
299 'https://www.random-result.com/fr/products/pampers/',
300 'outside-site',
301 13,
302 1,
303 '/fr/'
304 ],
305 ];
306 }
307
308 /**
309 * Scenario with three one site and three languages
310 * Site 1: /
311 * Language 0: /en/
312 * Language 1: /fr/
313 * Site 2: /mysubsite/
314 * Language: 2: /
315 * Site 3: /mysubsite/micro-site/
316 * Language: 13: /ru/
317 *
318 * @test
319 * @dataProvider detectProperLanguageByIncomingUrlDataProvider
320 */
321 public function detectProperLanguageByIncomingUrl($incomingUrl, $expectedSiteIdentifier, $expectedRootPageId, $expectedLanguageId, $expectedBase)
322 {
323 $this->siteFinder->_set('sites', [
324 'outside-site' => new Site('outside-site', 13, [
325 'base' => '/',
326 'languages' => [
327 0 => [
328 'languageId' => 0,
329 'locale' => 'en_US.UTF-8',
330 'base' => '/en/'
331 ],
332 1 => [
333 'languageId' => 1,
334 'locale' => 'fr_CA.UTF-8',
335 'base' => '/fr/'
336 ]
337 ]
338 ]),
339 'sub-site' => new Site('sub-site', 14, [
340 'base' => '/mysubsite/',
341 'languages' => [
342 2 => [
343 'languageId' => 2,
344 'locale' => 'it_IT.UTF-8',
345 'base' => '/'
346 ]
347 ]
348 ]),
349 'subsub-site' => new Site('subsub-site', 15, [
350 'base' => '/mysubsite/micro-site/',
351 'languages' => [
352 13 => [
353 'languageId' => 13,
354 'locale' => 'ru_RU.UTF-8',
355 'base' => '/ru/'
356 ]
357 ]
358 ]),
359 ]);
360
361 $request = new ServerRequest($incomingUrl, 'GET');
362 $subject = new SiteResolver($this->siteFinder);
363 $response = $subject->process($request, $this->siteFoundRequestHandler);
364 if ($response instanceof NullResponse) {
365 $this->fail('No site configuration found in URL ' . $incomingUrl . '.');
366 } else {
367 $result = $response->getBody()->getContents();
368 $result = json_decode($result, true);
369 $this->assertEquals($expectedSiteIdentifier, $result['site']);
370 $this->assertEquals($expectedRootPageId, $result['rootpage']);
371 $this->assertEquals($expectedLanguageId, $result['language-id']);
372 $this->assertEquals($expectedBase, $result['language-base']);
373 }
374 }
375 }