diff --git a/src/App/Webhook/Hook/AbstractHook.php b/src/App/Webhook/Hook/AbstractHook.php index c99df8bc4..6a27332f8 100644 --- a/src/App/Webhook/Hook/AbstractHook.php +++ b/src/App/Webhook/Hook/AbstractHook.php @@ -29,20 +29,7 @@ public function handle(Request $request): void */ public function validate(Request $request): bool { - return $this->eventMatches($request, $this->getHookEvent()); - } - - /** - * @param \Symfony\Component\HttpFoundation\Request $request - * @param string $hook - * - * @return bool - */ - protected function eventMatches(Request $request, string $hook): bool - { - $content = $this->getHookBody($request); - - return $request->headers->get('x-myparcel-hook') === $hook && $content['event'] === $hook; + return true; } /** diff --git a/src/App/Webhook/PdkWebhookManager.php b/src/App/Webhook/PdkWebhookManager.php index dc5923bda..ee0c4764e 100644 --- a/src/App/Webhook/PdkWebhookManager.php +++ b/src/App/Webhook/PdkWebhookManager.php @@ -91,15 +91,27 @@ public function processWebhook(Request $request): void continue; } - $hook->handle($request); - - Logger::debug('Webhook processed', $logContext); + $this->handleHook($hook, $request, $logContext); } catch (Throwable $exception) { Logger::error('Webhook failed', $logContext); } } } + /** + * @param \MyParcelNL\Pdk\App\Webhook\Contract\HookInterface $hook + * @param \Symfony\Component\HttpFoundation\Request $request + * @param array $logContext + * + * @return void + */ + protected function handleHook(HookInterface $hook, Request $request, array $logContext): void + { + $hook->handle($request); + + Logger::debug('Webhook processed', $logContext); + } + /** * @param string $event * diff --git a/tests/Bootstrap/MockPdkConfig.php b/tests/Bootstrap/MockPdkConfig.php index aedad3bc8..a0fe093f2 100644 --- a/tests/Bootstrap/MockPdkConfig.php +++ b/tests/Bootstrap/MockPdkConfig.php @@ -20,6 +20,7 @@ use MyParcelNL\Pdk\App\Order\Contract\PdkProductRepositoryInterface; use MyParcelNL\Pdk\App\ShippingMethod\Contract\PdkShippingMethodRepositoryInterface; use MyParcelNL\Pdk\App\Tax\Contract\TaxServiceInterface; +use MyParcelNL\Pdk\App\Webhook\Contract\PdkWebhookManagerInterface; use MyParcelNL\Pdk\App\Webhook\Contract\PdkWebhookServiceInterface; use MyParcelNL\Pdk\App\Webhook\Contract\PdkWebhooksRepositoryInterface; use MyParcelNL\Pdk\Base\Concern\PdkInterface; @@ -93,6 +94,7 @@ private static function getDefaultConfig(): array PdkOrderRepositoryInterface::class => get(MockPdkOrderRepository::class), PdkProductRepositoryInterface::class => get(MockPdkProductRepository::class), PdkShippingMethodRepositoryInterface::class => get(MockPdkShippingMethodRepository::class), + PdkWebhookManagerInterface::class => get(MockPdkWebhookManager::class), PdkWebhookServiceInterface::class => get(MockPdkWebhookService::class), PdkWebhooksRepositoryInterface::class => get(MockPdkWebhooksRepository::class), SettingsRepositoryInterface::class => get(MockSettingsRepository::class), diff --git a/tests/Bootstrap/MockPdkWebhookManager.php b/tests/Bootstrap/MockPdkWebhookManager.php new file mode 100644 index 000000000..434b5d412 --- /dev/null +++ b/tests/Bootstrap/MockPdkWebhookManager.php @@ -0,0 +1,35 @@ +calledHooks; + } + + public function reset(): void + { + $this->calledHooks = []; + } + + protected function handleHook(HookInterface $hook, Request $request, array $logContext): void + { + $this->calledHooks[] = $hook; + + parent::handleHook($hook, $request, $logContext); + } +} diff --git a/tests/Bootstrap/TestCase.php b/tests/Bootstrap/TestCase.php index 1ef744d70..8512521f4 100644 --- a/tests/Bootstrap/TestCase.php +++ b/tests/Bootstrap/TestCase.php @@ -25,6 +25,7 @@ protected function getResetServices(): array MockMemoryCacheStorage::class, MockOrderStatusService::class, MockPdkActionsService::class, + MockPdkWebhookManager::class, MockPdkWebhooksRepository::class, SharedFactoryState::class, ]; diff --git a/tests/Unit/App/Webhook/PdkWebhookManagerTest.php b/tests/Unit/App/Webhook/PdkWebhookManagerTest.php index 0febf3587..387a4f91e 100644 --- a/tests/Unit/App/Webhook/PdkWebhookManagerTest.php +++ b/tests/Unit/App/Webhook/PdkWebhookManagerTest.php @@ -7,7 +7,15 @@ use MyParcelNL\Pdk\App\Webhook\Contract\PdkWebhookManagerInterface; use MyParcelNL\Pdk\App\Webhook\Contract\PdkWebhooksRepositoryInterface; +use MyParcelNL\Pdk\App\Webhook\Hook\OrderStatusChangeWebhook; +use MyParcelNL\Pdk\App\Webhook\Hook\ShipmentLabelCreatedWebhook; +use MyParcelNL\Pdk\App\Webhook\Hook\ShipmentStatusChangeWebhook; +use MyParcelNL\Pdk\App\Webhook\Hook\ShopCarrierAccessibilityUpdatedWebhook; +use MyParcelNL\Pdk\App\Webhook\Hook\ShopCarrierConfigurationUpdatedWebhook; +use MyParcelNL\Pdk\App\Webhook\Hook\ShopUpdatedWebhook; +use MyParcelNL\Pdk\App\Webhook\Hook\SubscriptionCreatedOrUpdatedWebhook; use MyParcelNL\Pdk\Base\Contract\CronServiceInterface; +use MyParcelNL\Pdk\Base\Support\Arr; use MyParcelNL\Pdk\Facade\Pdk; use MyParcelNL\Pdk\Tests\Uses\UsesMockEachCron; use MyParcelNL\Pdk\Tests\Uses\UsesMockPdkInstance; @@ -21,10 +29,10 @@ usesShared(new UsesMockPdkInstance(), new UsesMockEachCron()); -it('dispatches and executes webhooks with myparcel header', function (string $hook, bool $hasHeader) { - /** @var PdkWebhooksRepositoryInterface $repository */ +it('dispatches and executes webhooks', function (string $hook, string $calledClass, bool $useHeader) { + /** @var \MyParcelNL\Pdk\App\Webhook\Contract\PdkWebhooksRepositoryInterface $repository */ $repository = Pdk::get(PdkWebhooksRepositoryInterface::class); - /** @var PdkWebhookManagerInterface $webhookManager */ + /** @var \MyParcelNL\Pdk\Tests\Bootstrap\MockPdkWebhookManager $webhookManager */ $webhookManager = Pdk::get(PdkWebhookManagerInterface::class); /** @var \MyParcelNL\Pdk\Tests\Bootstrap\MockCronService $cronService */ $cronService = Pdk::get(CronServiceInterface::class); @@ -40,11 +48,11 @@ [], [], [], - $hasHeader ? ['HTTP_X_MYPARCEL_HOOK' => $hook] : [], + ['HTTP_X_MYPARCEL_HOOK' => $hook], json_encode([ 'data' => [ 'hooks' => [ - ['event' => $hook], + $useHeader ? [] : ['event' => $hook], ], ], ]) @@ -58,9 +66,7 @@ expect($response->getStatusCode()) ->toBe(Response::HTTP_ACCEPTED) ->and($request->headers->get('x-myparcel-hook')) - ->toBe($hasHeader ? $hook : null) - ->and($request->getContent()) - ->toEqual('{"data":{"hooks":[{"event":"' . $hook . '"}]}}') + ->toBe($hook) ->and($response->getStatusCode()) ->toBe(Response::HTTP_ACCEPTED) ->and($scheduled->all()) @@ -74,8 +80,52 @@ ->toBe([$webhookManager, 'processWebhook']) ->and($scheduled->first()['args']) ->toBe([$request]); + + $cronService->executeScheduledTask(); + + $calledHooks = $webhookManager->getCalledHooks(); + + expect($calledHooks) + ->toHaveLength(1) + ->and(Arr::first($calledHooks)) + ->toBeInstanceOf($calledClass); }) - ->with(WebhookSubscription::ALL) + ->with([ + WebhookSubscription::SHIPMENT_STATUS_CHANGE => [ + WebhookSubscription::SHIPMENT_STATUS_CHANGE, + ShipmentStatusChangeWebhook::class, + ], + + WebhookSubscription::SHIPMENT_LABEL_CREATED => [ + WebhookSubscription::SHIPMENT_LABEL_CREATED, + ShipmentLabelCreatedWebhook::class, + ], + + WebhookSubscription::ORDER_STATUS_CHANGE => [ + WebhookSubscription::ORDER_STATUS_CHANGE, + OrderStatusChangeWebhook::class, + ], + + WebhookSubscription::SHOP_UPDATED => [ + WebhookSubscription::SHOP_UPDATED, + ShopUpdatedWebhook::class, + ], + + WebhookSubscription::SHOP_CARRIER_ACCESSIBILITY_UPDATED => [ + WebhookSubscription::SHOP_CARRIER_ACCESSIBILITY_UPDATED, + ShopCarrierAccessibilityUpdatedWebhook::class, + ], + + WebhookSubscription::SHOP_CARRIER_CONFIGURATION_UPDATED => [ + WebhookSubscription::SHOP_CARRIER_CONFIGURATION_UPDATED, + ShopCarrierConfigurationUpdatedWebhook::class, + ], + + WebhookSubscription::SUBSCRIPTION_CREATED_OR_UPDATED => [ + WebhookSubscription::SUBSCRIPTION_CREATED_OR_UPDATED, + SubscriptionCreatedOrUpdatedWebhook::class, + ], + ]) ->with([ 'no header' => [false], 'header' => [true],