Files
wp-graphql-woocommerce/tests/wpunit/DownloadableItemQueriesTest.php
T
506d352292 fix: downloadsRemaining returns null for numeric string values (#1000)
* fix: downloadsRemaining returns null for valid numeric string values

Use is_numeric() + intval() instead of 'integer' === gettype() to handle
cases where WooCommerce stores downloads_remaining as a numeric string.
Mirrors WooCommerce's own approach in its download templates.

Co-authored-by: Nestor Vera <hacknug@users.noreply.github.com>
Closes #937

* fix: Match CI phpcov php-code-coverage version to Docker container

The Docker container uses php-code-coverage 9.2.x which serializes
coverage with v9 classes (e.g. Xdebug3Driver). phpcov v9 bundles
php-code-coverage v11 with different classes, causing
__PHP_Incomplete_Class errors. Pin to phpcov v8 + php-code-coverage v9
to match the Docker environment.

---------

Co-authored-by: Nestor Vera <hacknug@users.noreply.github.com>
2026-03-26 23:03:30 -04:00

454 lines
12 KiB
PHP

<?php
class DownloadableItemQueriesTest extends \Tests\WPGraphQL\WooCommerce\TestCase\WooGraphQLTestCase {
public function setUp(): void {
// before
parent::setUp();
update_option( 'woocommerce_downloads_grant_access_after_payment', 'yes' );
}
// tests
public function testOrderToDownloadableItemsQuery() {
$downloadable_product = $this->factory->product->createSimple(
[
'downloadable' => true,
'downloads' => [ $this->factory->product->createDownload() ],
]
);
$order_id = $this->factory->order->createNew(
[
'status' => 'completed',
'customer_id' => $this->customer,
],
[
'line_items' => [
[
'product' => $downloadable_product,
'qty' => 1,
],
],
]
);
// Force download permission updated.
wc_downloadable_product_permissions( $order_id, true );
$order = wc_get_order( $order_id );
$downloadable_items = $order->get_downloadable_items();
$query = '
query {
customer {
orders {
nodes {
downloadableItems(first: 1) {
nodes {
url
accessExpires
downloadId
downloadsRemaining
name
product {
databaseId
}
download {
downloadId
}
}
}
}
}
}
}
';
/**
* Assertion One
*
* Tests query results
*/
$this->loginAsCustomer();
$response = $this->graphql( compact( 'query' ) );
$expected = array_map(
function ( $item ) {
return $this->expectedNode(
'customer.orders.nodes',
[
$this->expectedNode(
'downloadableItems.nodes',
[
$this->expectedField( 'url', $item['download_url'] ),
$this->expectedField( 'accessExpires', $item['access_expires'] ),
$this->expectedField( 'downloadId', $item['download_id'] ),
$this->expectedField(
'downloadsRemaining',
isset( $item['downloads_remaining'] ) && is_numeric( $item['downloads_remaining'] )
? intval( $item['downloads_remaining'] )
: static::IS_NULL
),
$this->expectedField( 'name', $item['download_name'] ),
$this->expectedField( 'product.databaseId', $item['product_id'] ),
$this->expectedField( 'download.downloadId', $item['download_id'] ),
]
),
],
0
);
},
$downloadable_items
);
$this->assertQuerySuccessful( $response, $expected );
}
public function testOrderToDownloadableItemsQueryArgs() {
$valid_product = $this->factory->product->createSimple(
[
'downloadable' => true,
'downloads' => [ $this->factory->product->createDownload() ],
]
);
$downloadable_product = $this->factory->product->createSimple(
[
'download_expiry' => 5,
'download_limit' => 3,
'downloadable' => true,
'downloads' => [ $this->factory->product->createDownload() ],
]
);
$downloaded_product = $this->factory->product->createSimple(
[
'download_limit' => 0,
'downloadable' => true,
'downloads' => [ $this->factory->product->createDownload() ],
]
);
$order_id = $this->factory->order->createNew(
[
'status' => 'completed',
'customer_id' => $this->customer,
],
[
'line_items' => [
[
'product' => $valid_product,
'qty' => 1,
],
[
'product' => $downloadable_product,
'qty' => 1,
],
[
'product' => $downloaded_product,
'qty' => 1,
],
],
]
);
// Force download permission updated.
wc_downloadable_product_permissions( $order_id, true );
$query = '
query($active: Boolean, $hasDownloadsRemaining: Boolean) {
customer {
orders {
nodes {
downloadableItems(where: { active: $active, hasDownloadsRemaining: $hasDownloadsRemaining }) {
nodes {
product {
databaseId
}
}
}
}
}
}
}
';
/**
* Assertion One
*
* Tests "active" whereArg
*/
$this->loginAsCustomer();
$variables = [ 'active' => true ];
$response = $this->graphql( compact( 'query', 'variables' ) );
$expected = [
$this->expectedNode(
'customer.orders.nodes',
[
$this->expectedNode(
'downloadableItems.nodes',
[ $this->expectedField( 'product.databaseId', $valid_product ) ]
),
$this->expectedNode(
'downloadableItems.nodes',
[ $this->expectedField( 'product.databaseId', $downloadable_product ) ]
),
$this->not()->expectedNode(
'downloadableItems.nodes',
[ $this->expectedField( 'product.databaseId', $downloaded_product ) ]
),
],
0
),
];
$this->assertQuerySuccessful( $response, $expected );
/**
* Assertion Two
*
* Tests "active" whereArg reversed
*/
$variables = [ 'active' => false ];
$response = $this->graphql( compact( 'query', 'variables' ) );
$expected = [
$this->expectedNode(
'customer.orders.nodes',
[
$this->expectedNode(
'downloadableItems.nodes',
[ $this->expectedField( 'product.databaseId', $downloaded_product ) ]
),
$this->not()->expectedNode(
'downloadableItems.nodes',
[ $this->expectedField( 'product.databaseId', $valid_product ) ]
),
$this->not()->expectedNode(
'downloadableItems.nodes',
[ $this->expectedField( 'product.databaseId', $downloadable_product ) ]
),
],
0
),
];
$this->assertQuerySuccessful( $response, $expected );
/**
* Assertion Three
*
* Tests "hasDownloadsRemaining" whereArg
*/
$variables = [ 'hasDownloadsRemaining' => true ];
$response = $this->graphql( compact( 'query', 'variables' ) );
$expected = [
$this->expectedNode(
'customer.orders.nodes',
[
$this->not()->expectedNode(
'downloadableItems.nodes',
[ $this->expectedField( 'product.databaseId', $downloaded_product ) ]
),
$this->expectedNode(
'downloadableItems.nodes',
[ $this->expectedField( 'product.databaseId', $valid_product ) ]
),
$this->expectedNode(
'downloadableItems.nodes',
[ $this->expectedField( 'product.databaseId', $downloadable_product ) ]
),
],
0
),
];
$this->assertQuerySuccessful( $response, $expected );
/**
* Assertion Four
*
* Tests "hasDownloadsRemaining" whereArg reversed
*/
$variables = [ 'hasDownloadsRemaining' => false ];
$response = $this->graphql( compact( 'query', 'variables' ) );
$expected = [
$this->expectedNode(
'customer.orders.nodes',
[
$this->expectedNode(
'downloadableItems.nodes',
[ $this->expectedField( 'product.databaseId', $downloaded_product ) ]
),
$this->not()->expectedNode(
'downloadableItems.nodes',
[ $this->expectedField( 'product.databaseId', $valid_product ) ]
),
$this->not()->expectedNode(
'downloadableItems.nodes',
[ $this->expectedField( 'product.databaseId', $downloadable_product ) ]
),
],
0
),
];
$this->assertQuerySuccessful( $response, $expected );
}
public function testCustomerToDownloadableItemsQuery() {
$downloadable_product = $this->factory->product->createSimple(
[
'downloadable' => true,
'downloads' => [ $this->factory->product->createDownload() ],
]
);
$order_id = $this->factory->order->createNew(
[
'status' => 'completed',
'customer_id' => $this->customer,
],
[
'line_items' => [
[
'product' => $downloadable_product,
'qty' => 1,
],
],
]
);
// Force download permission updated.
wc_downloadable_product_permissions( $order_id, true );
$downloadable_items = \wc_get_customer_available_downloads( $this->customer );
$query = '
query {
customer {
downloadableItems(first: 1) {
nodes {
url
accessExpires
downloadId
downloadsRemaining
name
product {
databaseId
}
download {
downloadId
}
}
}
}
}
';
/**
* Assertion One
*
* Tests query results
*/
$this->loginAsCustomer();
$response = $this->graphql( compact( 'query' ) );
$expected = array_map(
function ( $item ) {
return $this->expectedNode(
'customer.downloadableItems.nodes',
[
$this->expectedField( 'url', $item['download_url'] ),
$this->expectedField(
'accessExpires',
! empty( $item['access_expires'] ) ? $item['access_expires'] : static::IS_NULL
),
$this->expectedField( 'downloadId', $item['download_id'] ),
$this->expectedField(
'downloadsRemaining',
isset( $item['downloads_remaining'] ) && is_numeric( $item['downloads_remaining'] )
? intval( $item['downloads_remaining'] )
: static::IS_NULL
),
$this->expectedField( 'name', $item['download_name'] ),
$this->expectedField( 'product.databaseId', $item['product_id'] ),
$this->expectedField( 'download.downloadId', $item['download_id'] ),
],
0
);
},
$downloadable_items
);
$this->assertQuerySuccessful( $response, $expected );
}
public function testDownloadsRemainingHandlesNumericString() {
$downloadable_product = $this->factory->product->createSimple(
[
'download_limit' => 5,
'downloadable' => true,
'downloads' => [ $this->factory->product->createDownload() ],
]
);
$order_id = $this->factory->order->createNew(
[
'status' => 'completed',
'customer_id' => $this->customer,
],
[
'line_items' => [
[
'product' => $downloadable_product,
'qty' => 1,
],
],
]
);
wc_downloadable_product_permissions( $order_id, true );
// Force downloads_remaining to a numeric string as WooCommerce sometimes stores it.
global $wpdb;
$wpdb->update(
"{$wpdb->prefix}woocommerce_downloadable_product_permissions",
[ 'downloads_remaining' => '3' ],
[
'order_id' => $order_id,
'product_id' => $downloadable_product,
]
);
$this->loginAsCustomer();
$query = '
query {
customer {
orders {
nodes {
downloadableItems(first: 1) {
nodes {
downloadsRemaining
}
}
}
}
}
}
';
$response = $this->graphql( compact( 'query' ) );
$expected = [
$this->expectedNode(
'customer.orders.nodes',
[
$this->expectedNode(
'downloadableItems.nodes',
[
$this->expectedField( 'downloadsRemaining', 3 ),
]
),
],
0
),
];
$this->assertQuerySuccessful( $response, $expected );
}
}