diff --git a/Gateway/Command/Capture.php b/Gateway/Command/Capture.php index 5a52f97..b65b4fe 100644 --- a/Gateway/Command/Capture.php +++ b/Gateway/Command/Capture.php @@ -97,8 +97,10 @@ public function execute(array $commandSubject) $this->getValidator()::ACTION_TYPE_CAPTURE ); + $hasTracking = !empty($requestData['tracking']); + // if tracking info is invalid, stop capture - if (!$this->isTrackingInfoValid($requestData['tracking'] ?? null)) { + if ($hasTracking && !$this->isTrackingInfoValid($requestData['tracking'])) { return null; } @@ -122,7 +124,7 @@ public function execute(array $commandSubject) $requestData = $requestData->toArray(); } - if ($this->isProcessingShipment($requestData, $response)) { + if ($hasTracking && $this->isProcessingShipment($requestData, $response)) { $this->addShippingInfoToCapture( $response->getCaptureId(), $klarnaOrder->getReservationId(), @@ -154,9 +156,20 @@ private function addShippingInfoToCapture($captureId, $klarnaOrderId, $trackingD $invoice->addComment("Shipping info sent to Klarna API", false, false); return; } - foreach ($response->getErrorMessages() as $message) { - $invoice->addComment($message, false, false); + + $errorMessages = $response->getErrorMessages(); + + if (!$errorMessages) { + return; } + + $errorMessages = \implode('. ', $errorMessages); + + $invoice->addComment( + "Received error(s) when sending shipping info to Kustom API: {$errorMessages}.", + notify: false, + visibleOnFront: false + ); } /** @@ -166,13 +179,11 @@ private function addShippingInfoToCapture($captureId, $klarnaOrderId, $trackingD * @param DataObject $response * @return bool */ - private function isProcessingShipment(array $requestData, DataObject - $response): bool + private function isProcessingShipment(array $requestData, DataObject $response): bool { if (isset($requestData['invoice']['do_shipment']) && $requestData['invoice']['do_shipment'] === "1" - && $response->getCaptureId() - && $this->isTrackingInfoValid($requestData['tracking'] ?? null)) { + && $response->getCaptureId()) { return true; } return false; @@ -181,15 +192,11 @@ private function isProcessingShipment(array $requestData, DataObject /** * Validate tracking info * - * @param array|null $trackingInformation + * @param array $trackingInformation * @return bool */ - private function isTrackingInfoValid(?array $trackingInformation): bool + private function isTrackingInfoValid(array $trackingInformation): bool { - if ($trackingInformation === null) { - return true; - } - foreach ((array) $trackingInformation as $info) { if (empty($info['carrier_code']) || empty($info['title']) diff --git a/Model/Api/OrderManagement.php b/Model/Api/OrderManagement.php index 26a37f8..6a99404 100644 --- a/Model/Api/OrderManagement.php +++ b/Model/Api/OrderManagement.php @@ -308,7 +308,9 @@ private function prepareShippingInfo(array $shippingInfo) /** * Get Api Accepted shipping method,For merchant who implement this feature * Create Plugin to overwrite this default method code - * Allowed values matches (PickUpStore|Home|BoxReg|BoxUnreg|PickUpPoint|Own) + * Allowed values matches (PickUpStore|Home|BoxReg|BoxUnreg|PickUpPoint|Own|Postal|DHLPackstation|Digital + * |Undefined|PickUpWarehouse|ClickCollect|PalletDelivery) + * Doc: https://docs.kustom.co/contents/api/order-management/orders/appendordershippinginfo * * @param array $shipping * @return string diff --git a/Test/Integration/Gateway/Command/CaptureTest.php b/Test/Integration/Gateway/Command/CaptureTest.php new file mode 100644 index 0000000..e3bde1a --- /dev/null +++ b/Test/Integration/Gateway/Command/CaptureTest.php @@ -0,0 +1,372 @@ +paymentDataObjectFactory = $objectManager->get(PaymentDataObjectFactory::class); + $this->orderRepository = $objectManager->get(OrderRepositoryInterface::class); + $this->searchCriteriaBuilder = $objectManager->get(SearchCriteriaBuilder::class); + $this->invoiceRepository = $objectManager->get(InvoiceRepositoryInterface::class); + + $mockApiFactory = $this->createMock(Factory::class); + $mockValidator = $this->createMock(Validator::class); + $this->stubRequest = $objectManager->create(StubRequest::class); + $this->mockOrderManagement = $this->createMock(OrderManagement::class); + + $mockApiFactory->method('createOmApi')->willReturn($this->mockOrderManagement); + + $this->captureCommand = $objectManager->create( + Capture::class, + [ + 'omFactory' => $mockApiFactory, + 'validator' => $mockValidator, + 'request' => $this->stubRequest + ] + ); + } + + /** + * Test capture with tracking info present but shipment not processed. + * Scenario: Admin adds tracking to invoice after separate shipment or manual tracking entry. + * Verifies tracking presence alone doesn't trigger shipping API without do_shipment flag. + * + * @magentoDataFixture Klarna_Backend::Test/Integration/_files/invoice_with_klarna_payment.php + * @magentoDbIsolation enabled + * @magentoAppIsolation enabled + */ + public function testExecuteCaptureWithValidTrackingInfoButNoShipment(): void + { + $invoice = $this->getInvoiceByOrderIncrementId(self::TEST_ORDER_INCREMENT_ID); + $order = $invoice->getOrder(); + $payment = $order->getPayment(); + $payment->setData('invoice', $invoice); + + $this->stubRequest->setPostData([ + 'tracking' => [ + [ + 'carrier_code' => 'ups', + 'title' => 'UPS Ground', + 'number' => '1Z999AA10123456784', + ] + ] + ]); + + $this->mockOrderManagement->method('isFullyCaptured')->willReturn(false); + + $captureResponse = new DataObject(['capture_id' => self::TEST_CAPTURE_ID]); + $this->mockOrderManagement->expects($this->once()) + ->method('capture') + ->willReturn($captureResponse); + + $this->mockOrderManagement->expects($this->never()) + ->method('addShippingInfo'); + + $paymentDataObject = $this->paymentDataObjectFactory->create($payment); + + $this->captureCommand->execute([ + 'payment' => $paymentDataObject, + 'amount' => 100.00, + ]); + } + + /** + * Test capture with valid tracking info and do_shipment flag set. + * Scenario: Admin creates invoice with shipment and provides full carrier tracking details. + * Verifies capture proceeds and shipping info is sent to Klarna with a success comment on the invoice. + * + * @magentoDataFixture Klarna_Backend::Test/Integration/_files/invoice_with_klarna_payment.php + * @magentoDbIsolation enabled + * @magentoAppIsolation enabled + */ + public function testExecuteCaptureWithShipmentProcessing(): void + { + $invoice = $this->getInvoiceByOrderIncrementId(self::TEST_ORDER_INCREMENT_ID); + $order = $invoice->getOrder(); + $payment = $order->getPayment(); + $payment->setData('invoice', $invoice); + + $this->stubRequest->setPostData([ + 'invoice' => ['do_shipment' => '1'], + 'tracking' => [ + [ + 'carrier_code' => 'ups', + 'title' => 'UPS Ground', + 'number' => '1Z999AA10123456784', + ] + ] + ]); + + $this->mockOrderManagement->method('isFullyCaptured')->willReturn(false); + + $captureResponse = new DataObject(['capture_id' => self::TEST_CAPTURE_ID]); + $this->mockOrderManagement->expects($this->once()) + ->method('capture') + ->willReturn($captureResponse); + + $shippingResponse = new DataObject([ + 'is_successful' => true, + ]); + + $this->mockOrderManagement->expects($this->once()) + ->method('addShippingInfo') + ->with( + self::TEST_RESERVATION_ID, + self::TEST_CAPTURE_ID, + $this->isType('array') + ) + ->willReturn($shippingResponse); + + $paymentDataObject = $this->paymentDataObjectFactory->create($payment); + + $this->captureCommand->execute([ + 'payment' => $paymentDataObject, + 'amount' => 100.00 + ]); + + $this->invoiceRepository->save($invoice); + $reloadedInvoice = $this->invoiceRepository->get($invoice->getEntityId()); + $this->assertInvoiceHasComment($reloadedInvoice, 'Shipping info sent to Klarna API'); + } + + /** + * Test capture returns early when tracking info is present but missing required carrier_code. + * Scenario: Incomplete tracking data submitted — tracking array exists but carrier_code is absent. + * Verifies isTrackingInfoValid() rejects the data and capture is never called. + * + * @magentoDataFixture Klarna_Backend::Test/Integration/_files/invoice_with_klarna_payment.php + * @magentoDbIsolation enabled + * @magentoAppIsolation enabled + */ + public function testExecuteWithInvalidTrackingInfoReturnsEarly(): void + { + $invoice = $this->getInvoiceByOrderIncrementId(self::TEST_ORDER_INCREMENT_ID); + $order = $invoice->getOrder(); + $payment = $order->getPayment(); + + $this->stubRequest->setPostData([ + 'tracking' => [ + [ + 'title' => 'UPS', + 'number' => '123', + ] + ] + ]); + + $this->mockOrderManagement->expects($this->never()) + ->method('capture'); + + $paymentDataObject = $this->paymentDataObjectFactory->create($payment); + + $this->captureCommand->execute([ + 'payment' => $paymentDataObject, + 'amount' => 100.00, + ]); + } + + /** + * Test capture with do_shipment flag but empty tracking array skips shipping info call. + * Scenario: do_shipment is set but no tracking entries were provided (e.g., merchant forgot to add them). + * Verifies the $hasTracking guard prevents addShippingInfo from being called when tracking is empty. + * + * @magentoDataFixture Klarna_Backend::Test/Integration/_files/invoice_with_klarna_payment.php + * @magentoDbIsolation enabled + * @magentoAppIsolation enabled + */ + public function testExecuteWithShipmentButEmptyTrackingSkipsShippingInfo(): void + { + $invoice = $this->getInvoiceByOrderIncrementId(self::TEST_ORDER_INCREMENT_ID); + $order = $invoice->getOrder(); + $payment = $order->getPayment(); + $payment->setData('invoice', $invoice); + + $this->stubRequest->setPostData([ + 'invoice' => ['do_shipment' => '1'], + 'tracking' => [], + ]); + + $this->mockOrderManagement->method('isFullyCaptured')->willReturn(false); + + $captureResponse = new DataObject(['capture_id' => self::TEST_CAPTURE_ID]); + $this->mockOrderManagement->expects($this->once()) + ->method('capture') + ->willReturn($captureResponse); + + $this->mockOrderManagement->expects($this->never()) + ->method('addShippingInfo'); + + $paymentDataObject = $this->paymentDataObjectFactory->create($payment); + + $this->captureCommand->execute([ + 'payment' => $paymentDataObject, + 'amount' => 100.00, + ]); + + $this->invoiceRepository->save($invoice); + $reloadedInvoice = $this->invoiceRepository->get($invoice->getEntityId()); + $comments = $reloadedInvoice->getCommentsCollection(reload: true); + $this->assertCount(0, $comments, 'No comments should be added when tracking is empty'); + } + + /** + * Test capture with shipment processing when the Klarna shipping API returns a failure response. + * Scenario: Valid tracking info and do_shipment flag set, but Klarna API rejects the shipping info. + * Verifies each error message from the API response is recorded as a comment on the invoice. + * + * @magentoDataFixture Klarna_Backend::Test/Integration/_files/invoice_with_klarna_payment.php + * @magentoDbIsolation enabled + * @magentoAppIsolation enabled + */ + public function testExecuteWithShipmentProcessingApiFailure(): void + { + $invoice = $this->getInvoiceByOrderIncrementId(self::TEST_ORDER_INCREMENT_ID); + $order = $invoice->getOrder(); + $payment = $order->getPayment(); + $payment->setData('invoice', $invoice); + + $this->stubRequest->setPostData([ + 'invoice' => ['do_shipment' => '1'], + 'tracking' => [ + [ + 'carrier_code' => 'ups', + 'title' => 'UPS Ground', + 'number' => '1Z999AA10123456784', + ] + ] + ]); + + $this->mockOrderManagement->method('isFullyCaptured')->willReturn(false); + + $captureResponse = new DataObject(['capture_id' => self::TEST_CAPTURE_ID]); + $this->mockOrderManagement->expects($this->once()) + ->method('capture') + ->willReturn($captureResponse); + + $shippingResponse = new DataObject([ + 'is_successful' => false, + 'error_messages' => ['Invalid tracking number', 'Carrier not supported'], + ]); + + $this->mockOrderManagement->expects($this->once()) + ->method('addShippingInfo') + ->willReturn($shippingResponse); + + $paymentDataObject = $this->paymentDataObjectFactory->create($payment); + + $this->captureCommand->execute([ + 'payment' => $paymentDataObject, + 'amount' => 100.00, + ]); + + $this->invoiceRepository->save($invoice); + $reloadedInvoice = $this->invoiceRepository->get($invoice->getEntityId()); + $this->assertInvoiceHasComment( + $reloadedInvoice, + 'Received error(s) when sending shipping info to Kustom API: Invalid tracking number. Carrier not supported.' + ); + } + + /** + * Get invoice by order increment ID + * + * @param string $incrementId + * + * @return \Magento\Sales\Api\Data\InvoiceInterface + */ + private function getInvoiceByOrderIncrementId(string $incrementId): InvoiceInterface + { + /** @var \Magento\Sales\Model\Order $order */ + $searchCriteria = $this->searchCriteriaBuilder->addFilter('increment_id', $incrementId)->create(); + $order = $this->orderRepository->getList($searchCriteria)->getFirstItem(); + + if (!$order->getId()) { + throw new \RuntimeException("Order with increment ID {$incrementId} not found"); + } + + // Force reload of invoice collection + $order = $this->orderRepository->get($order->getId()); + $invoices = $order->getInvoiceCollection(); + + $this->assertGreaterThan( + 0, + $invoices->getSize(), + sprintf( + 'No invoice found for order %s. Order state: %s, can invoice: %s', + $incrementId, + $order->getState(), + $order->canInvoice() ? 'yes' : 'no' + ) + ); + + return $invoices->getFirstItem(); + } + + /** + * Assert that invoice has specific comment + * + * @param \Magento\Sales\Api\Data\InvoiceInterface $invoice + * @param string $commentText + */ + private function assertInvoiceHasComment(InvoiceInterface $invoice, string $commentText): void + { + // Use getCommentsCollection to load from database + $comments = $invoice->getCommentsCollection(reload: true); + $found = false; + $existingComments = []; + foreach ($comments as $comment) { + $existingComments[] = $comment->getComment(); + if (\str_contains($comment->getComment(), $commentText)) { + $found = true; + break; + } + } + $this->assertTrue( + $found, + sprintf( + "Invoice comment not found: '%s'. Existing comments: %s", + $commentText, + empty($existingComments) ? 'none' : implode('; ', $existingComments) + ) + ); + } +} diff --git a/Test/Integration/Stub/StubRequest.php b/Test/Integration/Stub/StubRequest.php new file mode 100644 index 0000000..5410e1b --- /dev/null +++ b/Test/Integration/Stub/StubRequest.php @@ -0,0 +1,48 @@ +postData = $data->toArray(); + } else { + $this->postData = $data; + } + } + + /** + * @inheritDoc + */ + public function getPost($name = null, $default = null) + { + if ($name === null) { + return $this->postData; + } + + return $this->postData[$name] ?? $default; + } +} diff --git a/Test/Integration/_files/invoice_with_klarna_payment.php b/Test/Integration/_files/invoice_with_klarna_payment.php new file mode 100644 index 0000000..5fd82a8 --- /dev/null +++ b/Test/Integration/_files/invoice_with_klarna_payment.php @@ -0,0 +1,64 @@ +requireDataFixture('Klarna_Backend::Test/Integration/_files/order_with_klarna_payment.php'); + +$objectManager = Bootstrap::getObjectManager(); + +/** @var Order $order */ +$order = $objectManager->get(OrderInterfaceFactory::class)->create()->loadByIncrementId('100000001'); + +if (!$order->getId()) { + throw new \RuntimeException( + 'Order with increment ID 100000001 not found. Make sure order_with_klarna_payment.php fixture runs successfully.' + ); +} + +// Check if order can be invoiced +if (!$order->canInvoice()) { + throw new \RuntimeException('Order cannot be invoiced. Order state: ' . $order->getState()); +} + +$orderService = $objectManager->create(InvoiceManagementInterface::class); +$invoice = $orderService->prepareInvoice($order); + +if (!$invoice->getTotalQty()) { + throw new \RuntimeException('Cannot create invoice with zero quantity.'); +} + +$invoice->setRequestedCaptureCase(\Magento\Sales\Model\Order\Invoice::CAPTURE_OFFLINE); +$invoice->register(); + +$order->setIsInProcess(true); + +$transactionSave = $objectManager->create(\Magento\Framework\DB\Transaction::class); +$transactionSave + ->addObject($invoice) + ->addObject($order) + ->save(); diff --git a/Test/Integration/_files/order_with_klarna_payment.php b/Test/Integration/_files/order_with_klarna_payment.php new file mode 100644 index 0000000..c4266aa --- /dev/null +++ b/Test/Integration/_files/order_with_klarna_payment.php @@ -0,0 +1,150 @@ +get(ProductRepositoryInterface::class); + + $product = $objectManager->create(Product::class); + $product->setTypeId(Type::TYPE_SIMPLE) + ->setAttributeSetId(4) + ->setWebsiteIds([1]) + ->setName('Simple Test Product') + ->setSku('simple-test-product-klarna') + ->setPrice(10) + ->setWeight(1) + ->setVisibility(Visibility::VISIBILITY_BOTH) + ->setStatus(Status::STATUS_ENABLED) + ->setStockData([ + 'use_config_manage_stock' => 1, + 'qty' => 100, + 'is_qty_decimal' => 0, + 'is_in_stock' => 1, + ]); + $productRepository->save($product); + + // Create order directly (bypass quote to avoid payment method issues) + /** @var Order $order */ + $order = $objectManager->create(Order::class); + + $storeManager = $objectManager->get(StoreManagerInterface::class); + $store = $storeManager->getStore(); + + // Set order data + $order->setIncrementId('100000001') + ->setState(Order::STATE_PROCESSING) + ->setStatus($order->getConfig()->getStateDefaultStatus(Order::STATE_PROCESSING)) + ->setCustomerIsGuest(true) + ->setCustomerEmail('john.doe.klarna@example.com') + ->setCustomerFirstname('John') + ->setCustomerLastname('Doe') + ->setStoreId($store->getId()) + ->setEmailSent(0) + ->setBaseCurrencyCode('USD') + ->setStoreCurrencyCode('USD') + ->setOrderCurrencyCode('USD'); + + // Set billing address + $billingAddress = $objectManager->create(OrderAddress::class); + $billingAddress->setFirstname('John') + ->setLastname('Doe') + ->setStreet(['123 Test Street']) + ->setCity('Test City') + ->setPostcode('12345') + ->setCountryId('US') + ->setRegionId(1) + ->setTelephone('555-1234') + ->setAddressType('billing'); + $order->setBillingAddress($billingAddress); + + // Set shipping address + $shippingAddress = $objectManager->create(OrderAddress::class); + $shippingAddress->setFirstname('John') + ->setLastname('Doe') + ->setStreet(['123 Test Street']) + ->setCity('Test City') + ->setPostcode('12345') + ->setCountryId('US') + ->setRegionId(1) + ->setTelephone('555-1234') + ->setAddressType('shipping'); + $order->setShippingAddress($shippingAddress); + + // Set payment - use checkmo (always available) to simulate Klarna + $payment = $objectManager->create(Payment::class); + $payment->setMethod('checkmo'); + $order->setPayment($payment); + + // Add order item + $orderItem = $objectManager->create(OrderItem::class); + $orderItem->setProductId($product->getId()) + ->setQtyOrdered(1) + ->setBasePrice($product->getPrice()) + ->setPrice($product->getPrice()) + ->setRowTotal($product->getPrice()) + ->setBaseRowTotal($product->getPrice()) + ->setProductType(Type::TYPE_SIMPLE) + ->setName($product->getName()) + ->setSku($product->getSku()); + $order->addItem($orderItem); + + // Set order totals + $shippingAmount = 5.00; + $order->setSubtotal($product->getPrice()) + ->setBaseSubtotal($product->getPrice()) + ->setGrandTotal($product->getPrice() + $shippingAmount) + ->setBaseGrandTotal($product->getPrice() + $shippingAmount) + ->setShippingAmount($shippingAmount) + ->setBaseShippingAmount($shippingAmount) + ->setShippingDescription('Flat Rate - Fixed'); + $order->save(); + + // Create Klarna order entry + $klarnaOrderFactory = $objectManager->get(KlarnaOrderFactory::class); + $klarnaOrderRepository = $objectManager->get(KlarnaOrderRepositoryInterface::class); + $klarnaOrder = $klarnaOrderFactory->create(); + $klarnaOrder->setOrderId((int)$order->getEntityId()) + ->setReservationId('test-reservation-id-12345') + ->setSessionId('test-session-id-67890') + ->setKlarnaOrderId('test-klarna-order-id-abcde'); + $klarnaOrderRepository->save($klarnaOrder); +} catch (\Exception $e) { + throw new \RuntimeException( + 'Failed to create order fixture: ' . $e->getMessage() . "\n" . $e->getTraceAsString() + ); +} diff --git a/Test/Integration/_files/order_with_klarna_payment_rollback.php b/Test/Integration/_files/order_with_klarna_payment_rollback.php new file mode 100644 index 0000000..a68699f --- /dev/null +++ b/Test/Integration/_files/order_with_klarna_payment_rollback.php @@ -0,0 +1,69 @@ +get(Registry::class); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +// Delete order +try { + /** @var \Magento\Sales\Model\Order $order */ + $order = $objectManager->get(OrderInterfaceFactory::class)->create()->loadByIncrementId('100000001'); + + if ($order->getId()) { + $klarnaOrderFactory = $objectManager->get(KlarnaOrderFactory::class); + $klarnaOrderResource = $objectManager->get(KlarnaOrderResource::class); + $klarnaOrder = $klarnaOrderFactory->create(); + $klarnaOrderResource->load($klarnaOrder, $order->getEntityId(), 'order_id'); + + if ($klarnaOrder->getId()) { + $klarnaOrderResource->delete($klarnaOrder); + } + + $order->delete(); + } +} catch (\Exception) { + // Order already deleted +} + +// Delete product +try { + $productRepository = $objectManager->get(ProductRepositoryInterface::class); + $product = $productRepository->get('simple-test-product-klarna'); + $productRepository->delete($product); +} catch (\Exception) { + // Product already deleted +} + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/Test/Unit/Model/Api/OrderManagementTest.php b/Test/Unit/Model/Api/OrderManagementTest.php index 27c4250..278d64f 100644 --- a/Test/Unit/Model/Api/OrderManagementTest.php +++ b/Test/Unit/Model/Api/OrderManagementTest.php @@ -5,12 +5,20 @@ * For the full copyright and license information, please view the NOTICE * and LICENSE files that were distributed with this source code. */ +declare(strict_types=1); namespace Klarna\Backend\Test\Model\Api; +use Klarna\AdminSettings\Model\Configurations\Api as KlarnaConfigurationsApi; +use Klarna\Backend\Model\Api\Builder as KlarnaApiBuilder; use Klarna\Backend\Model\Api\OrderManagement as ApiOrderManagement; -use Klarna\Backend\Model\Api\Rest\Service\Ordermanagement; -use Klarna\Orderlines\Model\Container\Parameter; +use Klarna\Backend\Model\Api\Rest\Service\Ordermanagement as ServiceOrderManagement; +use Klarna\Base\Helper\DataConverter as KlarnaDataConverter; +use Klarna\Orderlines\Model\Container\Parameter as KlarnaParameter; +use Magento\Framework\DataObject; +use Magento\Framework\DataObjectFactory; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; /** @@ -18,63 +26,164 @@ */ class OrderManagementTest extends TestCase { - /** - * @var Parameter|\PHPUnit\Framework\MockObject\MockObject - */ - private $mockParameter; - /** @var Ordermanagement */ - private $model; - - /** @var Ordermanagement | PHPUnit_Framework_MockObject_MockObject */ - private $mockOrderManagement; - - /** @var \Klarna\Base\Helper\KlarnaConfig | PHPUnit_Framework_MockObject_MockObject */ - private $mockKlarnaConfig; - - /** @var \Klarna\Base\Helper\DataConverter | PHPUnit_Framework_MockObject_MockObject */ - private $mockDataConverter; - - /** @var \Magento\Framework\DataObjectFactory | PHPUnit_Framework_MockObject_MockObject */ - private $mockDataObjectFactory; - - public function testGetKlarnaShippingMethod() - { - self::assertEquals( - ApiOrderManagement::KLARNA_API_SHIPPING_METHOD_HOME, - $this->model->getKlarnaShippingMethod([]) - ); - } + private ApiOrderManagement $model; + private ServiceOrderManagement|MockObject $mockSrvOrderManagement; + private DataObjectFactory|MockObject $mockDataObjectFactory; + private KlarnaParameter|MockObject $mockParameter; + private KlarnaDataConverter|MockObject $mockDataConverter; + private KlarnaConfigurationsApi|MockObject $mockApi; + private KlarnaApiBuilder|MockObject $mockBuilder; protected function setUp(): void { - $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->mockSrvOrderManagement = $this->getMockBuilder(ServiceOrderManagement::class) + ->disableOriginalConstructor() + ->getMock(); - $this->mockOrderManagement = $this->getMockBuilder(Ordermanagement::class) + $this->mockDataConverter = $this->getMockBuilder(KlarnaDataConverter::class) ->disableOriginalConstructor() ->getMock(); - $this->mockKlarnaConfig = $this->getMockBuilder(\Klarna\Base\Helper\KlarnaConfig::class) + + $this->mockParameter = $this->getMockBuilder(KlarnaParameter::class) ->disableOriginalConstructor() ->getMock(); - $this->mockDataConverter = $this->getMockBuilder(\Klarna\Base\Helper\DataConverter::class) + + $this->mockDataObjectFactory = $this->getMockBuilder(DataObjectFactory::class) ->disableOriginalConstructor() ->getMock(); - $this->mockParameter = $this->getMockBuilder(Parameter::class) + $this->mockDataObjectFactory->method('create')->willReturn(new DataObject()); + + $this->mockApi = $this->getMockBuilder(KlarnaConfigurationsApi::class) ->disableOriginalConstructor() ->getMock(); - $this->mockDataObjectFactory = $this->getMockBuilder(\Magento\Framework\DataObjectFactory::class) + + $this->mockBuilder = $this->getMockBuilder(KlarnaApiBuilder::class) ->disableOriginalConstructor() ->getMock(); - $this->model = $objectManager->getObject( - ApiOrderManagement::class, - [ - 'orderManagement' => $this->mockOrderManagement, - 'klarnaConfig' => $this->mockKlarnaConfig, - 'dataConverter' => $this->mockDataConverter, - 'dataObjectFactory' => $this->mockDataObjectFactory, - 'parameter' => $this->mockParameter, - 'builderType' => '' - ] + $this->model = new ApiOrderManagement( + $this->mockSrvOrderManagement, + $this->mockDataConverter, + $this->mockDataObjectFactory, + $this->mockParameter, + $this->mockApi, + $this->mockBuilder, + ); + } + + /** + * @dataProvider addShippingInfoFormatProvider + * + * Test that addShippingInfo transforms tracking entries into the correct Klarna API payload format. + * Scenario: Various combinations of tracking input — standard, oversized fields, multiple entries. + * Verifies the shipping_info wrapper key is present and each entry contains correctly mapped + * and truncated tracking_number, shipping_method, and shipping_company fields. + */ + #[DataProvider('addShippingInfoFormatProvider')] + public function testAddShippingInfoFormatsData(array $input, array $expectedPayload): void + { + $this->mockSrvOrderManagement->expects($this->once()) + ->method('addShippingInfo') + ->with( + $this->anything(), + $this->anything(), + $expectedPayload + ); + + $this->model->addShippingInfo( + orderId: 'order-id', + captureId: 'capture-id', + shippingInfo: $input ); } + + /** + * Provides input tracking arrays and the corresponding expected Klarna API payloads. + */ + public static function addShippingInfoFormatProvider(): array + { + return [ + 'single entry maps all fields correctly' => [ + 'input' => [ + [ + 'carrier_code' => 'ups', + 'title' => 'UPS Ground', + 'number' => '1Z999AA10123456784', + ], + ], + 'expectedPayload' => [ + 'shipping_info' => [ + [ + 'tracking_number' => '1Z999AA10123456784', + 'shipping_method' => ApiOrderManagement::KLARNA_API_SHIPPING_METHOD_HOME, + 'shipping_company' => 'UPS Ground', + ], + ], + ], + ], + 'tracking number longer than 100 chars is truncated' => [ + 'input' => [ + [ + 'carrier_code' => 'ups', + 'title' => 'UPS', + 'number' => \str_repeat('A', 101)], + ], + 'expectedPayload' => [ + 'shipping_info' => [ + [ + 'tracking_number' => \str_repeat('A', 100), + 'shipping_method' => ApiOrderManagement::KLARNA_API_SHIPPING_METHOD_HOME, + 'shipping_company' => 'UPS', + ], + ], + ], + ], + 'shipping company longer than 100 chars is truncated' => [ + 'input' => [ + [ + 'carrier_code' => 'ups', + 'title' => \str_repeat('B', 101), + 'number' => 'TRACK123', + ], + ], + 'expectedPayload' => [ + 'shipping_info' => [ + [ + 'tracking_number' => 'TRACK123', + 'shipping_method' => ApiOrderManagement::KLARNA_API_SHIPPING_METHOD_HOME, + 'shipping_company' => \str_repeat('B', 100), + ], + ], + ], + ], + 'multiple entries produce one shipping_info item each' => [ + 'input' => [ + [ + 'carrier_code' => 'ups', + 'title' => 'UPS Ground', + 'number' => 'TRACK001', + ], + [ + 'carrier_code' => 'fedex', + 'title' => 'FedEx', + 'number' => 'TRACK002', + ], + ], + 'expectedPayload' => [ + 'shipping_info' => [ + [ + 'tracking_number' => 'TRACK001', + 'shipping_method' => ApiOrderManagement::KLARNA_API_SHIPPING_METHOD_HOME, + 'shipping_company' => 'UPS Ground', + ], + [ + 'tracking_number' => 'TRACK002', + 'shipping_method' => ApiOrderManagement::KLARNA_API_SHIPPING_METHOD_HOME, + 'shipping_company' => 'FedEx', + ], + ], + ], + ], + ]; + } }