Skip to content

Commit 99751fa

Browse files
committed
Fix: test
1 parent be0527d commit 99751fa

5 files changed

Lines changed: 260 additions & 12 deletions

File tree

src/Controller/BaseController.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
use PhpList\RestApiClient\Endpoint\AuthClient;
88
use PhpList\RestApiClient\Entity\Administrator;
9-
use PhpList\RestApiClient\Exception\AuthenticationException;
9+
use PhpList\RestApiClient\Exception\ApiException;
1010
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
1111

1212
class BaseController extends AbstractController
@@ -20,7 +20,7 @@ protected function getAdmin(): ?Administrator
2020
{
2121
try {
2222
$admin = $this->authClient->getSessionUser();
23-
} catch (AuthenticationException $e) {
23+
} catch (ApiException) {
2424
$admin = null;
2525
}
2626

src/Controller/PublicSubscribeController.php

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public function __construct(
4141
public function unsubscribe(Request $request, int $pageId): Response
4242
{
4343
$page = $this->subscribePagesClient->getPublicSubscribePage($pageId);
44-
$pageData = $page->data;
44+
$pageData = $this->normalizePublicPageData($page->data);
4545
$languageFile = $pageData['language_file'] ?? 'english.inc';
4646
$languageTexts = $this->languageService->loadLanguageTexts(is_string($languageFile) ? $languageFile : null);
4747

@@ -93,16 +93,16 @@ public function subscribe(Request $request, int $pageId): Response
9393
{
9494
$admin = $this->getAdmin();
9595
$page = $this->subscribePagesClient->getPublicSubscribePage($pageId);
96-
$pageData = $page->data;
96+
$pageData = $this->normalizePublicPageData($page->data);
9797
$isSubmitted = $request->isMethod('POST');
9898

9999
$languageFile = $pageData['language_file'] ?? 'english.inc';
100100
$languageTexts = $this->languageService->loadLanguageTexts(is_string($languageFile) ? $languageFile : null);
101101

102102
$htmlChoice = $this->formBuilder->normalizeHtmlChoice($pageData['htmlchoice'] ?? null);
103-
$emailDoubleEntry = strtolower($pageData['emaildoubleentry'] ?? '') === 'yes';
103+
$emailDoubleEntry = strtolower((string) ($pageData['emaildoubleentry'] ?? '')) === 'yes';
104104

105-
$lists = $page->data['lists'];
105+
$lists = $pageData['lists'];
106106
$availableListIds = array_map(static fn ($list): int => (int) $list['id'], $lists);
107107

108108
$attributes = $this->formBuilder->buildAttributeConfig($pageData);
@@ -165,4 +165,67 @@ public function subscribe(Request $request, int $pageId): Response
165165
'signature' => $this->config->getValue(ConfigOption::PoweredByImage)
166166
]);
167167
}
168+
169+
/**
170+
* @param array<string,mixed> $pageData
171+
* @return array<string,mixed>
172+
*/
173+
private function normalizePublicPageData(array $pageData): array
174+
{
175+
$pageData['header'] = $this->scalarString($pageData['header'] ?? null);
176+
$pageData['footer'] = $this->scalarString($pageData['footer'] ?? null);
177+
$pageData['attributes'] = is_array($pageData['attributes'] ?? null) ? $pageData['attributes'] : [];
178+
$pageData['lists'] = $this->normalizePublicLists($pageData['lists'] ?? []);
179+
180+
return $pageData;
181+
}
182+
183+
/**
184+
* @return list<array{id:int,name:string,description:string,list_position:int}>
185+
*/
186+
private function normalizePublicLists(mixed $lists): array
187+
{
188+
if (!is_array($lists)) {
189+
return [];
190+
}
191+
192+
$normalizedLists = [];
193+
foreach ($lists as $list) {
194+
$normalizedList = $this->normalizePublicList($list);
195+
if ($normalizedList === null) {
196+
continue;
197+
}
198+
199+
$normalizedLists[] = $normalizedList;
200+
}
201+
202+
return $normalizedLists;
203+
}
204+
205+
/**
206+
* @return array{id:int,name:string,description:string,list_position:int}|null
207+
*/
208+
private function normalizePublicList(mixed $list): ?array
209+
{
210+
if (!is_array($list)) {
211+
return null;
212+
}
213+
214+
$id = (int) ($list['id'] ?? 0);
215+
if ($id <= 0) {
216+
return null;
217+
}
218+
219+
return [
220+
'id' => $id,
221+
'name' => $this->scalarString($list['name'] ?? null, 'List ' . $id),
222+
'description' => $this->scalarString($list['description'] ?? null),
223+
'list_position' => (int) ($list['list_position'] ?? 0),
224+
];
225+
}
226+
227+
private function scalarString(mixed $value, string $default = ''): string
228+
{
229+
return is_scalar($value) ? (string) $value : $default;
230+
}
168231
}

