diff --git a/app/code/Magento/Quote/Model/QuoteAddressValidator.php b/app/code/Magento/Quote/Model/QuoteAddressValidator.php index 01d4632ec77c6..22f2f48850c48 100644 --- a/app/code/Magento/Quote/Model/QuoteAddressValidator.php +++ b/app/code/Magento/Quote/Model/QuoteAddressValidator.php @@ -122,7 +122,17 @@ public function validate(AddressInterface $addressData): bool */ public function validateForCart(CartInterface $cart, AddressInterface $address): void { - $this->doValidate($address, $cart->getCustomerIsGuest() ? null : (int) $cart->getCustomer()->getId()); + // If cart has a customer ID, use it regardless of the is_guest flag + // This handles cases where the flags are out of sync + $customerId = null; + if ($cart instanceof Quote) { + $customerId = $cart->getCustomerId(); + } elseif (!$cart->getCustomerIsGuest()) { + $customer = $cart->getCustomer(); + $customerId = $customer ? $customer->getId() : null; + } + + $this->doValidate($address, $customerId ? (int) $customerId : null); } /** diff --git a/app/code/Magento/Quote/Test/Unit/Model/QuoteAddressValidatorTest.php b/app/code/Magento/Quote/Test/Unit/Model/QuoteAddressValidatorTest.php new file mode 100644 index 0000000000000..10a400c34f51b --- /dev/null +++ b/app/code/Magento/Quote/Test/Unit/Model/QuoteAddressValidatorTest.php @@ -0,0 +1,239 @@ +addressRepositoryMock = $this->getMockBuilder(AddressRepositoryInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->customerRepositoryMock = $this->getMockBuilder(CustomerRepositoryInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->customerSessionMock = $this->getMockBuilder(Session::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->validator = new QuoteAddressValidator( + $this->addressRepositoryMock, + $this->customerRepositoryMock, + $this->customerSessionMock + ); + } + + /** + * Test that validation uses customer ID when available, regardless of is_guest flag + * + * This tests the fix for the issue where is_guest flag and customer_id are out of sync + */ + public function testValidateForCartWithCustomerIdIgnoresGuestFlag() + { + $customerId = 123; + $customerAddressId = 456; + + $cartMock = $this->getMockBuilder(Quote::class) + ->disableOriginalConstructor() + ->getMock(); + + $customerMock = $this->getMockBuilder(CustomerInterface::class) + ->getMock(); + + $addressMock = $this->getMockBuilder(AddressInterface::class) + ->getMock(); + + $customerAddressMock = $this->getMockBuilder(CustomerAddressInterface::class) + ->getMock(); + + // Set up cart to have a customer ID + $cartMock->expects($this->once()) + ->method('getCustomerId') + ->willReturn($customerId); + + // Set up address with customer address ID + $addressMock->expects($this->once()) + ->method('getCustomerAddressId') + ->willReturn($customerAddressId); + + // Mock customer repository to return customer with addresses + $customerMock->expects($this->once()) + ->method('getAddresses') + ->willReturn([$customerAddressMock]); + + $customerAddressMock->expects($this->once()) + ->method('getId') + ->willReturn($customerAddressId); + + $this->customerRepositoryMock->expects($this->once()) + ->method('getById') + ->with($customerId) + ->willReturn($customerMock); + + $this->addressRepositoryMock->expects($this->once()) + ->method('getById') + ->with($customerAddressId) + ->willReturn($customerAddressMock); + + // Execute validation - should not throw exception + $this->validator->validateForCart($cartMock, $addressMock); + } + + /** + * Test that validation correctly handles guest cart (no customer ID) + */ + public function testValidateForCartWithGuestCart() + { + $cartMock = $this->getMockBuilder(Quote::class) + ->disableOriginalConstructor() + ->getMock(); + + $addressMock = $this->getMockBuilder(AddressInterface::class) + ->getMock(); + + // Set up cart without customer ID (guest) + $cartMock->expects($this->once()) + ->method('getCustomerId') + ->willReturn(null); + + // Address should not have customer address ID for guest + $addressMock->expects($this->once()) + ->method('getCustomerAddressId') + ->willReturn(null); + + // Execute validation - should not throw exception + $this->validator->validateForCart($cartMock, $addressMock); + } + + /** + * Test that validation throws exception when guest cart has customer address ID + */ + public function testValidateForCartThrowsExceptionWhenGuestHasCustomerAddress() + { + $this->expectException(NoSuchEntityException::class); + $this->expectExceptionMessage('Invalid customer address id 456'); + + $customerAddressId = 456; + + $cartMock = $this->getMockBuilder(Quote::class) + ->disableOriginalConstructor() + ->getMock(); + + $addressMock = $this->getMockBuilder(AddressInterface::class) + ->getMock(); + + // Set up cart without customer ID (guest) + $cartMock->expects($this->once()) + ->method('getCustomerId') + ->willReturn(null); + + // Address has customer address ID even though cart is guest + $addressMock->expects($this->once()) + ->method('getCustomerAddressId') + ->willReturn($customerAddressId); + + // Execute validation - should throw exception + $this->validator->validateForCart($cartMock, $addressMock); + } + + /** + * Test that validation throws exception when customer address doesn't belong to customer + */ + public function testValidateForCartThrowsExceptionWhenAddressDoesNotBelongToCustomer() + { + $this->expectException(NoSuchEntityException::class); + $this->expectExceptionMessage('Invalid customer address id 456'); + + $customerId = 123; + $customerAddressId = 456; + $differentAddressId = 789; + + $cartMock = $this->getMockBuilder(Quote::class) + ->disableOriginalConstructor() + ->getMock(); + + $customerMock = $this->getMockBuilder(CustomerInterface::class) + ->getMock(); + + $addressMock = $this->getMockBuilder(AddressInterface::class) + ->getMock(); + + $customerAddressMock = $this->getMockBuilder(CustomerAddressInterface::class) + ->getMock(); + + // Set up cart with customer ID + $cartMock->expects($this->once()) + ->method('getCustomerId') + ->willReturn($customerId); + + // Address has customer address ID + $addressMock->expects($this->once()) + ->method('getCustomerAddressId') + ->willReturn($customerAddressId); + + // Mock customer with different address + $customerMock->expects($this->once()) + ->method('getAddresses') + ->willReturn([$customerAddressMock]); + + $customerAddressMock->expects($this->once()) + ->method('getId') + ->willReturn($differentAddressId); + + $this->customerRepositoryMock->expects($this->once()) + ->method('getById') + ->with($customerId) + ->willReturn($customerMock); + + $this->addressRepositoryMock->expects($this->once()) + ->method('getById') + ->with($customerAddressId) + ->willReturn($customerAddressMock); + + // Execute validation - should throw exception + $this->validator->validateForCart($cartMock, $addressMock); + } +}