Browse Source

test(server): Add e2e tests for Order state transitions

Michael Bromley 7 years ago
parent
commit
353bf96e46

+ 17 - 0
README.md

@@ -181,6 +181,23 @@ so that any specific constraints can be imposed on the inputted data in a consis
 a custom-made form input to set the value in the admin UI. For example, a "location" field could use a visual map interface
 to set the coordiantes of a point. This would probably be a post-1.0 feature.
 
+### Orders Process
+
+The orders process is governed by a finite state machine which allows each Order to transition only from one valid state
+to another, as defined by the [OrderState definitions](server/src/service/helpers/order-state-machine/order-state.ts):
+
+```TypeScript
+export type OrderState =
+    | 'AddingItems'
+    | 'ArrangingShipping'
+    | 'ArrangingPayment'
+    | 'OrderComplete'
+    | 'Cancelled';
+```
+
+This process can augmented with extra states according to the needs of the business, and these states are defined
+in the `orderProcessOptions` property of the VendureConfig object which is used to bootstrap Vendure. Additional
+logic can also be defined which will be executed on transition from one state to another.
 
 ## License
 

+ 74 - 4
server/e2e/order.e2e-spec.ts

@@ -34,6 +34,15 @@ describe('Orders', () => {
             expect(client.getAuthToken()).toBe('');
         });
 
+        it('activeOrder() creates and returns a new order in the default state', async () => {
+            const result = await client.query(GET_ACTIVE_ORDER);
+            expect(result.activeOrder).toEqual({ id: 'T_1', state: 'AddingItems' });
+        });
+
+        it('activeOrder() creates an anonymous session', () => {
+            expect(client.getAuthToken()).not.toBe('');
+        });
+
         it('addItemToOrder() creates a new Order with an item', async () => {
             const result = await client.query(ADD_ITEM_TO_ORDER, {
                 productVariantId: 'T_1',
@@ -47,10 +56,6 @@ describe('Orders', () => {
             firstOrderItemId = result.addItemToOrder.lines[0].id;
         });
 
-        it('addItemToOrder() creates an anonymous session', () => {
-            expect(client.getAuthToken()).not.toBe('');
-        });
-
         it('addItemToOrder() errors with an invalid productVariantId', async () => {
             try {
                 await client.query(ADD_ITEM_TO_ORDER, {
@@ -154,6 +159,47 @@ describe('Orders', () => {
                 );
             }
         });
+
+        it('nextOrderStates() returns next valid states', async () => {
+            const result = await client.query(gql`
+                query {
+                    nextOrderStates
+                }
+            `);
+
+            expect(result.nextOrderStates).toEqual(['ArrangingShipping']);
+        });
+
+        it('transitionOrderToState() throws for an invalid state', async () => {
+            try {
+                await client.query(gql`
+                    mutation {
+                        transitionOrderToState(state: "Completed") {
+                            id
+                            state
+                        }
+                    }
+                `);
+                fail('Should have thrown');
+            } catch (err) {
+                expect(err.message).toEqual(
+                    expect.stringContaining(`Cannot transition Order from "AddingItems" to "Completed"`),
+                );
+            }
+        });
+
+        it('transitionOrderToState() transitions Order to the next valid state', async () => {
+            const result = await client.query(gql`
+                mutation {
+                    transitionOrderToState(state: "ArrangingShipping") {
+                        id
+                        state
+                    }
+                }
+            `);
+
+            expect(result.transitionOrderToState).toEqual({ id: 'T_1', state: 'ArrangingShipping' });
+        });
     });
 
     describe('as authenticated user', () => {
@@ -173,6 +219,11 @@ describe('Orders', () => {
             await client.asUserWithCredentials(customer.emailAddress, 'test');
         });
 
+        it('activeOrder() creates and returns a new order in the default state', async () => {
+            const result = await client.query(GET_ACTIVE_ORDER);
+            expect(result.activeOrder).toEqual({ id: 'T_2', state: 'AddingItems' });
+        });
+
         it('addItemToOrder() creates a new Order with an item', async () => {
             const result = await client.query(ADD_ITEM_TO_ORDER, {
                 productVariantId: 'T_1',
@@ -219,6 +270,16 @@ describe('Orders', () => {
             expect(result2.removeItemFromOrder.lines.length).toBe(1);
             expect(result2.removeItemFromOrder.lines.map(i => i.productVariant.id)).toEqual(['T_3']);
         });
+
+        it('nextOrderStates() returns next valid states', async () => {
+            const result = await client.query(gql`
+                query {
+                    nextOrderStates
+                }
+            `);
+
+            expect(result.nextOrderStates).toEqual(['ArrangingShipping']);
+        });
     });
 });
 
@@ -235,6 +296,15 @@ const TEST_ORDER_FRAGMENT = gql`
     }
 `;
 
+const GET_ACTIVE_ORDER = gql`
+    query {
+        activeOrder {
+            id
+            state
+        }
+    }
+`;
+
 const ADD_ITEM_TO_ORDER = gql`
     mutation AddItemToOrder($productVariantId: ID!, $quantity: Int!) {
         addItemToOrder(productVariantId: $productVariantId, quantity: $quantity) {

+ 2 - 0
server/src/i18n/messages/en.json

@@ -1,6 +1,8 @@
 {
   "error": {
     "cannot-modify-role": "The role '{ roleCode }' cannot be modified",
+    "cannot-transition-order-from-to": "Cannot transition Order from \"{ fromState }\" to \"{ toState }\"",
+    "cannot-transition-to-shipping-when-order-is-empty": "Cannot transition Order to the ArrangingShipping state when it is empty",
     "channel-not-found":  "No channel with the token \"{ token }\" exists",
     "entity-has-no-translation-in-language": "Translatable entity '{ entityName }' has not been translated into the requested language ({ languageCode })",
     "entity-with-id-not-found": "No { entityName } with the id '{ id }' could be found",

+ 1 - 1
server/src/service/helpers/order-state-machine/order-state-machine.ts

@@ -37,7 +37,7 @@ export class OrderStateMachine {
     private onTransitionStart(fromState: OrderState, toState: OrderState, data: OrderTransitionData) {
         if (toState === 'ArrangingShipping') {
             if (data.order.lines.length === 0) {
-                return `error.order-is-empty`;
+                return `error.cannot-transition-to-shipping-when-order-is-empty`;
             }
         }
     }