From 34eb9549dc3bbbafa18f2132bd2021e4686425c3 Mon Sep 17 00:00:00 2001 From: Rodel Date: Wed, 20 Oct 2021 17:50:08 +0800 Subject: [PATCH 01/13] Added tests for simple product --- .../tests/products/products.test.js | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/plugins/woocommerce/tests/e2e/api-core-tests/tests/products/products.test.js b/plugins/woocommerce/tests/e2e/api-core-tests/tests/products/products.test.js index 08b1e950b4b..c725d24139a 100644 --- a/plugins/woocommerce/tests/e2e/api-core-tests/tests/products/products.test.js +++ b/plugins/woocommerce/tests/e2e/api-core-tests/tests/products/products.test.js @@ -15,6 +15,7 @@ const { productsApi } = require('../../endpoints/products'); const PRODUCTS_COUNT = 20; let sampleData; + let productId; beforeAll( async () => { sampleData = await createSampleData(); @@ -770,4 +771,56 @@ const { productsApi } = require('../../endpoints/products'); } ); } ); } ); + + it( 'can add a simple product', async () => { + const product = { + name: 'A Simple Product', + regular_price: '25', + description: 'Description for this simple product.', + short_description: 'Shorter description.', + }; + const { status, body } = await productsApi.create.product( product ); + productId = body.id; + + expect( status ).toEqual( productsApi.create.responseCode ); + expect( productId ).toBeDefined(); + expect( body.type ).toEqual( 'simple' ); + expect( body.status ).toEqual( 'publish' ); + expect( body.virtual ).toEqual( false ); + expect( body.downloadable ).toEqual( false ); + } ); + + it( 'can view a single product', async () => { + const { status, body } = await productsApi.retrieve.product( productId ); + + expect( status ).toEqual( productsApi.retrieve.responseCode ); + expect( body.id ).toEqual( productId ); + } ); + + it( 'can update a single product', async () => { + const updatePayload = { + regular_price: '25.99', + }; + const { status, body } = await productsApi.update.product( + productId, + updatePayload + ); + + expect( status ).toEqual( productsApi.update.responseCode ); + expect( body.id ).toEqual( productId ); + expect( body.regular_price ).toEqual( updatePayload.regular_price ); + } ); + + it( 'can delete a product', async () => { + const { status, body } = await productsApi.delete.product( productId, true ); + + expect( status ).toEqual( productsApi.delete.responseCode ); + expect( body.id ).toEqual( productId ); + + // Verify that the product can no longer be retrieved. + const { + status: retrieveDeletedProductStatus, + } = await productsApi.retrieve.product( productId ); + expect( retrieveDeletedProductStatus ).toEqual( 404 ); + } ); } ); From 8c6252996d86c45dec2b6666f0631d544e6e7ac0 Mon Sep 17 00:00:00 2001 From: Rodel Date: Thu, 21 Oct 2021 15:00:58 +0800 Subject: [PATCH 02/13] Added test for virtual product --- .../tests/products/products.test.js | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/plugins/woocommerce/tests/e2e/api-core-tests/tests/products/products.test.js b/plugins/woocommerce/tests/e2e/api-core-tests/tests/products/products.test.js index c725d24139a..46aec534d60 100644 --- a/plugins/woocommerce/tests/e2e/api-core-tests/tests/products/products.test.js +++ b/plugins/woocommerce/tests/e2e/api-core-tests/tests/products/products.test.js @@ -784,10 +784,34 @@ const { productsApi } = require('../../endpoints/products'); expect( status ).toEqual( productsApi.create.responseCode ); expect( productId ).toBeDefined(); + expect( body ).toMatchObject( product ); expect( body.type ).toEqual( 'simple' ); expect( body.status ).toEqual( 'publish' ); expect( body.virtual ).toEqual( false ); expect( body.downloadable ).toEqual( false ); + expect( body.shipping_required ).toEqual( true ); + } ); + + it( 'can add a virtual product', async () => { + const virtualProduct = { + name: 'A Virtual Product', + regular_price: '10', + virtual: true, + }; + const { status, body } = await productsApi.create.product( + virtualProduct + ); + const virtualProductId = body.id; + + expect( status ).toEqual( productsApi.create.responseCode ); + expect( virtualProductId ).toBeDefined(); + expect( body ).toMatchObject( virtualProduct ); + expect( body.type ).toEqual( 'simple' ); + expect( body.status ).toEqual( 'publish' ); + expect( body.shipping_required ).toEqual( false ); + + // Cleanup: Delete the virtual product + await productsApi.delete.product( virtualProductId, true ); } ); it( 'can view a single product', async () => { From a309b4af2ce6bf3b125efac790776ac7be528981 Mon Sep 17 00:00:00 2001 From: Rodel Date: Thu, 21 Oct 2021 16:33:09 +0800 Subject: [PATCH 03/13] Added test for variable product --- .../tests/products/products.test.js | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/plugins/woocommerce/tests/e2e/api-core-tests/tests/products/products.test.js b/plugins/woocommerce/tests/e2e/api-core-tests/tests/products/products.test.js index 46aec534d60..1ba5d8594d9 100644 --- a/plugins/woocommerce/tests/e2e/api-core-tests/tests/products/products.test.js +++ b/plugins/woocommerce/tests/e2e/api-core-tests/tests/products/products.test.js @@ -814,6 +814,45 @@ const { productsApi } = require('../../endpoints/products'); await productsApi.delete.product( virtualProductId, true ); } ); + it( 'can add a variable product', async () => { + const variableProduct = { + name: 'A Variable Product', + type: 'variable', + attributes: [ + { + name: 'Colour', + visible: true, + variation: true, + options: [ 'Red', 'Green', 'Blue' ], + }, + { + name: 'Size', + visible: true, + variation: true, + options: [ 'Small', 'Medium', 'Large' ], + }, + { + name: 'Logo', + visible: true, + variation: true, + options: [ 'Woo', 'WordPress' ], + }, + ], + }; + const { status, body } = await productsApi.create.product( + variableProduct + ); + const variableProductId = body.id; + + expect( status ).toEqual( productsApi.create.responseCode ); + expect( variableProductId ).toBeDefined(); + expect( body ).toMatchObject( variableProduct ); + expect( body.status ).toEqual( 'publish' ); + + // Cleanup: Delete the variable product + await productsApi.delete.product( variableProductId, true ); + } ); + it( 'can view a single product', async () => { const { status, body } = await productsApi.retrieve.product( productId ); From f5cec2b9208d9bb3ee208bcb5eead57d5438b92b Mon Sep 17 00:00:00 2001 From: Rodel Date: Tue, 19 Oct 2021 20:35:18 +0800 Subject: [PATCH 04/13] Added changelog entry --- plugins/woocommerce/tests/e2e/api-core-tests/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/woocommerce/tests/e2e/api-core-tests/CHANGELOG.md b/plugins/woocommerce/tests/e2e/api-core-tests/CHANGELOG.md index db58d54b233..d341183b1cb 100644 --- a/plugins/woocommerce/tests/e2e/api-core-tests/CHANGELOG.md +++ b/plugins/woocommerce/tests/e2e/api-core-tests/CHANGELOG.md @@ -1,6 +1,7 @@ # Unreleased - Added API tests for the Coupons API. +- Added API tests for the Refunds API. # 0.1.0 From c08358ff484eeeceb69bde9629e83a21354eacec Mon Sep 17 00:00:00 2001 From: Rodel Date: Thu, 21 Oct 2021 14:09:27 +0800 Subject: [PATCH 05/13] Corrected Changelog entry --- plugins/woocommerce/tests/e2e/api-core-tests/CHANGELOG.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/plugins/woocommerce/tests/e2e/api-core-tests/CHANGELOG.md b/plugins/woocommerce/tests/e2e/api-core-tests/CHANGELOG.md index d341183b1cb..534e9720d8d 100644 --- a/plugins/woocommerce/tests/e2e/api-core-tests/CHANGELOG.md +++ b/plugins/woocommerce/tests/e2e/api-core-tests/CHANGELOG.md @@ -1,8 +1,5 @@ -# Unreleased - -- Added API tests for the Coupons API. -- Added API tests for the Refunds API. - # 0.1.0 - Initial/beta release +- Added API tests for the Coupons API. +- Added API tests for the Refunds API. From e6a009e598fd17fc295e3c8204b42a339e2ae04b Mon Sep 17 00:00:00 2001 From: Rodel Date: Thu, 21 Oct 2021 14:21:47 +0800 Subject: [PATCH 06/13] Corrected changelog format --- plugins/woocommerce/tests/e2e/api-core-tests/CHANGELOG.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/woocommerce/tests/e2e/api-core-tests/CHANGELOG.md b/plugins/woocommerce/tests/e2e/api-core-tests/CHANGELOG.md index 534e9720d8d..782ac2ca3c5 100644 --- a/plugins/woocommerce/tests/e2e/api-core-tests/CHANGELOG.md +++ b/plugins/woocommerce/tests/e2e/api-core-tests/CHANGELOG.md @@ -1,5 +1,7 @@ # 0.1.0 - Initial/beta release -- Added API tests for the Coupons API. -- Added API tests for the Refunds API. + +## Added +- Coupons API Tests +- Refunds API Tests From ea69de368c2b95e95a299b9c77eac36676c9672d Mon Sep 17 00:00:00 2001 From: Rodel Date: Thu, 21 Oct 2021 16:59:43 +0800 Subject: [PATCH 07/13] Added changelog entry --- plugins/woocommerce/tests/e2e/api-core-tests/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/woocommerce/tests/e2e/api-core-tests/CHANGELOG.md b/plugins/woocommerce/tests/e2e/api-core-tests/CHANGELOG.md index 782ac2ca3c5..0db666cd1f5 100644 --- a/plugins/woocommerce/tests/e2e/api-core-tests/CHANGELOG.md +++ b/plugins/woocommerce/tests/e2e/api-core-tests/CHANGELOG.md @@ -5,3 +5,4 @@ ## Added - Coupons API Tests - Refunds API Tests +- Products API Tests \ No newline at end of file From 9104089b01f598b7721ab4ee4b0b9cc754c8cd3c Mon Sep 17 00:00:00 2001 From: Rodel Date: Mon, 25 Oct 2021 20:02:17 +0800 Subject: [PATCH 08/13] Split products tests --- .../tests/products/product-list.test.js | 778 +++++++++++++++ .../tests/products/products-crud.test.js | 133 +++ .../tests/products/products.test.js | 893 ------------------ 3 files changed, 911 insertions(+), 893 deletions(-) create mode 100644 plugins/woocommerce/tests/e2e/api-core-tests/tests/products/product-list.test.js create mode 100644 plugins/woocommerce/tests/e2e/api-core-tests/tests/products/products-crud.test.js delete mode 100644 plugins/woocommerce/tests/e2e/api-core-tests/tests/products/products.test.js diff --git a/plugins/woocommerce/tests/e2e/api-core-tests/tests/products/product-list.test.js b/plugins/woocommerce/tests/e2e/api-core-tests/tests/products/product-list.test.js new file mode 100644 index 00000000000..ff83f2b364c --- /dev/null +++ b/plugins/woocommerce/tests/e2e/api-core-tests/tests/products/product-list.test.js @@ -0,0 +1,778 @@ +/** + * Internal dependencies + */ + const { createSampleData, deleteSampleData } = require( '../../data/products' ); + const { productsApi } = require('../../endpoints/products'); + + /** + * Tests for the WooCommerce "List all products" API. + * + * @group api + * @group products + * + */ + describe( 'Products API tests: List All Products', () => { + + 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 ] ); + } ); + } ); + } ); + } ); + } ); + \ No newline at end of file diff --git a/plugins/woocommerce/tests/e2e/api-core-tests/tests/products/products-crud.test.js b/plugins/woocommerce/tests/e2e/api-core-tests/tests/products/products-crud.test.js new file mode 100644 index 00000000000..d2df82a4d93 --- /dev/null +++ b/plugins/woocommerce/tests/e2e/api-core-tests/tests/products/products-crud.test.js @@ -0,0 +1,133 @@ +/** + * Internal dependencies + */ + const { productsApi } = require('../../endpoints/products'); + + /** + * Tests for the WooCommerce Products API. + * These tests cover API endpoints for creating, retrieving, updating, and deleting a single product. + * + * @group api + * @group products + * + */ + describe( 'Products CRUD API tests', () => { + + let productId; + + it( 'can add a simple product', async () => { + const product = { + name: 'A Simple Product', + regular_price: '25', + description: 'Description for this simple product.', + short_description: 'Shorter description.', + }; + const { status, body } = await productsApi.create.product( product ); + productId = body.id; + + expect( status ).toEqual( productsApi.create.responseCode ); + expect( productId ).toBeDefined(); + expect( body ).toMatchObject( product ); + expect( body.type ).toEqual( 'simple' ); + expect( body.status ).toEqual( 'publish' ); + expect( body.virtual ).toEqual( false ); + expect( body.downloadable ).toEqual( false ); + expect( body.shipping_required ).toEqual( true ); + } ); + + it( 'can add a virtual product', async () => { + const virtualProduct = { + name: 'A Virtual Product', + regular_price: '10', + virtual: true, + }; + const { status, body } = await productsApi.create.product( + virtualProduct + ); + const virtualProductId = body.id; + + expect( status ).toEqual( productsApi.create.responseCode ); + expect( virtualProductId ).toBeDefined(); + expect( body ).toMatchObject( virtualProduct ); + expect( body.type ).toEqual( 'simple' ); + expect( body.status ).toEqual( 'publish' ); + expect( body.shipping_required ).toEqual( false ); + + // Cleanup: Delete the virtual product + await productsApi.delete.product( virtualProductId, true ); + } ); + + it( 'can add a variable product', async () => { + const variableProduct = { + name: 'A Variable Product', + type: 'variable', + attributes: [ + { + name: 'Colour', + visible: true, + variation: true, + options: [ 'Red', 'Green', 'Blue' ], + }, + { + name: 'Size', + visible: true, + variation: true, + options: [ 'Small', 'Medium', 'Large' ], + }, + { + name: 'Logo', + visible: true, + variation: true, + options: [ 'Woo', 'WordPress' ], + }, + ], + }; + const { status, body } = await productsApi.create.product( + variableProduct + ); + const variableProductId = body.id; + + expect( status ).toEqual( productsApi.create.responseCode ); + expect( variableProductId ).toBeDefined(); + expect( body ).toMatchObject( variableProduct ); + expect( body.status ).toEqual( 'publish' ); + + // Cleanup: Delete the variable product + await productsApi.delete.product( variableProductId, true ); + } ); + + it( 'can view a single product', async () => { + const { status, body } = await productsApi.retrieve.product( productId ); + + expect( status ).toEqual( productsApi.retrieve.responseCode ); + expect( body.id ).toEqual( productId ); + } ); + + it( 'can update a single product', async () => { + const updatePayload = { + regular_price: '25.99', + }; + const { status, body } = await productsApi.update.product( + productId, + updatePayload + ); + + expect( status ).toEqual( productsApi.update.responseCode ); + expect( body.id ).toEqual( productId ); + expect( body.regular_price ).toEqual( updatePayload.regular_price ); + } ); + + it( 'can delete a product', async () => { + const { status, body } = await productsApi.delete.product( productId, true ); + + expect( status ).toEqual( productsApi.delete.responseCode ); + expect( body.id ).toEqual( productId ); + + // Verify that the product can no longer be retrieved. + const { + status: retrieveDeletedProductStatus, + } = await productsApi.retrieve.product( productId ); + expect( retrieveDeletedProductStatus ).toEqual( 404 ); + } ); + } ); + \ No newline at end of file diff --git a/plugins/woocommerce/tests/e2e/api-core-tests/tests/products/products.test.js b/plugins/woocommerce/tests/e2e/api-core-tests/tests/products/products.test.js deleted file mode 100644 index b111d55391b..00000000000 --- a/plugins/woocommerce/tests/e2e/api-core-tests/tests/products/products.test.js +++ /dev/null @@ -1,893 +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; - let productId; - - 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 ] ); - } ); - } ); - } ); - } ); - - it( 'can add a simple product', async () => { - const product = { - name: 'A Simple Product', - regular_price: '25', - description: 'Description for this simple product.', - short_description: 'Shorter description.', - }; - const { status, body } = await productsApi.create.product( product ); - productId = body.id; - - expect( status ).toEqual( productsApi.create.responseCode ); - expect( productId ).toBeDefined(); - expect( body ).toMatchObject( product ); - expect( body.type ).toEqual( 'simple' ); - expect( body.status ).toEqual( 'publish' ); - expect( body.virtual ).toEqual( false ); - expect( body.downloadable ).toEqual( false ); - expect( body.shipping_required ).toEqual( true ); - } ); - - it( 'can add a virtual product', async () => { - const virtualProduct = { - name: 'A Virtual Product', - regular_price: '10', - virtual: true, - }; - const { status, body } = await productsApi.create.product( - virtualProduct - ); - const virtualProductId = body.id; - - expect( status ).toEqual( productsApi.create.responseCode ); - expect( virtualProductId ).toBeDefined(); - expect( body ).toMatchObject( virtualProduct ); - expect( body.type ).toEqual( 'simple' ); - expect( body.status ).toEqual( 'publish' ); - expect( body.shipping_required ).toEqual( false ); - - // Cleanup: Delete the virtual product - await productsApi.delete.product( virtualProductId, true ); - } ); - - it( 'can add a variable product', async () => { - const variableProduct = { - name: 'A Variable Product', - type: 'variable', - attributes: [ - { - name: 'Colour', - visible: true, - variation: true, - options: [ 'Red', 'Green', 'Blue' ], - }, - { - name: 'Size', - visible: true, - variation: true, - options: [ 'Small', 'Medium', 'Large' ], - }, - { - name: 'Logo', - visible: true, - variation: true, - options: [ 'Woo', 'WordPress' ], - }, - ], - }; - const { status, body } = await productsApi.create.product( - variableProduct - ); - const variableProductId = body.id; - - expect( status ).toEqual( productsApi.create.responseCode ); - expect( variableProductId ).toBeDefined(); - expect( body ).toMatchObject( variableProduct ); - expect( body.status ).toEqual( 'publish' ); - - // Cleanup: Delete the variable product - await productsApi.delete.product( variableProductId, true ); - } ); - - it( 'can view a single product', async () => { - const { status, body } = await productsApi.retrieve.product( productId ); - - expect( status ).toEqual( productsApi.retrieve.responseCode ); - expect( body.id ).toEqual( productId ); - } ); - - it( 'can update a single product', async () => { - const updatePayload = { - regular_price: '25.99', - }; - const { status, body } = await productsApi.update.product( - productId, - updatePayload - ); - - expect( status ).toEqual( productsApi.update.responseCode ); - expect( body.id ).toEqual( productId ); - expect( body.regular_price ).toEqual( updatePayload.regular_price ); - } ); - - it( 'can delete a product', async () => { - const { status, body } = await productsApi.delete.product( productId, true ); - - expect( status ).toEqual( productsApi.delete.responseCode ); - expect( body.id ).toEqual( productId ); - - // Verify that the product can no longer be retrieved. - const { - status: retrieveDeletedProductStatus, - } = await productsApi.retrieve.product( productId ); - expect( retrieveDeletedProductStatus ).toEqual( 404 ); - } ); -} ); From 04d6e7acdb512c86ffa7f3bda57662476d18648d Mon Sep 17 00:00:00 2001 From: Rodel Date: Mon, 25 Oct 2021 20:03:46 +0800 Subject: [PATCH 09/13] Minor test suite name change --- .../e2e/api-core-tests/tests/products/products-crud.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce/tests/e2e/api-core-tests/tests/products/products-crud.test.js b/plugins/woocommerce/tests/e2e/api-core-tests/tests/products/products-crud.test.js index d2df82a4d93..7b7364a7548 100644 --- a/plugins/woocommerce/tests/e2e/api-core-tests/tests/products/products-crud.test.js +++ b/plugins/woocommerce/tests/e2e/api-core-tests/tests/products/products-crud.test.js @@ -11,7 +11,7 @@ * @group products * */ - describe( 'Products CRUD API tests', () => { + describe( 'Products API tests: CRUD', () => { let productId; From 054183cb5ae1d8d62db8d3c23032287b778af978 Mon Sep 17 00:00:00 2001 From: Rodel Date: Tue, 26 Oct 2021 15:40:00 +0800 Subject: [PATCH 10/13] Renamed data file for product-list tests --- .../e2e/api-core-tests/data/{products.js => product-list.js} | 0 .../e2e/api-core-tests/tests/products/product-list.test.js | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename plugins/woocommerce/tests/e2e/api-core-tests/data/{products.js => product-list.js} (100%) diff --git a/plugins/woocommerce/tests/e2e/api-core-tests/data/products.js b/plugins/woocommerce/tests/e2e/api-core-tests/data/product-list.js similarity index 100% rename from plugins/woocommerce/tests/e2e/api-core-tests/data/products.js rename to plugins/woocommerce/tests/e2e/api-core-tests/data/product-list.js diff --git a/plugins/woocommerce/tests/e2e/api-core-tests/tests/products/product-list.test.js b/plugins/woocommerce/tests/e2e/api-core-tests/tests/products/product-list.test.js index ff83f2b364c..cff3e18d1b6 100644 --- a/plugins/woocommerce/tests/e2e/api-core-tests/tests/products/product-list.test.js +++ b/plugins/woocommerce/tests/e2e/api-core-tests/tests/products/product-list.test.js @@ -1,7 +1,7 @@ /** * Internal dependencies */ - const { createSampleData, deleteSampleData } = require( '../../data/products' ); + const { createSampleData, deleteSampleData } = require( '../../data/product-list' ); const { productsApi } = require('../../endpoints/products'); /** From 751dc5da1fd024813bb398f19c7f3db84cb7cc06 Mon Sep 17 00:00:00 2001 From: Rodel Date: Tue, 26 Oct 2021 16:27:20 +0800 Subject: [PATCH 11/13] Data file for products CRUD tests --- .../e2e/api-core-tests/data/products-crud.js | 61 +++++ .../tests/products/products-crud.test.js | 233 ++++++++---------- 2 files changed, 165 insertions(+), 129 deletions(-) create mode 100644 plugins/woocommerce/tests/e2e/api-core-tests/data/products-crud.js diff --git a/plugins/woocommerce/tests/e2e/api-core-tests/data/products-crud.js b/plugins/woocommerce/tests/e2e/api-core-tests/data/products-crud.js new file mode 100644 index 00000000000..8c73945cc70 --- /dev/null +++ b/plugins/woocommerce/tests/e2e/api-core-tests/data/products-crud.js @@ -0,0 +1,61 @@ +/** + * This file contains objects that can be used as test data for scenarios around creating, retrieivng, updating, and deleting products. + * + * For more details on the Product properties, see: + * + * https://woocommerce.github.io/woocommerce-rest-api-docs/#products + * + */ + +/** + * A simple product. + */ +const simpleProduct = { + name: 'A Simple Product', + regular_price: '25', + description: 'Description for this simple product.', + short_description: 'Shorter description.', +}; + +/** + * A virtual product + */ +const virtualProduct = { + name: 'A Virtual Product', + regular_price: '10', + virtual: true, +}; + +/** + * A variable product + */ +const variableProduct = { + name: 'A Variable Product', + type: 'variable', + attributes: [ + { + name: 'Colour', + visible: true, + variation: true, + options: [ 'Red', 'Green', 'Blue' ], + }, + { + name: 'Size', + visible: true, + variation: true, + options: [ 'Small', 'Medium', 'Large' ], + }, + { + name: 'Logo', + visible: true, + variation: true, + options: [ 'Woo', 'WordPress' ], + }, + ], +}; + +module.exports = { + simpleProduct, + virtualProduct, + variableProduct, +}; diff --git a/plugins/woocommerce/tests/e2e/api-core-tests/tests/products/products-crud.test.js b/plugins/woocommerce/tests/e2e/api-core-tests/tests/products/products-crud.test.js index 7b7364a7548..4278edec238 100644 --- a/plugins/woocommerce/tests/e2e/api-core-tests/tests/products/products-crud.test.js +++ b/plugins/woocommerce/tests/e2e/api-core-tests/tests/products/products-crud.test.js @@ -1,133 +1,108 @@ /** * Internal dependencies */ - const { productsApi } = require('../../endpoints/products'); +const { + simpleProduct, + virtualProduct, + variableProduct, +} = require( '../../data/products-crud' ); +const { productsApi } = require( '../../endpoints/products' ); - /** - * Tests for the WooCommerce Products API. - * These tests cover API endpoints for creating, retrieving, updating, and deleting a single product. - * - * @group api - * @group products - * - */ - describe( 'Products API tests: CRUD', () => { - - let productId; - - it( 'can add a simple product', async () => { - const product = { - name: 'A Simple Product', - regular_price: '25', - description: 'Description for this simple product.', - short_description: 'Shorter description.', - }; - const { status, body } = await productsApi.create.product( product ); - productId = body.id; - - expect( status ).toEqual( productsApi.create.responseCode ); - expect( productId ).toBeDefined(); - expect( body ).toMatchObject( product ); - expect( body.type ).toEqual( 'simple' ); - expect( body.status ).toEqual( 'publish' ); - expect( body.virtual ).toEqual( false ); - expect( body.downloadable ).toEqual( false ); - expect( body.shipping_required ).toEqual( true ); - } ); - - it( 'can add a virtual product', async () => { - const virtualProduct = { - name: 'A Virtual Product', - regular_price: '10', - virtual: true, - }; - const { status, body } = await productsApi.create.product( - virtualProduct - ); - const virtualProductId = body.id; - - expect( status ).toEqual( productsApi.create.responseCode ); - expect( virtualProductId ).toBeDefined(); - expect( body ).toMatchObject( virtualProduct ); - expect( body.type ).toEqual( 'simple' ); - expect( body.status ).toEqual( 'publish' ); - expect( body.shipping_required ).toEqual( false ); - - // Cleanup: Delete the virtual product - await productsApi.delete.product( virtualProductId, true ); - } ); - - it( 'can add a variable product', async () => { - const variableProduct = { - name: 'A Variable Product', - type: 'variable', - attributes: [ - { - name: 'Colour', - visible: true, - variation: true, - options: [ 'Red', 'Green', 'Blue' ], - }, - { - name: 'Size', - visible: true, - variation: true, - options: [ 'Small', 'Medium', 'Large' ], - }, - { - name: 'Logo', - visible: true, - variation: true, - options: [ 'Woo', 'WordPress' ], - }, - ], - }; - const { status, body } = await productsApi.create.product( - variableProduct - ); - const variableProductId = body.id; - - expect( status ).toEqual( productsApi.create.responseCode ); - expect( variableProductId ).toBeDefined(); - expect( body ).toMatchObject( variableProduct ); - expect( body.status ).toEqual( 'publish' ); - - // Cleanup: Delete the variable product - await productsApi.delete.product( variableProductId, true ); - } ); - - it( 'can view a single product', async () => { - const { status, body } = await productsApi.retrieve.product( productId ); - - expect( status ).toEqual( productsApi.retrieve.responseCode ); - expect( body.id ).toEqual( productId ); - } ); - - it( 'can update a single product', async () => { - const updatePayload = { - regular_price: '25.99', - }; - const { status, body } = await productsApi.update.product( - productId, - updatePayload - ); - - expect( status ).toEqual( productsApi.update.responseCode ); - expect( body.id ).toEqual( productId ); - expect( body.regular_price ).toEqual( updatePayload.regular_price ); - } ); - - it( 'can delete a product', async () => { - const { status, body } = await productsApi.delete.product( productId, true ); - - expect( status ).toEqual( productsApi.delete.responseCode ); - expect( body.id ).toEqual( productId ); - - // Verify that the product can no longer be retrieved. - const { - status: retrieveDeletedProductStatus, - } = await productsApi.retrieve.product( productId ); - expect( retrieveDeletedProductStatus ).toEqual( 404 ); - } ); - } ); - \ No newline at end of file +/** + * Tests for the WooCommerce Products API. + * These tests cover API endpoints for creating, retrieving, updating, and deleting a single product. + * + * @group api + * @group products + * + */ +describe( 'Products API tests: CRUD', () => { + let productId; + + it( 'can add a simple product', async () => { + const { status, body } = await productsApi.create.product( + simpleProduct + ); + productId = body.id; + + expect( status ).toEqual( productsApi.create.responseCode ); + expect( typeof productId ).toEqual( 'number' ); + expect( body ).toMatchObject( simpleProduct ); + expect( body.type ).toEqual( 'simple' ); + expect( body.status ).toEqual( 'publish' ); + expect( body.virtual ).toEqual( false ); + expect( body.downloadable ).toEqual( false ); + expect( body.shipping_required ).toEqual( true ); + } ); + + it( 'can add a virtual product', async () => { + const { status, body } = await productsApi.create.product( + virtualProduct + ); + const virtualProductId = body.id; + + expect( status ).toEqual( productsApi.create.responseCode ); + expect( typeof virtualProductId ).toEqual( 'number' ); + expect( body ).toMatchObject( virtualProduct ); + expect( body.type ).toEqual( 'simple' ); + expect( body.status ).toEqual( 'publish' ); + expect( body.shipping_required ).toEqual( false ); + + // Cleanup: Delete the virtual product + await productsApi.delete.product( virtualProductId, true ); + } ); + + it( 'can add a variable product', async () => { + const { status, body } = await productsApi.create.product( + variableProduct + ); + const variableProductId = body.id; + + expect( status ).toEqual( productsApi.create.responseCode ); + expect( typeof variableProductId ).toEqual( 'number' ); + expect( body ).toMatchObject( variableProduct ); + expect( body.status ).toEqual( 'publish' ); + + // Cleanup: Delete the variable product + await productsApi.delete.product( variableProductId, true ); + } ); + + it( 'can view a single product', async () => { + const { status, body } = await productsApi.retrieve.product( + productId + ); + + expect( status ).toEqual( productsApi.retrieve.responseCode ); + expect( body.id ).toEqual( productId ); + } ); + + it( 'can update a single product', async () => { + const updatePayload = { + regular_price: '25.99', + }; + const { status, body } = await productsApi.update.product( + productId, + updatePayload + ); + + expect( status ).toEqual( productsApi.update.responseCode ); + expect( body.id ).toEqual( productId ); + expect( body.regular_price ).toEqual( updatePayload.regular_price ); + } ); + + it( 'can delete a product', async () => { + const { status, body } = await productsApi.delete.product( + productId, + true + ); + + expect( status ).toEqual( productsApi.delete.responseCode ); + expect( body.id ).toEqual( productId ); + + // Verify that the product can no longer be retrieved. + const { + status: retrieveDeletedProductStatus, + } = await productsApi.retrieve.product( productId ); + expect( retrieveDeletedProductStatus ).toEqual( 404 ); + } ); +} ); From 817db0aa14a35db453d48ff028d3ad6e2925819e Mon Sep 17 00:00:00 2001 From: Rodel Date: Tue, 26 Oct 2021 18:14:36 +0800 Subject: [PATCH 12/13] Minor doc update --- .../woocommerce/tests/e2e/api-core-tests/data/products-crud.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/woocommerce/tests/e2e/api-core-tests/data/products-crud.js b/plugins/woocommerce/tests/e2e/api-core-tests/data/products-crud.js index 8c73945cc70..b42b4deb2a6 100644 --- a/plugins/woocommerce/tests/e2e/api-core-tests/data/products-crud.js +++ b/plugins/woocommerce/tests/e2e/api-core-tests/data/products-crud.js @@ -8,7 +8,7 @@ */ /** - * A simple product. + * A simple, physical product. */ const simpleProduct = { name: 'A Simple Product', From ec6f839ee299b7002b615d72d1f9282086f541ac Mon Sep 17 00:00:00 2001 From: Rodel Date: Tue, 26 Oct 2021 18:14:57 +0800 Subject: [PATCH 13/13] Added tests for batch update products --- .../tests/products/products-crud.test.js | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/plugins/woocommerce/tests/e2e/api-core-tests/tests/products/products-crud.test.js b/plugins/woocommerce/tests/e2e/api-core-tests/tests/products/products-crud.test.js index 4278edec238..aefb30c0573 100644 --- a/plugins/woocommerce/tests/e2e/api-core-tests/tests/products/products-crud.test.js +++ b/plugins/woocommerce/tests/e2e/api-core-tests/tests/products/products-crud.test.js @@ -6,6 +6,7 @@ const { virtualProduct, variableProduct, } = require( '../../data/products-crud' ); +const { batch } = require( '../../data/shared/batch-update' ); const { productsApi } = require( '../../endpoints/products' ); /** @@ -105,4 +106,90 @@ describe( 'Products API tests: CRUD', () => { } = await productsApi.retrieve.product( productId ); expect( retrieveDeletedProductStatus ).toEqual( 404 ); } ); + + describe( 'Batch update products', () => { + const product1 = { ...simpleProduct, name: 'Batch Created Product 1' }; + const product2 = { ...simpleProduct, name: 'Batch Created Product 2' }; + const expectedProducts = [ product1, product2 ]; + + it( 'can batch create products', async () => { + // Send request to batch create products + const batchCreatePayload = batch( 'create', expectedProducts ); + const { status, body } = await productsApi.batch.products( + batchCreatePayload + ); + const actualBatchCreatedProducts = body.create; + + expect( status ).toEqual( productsApi.batch.responseCode ); + expect( actualBatchCreatedProducts ).toHaveLength( + expectedProducts.length + ); + + // Verify id and name of the batch created products + for ( let i = 0; i < actualBatchCreatedProducts.length; i++ ) { + const { id, name } = actualBatchCreatedProducts[ i ]; + + expect( typeof id ).toEqual( 'number' ); + expect( name ).toEqual( expectedProducts[ i ].name ); + + // Save the product ID for use later in the batch update and delete tests + expectedProducts[ i ].id = id; + } + } ); + + it( 'can batch update products', async () => { + // Send request to batch update the regular price + const newRegularPrice = '12.34'; + for ( let i = 0; i < expectedProducts.length; i++ ) { + expectedProducts[ i ].regular_price = newRegularPrice; + } + const batchUpdatePayload = batch( 'update', expectedProducts ); + const { status, body } = await productsApi.batch.products( + batchUpdatePayload + ); + const actualUpdatedProducts = body.update; + + expect( status ).toEqual( productsApi.batch.responseCode ); + expect( actualUpdatedProducts ).toHaveLength( + expectedProducts.length + ); + + // Verify that the regular price of each product was updated + for ( let i = 0; i < actualUpdatedProducts.length; i++ ) { + const { id, regular_price } = actualUpdatedProducts[ i ]; + + expect( id ).toEqual( expectedProducts[ i ].id ); + expect( regular_price ).toEqual( newRegularPrice ); + } + } ); + + it( 'can batch delete products', async () => { + // Send request to batch delete the products created earlier + const idsToDelete = expectedProducts.map( ( { id } ) => id ); + const batchDeletePayload = batch( 'delete', idsToDelete ); + const { status, body } = await productsApi.batch.products( + batchDeletePayload + ); + const actualBatchDeletedProducts = body.delete; + + expect( status ).toEqual( productsApi.batch.responseCode ); + expect( actualBatchDeletedProducts ).toHaveLength( + expectedProducts.length + ); + + // Verify that the correct products were deleted + for ( let i = 0; i < actualBatchDeletedProducts.length; i++ ) { + const deletedProduct = actualBatchDeletedProducts[ i ]; + + expect( deletedProduct ).toMatchObject( expectedProducts[ i ] ); + } + + // Verify that the deleted product ID's can no longer be retrieved + for ( const id of idsToDelete ) { + const { status } = await productsApi.retrieve.product( id ); + + expect( status ).toEqual( 404 ); + } + } ); + } ); } );