templates/public/base.html.twig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@
1414
<script src="{{ asset('build/app.js', 'phplist_web_frontend') }}" defer></script>
1515
{% endblock %}
1616

17-
{{ data['header']|unescape_header|raw }}
17+
{{ data['header']|default('')|unescape_header|raw }}
1818

1919
{% block body %}{% endblock %}
2020

2121
{{ signature|raw }}
2222

23-
{{ data['footer']|unescape_header|raw }}
23+
{{ data['footer']|default('')|unescape_header|raw }}

tests/Integration/Controller/PublicSubscribeControllerPantherTest.php

Lines changed: 92 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66

77
use PHPUnitRetry\RetryAnnotationTrait;
88
use PHPUnitRetry\RetryTrait;
9+
use Symfony\Component\Panther\Client;
910
use Symfony\Component\Panther\PantherTestCase;
11+
use Throwable;
1012

1113
/**
1214
* @retryAttempts 1
@@ -27,6 +29,7 @@ public function testAnonymousUserCanAccessPublicSubscribeAssetRoutes(string $pat
2729
'browser' => static::CHROME,
2830
'connection_timeout_in_ms' => 10000,
2931
]);
32+
$client->getCookieJar()->clear();
3033
$client->request('GET', $path);
3134

3235
$currentPath = (string) parse_url($client->getCurrentURL(), PHP_URL_PATH);
@@ -56,6 +59,7 @@ public function testAnonymousUserCanAccessPublicSubscribeAndUnsubscribePages(str
5659
'browser' => static::CHROME,
5760
'connection_timeout_in_ms' => 10000,
5861
]);
62+
$client->getCookieJar()->clear();
5963
$client->request('GET', $path);
6064

6165
$currentPath = (string) parse_url($client->getCurrentURL(), PHP_URL_PATH);
@@ -70,10 +74,18 @@ public function testSubscribePageDisplaysEmailFieldAndSubmitButton(): void
7074
'browser' => static::CHROME,
7175
'connection_timeout_in_ms' => 10000,
7276
]);
73-
$client->request('GET', '/index.php/subscribe/1');
74-
75-
$client->takeScreenshot('var/screenshots/public-subscribe-0.png');
76-
$client->waitFor('form.legacy-form', 10);
77+
try {
78+
$client->getCookieJar()->clear();
79+
$client->request('GET', '/index.php/subscribe/1');
80+
81+
$client->takeScreenshot('var/screenshots/public-subscribe-0.png');
82+
$client->waitFor('form.legacy-form', 10);
83+
} catch (Throwable $throwable) {
84+
$client->takeScreenshot('var/screenshots/public-subscribe-error.png');
85+
$this->writePublicSubscribeDiagnostics($client, $throwable);
86+
87+
throw $throwable;
88+
}
7789
$client->takeScreenshot('var/screenshots/public-subscribe-1.png');
7890

7991
$currentPath = (string) parse_url($client->getCurrentURL(), PHP_URL_PATH);
@@ -95,4 +107,80 @@ public function publicSubscribePageRoutesProvider(): array
95107
'unsubscribe page route' => ['/index.php/unsubscribe/1'],
96108
];
97109
}
110+
111+
private function writePublicSubscribeDiagnostics(Client $client, Throwable $throwable): void
112+
{
113+
$diagnosticsDir = __DIR__ . '/../../../var/screenshots';
114+
if (!is_dir($diagnosticsDir)) {
115+
mkdir($diagnosticsDir, 0777, true);
116+
}
117+
118+
$pageSource = $this->readPageSource($client);
119+
file_put_contents($diagnosticsDir . '/public-subscribe-error.html', $pageSource);
120+
file_put_contents(
121+
$diagnosticsDir . '/public-subscribe-error.txt',
122+
implode("\n\n", [
123+
'Exception: ' . $throwable::class,
124+
'Message: ' . $throwable->getMessage(),
125+
'Current URL: ' . $this->readCurrentUrl($client),
126+
'Page title: ' . $this->readPageTitle($client),
127+
'Body text: ' . $this->readBodyText($client),
128+
'Recent test log:' . "\n" . $this->readRecentLogLines('test.log'),
129+
'Recent dev log:' . "\n" . $this->readRecentLogLines('dev.log'),
130+
])
131+
);
132+
133+
fwrite(STDERR, "Public subscribe diagnostics written to var/screenshots/public-subscribe-error.*\n");
134+
}
135+
136+
private function readPageSource(Client $client): string
137+
{
138+
try {
139+
return $client->getPageSource();
140+
} catch (Throwable $throwable) {
141+
return 'Unable to read page source: ' . $throwable->getMessage();
142+
}
143+
}
144+
145+
private function readCurrentUrl(Client $client): string
146+
{
147+
try {
148+
return $client->getCurrentURL();
149+
} catch (Throwable $throwable) {
150+
return 'Unable to read current URL: ' . $throwable->getMessage();
151+
}
152+
}
153+
154+
private function readPageTitle(Client $client): string
155+
{
156+
try {
157+
return $client->getTitle();
158+
} catch (Throwable $throwable) {
159+
return 'Unable to read page title: ' . $throwable->getMessage();
160+
}
161+
}
162+
163+
private function readBodyText(Client $client): string
164+
{
165+
try {
166+
return $client->getCrawler()->filter('body')->text('', true);
167+
} catch (Throwable $throwable) {
168+
return 'Unable to read body text: ' . $throwable->getMessage();
169+
}
170+
}
171+
172+
private function readRecentLogLines(string $fileName): string
173+
{
174+
$path = __DIR__ . '/../../../var/logs/' . $fileName;
175+
if (!is_file($path)) {
176+
return sprintf('Log file %s does not exist.', $path);
177+
}
178+
179+
$lines = file($path, FILE_IGNORE_NEW_LINES);
180+
if ($lines === false) {
181+
return sprintf('Unable to read log file %s.', $path);
182+
}
183+
184+
return implode("\n", array_slice($lines, -80));
185+
}
98186
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpList\WebFrontend\Tests\Integration\Controller;
6+
7+
use PhpList\Core\Domain\Configuration\Model\ConfigOption;
8+
use PhpList\Core\Domain\Configuration\Service\Provider\ConfigProvider;
9+
use PhpList\RestApiClient\Endpoint\AuthClient;
10+
use PhpList\RestApiClient\Endpoint\SubscribePagesClient;
11+
use PhpList\RestApiClient\Entity\SubscribePagePublic;
12+
use PhpList\RestApiClient\Exception\ApiException;
13+
use PhpList\WebFrontend\Controller\PublicSubscribeController;
14+
use PhpList\WebFrontend\Service\LanguageService;
15+
use PhpList\WebFrontend\Service\PublicSubscribeFormBuilder;
16+
use PhpList\WebFrontend\Service\PublicSubscribeFormValidator;
17+
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
18+
use Symfony\Component\HttpFoundation\Request;
19+
20+
class PublicSubscribeControllerTest extends KernelTestCase
21+
{
22+
public function testSubscribePageRendersForAnonymousUserWhenAdminLookupApiFails(): void
23+
{
24+
self::bootKernel();
25+
26+
$subscribePagesClient = $this->createMock(SubscribePagesClient::class);
27+
$subscribePagesClient->expects(self::once())
28+
->method('getPublicSubscribePage')
29+
->with(1)
30+
->willReturn($this->createPublicSubscribePage());
31+
32+
$authClient = $this->createMock(AuthClient::class);
33+
$authClient->expects(self::once())
34+
->method('getSessionUser')
35+
->willThrowException(new ApiException('API error occurred', 500));
36+
37+
$config = $this->createMock(ConfigProvider::class);
38+
$config->expects(self::once())
39+
->method('getValue')
40+
->with(ConfigOption::PoweredByImage)
41+
->willReturn('');
42+
43+
$container = static::getContainer();
44+
$formBuilder = $this->createMock(PublicSubscribeFormBuilder::class);
45+
$formBuilder->expects(self::once())
46+
->method('normalizeHtmlChoice')
47+
->with('checkforhtml')
48+
->willReturn('checkforhtml');
49+
$formBuilder->expects(self::once())
50+
->method('buildAttributeConfig')
51+
->willReturn([]);
52+
$formBuilder->expects(self::once())
53+
->method('buildInitialFormData')
54+
->willReturn([
55+
'email' => '',
56+
'email_confirm' => '',
57+
'make_confirmed' => '0',
58+
'htmlemail' => true,
59+
'selected_lists' => [],
60+
'attributes' => [],
61+
]);
62+
63+
$controller = new PublicSubscribeController(
64+
$subscribePagesClient,
65+
$authClient,
66+
new LanguageService(),
67+
$formBuilder,
68+
$this->createMock(PublicSubscribeFormValidator::class),
69+
$config,
70+
false
71+
);
72+
$controller->setContainer($container);
73+
74+
$response = $controller->subscribe(Request::create('/subscribe/1'), 1);
75+
$content = (string) $response->getContent();
76+
77+
self::assertSame(200, $response->getStatusCode());
78+
self::assertStringContainsString('form method="post"', $content);
79+
self::assertStringContainsString('name="email"', $content);
80+
self::assertStringContainsString('type="email"', $content);
81+
self::assertStringContainsString('type="submit"', $content);
82+
self::assertStringNotContainsString('adminmessage', $content);
83+
}
84+
85+
private function createPublicSubscribePage(): SubscribePagePublic
86+
{
87+
return new SubscribePagePublic([
88+
'id' => 1,
89+
'title' => 'Subscribe to our newsletter',
90+
'data' => [
91+
'button' => 'Subscribe',
92+
'htmlchoice' => 'checkforhtml',
93+
'intro' => '<p>Subscribe to our newsletter</p>',
94+
],
95+
]);
96+
}
97+
}

0 commit comments

Comments
 (0)