Skip to content

Commit f8f9d8e

Browse files
authored
fix: system config webhook check (#174)
fixes shopware/shopware#7537
1 parent 31224c3 commit f8f9d8e

File tree

4 files changed

+272
-17
lines changed

4 files changed

+272
-17
lines changed

Diff for: src/Webhook/Registration/WebhookSubscriber.php

+19-13
Original file line numberDiff line numberDiff line change
@@ -62,33 +62,39 @@ public function removeSalesChannelWebhookConfiguration(EntityDeletedEvent $event
6262

6363
/**
6464
* system-config should be written by {@see SettingsSaver} only, which checks the webhook on its own.
65-
* Just in case new/changed credentials will be saved via the normal system config save route.
65+
* Just in case new/changed credentials will be saved via the normal system config.
6666
*/
6767
public function checkWebhookBefore(BeforeSystemConfigMultipleChangedEvent $event): void
6868
{
69-
$routeName = (string) $this->requestStack->getMainRequest()?->attributes->getString('_route');
70-
71-
if (!\str_contains($routeName, 'api.action.core.save.system-config')) {
72-
return;
69+
if ($config = $this->getConfigToCheck($event)) {
70+
$this->webhookSystemConfigHelper->checkWebhookBefore([$event->getSalesChannelId() => $config]);
7371
}
74-
75-
/** @var array<string, array<string, mixed>> $config */
76-
$config = $event->getConfig();
77-
$this->webhookSystemConfigHelper->checkWebhookBefore($config);
7872
}
7973

8074
/**
8175
* system-config should be written by {@see SettingsSaver} only, which checks the webhook on its own.
82-
* Just in case new/changed credentials will be saved via the normal system config save route.
76+
* Just in case new/changed credentials will be saved via the normal system config.
8377
*/
8478
public function checkWebhookAfter(SystemConfigMultipleChangedEvent $event): void
8579
{
80+
if ($this->getConfigToCheck($event)) {
81+
$this->webhookSystemConfigHelper->checkWebhookAfter([$event->getSalesChannelId()]);
82+
}
83+
}
84+
85+
/**
86+
* @return array<string, mixed>|null
87+
*/
88+
private function getConfigToCheck(BeforeSystemConfigMultipleChangedEvent|SystemConfigMultipleChangedEvent $event): ?array
89+
{
90+
/** @var array<string, mixed> $config */
91+
$config = $event->getConfig();
8692
$routeName = (string) $this->requestStack->getMainRequest()?->attributes->getString('_route');
8793

88-
if (!\str_contains($routeName, 'api.action.core.save.system-config')) {
89-
return;
94+
if (\str_contains($routeName, 'api.action.paypal.settings.save') || !$this->webhookSystemConfigHelper->needsCheck($config)) {
95+
return null;
9096
}
9197

92-
$this->webhookSystemConfigHelper->checkWebhookAfter(\array_keys($event->getConfig()));
98+
return $config;
9399
}
94100
}

Diff for: src/Webhook/Registration/WebhookSystemConfigHelper.php

+10-2
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public function checkWebhookBefore(array $newData): array
5959
$errors = [];
6060

6161
foreach ($newData as $salesChannelId => $newSettings) {
62-
if ($salesChannelId === 'null') {
62+
if (!$salesChannelId || $salesChannelId === 'null') {
6363
$salesChannelId = null;
6464
}
6565

@@ -102,7 +102,7 @@ public function checkWebhookAfter(array $salesChannelIds): array
102102
$errors = [];
103103

104104
foreach ($salesChannelIds as $salesChannelId) {
105-
if ($salesChannelId === 'null') {
105+
if (!$salesChannelId || $salesChannelId === 'null') {
106106
$salesChannelId = null;
107107
}
108108

@@ -129,6 +129,14 @@ public function checkWebhookAfter(array $salesChannelIds): array
129129
return $errors;
130130
}
131131

132+
/**
133+
* @param array<string, mixed> $config
134+
*/
135+
public function needsCheck(array $config): bool
136+
{
137+
return !empty($this->filterSettings($config));
138+
}
139+
132140
private function fetchSettings(?string $salesChannelId, bool $inherit = false): array
133141
{
134142
$settings = [];

Diff for: tests/Webhook/Registration/WebhookSubscriberTest.php

+160-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
namespace Swag\PayPal\Test\Webhook\Registration;
99

10+
use PHPUnit\Framework\MockObject\MockObject;
1011
use PHPUnit\Framework\TestCase;
1112
use Psr\Log\NullLogger;
1213
use Shopware\Core\Framework\Context;
@@ -29,6 +30,7 @@
2930
use Swag\PayPal\Webhook\Registration\WebhookSystemConfigHelper;
3031
use Swag\PayPal\Webhook\WebhookRegistry;
3132
use Swag\PayPal\Webhook\WebhookService;
33+
use Symfony\Component\HttpFoundation\Request;
3234
use Symfony\Component\HttpFoundation\RequestStack;
3335
use Symfony\Component\Routing\RouterInterface;
3436

@@ -44,9 +46,15 @@ class WebhookSubscriberTest extends TestCase
4446

4547
private SystemConfigService $systemConfigService;
4648

49+
private WebhookSystemConfigHelper&MockObject $webhookSystemConfigHelper;
50+
51+
private RequestStack $requestStack;
52+
4753
protected function setUp(): void
4854
{
4955
$this->systemConfigService = SystemConfigServiceMock::createWithCredentials();
56+
$this->webhookSystemConfigHelper = $this->createMock(WebhookSystemConfigHelper::class);
57+
$this->requestStack = new RequestStack();
5058
}
5159

5260
public function testRemoveWebhookWithInheritedConfiguration(): void
@@ -73,6 +81,156 @@ public function testRemoveWebhookWithNoConfiguration(): void
7381
static::assertEmpty($this->systemConfigService->getString(Settings::WEBHOOK_ID, TestDefaults::SALES_CHANNEL));
7482
}
7583

84+
public function testCheckWebhookBefore(): void
85+
{
86+
$event = new BeforeSystemConfigMultipleChangedEvent(['some-key' => 'some-value'], null);
87+
88+
$this->webhookSystemConfigHelper
89+
->expects(static::once())
90+
->method('checkWebhookBefore')
91+
->with(['' => ['some-key' => 'some-value']]);
92+
93+
$this->webhookSystemConfigHelper
94+
->expects(static::once())
95+
->method('needsCheck')
96+
->with(['some-key' => 'some-value'])
97+
->willReturn(true);
98+
99+
$this->createWebhookSubscriber(['' => null, TestDefaults::SALES_CHANNEL => null])
100+
->checkWebhookBefore($event);
101+
}
102+
103+
public function testCheckWebhookBeforeWithSalesChannelId(): void
104+
{
105+
$event = new BeforeSystemConfigMultipleChangedEvent(['some-key' => 'some-value'], 'some-sales-channel-id');
106+
107+
$this->webhookSystemConfigHelper
108+
->expects(static::once())
109+
->method('checkWebhookBefore')
110+
->with(['some-sales-channel-id' => ['some-key' => 'some-value']]);
111+
112+
$this->webhookSystemConfigHelper
113+
->expects(static::once())
114+
->method('needsCheck')
115+
->with(['some-key' => 'some-value'])
116+
->willReturn(true);
117+
118+
$this->createWebhookSubscriber(['' => null, TestDefaults::SALES_CHANNEL => null])
119+
->checkWebhookBefore($event);
120+
}
121+
122+
public function testCheckWebhookBeforeWithSaveRoute(): void
123+
{
124+
$request = new Request(attributes: ['_route' => 'api.action.paypal.settings.save']);
125+
$this->requestStack->push($request);
126+
127+
$event = new BeforeSystemConfigMultipleChangedEvent(['some-key' => 'some-value'], 'some-sales-channel-id');
128+
129+
$this->webhookSystemConfigHelper
130+
->expects(static::never())
131+
->method('checkWebhookBefore');
132+
133+
$this->webhookSystemConfigHelper
134+
->expects(static::never())
135+
->method('needsCheck');
136+
137+
$this->createWebhookSubscriber(['' => null, TestDefaults::SALES_CHANNEL => null])
138+
->checkWebhookBefore($event);
139+
}
140+
141+
public function testCheckWebhookBeforeWithNoCheckNeeded(): void
142+
{
143+
$event = new BeforeSystemConfigMultipleChangedEvent(['some-key' => 'some-value'], 'some-sales-channel-id');
144+
145+
$this->webhookSystemConfigHelper
146+
->expects(static::never())
147+
->method('checkWebhookBefore');
148+
149+
$this->webhookSystemConfigHelper
150+
->expects(static::once())
151+
->method('needsCheck')
152+
->with(['some-key' => 'some-value'])
153+
->willReturn(false);
154+
155+
$this->createWebhookSubscriber(['' => null, TestDefaults::SALES_CHANNEL => null])
156+
->checkWebhookBefore($event);
157+
}
158+
159+
public function testCheckWebhookAfter(): void
160+
{
161+
$event = new SystemConfigMultipleChangedEvent(['some-key' => 'some-value'], null);
162+
163+
$this->webhookSystemConfigHelper
164+
->expects(static::once())
165+
->method('checkWebhookAfter')
166+
->with(['']);
167+
168+
$this->webhookSystemConfigHelper
169+
->expects(static::once())
170+
->method('needsCheck')
171+
->with(['some-key' => 'some-value'])
172+
->willReturn(true);
173+
174+
$this->createWebhookSubscriber(['' => null, TestDefaults::SALES_CHANNEL => null])
175+
->checkWebhookAfter($event);
176+
}
177+
178+
public function testCheckWebhookAfterWithSalesChannelId(): void
179+
{
180+
$event = new SystemConfigMultipleChangedEvent(['some-key' => 'some-value'], 'some-sales-channel-id');
181+
182+
$this->webhookSystemConfigHelper
183+
->expects(static::once())
184+
->method('checkWebhookAfter')
185+
->with(['some-sales-channel-id']);
186+
187+
$this->webhookSystemConfigHelper
188+
->expects(static::once())
189+
->method('needsCheck')
190+
->with(['some-key' => 'some-value'])
191+
->willReturn(true);
192+
193+
$this->createWebhookSubscriber(['' => null, TestDefaults::SALES_CHANNEL => null])
194+
->checkWebhookAfter($event);
195+
}
196+
197+
public function testCheckWebhookAfterWithSaveRoute(): void
198+
{
199+
$request = new Request(attributes: ['_route' => 'api.action.paypal.settings.save']);
200+
$this->requestStack->push($request);
201+
202+
$event = new SystemConfigMultipleChangedEvent(['some-key' => 'some-value'], 'some-sales-channel-id');
203+
204+
$this->webhookSystemConfigHelper
205+
->expects(static::never())
206+
->method('checkWebhookAfter');
207+
208+
$this->webhookSystemConfigHelper
209+
->expects(static::never())
210+
->method('needsCheck');
211+
212+
$this->createWebhookSubscriber(['' => null, TestDefaults::SALES_CHANNEL => null])
213+
->checkWebhookAfter($event);
214+
}
215+
216+
public function testCheckWebhookAfterWithNoCheckNeeded(): void
217+
{
218+
$event = new SystemConfigMultipleChangedEvent(['some-key' => 'some-value'], 'some-sales-channel-id');
219+
220+
$this->webhookSystemConfigHelper
221+
->expects(static::never())
222+
->method('checkWebhookAfter');
223+
224+
$this->webhookSystemConfigHelper
225+
->expects(static::once())
226+
->method('needsCheck')
227+
->with(['some-key' => 'some-value'])
228+
->willReturn(false);
229+
230+
$this->createWebhookSubscriber(['' => null, TestDefaults::SALES_CHANNEL => null])
231+
->checkWebhookAfter($event);
232+
}
233+
76234
public function testSubscribedEvents(): void
77235
{
78236
static::assertEqualsCanonicalizing([
@@ -105,8 +263,8 @@ private function createWebhookSubscriber(array $configuration): WebhookSubscribe
105263
new NullLogger(),
106264
$this->systemConfigService,
107265
$webhookService,
108-
$this->createMock(WebhookSystemConfigHelper::class),
109-
new RequestStack(),
266+
$this->webhookSystemConfigHelper,
267+
$this->requestStack,
110268
);
111269
}
112270

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
<?php declare(strict_types=1);
2+
/*
3+
* (c) shopware AG <[email protected]>
4+
* For the full copyright and license information, please view the LICENSE
5+
* file that was distributed with this source code.
6+
*/
7+
8+
namespace Swag\PayPal\Test\Webhook\Registration;
9+
10+
use PHPUnit\Framework\Attributes\CoversClass;
11+
use PHPUnit\Framework\Attributes\DataProvider;
12+
use PHPUnit\Framework\TestCase;
13+
use Psr\Log\NullLogger;
14+
use Shopware\Core\Framework\Log\Package;
15+
use Shopware\Core\System\SystemConfig\SystemConfigService;
16+
use Swag\PayPal\Setting\Service\SettingsValidationServiceInterface;
17+
use Swag\PayPal\Setting\Settings;
18+
use Swag\PayPal\Webhook\Registration\WebhookSystemConfigHelper;
19+
use Swag\PayPal\Webhook\WebhookServiceInterface;
20+
21+
/**
22+
* @internal
23+
*/
24+
#[Package('checkout')]
25+
#[CoversClass(WebhookSystemConfigHelper::class)]
26+
class WebhookSystemConfigHelperTest extends TestCase
27+
{
28+
private const WEBHOOK_KEYS = [
29+
Settings::CLIENT_ID,
30+
Settings::CLIENT_SECRET,
31+
Settings::CLIENT_ID_SANDBOX,
32+
Settings::CLIENT_SECRET_SANDBOX,
33+
Settings::SANDBOX,
34+
Settings::WEBHOOK_ID,
35+
];
36+
37+
private WebhookSystemConfigHelper $helper;
38+
39+
protected function setUp(): void
40+
{
41+
$this->helper = new WebhookSystemConfigHelper(
42+
new NullLogger(),
43+
$this->createMock(WebhookServiceInterface::class),
44+
$this->createMock(SystemConfigService::class),
45+
$this->createMock(SettingsValidationServiceInterface::class),
46+
);
47+
}
48+
49+
/**
50+
* @param array<string, mixed> $config
51+
*/
52+
#[DataProvider('providerNeedsCheck')]
53+
public function testNeedsCheck(bool $expected, array $config): void
54+
{
55+
static::assertSame($expected, $this->helper->needsCheck($config));
56+
}
57+
58+
public static function providerNeedsCheck(): \Generator
59+
{
60+
foreach (self::WEBHOOK_KEYS as $key) {
61+
yield \sprintf('needs check for "%s"', $key) => [
62+
true,
63+
[$key => 'some-value'],
64+
];
65+
66+
yield \sprintf('needs check for "%s" with null value', $key) => [
67+
true,
68+
[$key => null],
69+
];
70+
}
71+
72+
foreach (Settings::DEFAULT_VALUES as $key => $value) {
73+
if (\in_array($key, self::WEBHOOK_KEYS, true)) {
74+
continue;
75+
}
76+
77+
yield \sprintf('no check for "%s"', $key) => [
78+
false,
79+
[$key => $value],
80+
];
81+
}
82+
}
83+
}

0 commit comments

Comments
 (0)