diff --git a/app/code/Magento/QuoteGraphQl/Test/Unit/Model/Cart/ValidateAddressFromSchemaTest.php b/app/code/Magento/QuoteGraphQl/Test/Unit/Model/Cart/ValidateAddressFromSchemaTest.php new file mode 100644 index 000000000000..6ea36db739bb --- /dev/null +++ b/app/code/Magento/QuoteGraphQl/Test/Unit/Model/Cart/ValidateAddressFromSchemaTest.php @@ -0,0 +1,137 @@ +typeRegistry = $this->createMock(TypeRegistry::class); + $this->cartAddressInputType = $this->createMock(InputObjectType::class); + $this->typeRegistry->method('get') + ->with('CartAddressInput') + ->willReturn($this->cartAddressInputType); + $this->model = new ValidateAddressFromSchema($this->typeRegistry); + } + + /** + * Address missing a nullable field passes validation. + * + * Covers the regression from ACP2E-4223 where CartAddressInput.telephone was changed + * to String! (NonNull), forcing telephone even when the store configures it as Optional. + * After the fix telephone is String (nullable) and this test must pass. + */ + public function testAddressWithoutNullableFieldPassesValidation(): void + { + $firstnameField = $this->makeField('firstname', true); + $telephoneField = $this->makeField('telephone', false); + + $this->cartAddressInputType->method('getFields') + ->willReturn(['firstname' => $firstnameField, 'telephone' => $telephoneField]); + + // telephone absent entirely + $this->assertTrue($this->model->execute(['firstname' => 'John'])); + + // telephone key present but null — nullable, so still valid + $this->assertTrue($this->model->execute(['firstname' => 'John', 'telephone' => null])); + } + + /** + * Address with a NonNull field present and filled passes validation. + */ + public function testAddressWithAllNonNullFieldsPresentPassesValidation(): void + { + $firstnameField = $this->makeField('firstname', true); + $telephoneField = $this->makeField('telephone', false); + + $this->cartAddressInputType->method('getFields') + ->willReturn(['firstname' => $firstnameField, 'telephone' => $telephoneField]); + + $this->assertTrue($this->model->execute([ + 'firstname' => 'John', + 'telephone' => '+15005550006', + ])); + } + + /** + * Address with a NonNull field key present but set to null fails validation. + */ + public function testAddressWithNonNullFieldSetToNullFailsValidation(): void + { + $firstnameField = $this->makeField('firstname', true); + + $this->cartAddressInputType->method('getFields') + ->willReturn(['firstname' => $firstnameField]); + + // Key exists, value is null → invalid for NonNull field + $this->assertFalse($this->model->execute(['firstname' => null])); + } + + /** + * Address missing a NonNull field key entirely still passes (output validator, not input). + * + * ValidateAddressFromSchema is used on the output side to decide whether a saved address + * is complete enough to return. A key simply not being present is allowed; only an + * explicitly-null value for a NonNull field is rejected. + */ + public function testAddressWithMissingNonNullFieldKeyPassesValidation(): void + { + $firstnameField = $this->makeField('firstname', true); + + $this->cartAddressInputType->method('getFields') + ->willReturn(['firstname' => $firstnameField]); + + // Key does not exist at all → validator treats as acceptable (saved address may be partial) + $this->assertTrue($this->model->execute([])); + } + + /** + * Build a mock InputObjectField with a name and a nullable/NonNull type. + */ + private function makeField(string $name, bool $isNonNull): InputObjectField + { + $field = $this->getMockBuilder(InputObjectField::class) + ->disableOriginalConstructor() + ->onlyMethods(['getType']) + ->getMock(); + $field->name = $name; + + if ($isNonNull) { + $type = $this->getMockBuilder(NonNull::class) + ->disableOriginalConstructor() + ->getMock(); + } else { + $type = $this->createMock(StringType::class); + } + + $field->method('getType')->willReturn($type); + + return $field; + } +} diff --git a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls index 1ace0a4332f5..5e584c99f976 100644 --- a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls +++ b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls @@ -173,7 +173,7 @@ input CartAddressInput @doc(description: "Defines the billing or shipping addres region_id: Int @doc(description: "An integer that defines the state or province of the billing or shipping address.") postcode: String @doc(description: "The ZIP or postal code of the billing or shipping address.") country_code: String! @doc(description: "The country code and label for the billing or shipping address.") - telephone: String! @doc(description: "The telephone number for the billing or shipping address.") + telephone: String @doc(description: "The telephone number for the billing or shipping address.") vat_id: String @doc(description: "The VAT company number for billing or shipping address.") save_in_address_book: Boolean @doc(description: "Determines whether to save the address in the customer's address book. The default value is true.") fax: String @doc(description: "The customer's fax number.")