Merge branch 'trunk' of github.com:woocommerce/woocommerce into update/31217

This commit is contained in:
Greg 2021-11-16 13:04:13 -07:00
commit b76b9f3d9d
20 changed files with 1753 additions and 5057 deletions

View File

@ -31,7 +31,7 @@ cd "$SCRIPTPATH/$(dirname "$REALPATH")/.."
# Run scripts
case $1 in
'test')
jest --group=$2
jest --group=$2 --runInBand
TESTRESULT=$?
;;
'make:collection')

View File

@ -0,0 +1,292 @@
/**
* Internal dependencies
*/
const {
postRequest,
deleteRequest,
getRequest,
putRequest,
} = require( '../utils/request' );
const productsTestSetup = require( './product-list' );
const { ordersApi } = require( '../endpoints/orders' );
const createCustomer = ( data ) => postRequest( 'customers', data );
const deleteCustomer = ( id ) => deleteRequest( `customers/${ id }`, true );
const createSampleData = async () => {
const testProductData = await productsTestSetup.createSampleData();
const orderedProducts = {
pocketHoodie: testProductData.simpleProducts.find(
( p ) => p.name === 'Hoodie with Pocket'
),
sunglasses: testProductData.simpleProducts.find(
( p ) => p.name === 'Sunglasses'
),
beanie: testProductData.simpleProducts.find(
( p ) => p.name === 'Beanie'
),
blueVneck: testProductData.variableProducts.vneckVariations.find(
( p ) => p.sku === 'woo-vneck-tee-blue'
),
pennant: testProductData.externalProducts[ 0 ],
};
const johnAddress = {
first_name: 'John',
last_name: 'Doe',
company: 'Automattic',
country: 'US',
address_1: '60 29th Street',
address_2: '#343',
city: 'San Francisco',
state: 'CA',
postcode: '94110',
phone: '123456789',
};
const tinaAddress = {
first_name: 'Tina',
last_name: 'Clark',
company: 'Automattic',
country: 'US',
address_1: 'Oxford Ave',
address_2: '',
city: 'Buffalo',
state: 'NY',
postcode: '14201',
phone: '123456789',
};
const guestShippingAddress = {
first_name: 'Ano',
last_name: 'Nymous',
company: '',
country: 'US',
address_1: '0 Incognito St',
address_2: '',
city: 'Erie',
state: 'PA',
postcode: '16515',
phone: '123456789',
};
const guestBillingAddress = {
first_name: 'Ben',
last_name: 'Efactor',
company: '',
country: 'US',
address_1: '200 W University Avenue',
address_2: '',
city: 'Gainesville',
state: 'FL',
postcode: '32601',
phone: '123456789',
email: 'ben.efactor@email.net',
};
const { body: john } = await createCustomer( {
first_name: 'John',
last_name: 'Doe',
username: 'john.doe',
email: 'john.doe@example.com',
billing: {
...johnAddress,
email: 'john.doe@example.com',
},
shipping: johnAddress,
} );
const { body: tina } = await createCustomer( {
first_name: 'Tina',
last_name: 'Clark',
username: 'tina.clark',
email: 'tina.clark@example.com',
billing: {
...tinaAddress,
email: 'tina.clark@example.com',
},
shipping: tinaAddress,
} );
const orderBaseData = {
payment_method: 'cod',
payment_method_title: 'Cash on Delivery',
status: 'processing',
set_paid: false,
currency: 'USD',
customer_id: 0,
};
const orders = [];
// Have "John" order all products.
Object.values( orderedProducts ).forEach( async ( product ) => {
const { body: order } = await ordersApi.create.order( {
...orderBaseData,
customer_id: john.id,
billing: {
...johnAddress,
email: 'john.doe@example.com',
},
shipping: johnAddress,
line_items: [
{
product_id: product.id,
quantity: 1,
},
],
} );
orders.push( order );
} );
// Have "Tina" order some sunglasses and make a child order.
// This somewhat resembles a subscription renewal, but we're just testing the `parent` field.
const { body: order2 } = await ordersApi.create.order( {
...orderBaseData,
status: 'completed',
set_paid: true,
customer_id: tina.id,
billing: {
...tinaAddress,
email: 'tina.clark@example.com',
},
shipping: tinaAddress,
line_items: [
{
product_id: orderedProducts.sunglasses.id,
quantity: 1,
},
],
} );
orders.push( order2 );
const { body: order3 } = await ordersApi.create.order( {
...orderBaseData,
parent_id: order2.id,
customer_id: tina.id,
billing: {
...tinaAddress,
email: 'tina.clark@example.com',
},
shipping: tinaAddress,
line_items: [
{
product_id: orderedProducts.sunglasses.id,
quantity: 1,
},
],
} );
orders.push( order3 );
// Guest order.
const { body: guestOrder } = await ordersApi.create.order( {
...orderBaseData,
billing: guestBillingAddress,
shipping: guestShippingAddress,
line_items: [
{
product_id: orderedProducts.pennant.id,
quantity: 2,
},
{
product_id: orderedProducts.beanie.id,
quantity: 1,
},
],
} );
// Create an order with all possible numerical fields (taxes, fees, refunds, etc).
const { body: taxSetting } = await getRequest(
'settings/general/woocommerce_calc_taxes'
);
await putRequest( 'settings/general/woocommerce_calc_taxes', {
value: 'yes',
} );
const { body: taxRate } = await postRequest( 'taxes', {
country: '*',
state: '*',
postcode: '*',
city: '*',
rate: '5.5000',
name: 'Tax',
rate: '5.5',
shipping: true,
} );
const { body: coupon } = await postRequest( 'coupons', {
code: 'save5',
amount: '5',
} );
const { body: order4 } = await ordersApi.create.order( {
...orderBaseData,
line_items: [
{
product_id: orderedProducts.blueVneck.id,
quantity: 1,
},
],
coupon_lines: [ { code: 'save5' } ],
shipping_lines: [
{
method_id: 'flat_rate',
total: '5.00',
},
],
fee_lines: [
{
total: '1.00',
name: 'Test Fee',
},
],
} );
await postRequest( `orders/${ order4.id }/refunds`, {
api_refund: false, // Prevent an actual refund request (fails with CoD),
line_items: [
{
id: order4.line_items[ 0 ].id,
quantity: 1,
refund_total: order4.line_items[ 0 ].total,
refund_tax: [
{
id: order4.line_items[ 0 ].taxes[ 0 ].id,
refund_total: order4.line_items[ 0 ].total_tax,
},
],
},
],
} );
orders.push( order4 );
return {
customers: { john, tina },
orders,
precisionOrder: order4,
hierarchicalOrders: {
parent: order2,
child: order3,
},
guestOrder,
testProductData,
};
};
const deleteSampleData = async ( sampleData ) => {
await productsTestSetup.deleteSampleData( sampleData.testProductData );
sampleData.orders
.concat( [ sampleData.guestOrder ] )
.forEach( async ( { id } ) => {
await ordersApi.delete.order( id, true );
} );
Object.values( sampleData.customers ).forEach( async ( { id } ) => {
await deleteCustomer( id );
} );
};
module.exports = {
createSampleData,
deleteSampleData,
};

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,3 @@
/**
* A basic refund.
*
@ -7,7 +6,7 @@
* https://woocommerce.github.io/woocommerce-rest-api-docs/#order-refund-properties
*
*/
const refund = {
const refund = {
api_refund: false,
amount: '1.00',
reason: 'Late delivery refund.',

View File

@ -1,8 +1,13 @@
/**
* Internal dependencies
*/
const { getRequest, postRequest, putRequest, deleteRequest } = require('../utils/request');
const { getOrderExample, shared } = require('../data');
const {
getRequest,
postRequest,
putRequest,
deleteRequest,
} = require( '../utils/request' );
const { getOrderExample, shared } = require( '../data' );
/**
* WooCommerce Orders endpoints.
@ -24,14 +29,16 @@ const ordersApi = {
method: 'GET',
path: 'orders/<id>',
responseCode: 200,
order: async ( orderId ) => getRequest( `orders/${orderId}` ),
order: async ( orderId, ordersQuery = {} ) =>
getRequest( `orders/${ orderId }`, ordersQuery ),
},
listAll: {
name: 'List all orders',
method: 'GET',
path: 'orders',
responseCode: 200,
orders: async () => getRequest( 'orders' ),
orders: async ( ordersQuery = {} ) =>
getRequest( 'orders', ordersQuery ),
},
update: {
name: 'Update an order',
@ -39,7 +46,8 @@ const ordersApi = {
path: 'orders/<id>',
responseCode: 200,
payload: getOrderExample(),
order: async ( orderId, orderDetails ) => putRequest( `orders/${orderId}`, orderDetails ),
order: async ( orderId, orderDetails ) =>
putRequest( `orders/${ orderId }`, orderDetails ),
},
delete: {
name: 'Delete an order',
@ -47,9 +55,10 @@ const ordersApi = {
path: 'orders/<id>',
responseCode: 200,
payload: {
force: false
force: false,
},
order: async ( orderId, deletePermanently ) => deleteRequest( `orders/${orderId}`, deletePermanently ),
order: async ( orderId, deletePermanently ) =>
deleteRequest( `orders/${ orderId }`, deletePermanently ),
},
batch: {
name: 'Batch update orders',
@ -57,7 +66,8 @@ const ordersApi = {
path: 'orders/batch',
responseCode: 200,
payload: shared.getBatchPayloadExample( getOrderExample() ),
orders: async ( batchUpdatePayload ) => postRequest( `orders/batch`, batchUpdatePayload ),
orders: async ( batchUpdatePayload ) =>
postRequest( `orders/batch`, batchUpdatePayload ),
},
};

View File

@ -1,8 +1,7 @@
/**
* Internal dependencies
*/
const {
const {
getRequest,
postRequest,
deleteRequest,

View File

@ -1,10 +1,11 @@
const { ordersApi } = require('../../endpoints/orders');
const { order } = require('../../data');
const { ordersApi } = require( '../../endpoints/orders' );
const { order } = require( '../../data' );
const { createSampleData, deleteSampleData } = require( '../../data/orders' );
/**
* Billing properties to update.
*/
const updatedCustomerBilling = {
const updatedCustomerBilling = {
first_name: 'Jane',
last_name: 'Doe',
company: 'Automattic',
@ -41,43 +42,523 @@ const updatedCustomerShipping = {
* @group orders
*
*/
describe('Orders API tests', () => {
let orderId;
describe( 'Orders API tests', () => {
let orderId, sampleData;
it('can create an order', async () => {
beforeAll( async () => {
sampleData = await createSampleData();
}, 100000 );
afterAll( async () => {
await deleteSampleData( sampleData );
}, 10000 );
it( 'can create an order', async () => {
const response = await ordersApi.create.order( order );
expect( response.status ).toEqual( ordersApi.create.responseCode );
expect( response.body.id ).toBeDefined();
orderId = response.body.id;
// Validate the data type and verify the order is in a pending state
expect( typeof response.body.status ).toBe('string');
expect( response.body.status ).toEqual('pending');
});
expect( typeof response.body.status ).toBe( 'string' );
expect( response.body.status ).toEqual( 'pending' );
} );
it('can retrieve an order', async () => {
it( 'can retrieve an order', async () => {
const response = await ordersApi.retrieve.order( orderId );
expect( response.status ).toEqual( ordersApi.retrieve.responseCode );
expect( response.body.id ).toEqual( orderId );
});
} );
it('can add shipping and billing contacts to an order', async () => {
it( 'can add shipping and billing contacts to an order', async () => {
// Update the billing and shipping fields on the order
order.billing = updatedCustomerBilling;
order.shipping = updatedCustomerShipping;
const response = await ordersApi.update.order( orderId, order );
expect( response.status).toEqual( ordersApi.update.responseCode );
expect( response.status ).toEqual( ordersApi.update.responseCode );
expect( response.body.billing ).toEqual( updatedCustomerBilling );
expect( response.body.shipping ).toEqual( updatedCustomerShipping );
});
} );
it('can permanently delete an order', async () => {
it( 'can permanently delete an order', async () => {
const response = await ordersApi.delete.order( orderId, true );
expect( response.status ).toEqual( ordersApi.delete.responseCode );
const getOrderResponse = await ordersApi.retrieve.order( orderId );
expect( getOrderResponse.status ).toEqual( 404 );
});
});
} );
describe( 'List all orders', () => {
const ORDERS_COUNT = 10;
it( 'pagination', async () => {
const pageSize = 4;
const page1 = await ordersApi.listAll.orders( {
per_page: pageSize,
} );
const page2 = await ordersApi.listAll.orders( {
per_page: pageSize,
page: 2,
} );
expect( page1.statusCode ).toEqual( 200 );
expect( page2.statusCode ).toEqual( 200 );
// Verify total page count.
expect( page1.headers[ 'x-wp-total' ] ).toEqual(
ORDERS_COUNT.toString()
);
expect( page1.headers[ 'x-wp-totalpages' ] ).toEqual( '3' );
// Verify we get pageSize'd arrays.
expect( Array.isArray( page1.body ) ).toBe( true );
expect( Array.isArray( page2.body ) ).toBe( true );
expect( page1.body ).toHaveLength( pageSize );
expect( page2.body ).toHaveLength( pageSize );
// Ensure all of the order IDs are unique (no page overlap).
const allOrderIds = page1.body
.concat( page2.body )
.reduce( ( acc, { id } ) => {
acc[ id ] = 1;
return acc;
}, {} );
expect( Object.keys( allOrderIds ) ).toHaveLength( pageSize * 2 );
// Verify that offset takes precedent over page number.
const page2Offset = await ordersApi.listAll.orders( {
per_page: pageSize,
page: 2,
offset: pageSize + 1,
} );
// The offset pushes the result set 1 order past the start of page 2.
expect( page2Offset.body ).toEqual(
expect.not.arrayContaining( [
expect.objectContaining( { id: page2.body[ 0 ].id } ),
] )
);
expect( page2Offset.body[ 0 ].id ).toEqual( page2.body[ 1 ].id );
// Verify the last page only has 1 order as we expect.
const lastPage = await ordersApi.listAll.orders( {
per_page: pageSize,
page: 3,
} );
expect( Array.isArray( lastPage.body ) ).toBe( true );
expect( lastPage.body ).toHaveLength( 2 );
// Verify a page outside the total page count is empty.
const page6 = await ordersApi.listAll.orders( {
per_page: pageSize,
page: 6,
} );
expect( Array.isArray( page6.body ) ).toBe( true );
expect( page6.body ).toHaveLength( 0 );
} );
it( 'inclusion / exclusion', async () => {
const allOrders = await ordersApi.listAll.orders( {
per_page: 10,
} );
expect( allOrders.statusCode ).toEqual( 200 );
const allOrdersIds = allOrders.body.map( ( order ) => order.id );
expect( allOrdersIds ).toHaveLength( ORDERS_COUNT );
const ordersToFilter = [
allOrdersIds[ 0 ],
allOrdersIds[ 2 ],
allOrdersIds[ 4 ],
allOrdersIds[ 7 ],
];
const included = await ordersApi.listAll.orders( {
per_page: 20,
include: ordersToFilter.join( ',' ),
} );
expect( included.statusCode ).toEqual( 200 );
expect( included.body ).toHaveLength( ordersToFilter.length );
expect( included.body ).toEqual(
expect.arrayContaining(
ordersToFilter.map( ( id ) =>
expect.objectContaining( { id } )
)
)
);
const excluded = await ordersApi.listAll.orders( {
per_page: 20,
exclude: ordersToFilter.join( ',' ),
} );
expect( excluded.statusCode ).toEqual( 200 );
expect( excluded.body ).toHaveLength(
ORDERS_COUNT - ordersToFilter.length
);
expect( excluded.body ).toEqual(
expect.not.arrayContaining(
ordersToFilter.map( ( id ) =>
expect.objectContaining( { id } )
)
)
);
} );
it( 'parent', async () => {
const result1 = await ordersApi.listAll.orders( {
parent: sampleData.hierarchicalOrders.parent.id,
} );
expect( result1.statusCode ).toEqual( 200 );
expect( result1.body ).toHaveLength( 1 );
expect( result1.body[ 0 ].id ).toBe(
sampleData.hierarchicalOrders.child.id
);
const result2 = await ordersApi.listAll.orders( {
parent_exclude: sampleData.hierarchicalOrders.parent.id,
} );
expect( result2.statusCode ).toEqual( 200 );
expect( result2.body ).toEqual(
expect.not.arrayContaining( [
expect.objectContaining( {
id: sampleData.hierarchicalOrders.child.id,
} ),
] )
);
} );
it( 'status', async () => {
const result1 = await ordersApi.listAll.orders( {
status: 'completed',
} );
expect( result1.statusCode ).toEqual( 200 );
expect( result1.body ).toHaveLength( 2 );
expect( result1.body ).toEqual(
expect.arrayContaining( [
expect.objectContaining( {
status: 'completed',
customer_id: 0,
line_items: expect.arrayContaining( [
expect.objectContaining( {
name: 'Single',
quantity: 2,
} ),
expect.objectContaining( {
name: 'Beanie with Logo',
quantity: 3,
} ),
expect.objectContaining( {
name: 'T-Shirt',
quantity: 1,
} ),
] ),
} ),
expect.objectContaining( {
status: 'completed',
customer_id: sampleData.customers.tina.id,
line_items: expect.arrayContaining( [
expect.objectContaining( {
name: 'Sunglasses',
quantity: 1,
} ),
] ),
} ),
] )
);
const result2 = await ordersApi.listAll.orders( {
status: 'processing',
} );
expect( result2.statusCode ).toEqual( 200 );
expect( result2.body ).toHaveLength( 8 );
expect( result2.body ).toEqual(
expect.not.arrayContaining(
result1.body.map( ( { id } ) =>
expect.objectContaining( { id } )
)
)
);
} );
it( 'customer', async () => {
const result1 = await ordersApi.listAll.orders( {
customer: sampleData.customers.john.id,
} );
expect( result1.statusCode ).toEqual( 200 );
expect( result1.body ).toHaveLength( 5 );
result1.body.forEach( ( order ) =>
expect( order ).toEqual(
expect.objectContaining( {
customer_id: sampleData.customers.john.id,
} )
)
);
const result2 = await ordersApi.listAll.orders( {
customer: 0,
} );
expect( result2.statusCode ).toEqual( 200 );
expect( result2.body ).toHaveLength( 3 );
result2.body.forEach( ( order ) =>
expect( order ).toEqual(
expect.objectContaining( {
customer_id: 0,
} )
)
);
} );
it( 'product', async () => {
const beanie = sampleData.testProductData.simpleProducts.find(
( p ) => p.name === 'Beanie'
);
const result1 = await ordersApi.listAll.orders( {
product: beanie.id,
} );
expect( result1.statusCode ).toEqual( 200 );
expect( result1.body ).toHaveLength( 2 );
result1.body.forEach( ( order ) =>
expect( order ).toEqual(
expect.objectContaining( {
line_items: expect.arrayContaining( [
expect.objectContaining( {
name: 'Beanie',
} ),
] ),
} )
)
);
} );
// NOTE: This does not verify the `taxes` array nested in line items.
// While the precision parameter doesn't affect those values, after some
// discussion it seems `dp` may not be supported in v4 of the API.
it( 'dp (precision)', async () => {
const expectPrecisionToMatch = ( value, dp ) => {
expect( value ).toEqual(
Number.parseFloat( value ).toFixed( dp )
);
};
const verifyOrderPrecision = ( order, dp ) => {
expectPrecisionToMatch( order[ 'discount_total' ], dp );
expectPrecisionToMatch( order[ 'discount_tax' ], dp );
expectPrecisionToMatch( order[ 'shipping_total' ], dp );
expectPrecisionToMatch( order[ 'shipping_tax' ], dp );
expectPrecisionToMatch( order[ 'cart_tax' ], dp );
expectPrecisionToMatch( order[ 'total' ], dp );
expectPrecisionToMatch( order[ 'total_tax' ], dp );
order[ 'line_items' ].forEach( ( lineItem ) => {
expectPrecisionToMatch( lineItem[ 'total' ], dp );
expectPrecisionToMatch( lineItem[ 'total_tax' ], dp );
} );
order[ 'tax_lines' ].forEach( ( taxLine ) => {
expectPrecisionToMatch( taxLine[ 'tax_total' ], dp );
expectPrecisionToMatch(
taxLine[ 'shipping_tax_total' ],
dp
);
} );
order[ 'shipping_lines' ].forEach( ( shippingLine ) => {
expectPrecisionToMatch( shippingLine[ 'total' ], dp );
expectPrecisionToMatch( shippingLine[ 'total_tax' ], dp );
} );
order[ 'fee_lines' ].forEach( ( feeLine ) => {
expectPrecisionToMatch( feeLine[ 'total' ], dp );
expectPrecisionToMatch( feeLine[ 'total_tax' ], dp );
} );
order[ 'refunds' ].forEach( ( refund ) => {
expectPrecisionToMatch( refund[ 'total' ], dp );
} );
};
const result1 = await ordersApi.retrieve.order(
sampleData.precisionOrder.id,
{
dp: 1,
}
);
expect( result1.statusCode ).toEqual( 200 );
verifyOrderPrecision( result1.body, 1 );
const result2 = await ordersApi.retrieve.order(
sampleData.precisionOrder.id,
{
dp: 3,
}
);
expect( result2.statusCode ).toEqual( 200 );
verifyOrderPrecision( result2.body, 3 );
const result3 = await ordersApi.retrieve.order(
sampleData.precisionOrder.id
);
expect( result3.statusCode ).toEqual( 200 );
verifyOrderPrecision( result3.body, 2 ); // The default value for 'dp' is 2.
} );
it( 'search', async () => {
// By default, 'search' looks in:
// - _billing_address_index
// - _shipping_address_index
// - _billing_last_name
// - _billing_email
// - order_item_name
// Test billing email.
const result1 = await ordersApi.listAll.orders( {
search: 'example.com',
} );
expect( result1.statusCode ).toEqual( 200 );
expect( result1.body ).toHaveLength( 7 );
result1.body.forEach( ( order ) =>
expect( order.billing.email ).toContain( 'example.com' )
);
// Test billing address.
const result2 = await ordersApi.listAll.orders( {
search: 'gainesville', // Intentionally lowercase.
} );
expect( result2.statusCode ).toEqual( 200 );
expect( result2.body ).toHaveLength( 1 );
expect( result2.body[ 0 ].id ).toEqual( sampleData.guestOrder.id );
// Test shipping address.
const result3 = await ordersApi.listAll.orders( {
search: 'Incognito',
} );
expect( result3.statusCode ).toEqual( 200 );
expect( result3.body ).toHaveLength( 1 );
expect( result3.body[ 0 ].id ).toEqual( sampleData.guestOrder.id );
// Test billing last name.
const result4 = await ordersApi.listAll.orders( {
search: 'Doe',
} );
expect( result4.statusCode ).toEqual( 200 );
expect( result4.body ).toHaveLength( 5 );
result4.body.forEach( ( order ) =>
expect( order.billing.last_name ).toEqual( 'Doe' )
);
// Test order item name.
const result5 = await ordersApi.listAll.orders( {
search: 'Pennant',
} );
expect( result5.statusCode ).toEqual( 200 );
expect( result5.body ).toHaveLength( 2 );
result5.body.forEach( ( order ) =>
expect( order ).toEqual(
expect.objectContaining( {
line_items: expect.arrayContaining( [
expect.objectContaining( {
name: 'WordPress Pennant',
} ),
] ),
} )
)
);
} );
describe( 'orderby', () => {
// The orders endpoint `orderby` parameter uses WP_Query, so our tests won't
// include slug and title, since they are programmatically generated.
it( 'default', async () => {
// Default = date desc.
const result = await ordersApi.listAll.orders();
expect( result.statusCode ).toEqual( 200 );
// Verify all dates are in descending order.
let lastDate = Date.now();
result.body.forEach( ( { date_created } ) => {
const created = Date.parse( date_created + '.000Z' );
expect( lastDate ).toBeGreaterThanOrEqual( created );
lastDate = created;
} );
} );
it( 'date', async () => {
const result = await ordersApi.listAll.orders( {
order: 'asc',
orderby: 'date',
} );
expect( result.statusCode ).toEqual( 200 );
// Verify all dates are in ascending order.
let lastDate = 0;
result.body.forEach( ( { date_created } ) => {
const created = Date.parse( date_created + '.000Z' );
expect( created ).toBeGreaterThanOrEqual( lastDate );
lastDate = created;
} );
} );
it( 'id', async () => {
const result1 = await ordersApi.listAll.orders( {
order: 'asc',
orderby: 'id',
} );
expect( result1.statusCode ).toEqual( 200 );
// Verify all results are in ascending order.
let lastId = 0;
result1.body.forEach( ( { id } ) => {
expect( id ).toBeGreaterThan( lastId );
lastId = id;
} );
const result2 = await ordersApi.listAll.orders( {
order: 'desc',
orderby: 'id',
} );
expect( result2.statusCode ).toEqual( 200 );
// Verify all results are in descending order.
lastId = Number.MAX_SAFE_INTEGER;
result2.body.forEach( ( { id } ) => {
expect( lastId ).toBeGreaterThan( id );
lastId = id;
} );
} );
it( 'include', async () => {
const includeIds = [
sampleData.precisionOrder.id,
sampleData.hierarchicalOrders.parent.id,
sampleData.guestOrder.id,
];
const result1 = await ordersApi.listAll.orders( {
order: 'asc',
orderby: 'include',
include: includeIds.join( ',' ),
} );
expect( result1.statusCode ).toEqual( 200 );
expect( result1.body ).toHaveLength( includeIds.length );
// Verify all results are in proper order.
result1.body.forEach( ( { id }, idx ) => {
expect( id ).toBe( includeIds[ idx ] );
} );
const result2 = await ordersApi.listAll.orders( {
order: 'desc',
orderby: 'include',
include: includeIds.join( ',' ),
} );
expect( result2.statusCode ).toEqual( 200 );
expect( result2.body ).toHaveLength( includeIds.length );
// Verify all results are in proper order.
result2.body.forEach( ( { id }, idx ) => {
expect( id ).toBe( includeIds[ idx ] );
} );
} );
} );
} );
} );

View File

@ -1,777 +0,0 @@
/**
* Internal dependencies
*/
const { createSampleData, deleteSampleData } = require( '../../data/products' );
const { productsApi } = require('../../endpoints/products');
/**
* Tests for the WooCommerce Products API.
*
* @group api
* @group products
*
*/
describe( 'Products API tests', () => {
const PRODUCTS_COUNT = 20;
let sampleData;
beforeAll( async () => {
sampleData = await createSampleData();
}, 10000 );
afterAll( async () => {
await deleteSampleData( sampleData );
}, 10000 );
describe( 'List all products', () => {
it( 'defaults', async () => {
const result = await productsApi.listAll.products();
expect( result.statusCode ).toEqual( 200 );
expect( result.headers['x-wp-total'] ).toEqual( PRODUCTS_COUNT.toString() );
expect( result.headers['x-wp-totalpages'] ).toEqual( '2' );
} );
it( 'pagination', async () => {
const pageSize = 6;
const page1 = await productsApi.listAll.products( {
per_page: pageSize,
} );
const page2 = await productsApi.listAll.products( {
per_page: pageSize,
page: 2,
} );
expect( page1.statusCode ).toEqual( 200 );
expect( page2.statusCode ).toEqual( 200 );
// Verify total page count.
expect( page1.headers['x-wp-total'] ).toEqual( PRODUCTS_COUNT.toString() );
expect( page1.headers['x-wp-totalpages'] ).toEqual( '4' );
// Verify we get pageSize'd arrays.
expect( Array.isArray( page1.body ) ).toBe( true );
expect( Array.isArray( page2.body ) ).toBe( true );
expect( page1.body ).toHaveLength( pageSize );
expect( page2.body ).toHaveLength( pageSize );
// Ensure all of the product IDs are unique (no page overlap).
const allProductIds = page1.body.concat( page2.body ).reduce( ( acc, product ) => {
acc[ product.id ] = 1;
return acc;
}, {} );
expect( Object.keys( allProductIds ) ).toHaveLength( pageSize * 2 );
// Verify that offset takes precedent over page number.
const page2Offset = await productsApi.listAll.products( {
per_page: pageSize,
page: 2,
offset: pageSize + 1,
} );
// The offset pushes the result set 1 product past the start of page 2.
expect( page2Offset.body ).toEqual(
expect.not.arrayContaining( [
expect.objectContaining( { id: page2.body[0].id } )
] )
);
expect( page2Offset.body[0].id ).toEqual( page2.body[1].id );
// Verify the last page only has 2 products as we expect.
const lastPage = await productsApi.listAll.products( {
per_page: pageSize,
page: 4,
} );
expect( Array.isArray( lastPage.body ) ).toBe( true );
expect( lastPage.body ).toHaveLength( 2 );
// Verify a page outside the total page count is empty.
const page6 = await productsApi.listAll.products( {
per_page: pageSize,
page: 6,
} );
expect( Array.isArray( page6.body ) ).toBe( true );
expect( page6.body ).toHaveLength( 0 );
} );
it( 'search', async () => {
// Match in the short description.
const result1 = await productsApi.listAll.products( {
search: 'external'
} );
expect( result1.statusCode ).toEqual( 200 );
expect( result1.body ).toHaveLength( 1 );
expect( result1.body[0].name ).toBe( 'WordPress Pennant' );
// Match in the product name.
const result2 = await productsApi.listAll.products( {
search: 'pocket'
} );
expect( result2.statusCode ).toEqual( 200 );
expect( result2.body ).toHaveLength( 1 );
expect( result2.body[0].name ).toBe( 'Hoodie with Pocket' );
} );
it( 'inclusion / exclusion', async () => {
const allProducts = await productsApi.listAll.products( {
per_page: 20,
} );
expect( allProducts.statusCode ).toEqual( 200 );
const allProductIds = allProducts.body.map( product => product.id );
expect( allProductIds ).toHaveLength( PRODUCTS_COUNT );
const productsToFilter = [
allProductIds[2],
allProductIds[4],
allProductIds[7],
allProductIds[13],
];
const included = await productsApi.listAll.products( {
per_page: 20,
include: productsToFilter.join( ',' ),
} );
expect( included.statusCode ).toEqual( 200 );
expect( included.body ).toHaveLength( productsToFilter.length );
expect( included.body ).toEqual(
expect.arrayContaining(
productsToFilter.map( id => expect.objectContaining( { id } ) )
)
);
const excluded = await productsApi.listAll.products( {
per_page: 20,
exclude: productsToFilter.join( ',' ),
} );
expect( excluded.statusCode ).toEqual( 200 );
expect( excluded.body ).toHaveLength( PRODUCTS_COUNT - productsToFilter.length );
expect( excluded.body ).toEqual(
expect.not.arrayContaining(
productsToFilter.map( id => expect.objectContaining( { id } ) )
)
);
} );
it( 'slug', async () => {
// Match by slug.
const result1 = await productsApi.listAll.products( {
slug: 't-shirt-with-logo'
} );
expect( result1.statusCode ).toEqual( 200 );
expect( result1.body ).toHaveLength( 1 );
expect( result1.body[0].slug ).toBe( 't-shirt-with-logo' );
// No matches
const result2 = await productsApi.listAll.products( {
slug: 'no-product-with-this-slug'
} );
expect( result2.statusCode ).toEqual( 200 );
expect( result2.body ).toHaveLength( 0 );
} );
it( 'sku', async () => {
// Match by SKU.
const result1 = await productsApi.listAll.products( {
sku: 'woo-sunglasses'
} );
expect( result1.statusCode ).toEqual( 200 );
expect( result1.body ).toHaveLength( 1 );
expect( result1.body[0].sku ).toBe( 'woo-sunglasses' );
// No matches
const result2 = await productsApi.listAll.products( {
sku: 'no-product-with-this-sku'
} );
expect( result2.statusCode ).toEqual( 200 );
expect( result2.body ).toHaveLength( 0 );
} );
it( 'type', async () => {
const result1 = await productsApi.listAll.products( {
type: 'simple'
} );
expect( result1.statusCode ).toEqual( 200 );
expect( result1.headers['x-wp-total'] ).toEqual( '16' );
const result2 = await productsApi.listAll.products( {
type: 'external'
} );
expect( result2.statusCode ).toEqual( 200 );
expect( result2.body ).toHaveLength( 1 );
expect( result2.body[0].name ).toBe( 'WordPress Pennant' );
const result3 = await productsApi.listAll.products( {
type: 'variable'
} );
expect( result3.statusCode ).toEqual( 200 );
expect( result3.body ).toHaveLength( 2 );
const result4 = await productsApi.listAll.products( {
type: 'grouped'
} );
expect( result4.statusCode ).toEqual( 200 );
expect( result4.body ).toHaveLength( 1 );
expect( result4.body[0].name ).toBe( 'Logo Collection' );
} );
it( 'featured', async () => {
const featured = [
expect.objectContaining( { name: 'Hoodie with Zipper' } ),
expect.objectContaining( { name: 'Hoodie with Pocket' } ),
expect.objectContaining( { name: 'Sunglasses' } ),
expect.objectContaining( { name: 'Cap' } ),
expect.objectContaining( { name: 'V-Neck T-Shirt' } ),
];
const result1 = await productsApi.listAll.products( {
featured: true,
} );
expect( result1.statusCode ).toEqual( 200 );
expect( result1.body ).toHaveLength( featured.length );
expect( result1.body ).toEqual( expect.arrayContaining( featured ) );
const result2 = await productsApi.listAll.products( {
featured: false,
} );
expect( result2.statusCode ).toEqual( 200 );
expect( result2.body ).toEqual( expect.not.arrayContaining( featured ) );
} );
it( 'categories', async () => {
const accessory = [
expect.objectContaining( { name: 'Beanie' } ),
]
const hoodies = [
expect.objectContaining( { name: 'Hoodie with Zipper' } ),
expect.objectContaining( { name: 'Hoodie with Pocket' } ),
expect.objectContaining( { name: 'Hoodie with Logo' } ),
expect.objectContaining( { name: 'Hoodie' } ),
];
// Verify that subcategories are included.
const result1 = await productsApi.listAll.products( {
per_page: 20,
category: sampleData.categories.clothing.id,
} );
expect( result1.statusCode ).toEqual( 200 );
expect( result1.body ).toEqual( expect.arrayContaining( accessory ) );
expect( result1.body ).toEqual( expect.arrayContaining( hoodies ) );
// Verify sibling categories are not.
const result2 = await productsApi.listAll.products( {
category: sampleData.categories.hoodies.id,
} );
expect( result2.statusCode ).toEqual( 200 );
expect( result2.body ).toEqual( expect.not.arrayContaining( accessory ) );
expect( result2.body ).toEqual( expect.arrayContaining( hoodies ) );
} );
it( 'on sale', async () => {
const onSale = [
expect.objectContaining( { name: 'Beanie with Logo' } ),
expect.objectContaining( { name: 'Hoodie with Pocket' } ),
expect.objectContaining( { name: 'Single' } ),
expect.objectContaining( { name: 'Cap' } ),
expect.objectContaining( { name: 'Belt' } ),
expect.objectContaining( { name: 'Beanie' } ),
expect.objectContaining( { name: 'Hoodie' } ),
];
const result1 = await productsApi.listAll.products( {
on_sale: true,
} );
expect( result1.statusCode ).toEqual( 200 );
expect( result1.body ).toHaveLength( onSale.length );
expect( result1.body ).toEqual( expect.arrayContaining( onSale ) );
const result2 = await productsApi.listAll.products( {
on_sale: false,
} );
expect( result2.statusCode ).toEqual( 200 );
expect( result2.body ).toEqual( expect.not.arrayContaining( onSale ) );
} );
it( 'price', async () => {
const result1 = await productsApi.listAll.products( {
min_price: 21,
max_price: 28,
} );
expect( result1.statusCode ).toEqual( 200 );
expect( result1.body ).toHaveLength( 1 );
expect( result1.body[0].name ).toBe( 'Long Sleeve Tee' );
expect( result1.body[0].price ).toBe( '25' );
const result2 = await productsApi.listAll.products( {
max_price: 5,
} );
expect( result2.statusCode ).toEqual( 200 );
expect( result2.body ).toHaveLength( 1 );
expect( result2.body[0].name ).toBe( 'Single' );
expect( result2.body[0].price ).toBe( '2' );
const result3 = await productsApi.listAll.products( {
min_price: 5,
order: 'asc',
orderby: 'price',
} );
expect( result3.statusCode ).toEqual( 200 );
expect( result3.body ).toEqual(
expect.not.arrayContaining( [
expect.objectContaining( { name: 'Single' } )
] )
);
} );
it( 'before / after', async () => {
const before = [
expect.objectContaining( { name: 'Album' } ),
expect.objectContaining( { name: 'Single' } ),
expect.objectContaining( { name: 'T-Shirt with Logo' } ),
expect.objectContaining( { name: 'Beanie with Logo' } ),
];
const after = [
expect.objectContaining( { name: 'Hoodie' } ),
expect.objectContaining( { name: 'V-Neck T-Shirt' } ),
expect.objectContaining( { name: 'Parent Product' } ),
expect.objectContaining( { name: 'Child Product' } ),
];
const result1 = await productsApi.listAll.products( {
before: '2021-09-05T15:50:19',
} );
expect( result1.statusCode ).toEqual( 200 );
expect( result1.body ).toHaveLength( before.length );
expect( result1.body ).toEqual( expect.arrayContaining( before ) );
const result2 = await productsApi.listAll.products( {
after: '2021-09-18T15:50:18',
} );
expect( result2.statusCode ).toEqual( 200 );
expect( result2.body ).toEqual( expect.not.arrayContaining( before ) );
expect( result2.body ).toHaveLength( after.length );
expect( result2.body ).toEqual( expect.arrayContaining( after ) );
} );
it( 'attributes', async () => {
const red = sampleData.attributes.colors.find( term => term.name === 'Red' );
const redProducts = [
expect.objectContaining( { name: 'V-Neck T-Shirt' } ),
expect.objectContaining( { name: 'Hoodie' } ),
expect.objectContaining( { name: 'Beanie' } ),
expect.objectContaining( { name: 'Beanie with Logo' } ),
];
const result = await productsApi.listAll.products( {
attribute: 'pa_color',
attribute_term: red.id,
} );
expect( result.statusCode ).toEqual( 200 );
expect( result.body ).toHaveLength( redProducts.length );
expect( result.body ).toEqual( expect.arrayContaining( redProducts ) );
} );
it( 'status', async () => {
const result1 = await productsApi.listAll.products( {
status: 'pending'
} );
expect( result1.statusCode ).toEqual( 200 );
expect( result1.body ).toHaveLength( 1 );
expect( result1.body[0].name ).toBe( 'Polo' );
const result2 = await productsApi.listAll.products( {
status: 'draft'
} );
expect( result2.statusCode ).toEqual( 200 );
expect( result2.body ).toHaveLength( 0 );
} );
it( 'shipping class', async () => {
const result = await productsApi.listAll.products( {
shipping_class: sampleData.shippingClasses.freight.id,
} );
expect( result.statusCode ).toEqual( 200 );
expect( result.body ).toHaveLength( 1 );
expect( result.body[0].name ).toBe( 'Long Sleeve Tee' );
} );
it( 'tax class', async () => {
const result = await productsApi.listAll.products( {
tax_class: 'reduced-rate',
} );
expect( result.statusCode ).toEqual( 200 );
expect( result.body ).toHaveLength( 1 );
expect( result.body[0].name ).toBe( 'Sunglasses' );
} );
it( 'stock status', async () => {
const result = await productsApi.listAll.products( {
stock_status: 'onbackorder',
} );
expect( result.statusCode ).toEqual( 200 );
expect( result.body ).toHaveLength( 1 );
expect( result.body[0].name ).toBe( 'T-Shirt' );
} );
it( 'tags', async () => {
const coolProducts = [
expect.objectContaining( { name: 'Sunglasses' } ),
expect.objectContaining( { name: 'Hoodie with Pocket' } ),
expect.objectContaining( { name: 'Beanie' } ),
];
const result = await productsApi.listAll.products( {
tag: sampleData.tags.cool.id,
} );
expect( result.statusCode ).toEqual( 200 );
expect( result.body ).toHaveLength( coolProducts.length );
expect( result.body ).toEqual( expect.arrayContaining( coolProducts ) );
} );
it( 'parent', async () => {
const result1 = await productsApi.listAll.products( {
parent: sampleData.hierarchicalProducts.parent.id,
} );
expect( result1.statusCode ).toEqual( 200 );
expect( result1.body ).toHaveLength( 1 );
expect( result1.body[0].name ).toBe( 'Child Product' );
const result2 = await productsApi.listAll.products( {
parent_exclude: sampleData.hierarchicalProducts.parent.id,
} );
expect( result2.statusCode ).toEqual( 200 );
expect( result2.body ).toEqual( expect.not.arrayContaining( [
expect.objectContaining( { name: 'Child Product' } ),
] ) );
} );
describe( 'orderby', () => {
const productNamesAsc = [
'Album',
'Beanie',
'Beanie with Logo',
'Belt',
'Cap',
'Child Product',
'Hoodie',
'Hoodie with Logo',
'Hoodie with Pocket',
'Hoodie with Zipper',
'Logo Collection',
'Long Sleeve Tee',
'Parent Product',
'Polo',
'Single',
'Sunglasses',
'T-Shirt',
'T-Shirt with Logo',
'V-Neck T-Shirt',
'WordPress Pennant',
];
const productNamesDesc = [ ...productNamesAsc ].reverse();
const productNamesByRatingAsc = [
'Sunglasses',
'Cap',
'T-Shirt',
];
const productNamesByRatingDesc = [ ...productNamesByRatingAsc ].reverse();
const productNamesByPopularityDesc = [
'Beanie with Logo',
'Single',
'T-Shirt',
];
const productNamesByPopularityAsc = [ ...productNamesByPopularityDesc ].reverse();
it( 'default', async () => {
// Default = date desc.
const result = await productsApi.listAll.products();
expect( result.statusCode ).toEqual( 200 );
// Verify all dates are in descending order.
let lastDate = Date.now();
result.body.forEach( ( { date_created_gmt } ) => {
const created = Date.parse( date_created_gmt + '.000Z' );
expect( lastDate ).toBeGreaterThan( created );
lastDate = created;
} );
} );
it( 'date', async () => {
const result = await productsApi.listAll.products( {
order: 'asc',
orderby: 'date',
} );
expect( result.statusCode ).toEqual( 200 );
// Verify all dates are in ascending order.
let lastDate = 0;
result.body.forEach( ( { date_created_gmt } ) => {
const created = Date.parse( date_created_gmt + '.000Z' );
expect( created ).toBeGreaterThan( lastDate );
lastDate = created;
} );
} );
it( 'id', async () => {
const result1 = await productsApi.listAll.products( {
order: 'asc',
orderby: 'id',
} );
expect( result1.statusCode ).toEqual( 200 );
// Verify all results are in ascending order.
let lastId = 0;
result1.body.forEach( ( { id } ) => {
expect( id ).toBeGreaterThan( lastId );
lastId = id;
} );
const result2 = await productsApi.listAll.products( {
order: 'desc',
orderby: 'id',
} );
expect( result2.statusCode ).toEqual( 200 );
// Verify all results are in descending order.
lastId = Number.MAX_SAFE_INTEGER;
result2.body.forEach( ( { id } ) => {
expect( lastId ).toBeGreaterThan( id );
lastId = id;
} );
} );
it( 'title', async () => {
const result1 = await productsApi.listAll.products( {
order: 'asc',
orderby: 'title',
per_page: productNamesAsc.length,
} );
expect( result1.statusCode ).toEqual( 200 );
// Verify all results are in ascending order.
result1.body.forEach( ( { name }, idx ) => {
expect( name ).toBe( productNamesAsc[ idx ] );
} );
const result2 = await productsApi.listAll.products( {
order: 'desc',
orderby: 'title',
per_page: productNamesDesc.length,
} );
expect( result2.statusCode ).toEqual( 200 );
// Verify all results are in descending order.
result2.body.forEach( ( { name }, idx ) => {
expect( name ).toBe( productNamesDesc[ idx ] );
} );
} );
it( 'slug', async () => {
const productNamesBySlugAsc = [
'Polo', // The Polo isn't published so it has an empty slug.
...productNamesAsc.filter( p => p !== 'Polo' ),
];
const productNamesBySlugDesc = [ ...productNamesBySlugAsc ].reverse();
const result1 = await productsApi.listAll.products( {
order: 'asc',
orderby: 'slug',
per_page: productNamesBySlugAsc.length,
} );
expect( result1.statusCode ).toEqual( 200 );
// Verify all results are in ascending order.
result1.body.forEach( ( { name }, idx ) => {
expect( name ).toBe( productNamesBySlugAsc[ idx ] );
} );
const result2 = await productsApi.listAll.products( {
order: 'desc',
orderby: 'slug',
per_page: productNamesBySlugDesc.length,
} );
expect( result2.statusCode ).toEqual( 200 );
// Verify all results are in descending order.
result2.body.forEach( ( { name }, idx ) => {
expect( name ).toBe( productNamesBySlugDesc[ idx ] );
} );
} );
it( 'price', async () => {
const productNamesMinPriceAsc = [
'Parent Product',
'Child Product',
'Single',
'WordPress Pennant',
'Album',
'V-Neck T-Shirt',
'Cap',
'Beanie with Logo',
'T-Shirt with Logo',
'Beanie',
'T-Shirt',
'Logo Collection',
'Polo',
'Long Sleeve Tee',
'Hoodie with Pocket',
'Hoodie',
'Hoodie with Zipper',
'Hoodie with Logo',
'Belt',
'Sunglasses',
];
const result1 = await productsApi.listAll.products( {
order: 'asc',
orderby: 'price',
per_page: productNamesMinPriceAsc.length
} );
expect( result1.statusCode ).toEqual( 200 );
expect( result1.body ).toHaveLength( productNamesMinPriceAsc.length );
// Verify all results are in ascending order.
// The query uses the min price calculated in the product meta lookup table,
// so we can't just check the price property of the response.
result1.body.forEach( ( { name }, idx ) => {
expect( name ).toBe( productNamesMinPriceAsc[ idx ] );
} );
const productNamesMaxPriceDesc = [
'Sunglasses',
'Belt',
'Hoodie',
'Logo Collection',
'Hoodie with Logo',
'Hoodie with Zipper',
'Hoodie with Pocket',
'Long Sleeve Tee',
'V-Neck T-Shirt',
'Polo',
'T-Shirt',
'Beanie',
'T-Shirt with Logo',
'Beanie with Logo',
'Cap',
'Album',
'WordPress Pennant',
'Single',
'Child Product',
'Parent Product',
];
const result2 = await productsApi.listAll.products( {
order: 'desc',
orderby: 'price',
per_page: productNamesMaxPriceDesc.length
} );
expect( result2.statusCode ).toEqual( 200 );
expect( result2.body ).toHaveLength( productNamesMaxPriceDesc.length );
// Verify all results are in descending order.
// The query uses the max price calculated in the product meta lookup table,
// so we can't just check the price property of the response.
result2.body.forEach( ( { name }, idx ) => {
expect( name ).toBe( productNamesMaxPriceDesc[ idx ] );
} );
} );
// This case will remain skipped until orderby include is fixed.
// See: https://github.com/woocommerce/woocommerce/issues/30354#issuecomment-925955099.
it( 'include', async () => {
const includeIds = [
sampleData.groupedProducts[ 0 ].id,
sampleData.simpleProducts[ 3 ].id,
sampleData.hierarchicalProducts.parent.id,
];
const result1 = await productsApi.listAll.products( {
order: 'asc',
orderby: 'include',
include: includeIds.join( ',' ),
} );
expect( result1.statusCode ).toEqual( 200 );
expect( result1.body ).toHaveLength( includeIds.length );
// Verify all results are in proper order.
result1.body.forEach( ( { id }, idx ) => {
expect( id ).toBe( includeIds[ idx ] );
} );
const result2 = await productsApi.listAll.products( {
order: 'desc',
orderby: 'include',
include: includeIds.join( ',' ),
} );
expect( result2.statusCode ).toEqual( 200 );
expect( result2.body ).toHaveLength( includeIds.length );
// Verify all results are in proper order.
result2.body.forEach( ( { id }, idx ) => {
expect( id ).toBe( includeIds[ idx ] );
} );
} );
it( 'rating (desc)', async () => {
const result2 = await productsApi.listAll.products( {
order: 'desc',
orderby: 'rating',
per_page: productNamesByRatingDesc.length,
} );
expect( result2.statusCode ).toEqual( 200 );
// Verify all results are in descending order.
result2.body.forEach( ( { name }, idx ) => {
expect( name ).toBe( productNamesByRatingDesc[ idx ] );
} );
} );
// This case will remain skipped until ratings can be sorted ascending.
// See: https://github.com/woocommerce/woocommerce/issues/30354#issuecomment-925955099.
it.skip( 'rating (asc)', async () => {
const result1 = await productsApi.listAll.products( {
order: 'asc',
orderby: 'rating',
per_page: productNamesByRatingAsc.length,
} );
expect( result1.statusCode ).toEqual( 200 );
// Verify all results are in ascending order.
result1.body.forEach( ( { name }, idx ) => {
expect( name ).toBe( productNamesByRatingAsc[ idx ] );
} );
} );
it( 'popularity (desc)', async () => {
const result2 = await productsApi.listAll.products( {
order: 'desc',
orderby: 'popularity',
per_page: productNamesByPopularityDesc.length,
} );
expect( result2.statusCode ).toEqual( 200 );
// Verify all results are in descending order.
result2.body.forEach( ( { name }, idx ) => {
expect( name ).toBe( productNamesByPopularityDesc[ idx ] );
} );
} );
// This case will remain skipped until popularity can be sorted ascending.
// See: https://github.com/woocommerce/woocommerce/issues/30354#issuecomment-925955099.
it.skip( 'popularity (asc)', async () => {
const result1 = await productsApi.listAll.products( {
order: 'asc',
orderby: 'popularity',
per_page: productNamesByPopularityAsc.length,
} );
expect( result1.statusCode ).toEqual( 200 );
// Verify all results are in ascending order.
result1.body.forEach( ( { name }, idx ) => {
expect( name ).toBe( productNamesByPopularityAsc[ idx ] );
} );
} );
} );
} );
} );

View File

@ -10,7 +10,7 @@ rm -rf "$BUILD_PATH"
mkdir -p "$DEST_PATH"
echo "Installing PHP and JS dependencies..."
pnpm run install:no-e2e
pnpm install
composer install || exit "$?"
echo "Running JS Build..."
pnpm run build:core || exit "$?"

View File

@ -9,5 +9,5 @@ runOnChange() {
fi
}
runOnChange "package-lock.json" "pnpm run install:no-e2e"
runOnChange "package-lock.json" "pnpm install"
runOnChange "composer.lock" "SKIP_UPDATE_TEXTDOMAINS=true composer install"

View File

@ -15,10 +15,7 @@
"scripts": {
"composer:install": "composer install",
"composer:dump-autoload": "composer dump-autoload",
"check:subset-installed": "pnpm list --depth 1 install-subset > /dev/null 2>&1",
"preinstall": "npx only-allow pnpm",
"install:subset-only": "pnpm install --no-package-lock --no-save install-subset",
"install:no-e2e": "pnpm run check:subset-installed --silent || pnpm run install:subset-only && pnpx install-subset i no-e2e",
"build": "./bin/build-zip.sh",
"build:core": "grunt && pnpm run makepot",
"build-watch": "grunt watch",
@ -128,17 +125,5 @@
"> 0.1%",
"ie 8",
"ie 9"
],
"subsets": {
"no-e2e": {
"exclude": [
"@woocommerce/api",
"@woocommerce/api-core-tests",
"@woocommerce/e2e-core-tests",
"@woocommerce/e2e-environment",
"@woocommerce/e2e-utils",
"@wordpress/e2e-test-utils"
]
}
}
]
}

View File

@ -1,18 +0,0 @@
/**
* A basic refund.
*
* For more details on the order refund properties, see:
*
* https://woocommerce.github.io/woocommerce-rest-api-docs/#order-refund-properties
*
*/
const refund = {
api_refund: false,
amount: '1.00',
reason: 'Late delivery refund.',
line_items: [],
};
module.exports = {
refund: refund,
};

View File

@ -1,60 +0,0 @@
/**
* Internal dependencies
*/
const {
getRequest,
postRequest,
putRequest,
deleteRequest,
} = require( '../utils/request' );
/**
* WooCommerce Refunds endpoints.
*
* https://woocommerce.github.io/woocommerce-rest-api-docs/#refunds
*/
const refundsApi = {
name: 'Refunds',
create: {
name: 'Create a refund',
method: 'POST',
path: 'orders/<id>/refunds',
responseCode: 201,
refund: async ( orderId, refundDetails ) =>
postRequest( `orders/${ orderId }/refunds`, refundDetails ),
},
retrieve: {
name: 'Retrieve a refund',
method: 'GET',
path: 'orders/<id>/refunds/<refund_id>',
responseCode: 200,
refund: async ( orderId, refundId ) =>
getRequest( `orders/${ orderId }/refunds/${ refundId }` ),
},
listAll: {
name: 'List all refunds',
method: 'GET',
path: 'orders/<id>/refunds',
responseCode: 200,
refunds: async ( orderId ) =>
getRequest( `orders/${ orderId }/refunds` ),
},
delete: {
name: 'Delete a refund',
method: 'DELETE',
path: 'orders/<id>/refunds/<refund_id>',
responseCode: 200,
payload: {
force: false,
},
refund: async ( orderId, refundId, deletePermanently ) =>
deleteRequest(
`orders/${ orderId }/refunds/${ refundId }`,
deletePermanently
),
},
};
module.exports = {
refundsApi: refundsApi,
};

View File

@ -1,122 +0,0 @@
const { refundsApi } = require( '../../endpoints/refunds' );
const { ordersApi } = require( '../../endpoints/orders' );
const { productsApi } = require( '../../endpoints/products' );
const { refund } = require( '../../data' );
/**
* Tests for the WooCommerce Refunds API.
*
* @group api
* @group refunds
*
*/
describe( 'Refunds API tests', () => {
let expectedRefund;
let orderId;
let productId;
beforeAll( async () => {
// Create a product and save its product ID
const product = {
name: 'Simple Product for Refunds API tests',
regular_price: '100',
};
const createProductResponse = await productsApi.create.product(
product
);
productId = createProductResponse.body.id;
// Create an order with a product line item, and save its Order ID
const order = {
status: 'pending',
line_items: [
{
product_id: productId,
},
],
};
const createOrderResponse = await ordersApi.create.order( order );
orderId = createOrderResponse.body.id;
// Setup the expected refund object
expectedRefund = {
...refund,
line_items: [
{
product_id: productId,
},
],
};
} );
afterAll( async () => {
// Cleanup the created product and order
await productsApi.delete.product( productId, true );
await ordersApi.delete.order( orderId, true );
} );
it( 'can create a refund', async () => {
const { status, body } = await refundsApi.create.refund(
orderId,
expectedRefund
);
expect( status ).toEqual( refundsApi.create.responseCode );
expect( body.id ).toBeDefined();
// Save the refund ID
expectedRefund.id = body.id;
// Verify that the order was refunded.
const getOrderResponse = await ordersApi.retrieve.order( orderId );
expect( getOrderResponse.body.refunds ).toHaveLength( 1 );
expect( getOrderResponse.body.refunds[ 0 ].id ).toEqual(
expectedRefund.id
);
expect( getOrderResponse.body.refunds[ 0 ].reason ).toEqual(
expectedRefund.reason
);
expect( getOrderResponse.body.refunds[ 0 ].total ).toEqual(
`-${ expectedRefund.amount }`
);
} );
it( 'can retrieve a refund', async () => {
const { status, body } = await refundsApi.retrieve.refund(
orderId,
expectedRefund.id
);
expect( status ).toEqual( refundsApi.retrieve.responseCode );
expect( body.id ).toEqual( expectedRefund.id );
} );
it( 'can list all refunds', async () => {
const { status, body } = await refundsApi.listAll.refunds( orderId );
expect( status ).toEqual( refundsApi.listAll.responseCode );
expect( body ).toHaveLength( 1 );
expect( body[ 0 ].id ).toEqual( expectedRefund.id );
} );
it( 'can delete a refund', async () => {
const { status, body } = await refundsApi.delete.refund(
orderId,
expectedRefund.id,
true
);
expect( status ).toEqual( refundsApi.delete.responseCode );
expect( body.id ).toEqual( expectedRefund.id );
// Verify that the refund cannot be retrieved
const retrieveRefundResponse = await refundsApi.retrieve.refund(
orderId,
expectedRefund.id
);
expect( retrieveRefundResponse.status ).toEqual( 404 );
// Verify that the order no longer has a refund
const retrieveOrderResponse = await ordersApi.retrieve.order( orderId );
expect( retrieveOrderResponse.body.refunds ).toHaveLength( 0 );
} );
} );

File diff suppressed because it is too large Load Diff