From 3f7dbcd43b87b8806a5b7e5f5bf048026594bf9d Mon Sep 17 00:00:00 2001 From: Jeff Stieler Date: Tue, 30 Nov 2021 13:57:55 -0500 Subject: [PATCH 1/4] Add coupon meta data to backend and API orders. --- .../includes/abstracts/abstract-wc-order.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/plugins/woocommerce/includes/abstracts/abstract-wc-order.php b/plugins/woocommerce/includes/abstracts/abstract-wc-order.php index ce04a4c8786..b0cb6e02780 100644 --- a/plugins/woocommerce/includes/abstracts/abstract-wc-order.php +++ b/plugins/woocommerce/includes/abstracts/abstract-wc-order.php @@ -1323,6 +1323,16 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order { if ( ! $item_id ) { $coupon_item = new WC_Order_Item_Coupon(); $coupon_item->set_code( $coupon_code ); + + // Add coupon data. + $coupon_id = wc_get_coupon_id_by_code( $coupon_code ); + $coupon = new WC_Coupon( $coupon_id ); + + // Avoid storing used_by - it's not needed and can get large. + $coupon_data = $coupon->get_data(); + unset( $coupon_data['used_by'] ); + + $coupon_item->add_meta_data( 'coupon_data', $coupon_data ); } else { $coupon_item = $this->get_item( $item_id, false ); } From 35c7ad75d535f0b43301203e7b59a3d70d9e97fa Mon Sep 17 00:00:00 2001 From: Jeff Stieler Date: Tue, 30 Nov 2021 14:58:18 -0500 Subject: [PATCH 2/4] Add unit test. --- .../class-wc-abstract-order-test.php | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/plugins/woocommerce/tests/php/includes/abstracts/class-wc-abstract-order-test.php b/plugins/woocommerce/tests/php/includes/abstracts/class-wc-abstract-order-test.php index 6be91389917..5a8cdf5e67b 100644 --- a/plugins/woocommerce/tests/php/includes/abstracts/class-wc-abstract-order-test.php +++ b/plugins/woocommerce/tests/php/includes/abstracts/class-wc-abstract-order-test.php @@ -246,4 +246,27 @@ class WC_Abstract_Order_Test extends WC_Unit_Test_Case { $this->assertEquals( 0, ( new WC_Coupon( $coupon_code_2 ) )->get_usage_count() ); $this->assertEquals( 0, ( new WC_Coupon( $coupon_code_3 ) )->get_usage_count() ); } + + /** + * Test apply_coupon() stores coupon meta data. + * See: https://github.com/woocommerce/woocommerce/issues/28166. + */ + public function test_apply_coupon_stores_meta_data() { + $coupon_code = 'coupon_test_meta_data'; + $coupon = WC_Helper_Coupon::create_coupon( $coupon_code ); + $order = WC_Helper_Order::create_order(); + $order->set_status( 'processing' ); + $order->save(); + $order->apply_coupon( $coupon_code ); + + $coupon_items = $order->get_items( 'coupon' ); + $this->assertCount( 1, $coupon_items ); + + $coupon_data = ( current( $coupon_items ) )->get_meta( 'coupon_data' ); + $this->assertNotEmpty( $coupon_data, 'WC_Order_Item_Coupon missing `coupon_data` meta.' ); + $this->assertArrayHasKey( 'id', $coupon_data ); + $this->assertArrayHasKey( 'code', $coupon_data ); + $this->assertEquals( $coupon->get_id(), $coupon_data['id'] ); + $this->assertEquals( $coupon_code, $coupon_data['code'] ); + } } From 4366de753ea21cdb863ffd0de0dc3cb4a10316c9 Mon Sep 17 00:00:00 2001 From: Jeff Stieler Date: Tue, 30 Nov 2021 15:18:40 -0500 Subject: [PATCH 3/4] Format test file. --- .../tests/coupons/coupons.test.js | 256 +++++++++--------- 1 file changed, 133 insertions(+), 123 deletions(-) diff --git a/packages/js/api-core-tests/tests/coupons/coupons.test.js b/packages/js/api-core-tests/tests/coupons/coupons.test.js index 74caba1cdf9..dd4d45ca138 100644 --- a/packages/js/api-core-tests/tests/coupons/coupons.test.js +++ b/packages/js/api-core-tests/tests/coupons/coupons.test.js @@ -1,6 +1,6 @@ -const { couponsApi } = require('../../endpoints/coupons'); -const { ordersApi } = require('../../endpoints/orders'); -const { coupon, order } = require('../../data'); +const { couponsApi } = require( '../../endpoints/coupons' ); +const { ordersApi } = require( '../../endpoints/orders' ); +const { coupon, order } = require( '../../data' ); /** * Tests for the WooCommerce Coupons API. @@ -9,37 +9,39 @@ const { coupon, order } = require('../../data'); * @group coupons * */ -describe('Coupons API tests', () => { +describe( 'Coupons API tests', () => { let couponId; - it('can create a coupon', async () => { + it( 'can create a coupon', async () => { const testCoupon = { ...coupon, - code: `${coupon.code}-${Date.now()}`, + code: `${ coupon.code }-${ Date.now() }`, }; - const response = await couponsApi.create.coupon(testCoupon); + const response = await couponsApi.create.coupon( testCoupon ); - expect(response.statusCode).toEqual(couponsApi.create.responseCode); - expect(response.body.id).toBeDefined(); + expect( response.statusCode ).toEqual( couponsApi.create.responseCode ); + expect( response.body.id ).toBeDefined(); couponId = response.body.id; // Validate the created coupon object has the correct code, amount, and discount type - expect(response.body).toEqual( - expect.objectContaining({ + expect( response.body ).toEqual( + expect.objectContaining( { code: testCoupon.code, - amount: Number(coupon.amount).toFixed(2), + amount: Number( coupon.amount ).toFixed( 2 ), discount_type: coupon.discount_type, - }) + } ) ); - }); + } ); - it('can retrieve a coupon', async () => { - const response = await couponsApi.retrieve.coupon(couponId); - expect(response.statusCode).toEqual(couponsApi.retrieve.responseCode); - expect(response.body.id).toEqual(couponId); - }); + it( 'can retrieve a coupon', async () => { + const response = await couponsApi.retrieve.coupon( couponId ); + expect( response.statusCode ).toEqual( + couponsApi.retrieve.responseCode + ); + expect( response.body.id ).toEqual( couponId ); + } ); - it('can update a coupon', async () => { + it( 'can update a coupon', async () => { const updatedCouponDetails = { description: '10% off storewide', maximum_amount: '500.00', @@ -50,40 +52,40 @@ describe('Coupons API tests', () => { couponId, updatedCouponDetails ); - expect(response.statusCode).toEqual(couponsApi.update.responseCode); - expect(response.body).toEqual( - expect.objectContaining(updatedCouponDetails) + expect( response.statusCode ).toEqual( couponsApi.update.responseCode ); + expect( response.body ).toEqual( + expect.objectContaining( updatedCouponDetails ) ); - }); + } ); - it('can permanently delete a coupon', async () => { - const response = await couponsApi.delete.coupon(couponId, true); + it( 'can permanently delete a coupon', async () => { + const response = await couponsApi.delete.coupon( couponId, true ); - expect(response.statusCode).toEqual(couponsApi.delete.responseCode); + expect( response.statusCode ).toEqual( couponsApi.delete.responseCode ); - const getCouponResponse = await couponsApi.retrieve.coupon(couponId); - expect(getCouponResponse.statusCode).toEqual(404); - }); + const getCouponResponse = await couponsApi.retrieve.coupon( couponId ); + expect( getCouponResponse.statusCode ).toEqual( 404 ); + } ); - describe('Batch update coupons', () => { + describe( 'Batch update coupons', () => { /** * Coupons to be created, updated, and deleted. */ const expectedCoupons = [ { - code: `batchcoupon-${Date.now()}`, + code: `batchcoupon-${ Date.now() }`, discount_type: 'percent', amount: '10', free_shipping: false, }, { - code: `batchcoupon-${Date.now() + 1}`, + code: `batchcoupon-${ Date.now() + 1 }`, discount_type: 'percent', amount: '20', }, ]; - it('can batch create coupons', async () => { + it( 'can batch create coupons', async () => { // Batch create 2 new coupons. const batchCreatePayload = { create: expectedCoupons, @@ -91,36 +93,36 @@ describe('Coupons API tests', () => { const batchCreateResponse = await couponsApi.batch.coupons( batchCreatePayload ); - expect(batchCreateResponse.status).toEqual( + expect( batchCreateResponse.status ).toEqual( couponsApi.batch.responseCode ); // Verify that the 2 new coupons were created const actualCoupons = batchCreateResponse.body.create; - expect(actualCoupons).toHaveLength(expectedCoupons.length); - for (let i = 0; i < actualCoupons.length; i++) { - const { id, code } = actualCoupons[i]; - const expectedCouponCode = expectedCoupons[i].code; + expect( actualCoupons ).toHaveLength( expectedCoupons.length ); + for ( let i = 0; i < actualCoupons.length; i++ ) { + const { id, code } = actualCoupons[ i ]; + const expectedCouponCode = expectedCoupons[ i ].code; - expect(id).toBeDefined(); - expect(code).toEqual(expectedCouponCode); + expect( id ).toBeDefined(); + expect( code ).toEqual( expectedCouponCode ); // Save the coupon id - expectedCoupons[i].id = id; + expectedCoupons[ i ].id = id; } - }); + } ); - it('can batch update coupons', async () => { + it( 'can batch update coupons', async () => { // Update the 1st coupon to free shipping. // Update the amount of the 2nd coupon to 25. const batchUpdatePayload = { update: [ { - id: expectedCoupons[0].id, + id: expectedCoupons[ 0 ].id, free_shipping: true, }, { - id: expectedCoupons[1].id, + id: expectedCoupons[ 1 ].id, amount: '25.00', }, ], @@ -131,23 +133,23 @@ describe('Coupons API tests', () => { // Verify the response code and the number of coupons that were updated. const updatedCoupons = batchUpdateResponse.body.update; - expect(batchUpdateResponse.status).toEqual( + expect( batchUpdateResponse.status ).toEqual( couponsApi.batch.responseCode ); - expect(updatedCoupons).toHaveLength(expectedCoupons.length); + expect( updatedCoupons ).toHaveLength( expectedCoupons.length ); // Verify that the 1st coupon was updated to free shipping. - expect(updatedCoupons[0].id).toEqual(expectedCoupons[0].id); - expect(updatedCoupons[0].free_shipping).toEqual(true); + expect( updatedCoupons[ 0 ].id ).toEqual( expectedCoupons[ 0 ].id ); + expect( updatedCoupons[ 0 ].free_shipping ).toEqual( true ); // Verify that the amount of the 2nd coupon was updated to 25. - expect(updatedCoupons[1].id).toEqual(expectedCoupons[1].id); - expect(updatedCoupons[1].amount).toEqual('25.00'); - }); + expect( updatedCoupons[ 1 ].id ).toEqual( expectedCoupons[ 1 ].id ); + expect( updatedCoupons[ 1 ].amount ).toEqual( '25.00' ); + } ); - it('can batch delete coupons', async () => { + it( 'can batch delete coupons', async () => { // Batch delete the 2 coupons. - const couponIdsToDelete = expectedCoupons.map(({ id }) => id); + const couponIdsToDelete = expectedCoupons.map( ( { id } ) => id ); const batchDeletePayload = { delete: couponIdsToDelete, }; @@ -157,151 +159,159 @@ describe('Coupons API tests', () => { // Verify that the response shows the 2 coupons. const deletedCouponIds = batchDeleteResponse.body.delete.map( - ({ id }) => id + ( { id } ) => id ); - expect(batchDeleteResponse.status).toEqual( + expect( batchDeleteResponse.status ).toEqual( couponsApi.batch.responseCode ); - expect(deletedCouponIds).toEqual(couponIdsToDelete); + expect( deletedCouponIds ).toEqual( couponIdsToDelete ); // Verify that the 2 deleted coupons cannot be retrieved. - for (const couponId of couponIdsToDelete) { - const { status } = await couponsApi.retrieve.coupon(couponId); + for ( const couponId of couponIdsToDelete ) { + const { status } = await couponsApi.retrieve.coupon( couponId ); - expect(status).toEqual(404); + expect( status ).toEqual( 404 ); } - }); - }); + } ); + } ); - describe('List coupons', () => { + describe( 'List coupons', () => { const allCoupons = [ { ...coupon, - code: `listcoupons-01-${Date.now()}`, - description: `description-01-${Date.now()}`, + code: `listcoupons-01-${ Date.now() }`, + description: `description-01-${ Date.now() }`, }, { ...coupon, - code: `listcoupons-02-${Date.now()}`, - description: `description-02-${Date.now()}`, + code: `listcoupons-02-${ Date.now() }`, + description: `description-02-${ Date.now() }`, }, { ...coupon, - code: `listcoupons-03-${Date.now()}`, - description: `description-03-${Date.now()}`, + code: `listcoupons-03-${ Date.now() }`, + description: `description-03-${ Date.now() }`, }, ]; - beforeAll(async () => { + beforeAll( async () => { // Create list of coupons for testing. - const response = await couponsApi.batch.coupons({ + const response = await couponsApi.batch.coupons( { create: allCoupons, - }); + } ); const actualCreatedCoupons = response.body.create; // Save their coupon ID's - for (const coupon of allCoupons) { + for ( const coupon of allCoupons ) { const { id } = actualCreatedCoupons.find( - ({ code }) => coupon.code === code + ( { code } ) => coupon.code === code ); coupon.id = id; } - }); + } ); - afterAll(async () => { + afterAll( async () => { // Clean up created coupons const batchDeletePayload = { - delete: allCoupons.map(({ id }) => id), + delete: allCoupons.map( ( { id } ) => id ), }; - await couponsApi.batch.coupons(batchDeletePayload); - }); + await couponsApi.batch.coupons( batchDeletePayload ); + } ); - it('can list all coupons by default', async () => { + it( 'can list all coupons by default', async () => { const response = await couponsApi.listAll.coupons(); const listedCoupons = response.body; - const actualCouponIdsList = listedCoupons.map(({ id }) => id); - const expectedCouponIdsList = allCoupons.map(({ id }) => id); + const actualCouponIdsList = listedCoupons.map( ( { id } ) => id ); + const expectedCouponIdsList = allCoupons.map( ( { id } ) => id ); - expect(response.status).toEqual(couponsApi.listAll.responseCode); - expect(actualCouponIdsList).toEqual( - expect.arrayContaining(expectedCouponIdsList) + expect( response.status ).toEqual( + couponsApi.listAll.responseCode ); - }); + expect( actualCouponIdsList ).toEqual( + expect.arrayContaining( expectedCouponIdsList ) + ); + } ); - it('can limit result set to matching code', async () => { - const matchingCoupon = allCoupons[1]; + it( 'can limit result set to matching code', async () => { + const matchingCoupon = allCoupons[ 1 ]; const payload = { code: matchingCoupon.code }; - const { status, body } = await couponsApi.listAll.coupons(payload); + const { status, body } = await couponsApi.listAll.coupons( + payload + ); - expect(status).toEqual(couponsApi.listAll.responseCode); - expect(body).toHaveLength(1); - expect(body[0].id).toEqual(matchingCoupon.id); - }); + expect( status ).toEqual( couponsApi.listAll.responseCode ); + expect( body ).toHaveLength( 1 ); + expect( body[ 0 ].id ).toEqual( matchingCoupon.id ); + } ); - it('can paginate results', async () => { + it( 'can paginate results', async () => { const pageSize = 2; const payload = { page: 1, per_page: pageSize, }; - const { status, body } = await couponsApi.listAll.coupons(payload); + const { status, body } = await couponsApi.listAll.coupons( + payload + ); - expect(status).toEqual(couponsApi.listAll.responseCode); - expect(body).toHaveLength(pageSize); - }); + expect( status ).toEqual( couponsApi.listAll.responseCode ); + expect( body ).toHaveLength( pageSize ); + } ); - it('can limit results to matching string', async () => { + it( 'can limit results to matching string', async () => { // Search by description - const matchingCoupon = allCoupons[2]; + const matchingCoupon = allCoupons[ 2 ]; const matchingString = matchingCoupon.description; const payload = { search: matchingString, }; - const { status, body } = await couponsApi.listAll.coupons(payload); + const { status, body } = await couponsApi.listAll.coupons( + payload + ); - expect(status).toEqual(couponsApi.listAll.responseCode); - expect(body).toHaveLength(1); - expect(body[0].id).toEqual(matchingCoupon.id); - }); - }); + expect( status ).toEqual( couponsApi.listAll.responseCode ); + expect( body ).toHaveLength( 1 ); + expect( body[ 0 ].id ).toEqual( matchingCoupon.id ); + } ); + } ); - describe('Add coupon to order', () => { + describe( 'Add coupon to order', () => { const testCoupon = { - code: `coupon-${Date.now()}`, + code: `coupon-${ Date.now() }`, discount_type: 'percent', amount: '10', }; let orderId; - beforeAll(async () => { + beforeAll( async () => { // Create a coupon const createCouponResponse = await couponsApi.create.coupon( testCoupon ); testCoupon.id = createCouponResponse.body.id; - }); + } ); // Clean up created coupon and order - afterAll(async () => { - await couponsApi.delete.coupon(testCoupon.id, true); - await ordersApi.delete.order(orderId, true); - }); + afterAll( async () => { + await couponsApi.delete.coupon( testCoupon.id, true ); + await ordersApi.delete.order( orderId, true ); + } ); - it('can add coupon to an order', async () => { + it( 'can add coupon to an order', async () => { const orderWithCoupon = { ...order, - coupon_lines: [{ code: testCoupon.code }], + coupon_lines: [ { code: testCoupon.code } ], }; const { status, body } = await ordersApi.create.order( orderWithCoupon ); orderId = body.id; - expect(status).toEqual(ordersApi.create.responseCode); - expect(body.coupon_lines).toHaveLength(1); - expect(body.coupon_lines[0].code).toEqual(testCoupon.code); - }); - }); -}); \ No newline at end of file + expect( status ).toEqual( ordersApi.create.responseCode ); + expect( body.coupon_lines ).toHaveLength( 1 ); + expect( body.coupon_lines[ 0 ].code ).toEqual( testCoupon.code ); + } ); + } ); +} ); From 21f6ea6ae5ad9f0f0a12bc80c92c3689fc40254f Mon Sep 17 00:00:00 2001 From: Jeff Stieler Date: Tue, 30 Nov 2021 15:19:01 -0500 Subject: [PATCH 4/4] Add test for coupon meta data to API test. --- .../js/api-core-tests/tests/coupons/coupons.test.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/js/api-core-tests/tests/coupons/coupons.test.js b/packages/js/api-core-tests/tests/coupons/coupons.test.js index dd4d45ca138..524de21566e 100644 --- a/packages/js/api-core-tests/tests/coupons/coupons.test.js +++ b/packages/js/api-core-tests/tests/coupons/coupons.test.js @@ -312,6 +312,18 @@ describe( 'Coupons API tests', () => { expect( status ).toEqual( ordersApi.create.responseCode ); expect( body.coupon_lines ).toHaveLength( 1 ); expect( body.coupon_lines[ 0 ].code ).toEqual( testCoupon.code ); + // Test that the coupon meta data exists. + // See: https://github.com/woocommerce/woocommerce/issues/28166. + expect( body.coupon_lines[ 0 ].meta_data ).toEqual( + expect.arrayContaining( [ + expect.objectContaining( { + key: 'coupon_data', + value: expect.objectContaining( { + code: testCoupon.code, + } ), + } ), + ] ) + ); } ); } ); } );