Files
wp-graphql-woocommerce/tests/wpunit/VariationAttributeQueriesTest.php
Geoff TaylorandGitHub f0c7667aad fix: v1.0.1 bugfixes — session key, cart persistence, null attrs (#1012)
* fix: session secret key, cart session persistence, null variation attributes

- Use wp_salt() as fallback secret key instead of hardcoded 24-byte string
  to satisfy php-jwt v7's HS256 minimum key length requirement (Closes #1009)
- Call get_cart_from_session() after wc_load_cart() in
  initialize_session_and_cart() to prevent cart queries from clearing
  persisted session data (Closes #1010)
- Guard against null $attrs in variation_attributes_to_data_array()
  to prevent PHP warnings when variations have no attributes (Closes #1011)
- Return empty array instead of null for product attribute options
  when no terms exist

* chore: Linter compliances met
2026-04-01 13:17:30 -04:00

335 lines
10 KiB
PHP

<?php
class VariationAttributeQueriesTest extends \Tests\WPGraphQL\WooCommerce\TestCase\WooGraphQLTestCase {
public function expectedAttributes( $id ) {
$product = wc_get_product( $id );
$attributes = 'variable' === $product->get_type()
? $product->get_default_attributes()
: $product->get_attributes();
$expected = [];
foreach ( $attributes as $name => $value ) {
$term = \get_term_by( 'slug', $value, $name );
$expected_id = base64_encode( $id . '||' . $name . '||' . $value ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
if ( ! $term instanceof \WP_Term ) {
$expected[] = $this->expectedNode(
'nodes',
[
$this->expectedField( 'id', $expected_id ),
$this->expectedField( 'attributeId', 0 ),
$this->expectedField( 'name', $name ),
$this->expectedField( 'value', $value ),
]
);
} else {
$expected[] = $this->expectedNode(
'nodes',
[
$this->expectedField( 'id', $expected_id ),
$this->expectedField( 'attributeId', $term->term_id ),
$this->expectedField( 'name', $term->taxonomy ),
$this->expectedField( 'value', $term->name ),
]
);
}
}
return $expected;
}
public function expectedDefaultAttributes( $id ) {
$product = wc_get_product( $id );
$attributes = $product->get_attributes();
$expected = [];
foreach ( $attributes as $attribute ) {
$name = $attribute->get_name();
if ( $attribute->is_taxonomy() ) {
$attribute_values = wc_get_product_terms( $id, $attribute->get_name(), [ 'fields' => 'all' ] );
foreach ( $attribute_values as $attribute_value ) {
$expected[] = $this->expectedNode(
'nodes',
[
$this->expectedField( 'id', base64_encode( $id . '|' . $name . '|' . $attribute_value->name ) ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
$this->expectedField( 'attributeId', $attribute_value->term_id ),
$this->expectedField( 'name', $name ),
$this->expectedField( 'value', $attribute_value->name ),
]
);
}
} else {
$values = $attribute->get_options();
foreach ( $values as $attribute_value ) {
$expected[] = $this->expectedNode(
'nodes',
[
$this->expectedField( 'id', base64_encode( $id . '|' . $name . '|' . $attribute_value ) ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
$this->expectedField( 'attributeId', 0 ),
$this->expectedField( 'name', $name ),
$this->expectedField( 'value', $attribute_value ),
]
);
}
}
}
return $expected;
}
// tests
public function testProductVariationToVariationAttributeQuery() {
// Create a product and variation.
$product_ids = $this->factory->product_variation->createSome();
$variation_id = $product_ids['variations'][0];
// Create a query.
$query = '
query fromVariationQuery( $id: ID! ) {
productVariation( id: $id ) {
id
attributes {
nodes {
id
attributeId
name
value
}
}
}
}
';
/**
* Assertion One
*
* Test query and results
*/
$variables = [ 'id' => $this->toRelayId( 'post', $variation_id ) ];
$response = $this->graphql( compact( 'query', 'variables' ) );
$expected = [
$this->expectedField( 'productVariation.id', $this->toRelayId( 'post', $variation_id ) ),
$this->expectedObject( 'productVariation.attributes', $this->expectedAttributes( $variation_id ) ),
];
$this->assertQuerySuccessful( $response, $expected );
}
public function testVariableProductToVariationAttributeQuery() {
// Create a variable product w/ default attributes..
$product_ids = $this->factory->product_variation->createSome();
$product_id = $product_ids['product'];
// Create a query.
$query = '
query ( $id: ID! ) {
product( id: $id ) {
... on VariableProduct {
id
defaultAttributes {
nodes {
id
attributeId
name
value
}
}
}
}
}
';
/**
* Assertion One
*
* Test query and results
*/
$variables = [ 'id' => $this->toRelayId( 'post', $product_id ) ];
$response = $this->graphql( compact( 'query', 'variables' ) );
$expected = [
$this->expectedField( 'product.id', $this->toRelayId( 'post', $product_id ) ),
$this->expectedObject( 'product.defaultAttributes', $this->expectedAttributes( $product_id ) ),
];
$this->assertQuerySuccessful( $response, $expected );
}
public function testSimpleProductToVariationAttributeQuery() {
// Create a product w/ default attributes.
$attribute_data = [
$this->factory->product->createAttribute( 'size', [ 'small' ] ),
$this->factory->product->createAttribute( 'color', [ 'red' ] ),
[
'attribute_id' => 0,
'attribute_taxonomy' => 'logo',
'term_ids' => [ 'Yes' ],
],
];
$attributes = array_map(
static function ( $data, $index ) {
$attribute = new \WC_Product_Attribute();
$attribute->set_id( $data['attribute_id'] );
$attribute->set_name( $data['attribute_taxonomy'] );
$attribute->set_options( $data['term_ids'] );
$attribute->set_position( $index );
$attribute->set_visible( true );
$attribute->set_variation( false );
return $attribute;
},
$attribute_data,
array_keys( $attribute_data )
);
$product_id = $this->factory->product->createSimple(
[
'attributes' => $attributes,
'default_attributes' => [
'pa_size' => 'small',
'pa_color' => 'red',
'logo' => 'Yes',
],
]
);
// Create a query.
$query = '
query ( $id: ID! ) {
product( id: $id ) {
... on SimpleProduct {
id
defaultAttributes {
nodes {
id
attributeId
name
value
}
}
}
}
}
';
/**
* Assertion One
*
* Test query and results
*/
$variables = [ 'id' => $this->toRelayId( 'post', $product_id ) ];
$response = $this->graphql( compact( 'query', 'variables' ) );
$expected = [
$this->expectedField( 'product.id', $this->toRelayId( 'post', $product_id ) ),
$this->expectedObject( 'product.defaultAttributes', $this->expectedDefaultAttributes( $product_id ) ),
];
$this->assertQuerySuccessful( $response, $expected );
}
/**
* Test that VariationAttribute.label returns human-readable values.
*
* Global attributes should return the term name (e.g., "Small" not "small").
* Local attributes should return the attribute value (e.g., "Yes" not "logo").
*
* @see https://github.com/wp-graphql/wp-graphql-woocommerce/issues/965
*/
public function testVariationAttributeLabelReturnsHumanReadableValues() {
$product_ids = $this->factory->product_variation->createSome();
$variation_id = $product_ids['variations'][0];
$query = '
query ($id: ID!) {
productVariation(id: $id) {
attributes {
nodes {
name
label
value
}
}
}
}
';
$variables = [ 'id' => $this->toRelayId( 'post', $variation_id ) ];
$response = $this->graphql( compact( 'query', 'variables' ) );
// Build expected labels from the variation's attributes.
$product = wc_get_product( $variation_id );
$attributes = $product->get_attributes();
$expected = [];
foreach ( $attributes as $name => $value ) {
$term = get_term_by( 'slug', $value, $name );
if ( $term instanceof \WP_Term ) {
// Global attribute: label should be the human-readable attribute label.
$expected[] = $this->expectedNode(
'productVariation.attributes.nodes',
[
$this->expectedField( 'name', $term->taxonomy ),
$this->expectedField( 'label', wc_attribute_label( $term->taxonomy ) ),
$this->expectedField( 'value', $term->slug ),
]
);
} else {
// Local attribute: label should be the attribute name.
$expected[] = $this->expectedNode(
'productVariation.attributes.nodes',
[
$this->expectedField( 'name', $name ),
$this->expectedField( 'label', $name ),
$this->expectedField( 'value', $value ),
]
);
}
}
$this->assertQuerySuccessful( $response, $expected );
}
/**
* Test that querying variation attributes returns empty nodes when
* the variation has null attributes (e.g. corrupted or missing data).
*
* @see https://github.com/wp-graphql/wp-graphql-woocommerce/issues/1011
*/
public function testVariationWithNullAttributesReturnsEmptyNodes() {
// Create a variable product with no attributes, then create a bare variation.
$product_id = $this->factory->product->createVariable( [ 'attribute_data' => [] ] );
$variation_id = $this->factory->product_variation->create(
[
'parent_id' => $product_id,
'regular_price' => 10,
]
);
// Confirm WooCommerce returns empty attributes for this variation.
$variation = wc_get_product( $variation_id );
$this->assertEmpty( $variation->get_attributes(), 'Variation should have no attributes.' );
$query = '
query ($id: ID!) {
productVariation(id: $id) {
id
attributes {
nodes {
id
name
value
}
}
}
}
';
$variables = [ 'id' => $this->toRelayId( 'post', $variation_id ) ];
$response = $this->graphql( compact( 'query', 'variables' ) );
// The query should succeed without PHP warnings or errors,
// and attributes should be null (no populated attribute data).
$this->assertQuerySuccessful(
$response,
[
$this->expectedField( 'productVariation.id', $this->toRelayId( 'post', $variation_id ) ),
$this->expectedField( 'productVariation.attributes', static::IS_NULL ),
]
);
}
}