From 71a5f862392181d1d51abf814325e70aa69a6445 Mon Sep 17 00:00:00 2001 From: Philipp Hofmann Date: Fri, 1 Nov 2024 12:08:16 +0100 Subject: [PATCH 01/14] fix: ignore .idea folder as well --- .gitignore | 3 +++ application-templates/typescript/.gitignore | 1 + 2 files changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 3123401..4b9e734 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,6 @@ application-templates/**/yarn.lock application-templates/starter-typescript/job/.env application-templates/starter-typescript/job/.env.* !application-templates/starter-typescript/job/.env.example + +#IDE +.idea diff --git a/application-templates/typescript/.gitignore b/application-templates/typescript/.gitignore index d2a7535..9b9b5e4 100644 --- a/application-templates/typescript/.gitignore +++ b/application-templates/typescript/.gitignore @@ -6,6 +6,7 @@ coverage .DS_Store .vscode +.idea build/ .env public/ \ No newline at end of file From eac3991d2e7fdb1574f19d42bfbc5f7a13c9f6ba Mon Sep 17 00:00:00 2001 From: Philipp Hofmann Date: Fri, 1 Nov 2024 12:19:57 +0100 Subject: [PATCH 02/14] fix: ignore .env and all .env.* except .env.example files --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 4b9e734..8ee12b2 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,9 @@ application-templates/**/yarn.lock application-templates/starter-typescript/job/.env application-templates/starter-typescript/job/.env.* !application-templates/starter-typescript/job/.env.example +application-templates/typescript/event/.env +application-templates/typescript/event/.env.* +!application-templates/typescript/event/.env.example #IDE .idea From 808987fe0ac2feeb4fd86962c5d4d9a2f5c188b4 Mon Sep 17 00:00:00 2001 From: Philipp Hofmann Date: Fri, 1 Nov 2024 14:01:11 +0100 Subject: [PATCH 03/14] feat: adding a "real" testcase --- .../typescript/event/jest.config.cjs | 2 + .../event/tests/integration/event.spec.ts | 89 +++++++++++++++++++ .../typescript/event/tests/setup-tests.ts | 3 + .../typescript/event/tsconfig.json | 2 +- 4 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 application-templates/typescript/event/tests/integration/event.spec.ts create mode 100644 application-templates/typescript/event/tests/setup-tests.ts diff --git a/application-templates/typescript/event/jest.config.cjs b/application-templates/typescript/event/jest.config.cjs index 71f629d..096067f 100644 --- a/application-templates/typescript/event/jest.config.cjs +++ b/application-templates/typescript/event/jest.config.cjs @@ -2,6 +2,8 @@ module.exports = { displayName: 'Tests Typescript Application - Event', moduleDirectories: ['node_modules', 'src'], testMatch: ['**/tests/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[tj]s?(x)'], + testPathIgnorePatterns: ['tests/setup-tests.ts'], preset: 'ts-jest', testEnvironment: 'node', + setupFiles: ['/tests/setup-tests.ts'], }; diff --git a/application-templates/typescript/event/tests/integration/event.spec.ts b/application-templates/typescript/event/tests/integration/event.spec.ts new file mode 100644 index 0000000..c7cb285 --- /dev/null +++ b/application-templates/typescript/event/tests/integration/event.spec.ts @@ -0,0 +1,89 @@ +import { describe, expect } from '@jest/globals'; +import { CustomerCreatedMessage, Customer } from '@commercetools/platform-sdk'; +import request from 'supertest'; +import app from '../../src/app'; +import { createApiRoot } from '../../src/client/create.client'; + +jest.mock('../../src/client/create.client', () => { + const mockCreateApiRoot = jest.fn(); + return { + createApiRoot: mockCreateApiRoot, + }; +}); + +const customerId = 'd93a8493-1bfd-4e2d-8b46-20a765dd0978'; + +const customer: Customer = { + id: customerId, + createdAt: '', + lastModifiedAt: '', + version: 0, + email: '', + isEmailVerified: true, + addresses: [], + authenticationMode: 'Password', +}; + +const orderCreatedMessage: CustomerCreatedMessage = { + createdAt: '', + id: '', + lastModifiedAt: '', + resource: { id: customerId, typeId: 'customer' }, + resourceVersion: 0, + sequenceNumber: 0, + type: 'CustomerCreated', + version: 0, + customer: customer, +}; + +describe('Testing Event Controller', () => { + it('Customer Created (Mocked)', async () => { + // Define a mock root to be returned + const withId = jest.fn().mockReturnValueOnce({ + get: jest.fn().mockReturnValueOnce({ + execute: jest.fn().mockReturnValueOnce(customer), + }), + }); + const mockRoot = { + customers: jest.fn().mockReturnValueOnce({ + withId: withId, + }), + }; + + // Set the mock implementation for createApiRoot to return mockRoot + (createApiRoot as jest.Mock).mockReturnValueOnce(mockRoot); + + const response = await request(app) + .post('/event') + .send({ + message: { + data: Buffer.from(JSON.stringify(orderCreatedMessage)).toString( + 'base64' + ), + }, + }); + expect(response.status).toBe(204); + expect(withId).toHaveBeenCalledWith({ ID: customerId }); + }); + + /* + * This Test really want to call the commercetools APIs. To do so, + * - remove the mocking code in line 7-12 + * - configure a "real" customerId + * - remove the skip below + * + * The code below comes in handy to test during development. + */ + it.skip('Customer Created', async () => { + const response = await request(app) + .post('/event') + .send({ + message: { + data: Buffer.from(JSON.stringify(orderCreatedMessage)).toString( + 'base64' + ), + }, + }); + expect(response.status).toBe(204); + }); +}); diff --git a/application-templates/typescript/event/tests/setup-tests.ts b/application-templates/typescript/event/tests/setup-tests.ts new file mode 100644 index 0000000..b7998b4 --- /dev/null +++ b/application-templates/typescript/event/tests/setup-tests.ts @@ -0,0 +1,3 @@ +import { config } from 'dotenv'; +config(); +config({ path: `.env.local`, override: true }); diff --git a/application-templates/typescript/event/tsconfig.json b/application-templates/typescript/event/tsconfig.json index fb87b83..fc83f98 100644 --- a/application-templates/typescript/event/tsconfig.json +++ b/application-templates/typescript/event/tsconfig.json @@ -1,6 +1,6 @@ { "extends": "@tsconfig/recommended/tsconfig.json", - "exclude": ["node_modules", "**/*.spec.ts"], + "exclude": ["node_modules", "**/*.spec.ts", "**/setup-tests.ts"], "compilerOptions": { "resolveJsonModule": true, "outDir": "./build", From b3f0153692f42423aff168e8d2e3585ceed23d3d Mon Sep 17 00:00:00 2001 From: Philipp Hofmann Date: Fri, 1 Nov 2024 14:01:46 +0100 Subject: [PATCH 04/14] feat: typing jsonData and validating that the messagetype is really of type "CustomerCreated" --- .../typescript/event/src/controllers/event.controller.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/application-templates/typescript/event/src/controllers/event.controller.ts b/application-templates/typescript/event/src/controllers/event.controller.ts index 622a773..6031185 100644 --- a/application-templates/typescript/event/src/controllers/event.controller.ts +++ b/application-templates/typescript/event/src/controllers/event.controller.ts @@ -2,6 +2,7 @@ import { Request, Response } from 'express'; import { createApiRoot } from '../client/create.client'; import CustomError from '../errors/custom.error'; import { logger } from '../utils/logger.utils'; +import { Message } from '@commercetools/platform-sdk'; /** * Exposed event POST endpoint. @@ -36,9 +37,13 @@ export const post = async (request: Request, response: Response) => { : undefined; if (decodedData) { - const jsonData = JSON.parse(decodedData); + const jsonData: Message | null = JSON.parse(decodedData); - customerId = jsonData.customer.id; + if (jsonData) { + if (jsonData.type === 'CustomerCreated') { + customerId = jsonData.customer.id; + } + } } if (!customerId) { From 3dc91597fbd826b757fbf82e42a9085ef9d1ba4d Mon Sep 17 00:00:00 2001 From: Philipp Hofmann Date: Mon, 4 Nov 2024 10:37:59 +0100 Subject: [PATCH 05/14] feat: removing customercreated from e.g. key and subscriptions to allow the template to be reused more often without adaptions --- .../typescript/event/src/connector/actions.ts | 17 ++++++++--------- .../event/src/connector/post-deploy.ts | 15 ++++----------- .../event/src/connector/pre-undeploy.ts | 4 ++-- 3 files changed, 14 insertions(+), 22 deletions(-) diff --git a/application-templates/typescript/event/src/connector/actions.ts b/application-templates/typescript/event/src/connector/actions.ts index 78cf9cc..f2e08a4 100644 --- a/application-templates/typescript/event/src/connector/actions.ts +++ b/application-templates/typescript/event/src/connector/actions.ts @@ -5,10 +5,9 @@ import { } from '@commercetools/platform-sdk'; import { ByProjectKeyRequestBuilder } from '@commercetools/platform-sdk/dist/declarations/src/generated/client/by-project-key-request-builder'; -const CUSTOMER_CREATE_SUBSCRIPTION_KEY = - 'myconnector-customerCreateSubscription'; +const SUBSCRIPTION_KEY = 'myconnector-customerCreateSubscription'; -export async function createGcpPubSubCustomerCreateSubscription( +export async function createGcpPubSubSubscription( apiRoot: ByProjectKeyRequestBuilder, topicName: string, projectId: string @@ -21,7 +20,7 @@ export async function createGcpPubSubCustomerCreateSubscription( await createSubscription(apiRoot, destination); } -export async function createAzureServiceBusCustomerCreateSubscription( +export async function createAzureServiceBusSubscription( apiRoot: ByProjectKeyRequestBuilder, connectionString: string ): Promise { @@ -36,12 +35,12 @@ async function createSubscription( apiRoot: ByProjectKeyRequestBuilder, destination: Destination ) { - await deleteCustomerCreateSubscription(apiRoot); + await deleteSubscription(apiRoot); await apiRoot .subscriptions() .post({ body: { - key: CUSTOMER_CREATE_SUBSCRIPTION_KEY, + key: SUBSCRIPTION_KEY, destination, messages: [ { @@ -54,7 +53,7 @@ async function createSubscription( .execute(); } -export async function deleteCustomerCreateSubscription( +export async function deleteSubscription( apiRoot: ByProjectKeyRequestBuilder ): Promise { const { @@ -63,7 +62,7 @@ export async function deleteCustomerCreateSubscription( .subscriptions() .get({ queryArgs: { - where: `key = "${CUSTOMER_CREATE_SUBSCRIPTION_KEY}"`, + where: `key = "${SUBSCRIPTION_KEY}"`, }, }) .execute(); @@ -73,7 +72,7 @@ export async function deleteCustomerCreateSubscription( await apiRoot .subscriptions() - .withKey({ key: CUSTOMER_CREATE_SUBSCRIPTION_KEY }) + .withKey({ key: SUBSCRIPTION_KEY }) .delete({ queryArgs: { version: subscription.version, diff --git a/application-templates/typescript/event/src/connector/post-deploy.ts b/application-templates/typescript/event/src/connector/post-deploy.ts index 3270ee4..3cf33f4 100644 --- a/application-templates/typescript/event/src/connector/post-deploy.ts +++ b/application-templates/typescript/event/src/connector/post-deploy.ts @@ -4,8 +4,8 @@ dotenv.config(); import { createApiRoot } from '../client/create.client'; import { assertError, assertString } from '../utils/assert.utils'; import { - createAzureServiceBusCustomerCreateSubscription, - createGcpPubSubCustomerCreateSubscription, + createAzureServiceBusSubscription, + createGcpPubSubSubscription, } from './actions'; const CONNECT_GCP_TOPIC_NAME_KEY = 'CONNECT_GCP_TOPIC_NAME'; @@ -24,10 +24,7 @@ async function postDeploy(properties: Map): Promise { CONNECT_AZURE_CONNECTION_STRING_KEY ); assertString(connectionString, CONNECT_AZURE_CONNECTION_STRING_KEY); - await createAzureServiceBusCustomerCreateSubscription( - apiRoot, - connectionString - ); + await createAzureServiceBusSubscription(apiRoot, connectionString); break; } default: { @@ -35,11 +32,7 @@ async function postDeploy(properties: Map): Promise { const projectId = properties.get(CONNECT_GCP_PROJECT_ID_KEY); assertString(topicName, CONNECT_GCP_TOPIC_NAME_KEY); assertString(projectId, CONNECT_GCP_PROJECT_ID_KEY); - await createGcpPubSubCustomerCreateSubscription( - apiRoot, - topicName, - projectId - ); + await createGcpPubSubSubscription(apiRoot, topicName, projectId); } } } diff --git a/application-templates/typescript/event/src/connector/pre-undeploy.ts b/application-templates/typescript/event/src/connector/pre-undeploy.ts index 3b7e721..4a4d852 100644 --- a/application-templates/typescript/event/src/connector/pre-undeploy.ts +++ b/application-templates/typescript/event/src/connector/pre-undeploy.ts @@ -3,11 +3,11 @@ dotenv.config(); import { createApiRoot } from '../client/create.client'; import { assertError } from '../utils/assert.utils'; -import { deleteCustomerCreateSubscription } from './actions'; +import { deleteSubscription } from './actions'; async function preUndeploy(): Promise { const apiRoot = createApiRoot(); - await deleteCustomerCreateSubscription(apiRoot); + await deleteSubscription(apiRoot); } async function run(): Promise { From 8b3e2e36e5e943099f58fe311c04d694f13f1689 Mon Sep 17 00:00:00 2001 From: Philipp Hofmann Date: Mon, 4 Nov 2024 10:38:24 +0100 Subject: [PATCH 06/14] chore: fixed typo --- .../typescript/event/tests/integration/event.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application-templates/typescript/event/tests/integration/event.spec.ts b/application-templates/typescript/event/tests/integration/event.spec.ts index c7cb285..0c07cb9 100644 --- a/application-templates/typescript/event/tests/integration/event.spec.ts +++ b/application-templates/typescript/event/tests/integration/event.spec.ts @@ -67,7 +67,7 @@ describe('Testing Event Controller', () => { }); /* - * This Test really want to call the commercetools APIs. To do so, + * This Test really calls the commercetools APIs. To do so, * - remove the mocking code in line 7-12 * - configure a "real" customerId * - remove the skip below From 145b8b94b37e84f2bb97c8b2f9cdb0deb2380813 Mon Sep 17 00:00:00 2001 From: Philipp Hofmann Date: Wed, 6 Nov 2024 08:28:41 +0100 Subject: [PATCH 07/14] fix: ignore .env.local and other unnecessary files --- .gitignore | 3 +++ application-templates/typescript/.gitignore | 8 +++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 8ee12b2..4de60b6 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,9 @@ application-templates/starter-typescript/job/.env.* application-templates/typescript/event/.env application-templates/typescript/event/.env.* !application-templates/typescript/event/.env.example +application-templates/typescript/service/.env +application-templates/typescript/service/.env.* +!application-templates/typescript/service/.env.example #IDE .idea diff --git a/application-templates/typescript/.gitignore b/application-templates/typescript/.gitignore index 9b9b5e4..5e9fc05 100644 --- a/application-templates/typescript/.gitignore +++ b/application-templates/typescript/.gitignore @@ -3,10 +3,12 @@ coverage .terraform *.tfstate* *.tfvars - +.yarn .DS_Store .vscode .idea build/ -.env -public/ \ No newline at end of file +!.env +.env.local +public/ +.connect \ No newline at end of file From aceb6bcc2f36c4ade4c065bcc197fe9331196a9a Mon Sep 17 00:00:00 2001 From: Philipp Hofmann Date: Wed, 6 Nov 2024 08:29:21 +0100 Subject: [PATCH 08/14] fix: add default implementation for order and payment as template code --- .../src/controllers/order.controller.ts | 40 +++++++++++++++++ .../src/controllers/orders.controller.ts | 3 -- .../src/controllers/payment.controller.ts | 43 +++++++++++++++++++ .../src/controllers/payments.controller.ts | 3 -- 4 files changed, 83 insertions(+), 6 deletions(-) create mode 100644 application-templates/typescript/service/src/controllers/order.controller.ts delete mode 100644 application-templates/typescript/service/src/controllers/orders.controller.ts create mode 100644 application-templates/typescript/service/src/controllers/payment.controller.ts delete mode 100644 application-templates/typescript/service/src/controllers/payments.controller.ts diff --git a/application-templates/typescript/service/src/controllers/order.controller.ts b/application-templates/typescript/service/src/controllers/order.controller.ts new file mode 100644 index 0000000..570b064 --- /dev/null +++ b/application-templates/typescript/service/src/controllers/order.controller.ts @@ -0,0 +1,40 @@ +import { Order } from '@commercetools/platform-sdk'; +import { logger } from '../utils/logger.utils'; +import { ExtensionAction } from '@commercetools/platform-sdk/dist/declarations/src/generated/models/extension'; +import CustomError from '../errors/custom.error'; +import { ControllerResult } from '../types/controller.types'; + +/** + * Handle the create action + */ +const create = (item: Order): ControllerResult => { + logger.info(`Running API Extension for Order Update (ID: ${item.id})`); + return; +}; + +/** + * Handle the update action + */ +const update = (item: Order): ControllerResult => { + logger.info(`Running API Extension for Order Update (ID: ${item.id})`); + return; +}; + +/** + * Handle the controller according to the action + */ +export const orderController = (action: ExtensionAction, resource: Order) => { + switch (action) { + case 'Create': + return create(resource); + case 'Update': + return update(resource); + + default: + logger.error(` Action ('${action}') not recognized.`); + throw new CustomError( + 500, + `Internal Server Error - Resource not recognized. Allowed values are 'Create' or 'Update'.` + ); + } +}; diff --git a/application-templates/typescript/service/src/controllers/orders.controller.ts b/application-templates/typescript/service/src/controllers/orders.controller.ts deleted file mode 100644 index 5c47835..0000000 --- a/application-templates/typescript/service/src/controllers/orders.controller.ts +++ /dev/null @@ -1,3 +0,0 @@ -//const get = () => {}; - -//const post = () => {}; diff --git a/application-templates/typescript/service/src/controllers/payment.controller.ts b/application-templates/typescript/service/src/controllers/payment.controller.ts new file mode 100644 index 0000000..0d37350 --- /dev/null +++ b/application-templates/typescript/service/src/controllers/payment.controller.ts @@ -0,0 +1,43 @@ +import { Payment } from '@commercetools/platform-sdk'; +import { logger } from '../utils/logger.utils'; +import { ExtensionAction } from '@commercetools/platform-sdk/dist/declarations/src/generated/models/extension'; +import CustomError from '../errors/custom.error'; +import { ControllerResult } from '../types/controller.types'; + +/** + * Handle the create action + */ +const create = (item: Payment): ControllerResult => { + logger.info(`Running API Extension for Payment Update (ID: ${item.id})`); + return; +}; + +/** + * Handle the update action + */ +const update = (item: Payment): ControllerResult => { + logger.info(`Running API Extension for Payment Update (ID: ${item.id})`); + return; +}; + +/** + * Handle the controller according to the action + */ +export const paymentController = ( + action: ExtensionAction, + resource: Payment +) => { + switch (action) { + case 'Create': + return create(resource); + case 'Update': + return update(resource); + + default: + logger.error(` Action ('${action}') not recognized.`); + throw new CustomError( + 500, + `Internal Server Error - Resource not recognized. Allowed values are 'Create' or 'Update'.` + ); + } +}; diff --git a/application-templates/typescript/service/src/controllers/payments.controller.ts b/application-templates/typescript/service/src/controllers/payments.controller.ts deleted file mode 100644 index f408097..0000000 --- a/application-templates/typescript/service/src/controllers/payments.controller.ts +++ /dev/null @@ -1,3 +0,0 @@ -// const get = () => {}; - -// const post = () => {}; From db9baacf16ee5b97f1e24dc7f905ed111d9a4165 Mon Sep 17 00:00:00 2001 From: Philipp Hofmann Date: Wed, 6 Nov 2024 08:31:55 +0100 Subject: [PATCH 09/14] fix: the api extension to be created is most likely not only for cart creation, therefore making it more anonymous from a naming perspective --- .../service/src/connector/actions.ts | 74 +++---------------- .../service/src/connector/post-deploy.ts | 8 +- .../service/src/connector/pre-undeploy.ts | 4 +- 3 files changed, 15 insertions(+), 71 deletions(-) diff --git a/application-templates/typescript/service/src/connector/actions.ts b/application-templates/typescript/service/src/connector/actions.ts index 356dd25..7668ca5 100644 --- a/application-templates/typescript/service/src/connector/actions.ts +++ b/application-templates/typescript/service/src/connector/actions.ts @@ -1,9 +1,8 @@ import { ByProjectKeyRequestBuilder } from '@commercetools/platform-sdk/dist/declarations/src/generated/client/by-project-key-request-builder'; -const CART_UPDATE_EXTENSION_KEY = 'myconnector-cartUpdateExtension'; -const CART_DISCOUNT_TYPE_KEY = 'myconnector-cartDiscountType'; +const EXTENSION_KEY = 'myconnector-cartUpdateExtension'; -export async function createCartUpdateExtension( +export async function createExtension( apiRoot: ByProjectKeyRequestBuilder, applicationUrl: string ): Promise { @@ -13,7 +12,7 @@ export async function createCartUpdateExtension( .extensions() .get({ queryArgs: { - where: `key = "${CART_UPDATE_EXTENSION_KEY}"`, + where: `key = "${EXTENSION_KEY}"`, }, }) .execute(); @@ -23,7 +22,7 @@ export async function createCartUpdateExtension( await apiRoot .extensions() - .withKey({ key: CART_UPDATE_EXTENSION_KEY }) + .withKey({ key: EXTENSION_KEY }) .delete({ queryArgs: { version: extension.version, @@ -32,11 +31,14 @@ export async function createCartUpdateExtension( .execute(); } + // Have a look here to figure out what kind of triggers are available: + // https://docs.commercetools.com/api/projects/api-extensions#ctp:api:type:ExtensionTrigger + await apiRoot .extensions() .post({ body: { - key: CART_UPDATE_EXTENSION_KEY, + key: EXTENSION_KEY, destination: { type: 'HTTP', url: applicationUrl, @@ -52,7 +54,7 @@ export async function createCartUpdateExtension( .execute(); } -export async function deleteCartUpdateExtension( +export async function deleteExtension( apiRoot: ByProjectKeyRequestBuilder ): Promise { const { @@ -61,7 +63,7 @@ export async function deleteCartUpdateExtension( .extensions() .get({ queryArgs: { - where: `key = "${CART_UPDATE_EXTENSION_KEY}"`, + where: `key = "${EXTENSION_KEY}"`, }, }) .execute(); @@ -71,7 +73,7 @@ export async function deleteCartUpdateExtension( await apiRoot .extensions() - .withKey({ key: CART_UPDATE_EXTENSION_KEY }) + .withKey({ key: EXTENSION_KEY }) .delete({ queryArgs: { version: extension.version, @@ -80,57 +82,3 @@ export async function deleteCartUpdateExtension( .execute(); } } - -export async function createCustomCartDiscountType( - apiRoot: ByProjectKeyRequestBuilder -): Promise { - const { - body: { results: types }, - } = await apiRoot - .types() - .get({ - queryArgs: { - where: `key = "${CART_DISCOUNT_TYPE_KEY}"`, - }, - }) - .execute(); - - if (types.length > 0) { - const type = types[0]; - - await apiRoot - .types() - .withKey({ key: CART_DISCOUNT_TYPE_KEY }) - .delete({ - queryArgs: { - version: type.version, - }, - }) - .execute(); - } - - await apiRoot - .types() - .post({ - body: { - key: CART_DISCOUNT_TYPE_KEY, - name: { - en: 'Custom type to store a string', - }, - resourceTypeIds: ['cart-discount'], - fieldDefinitions: [ - { - type: { - name: 'String', - }, - name: 'customCartField', - label: { - en: 'Custom cart field', - }, - required: false, - }, - ], - }, - }) - .execute(); -} diff --git a/application-templates/typescript/service/src/connector/post-deploy.ts b/application-templates/typescript/service/src/connector/post-deploy.ts index c4da616..6b50824 100644 --- a/application-templates/typescript/service/src/connector/post-deploy.ts +++ b/application-templates/typescript/service/src/connector/post-deploy.ts @@ -3,10 +3,7 @@ dotenv.config(); import { createApiRoot } from '../client/create.client'; import { assertError, assertString } from '../utils/assert.utils'; -import { - createCustomCartDiscountType, - createCartUpdateExtension, -} from './actions'; +import { createExtension } from './actions'; const CONNECT_APPLICATION_URL_KEY = 'CONNECT_SERVICE_URL'; @@ -16,8 +13,7 @@ async function postDeploy(properties: Map): Promise { assertString(applicationUrl, CONNECT_APPLICATION_URL_KEY); const apiRoot = createApiRoot(); - await createCartUpdateExtension(apiRoot, applicationUrl); - await createCustomCartDiscountType(apiRoot); + await createExtension(apiRoot, applicationUrl); } async function run(): Promise { diff --git a/application-templates/typescript/service/src/connector/pre-undeploy.ts b/application-templates/typescript/service/src/connector/pre-undeploy.ts index bf41011..a086f65 100644 --- a/application-templates/typescript/service/src/connector/pre-undeploy.ts +++ b/application-templates/typescript/service/src/connector/pre-undeploy.ts @@ -3,11 +3,11 @@ dotenv.config(); import { createApiRoot } from '../client/create.client'; import { assertError } from '../utils/assert.utils'; -import { deleteCartUpdateExtension } from './actions'; +import { deleteExtension } from './actions'; async function preUndeploy(): Promise { const apiRoot = createApiRoot(); - await deleteCartUpdateExtension(apiRoot); + await deleteExtension(apiRoot); } async function run(): Promise { From 6431e458f8045317ccba9f35a45b588569d7e70c Mon Sep 17 00:00:00 2001 From: Philipp Hofmann Date: Wed, 6 Nov 2024 08:35:47 +0100 Subject: [PATCH 10/14] fix: supertest is being used and should be installed --- .../typescript/service/package.json | 2 + package-lock.json | 326 ++++++++++++++++++ 2 files changed, 328 insertions(+) diff --git a/application-templates/typescript/service/package.json b/application-templates/typescript/service/package.json index 04e7d10..6f83a20 100644 --- a/application-templates/typescript/service/package.json +++ b/application-templates/typescript/service/package.json @@ -23,6 +23,7 @@ "@types/express": "^4.17.14", "@types/jest": "^29.0.3", "@types/node": "^18.7.18", + "@types/supertest": "^6.0.2", "@types/validator": "^13.7.10", "@typescript-eslint/eslint-plugin": "^5.46.0", "@typescript-eslint/parser": "^5.46.0", @@ -32,6 +33,7 @@ "nodemon": "^3.0.1", "prettier": "^3.0.1", "rimraf": "^5.0.1", + "supertest": "^7.0.0", "ts-jest": "^29.1.1", "typescript": "^5.1.6" }, diff --git a/package-lock.json b/package-lock.json index 10a18e4..750f247 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "@tsconfig/node16": "^1.0.3", "@tsconfig/recommended": "^1.0.3", "@types/node": "^18.7.16", + "@types/supertest": "^6.0.2", "@typescript-eslint/eslint-plugin": "^5.36.2", "@typescript-eslint/parser": "^5.36.2", "eslint": "^8.46.0", @@ -25,6 +26,7 @@ "jest": "^29.6.2", "prettier": "^3.0.1", "rimraf": "^5.0.1", + "supertest": "^7.0.0", "typescript": "^5.1.6" }, "engines": { @@ -245,6 +247,7 @@ "@types/express": "^4.17.14", "@types/jest": "^29.0.3", "@types/node": "^18.7.18", + "@types/supertest": "^6.0.2", "@types/validator": "^13.7.10", "@typescript-eslint/eslint-plugin": "^5.46.0", "@typescript-eslint/parser": "^5.46.0", @@ -254,6 +257,7 @@ "nodemon": "^3.0.1", "prettier": "^3.0.1", "rimraf": "^5.0.1", + "supertest": "^7.0.0", "ts-jest": "^29.1.1", "typescript": "^5.1.6" } @@ -4902,6 +4906,12 @@ "@types/node": "*" } }, + "node_modules/@types/cookiejar": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz", + "integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==", + "dev": true + }, "node_modules/@types/estree": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", @@ -4987,6 +4997,12 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, + "node_modules/@types/methods": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", + "integrity": "sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==", + "dev": true + }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -5058,6 +5074,28 @@ "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", "dev": true }, + "node_modules/@types/superagent": { + "version": "8.1.9", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.9.tgz", + "integrity": "sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==", + "dev": true, + "dependencies": { + "@types/cookiejar": "^2.1.5", + "@types/methods": "^1.1.4", + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/@types/supertest": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-6.0.2.tgz", + "integrity": "sha512-137ypx2lk/wTQbW6An6safu9hXmajAifU/s7szAHLN/FeIm5w7yR0Wkl9fdJMRSHwOn4HLAI0DaB2TOORuhPDg==", + "dev": true, + "dependencies": { + "@types/methods": "^1.1.4", + "@types/superagent": "^8.1.0" + } + }, "node_modules/@types/triple-beam": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.2.tgz", @@ -5459,6 +5497,12 @@ "node": ">=0.10.0" } }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true + }, "node_modules/assets": { "resolved": "application-templates/javascript/assets", "link": true @@ -5472,6 +5516,12 @@ "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, "node_modules/babel-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", @@ -6215,6 +6265,18 @@ "text-hex": "1.0.x" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/compare-func": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", @@ -6225,6 +6287,15 @@ "dot-prop": "^5.1.0" } }, + "node_modules/component-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -6423,6 +6494,12 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, + "node_modules/cookiejar": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", + "dev": true + }, "node_modules/core-js-compat": { "version": "3.37.1", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.37.1.tgz", @@ -6740,6 +6817,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -6774,6 +6860,16 @@ "node": ">=8" } }, + "node_modules/dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dev": true, + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -7723,6 +7819,34 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/formidable": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.2.tgz", + "integrity": "sha512-Jqc1btCy3QzRbJaICGwKcBfGWuLADRerLzDqi2NwSt/UkXLsHJw2TVResiaoBufHVHy9aSgClOHCeJsSsFLTbg==", + "dev": true, + "dependencies": { + "dezalgo": "^1.0.4", + "hexoid": "^2.0.0", + "once": "^1.4.0" + }, + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -8017,6 +8141,15 @@ "node": ">= 0.4" } }, + "node_modules/hexoid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-2.0.0.tgz", + "integrity": "sha512-qlspKUK7IlSQv2o+5I7yhUd7TxlOG2Vr5LTa3ve2XSNVKAL/n/u/7KLvKmFNimomDIKvZFXWHv0T12mv7rT8Aw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/hosted-git-info": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", @@ -12653,6 +12786,51 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/superagent": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-9.0.2.tgz", + "integrity": "sha512-xuW7dzkUpcJq7QnhOsnNUgtYp3xRwpt2F7abdRYIpCsAt0hhUqia0EdxyXZQQpNmGtsCzYHryaKSV3q3GJnq7w==", + "dev": true, + "dependencies": { + "component-emitter": "^1.3.0", + "cookiejar": "^2.1.4", + "debug": "^4.3.4", + "fast-safe-stringify": "^2.1.1", + "form-data": "^4.0.0", + "formidable": "^3.5.1", + "methods": "^1.1.2", + "mime": "2.6.0", + "qs": "^6.11.0" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/superagent/node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/supertest": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.0.0.tgz", + "integrity": "sha512-qlsr7fIC0lSddmA3tzojvzubYxvlGtzumcdHgPwbFWMISQwL22MhM2Y3LNt+6w9Yyx7559VW5ab70dgphm8qQA==", + "dev": true, + "dependencies": { + "methods": "^1.1.2", + "superagent": "^9.0.1" + }, + "engines": { + "node": ">=14.18.0" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -16897,6 +17075,12 @@ "@types/node": "*" } }, + "@types/cookiejar": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz", + "integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==", + "dev": true + }, "@types/estree": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", @@ -16982,6 +17166,12 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, + "@types/methods": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", + "integrity": "sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==", + "dev": true + }, "@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -17053,6 +17243,28 @@ "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", "dev": true }, + "@types/superagent": { + "version": "8.1.9", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.9.tgz", + "integrity": "sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==", + "dev": true, + "requires": { + "@types/cookiejar": "^2.1.5", + "@types/methods": "^1.1.4", + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "@types/supertest": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-6.0.2.tgz", + "integrity": "sha512-137ypx2lk/wTQbW6An6safu9hXmajAifU/s7szAHLN/FeIm5w7yR0Wkl9fdJMRSHwOn4HLAI0DaB2TOORuhPDg==", + "dev": true, + "requires": { + "@types/methods": "^1.1.4", + "@types/superagent": "^8.1.0" + } + }, "@types/triple-beam": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.2.tgz", @@ -17316,6 +17528,12 @@ "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", "dev": true }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true + }, "assets": { "version": "file:application-templates/javascript/assets", "requires": { @@ -17334,6 +17552,12 @@ "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==" }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, "babel-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", @@ -17861,6 +18085,15 @@ "text-hex": "1.0.x" } }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, "compare-func": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", @@ -17871,6 +18104,12 @@ "dot-prop": "^5.1.0" } }, + "component-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "dev": true + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -18015,6 +18254,12 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, + "cookiejar": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", + "dev": true + }, "core-js-compat": { "version": "3.37.1", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.37.1.tgz", @@ -18228,6 +18473,12 @@ "gopd": "^1.0.1" } }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true + }, "depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -18249,6 +18500,16 @@ "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", "dev": true }, + "dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dev": true, + "requires": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, "diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -19042,6 +19303,28 @@ } } }, + "form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "formidable": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.2.tgz", + "integrity": "sha512-Jqc1btCy3QzRbJaICGwKcBfGWuLADRerLzDqi2NwSt/UkXLsHJw2TVResiaoBufHVHy9aSgClOHCeJsSsFLTbg==", + "dev": true, + "requires": { + "dezalgo": "^1.0.4", + "hexoid": "^2.0.0", + "once": "^1.4.0" + } + }, "forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -19238,6 +19521,12 @@ "function-bind": "^1.1.2" } }, + "hexoid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-2.0.0.tgz", + "integrity": "sha512-qlspKUK7IlSQv2o+5I7yhUd7TxlOG2Vr5LTa3ve2XSNVKAL/n/u/7KLvKmFNimomDIKvZFXWHv0T12mv7rT8Aw==", + "dev": true + }, "hosted-git-info": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", @@ -22346,6 +22635,7 @@ "@types/express": "^4.17.14", "@types/jest": "^29.0.3", "@types/node": "^18.7.18", + "@types/supertest": "^6.0.2", "@types/validator": "^13.7.10", "@typescript-eslint/eslint-plugin": "^5.46.0", "@typescript-eslint/parser": "^5.46.0", @@ -22358,6 +22648,7 @@ "nodemon": "^3.0.1", "prettier": "^3.0.1", "rimraf": "^5.0.1", + "supertest": "^7.0.0", "ts-jest": "^29.1.1", "typescript": "^5.1.6", "validator": "^13.11.0" @@ -22717,6 +23008,41 @@ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, + "superagent": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-9.0.2.tgz", + "integrity": "sha512-xuW7dzkUpcJq7QnhOsnNUgtYp3xRwpt2F7abdRYIpCsAt0hhUqia0EdxyXZQQpNmGtsCzYHryaKSV3q3GJnq7w==", + "dev": true, + "requires": { + "component-emitter": "^1.3.0", + "cookiejar": "^2.1.4", + "debug": "^4.3.4", + "fast-safe-stringify": "^2.1.1", + "form-data": "^4.0.0", + "formidable": "^3.5.1", + "methods": "^1.1.2", + "mime": "2.6.0", + "qs": "^6.11.0" + }, + "dependencies": { + "mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true + } + } + }, + "supertest": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.0.0.tgz", + "integrity": "sha512-qlsr7fIC0lSddmA3tzojvzubYxvlGtzumcdHgPwbFWMISQwL22MhM2Y3LNt+6w9Yyx7559VW5ab70dgphm8qQA==", + "dev": true, + "requires": { + "methods": "^1.1.2", + "superagent": "^9.0.1" + } + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", From a5c328f12d48e532628ef70f1fc580d4f2106712 Mon Sep 17 00:00:00 2001 From: Philipp Hofmann Date: Wed, 6 Nov 2024 08:39:10 +0100 Subject: [PATCH 11/14] fix: updated code and tested it --- .../src/controllers/cart.controller.ts | 72 ++++++++------- .../src/controllers/service.controller.ts | 90 ++++++++++++++----- .../src/interfaces/resource.interface.ts | 2 - .../service/src/types/controller.types.ts | 5 ++ 4 files changed, 109 insertions(+), 60 deletions(-) delete mode 100644 application-templates/typescript/service/src/interfaces/resource.interface.ts create mode 100644 application-templates/typescript/service/src/types/controller.types.ts diff --git a/application-templates/typescript/service/src/controllers/cart.controller.ts b/application-templates/typescript/service/src/controllers/cart.controller.ts index d812f1d..fb164c4 100644 --- a/application-templates/typescript/service/src/controllers/cart.controller.ts +++ b/application-templates/typescript/service/src/controllers/cart.controller.ts @@ -2,36 +2,34 @@ import { UpdateAction } from '@commercetools/sdk-client-v2'; import { createApiRoot } from '../client/create.client'; import CustomError from '../errors/custom.error'; -import { Resource } from '../interfaces/resource.interface'; +import { ControllerResult } from '../types/controller.types'; +import { logger } from '../utils/logger.utils'; +import { Cart } from '@commercetools/platform-sdk'; +import { ExtensionAction } from '@commercetools/platform-sdk/dist/declarations/src/generated/models/extension'; /** * Handle the create action - * - * @param {Resource} resource The resource from the request body - * @returns {object} */ -const create = async (resource: Resource) => { - let productId = undefined; - +const create = async (cart: Cart): Promise => { try { + logger.info(`Running API Extension for Cart Create (ID: ${cart.id})`); const updateActions: Array = []; - // Deserialize the resource to a CartDraft - const cartDraft = JSON.parse(JSON.stringify(resource)); - - if (cartDraft.obj.lineItems.length !== 0) { - productId = cartDraft.obj.lineItems[0].productId; - } - - // Fetch the product with the ID - if (productId) { - await createApiRoot() - .products() - .withId({ ID: productId }) - .get() - .execute(); + // This is demo code. + // You can do something e.g. with each lineitem + // If you need the whole product, load it first... + if (cart.lineItems.length !== 0) { + const productId = cart.lineItems[0].productId; + // Fetch the product with the ID + if (productId) { + await createApiRoot() + .products() + .withId({ ID: productId }) + .get() + .execute(); - // Work with the product + // Work with the product + } } // Create the UpdateActions Object to return it to the client @@ -42,10 +40,13 @@ const create = async (resource: Resource) => { updateActions.push(updateAction); + // API Extensions will perform the update actions that you return. You don't have to do this yourself. + // https://docs.commercetools.com/api/projects/api-extensions#updates-requested return { statusCode: 200, actions: updateActions }; } catch (error) { // Retry or handle the error // Create an error object + logger.error(`Internal server error on CartController: ${error}`); if (error instanceof Error) { throw new CustomError( 400, @@ -55,26 +56,29 @@ const create = async (resource: Resource) => { } }; -// Controller for update actions -// const update = (resource: Resource) => {}; +/** + * Handle the update action + */ +const update = (cart: Cart): ControllerResult => { + logger.info(`Running API Extension for Cart Update (ID: ${cart.id})`); + return; +}; /** * Handle the cart controller according to the action - * - * @param {string} action The action that comes with the request. Could be `Create` or `Update` - * @param {Resource} resource The resource from the request body - * @returns {Promise} The data from the method that handles the action */ -export const cartController = async (action: string, resource: Resource) => { +export const cartController = async ( + action: ExtensionAction, + resource: Cart +) => { switch (action) { - case 'Create': { - const data = create(resource); - return data; - } + case 'Create': + return create(resource); case 'Update': - break; + return update(resource); default: + logger.error(` Action ('${action}') not recognized.`); throw new CustomError( 500, `Internal Server Error - Resource not recognized. Allowed values are 'Create' or 'Update'.` diff --git a/application-templates/typescript/service/src/controllers/service.controller.ts b/application-templates/typescript/service/src/controllers/service.controller.ts index 22fa31b..a688755 100644 --- a/application-templates/typescript/service/src/controllers/service.controller.ts +++ b/application-templates/typescript/service/src/controllers/service.controller.ts @@ -2,6 +2,22 @@ import { Request, Response } from 'express'; import { apiSuccess } from '../api/success.api'; import CustomError from '../errors/custom.error'; import { cartController } from './cart.controller'; +import { logger } from '../utils/logger.utils'; +import { ParamsDictionary } from 'express-serve-static-core'; +import { BusinessUnitReference } from '@commercetools/platform-sdk/dist/declarations/src/generated/models/business-unit'; +import { CartReference } from '@commercetools/platform-sdk/dist/declarations/src/generated/models/cart'; +import { CustomerGroupReference } from '@commercetools/platform-sdk/dist/declarations/src/generated/models/customer-group'; +import { CustomerReference } from '@commercetools/platform-sdk/dist/declarations/src/generated/models/customer'; +import { OrderReference } from '@commercetools/platform-sdk/dist/declarations/src/generated/models/order'; +import { PaymentReference } from '@commercetools/platform-sdk/dist/declarations/src/generated/models/payment'; +import { QuoteReference } from '@commercetools/platform-sdk/dist/declarations/src/generated/models/quote'; +import { QuoteRequestReference } from '@commercetools/platform-sdk/dist/declarations/src/generated/models/quote-request'; +import { ShoppingListReference } from '@commercetools/platform-sdk/dist/declarations/src/generated/models/shopping-list'; +import { StagedQuoteReference } from '@commercetools/platform-sdk/dist/declarations/src/generated/models/staged-quote'; +import { ExtensionAction } from '@commercetools/platform-sdk/dist/declarations/src/generated/models/extension'; +import { ControllerResult } from '../types/controller.types'; +import { paymentController } from './payment.controller'; +import { orderController } from './order.controller'; /** * Exposed service endpoint. @@ -12,47 +28,73 @@ import { cartController } from './cart.controller'; * @param {Response} response The express response * @returns */ -export const post = async (request: Request, response: Response) => { - // Deserialize the action and resource from the body +export const post = async ( + request: Request< + ParamsDictionary, + any, + { + action: ExtensionAction; + resource?: + | BusinessUnitReference + | CartReference + | CustomerGroupReference + | CustomerReference + | OrderReference + | PaymentReference + | QuoteReference + | QuoteRequestReference + | ShoppingListReference + | StagedQuoteReference; + } + >, + response: Response +) => { + // Check request body + if (!request.body) { + logger.error('Missing request body.'); + throw new CustomError(400, 'Bad request: No Pub/Sub message was received'); + } + + // Get the action and resource from the body const { action, resource } = request.body; - if (!action || !resource) { - throw new CustomError(400, 'Bad request - Missing body parameters.'); + if (!action) { + logger.error('Missing action in body.'); + throw new CustomError(400, 'Bad request - Missing action parameter.'); + } + if (!resource) { + logger.error('Missing resource in body.'); + throw new CustomError(400, 'Bad request - Missing resource parameter.'); + } + if (!resource.obj) { + logger.error('Missing obj in resource.'); + throw new CustomError(400, 'Bad request - Missing obj in resource.'); } // Identify the type of resource in order to redirect - // to the correct controller + // to the correct controller; + let data: ControllerResult = undefined; switch (resource.typeId) { - case 'cart': - try { - const data = await cartController(action, resource); - - if (data && data.statusCode === 200) { - apiSuccess(200, data.actions, response); - return; - } - - throw new CustomError( - data ? data.statusCode : 400, - JSON.stringify(data) - ); - } catch (error) { - if (error instanceof Error) { - throw new CustomError(500, error.message); - } - } - + case 'cart': { + data = await cartController(action, resource.obj); break; + } case 'payment': + data = paymentController(action, resource.obj); break; case 'order': + data = orderController(action, resource.obj); break; default: + logger.error( + " Resource not recognized. Allowed values are 'cart', 'payment' or 'order'." + ); throw new CustomError( 500, `Internal Server Error - Resource not recognized. Allowed values are 'cart', 'payments' or 'orders'.` ); } + apiSuccess(data?.statusCode || 200, data?.actions || [], response); }; diff --git a/application-templates/typescript/service/src/interfaces/resource.interface.ts b/application-templates/typescript/service/src/interfaces/resource.interface.ts deleted file mode 100644 index 0dbda1d..0000000 --- a/application-templates/typescript/service/src/interfaces/resource.interface.ts +++ /dev/null @@ -1,2 +0,0 @@ -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface Resource {} diff --git a/application-templates/typescript/service/src/types/controller.types.ts b/application-templates/typescript/service/src/types/controller.types.ts new file mode 100644 index 0000000..1de9ae0 --- /dev/null +++ b/application-templates/typescript/service/src/types/controller.types.ts @@ -0,0 +1,5 @@ +import { UpdateAction } from '@commercetools/sdk-client-v2'; + +export type ControllerResult = + | { statusCode: number; actions: Array } + | undefined; From e13d3554e044deea6aaff33ddd8a003ef550c398 Mon Sep 17 00:00:00 2001 From: Philipp Hofmann Date: Wed, 6 Nov 2024 08:40:12 +0100 Subject: [PATCH 12/14] fix: introduced tests for service --- .../service/tests/integration/cart.spec.ts | 138 ++++++++++++++++++ .../service/tests/integration/routes.spec.ts | 4 +- .../typescript/service/tests/setup-tests.ts | 3 + 3 files changed, 143 insertions(+), 2 deletions(-) create mode 100644 application-templates/typescript/service/tests/integration/cart.spec.ts create mode 100644 application-templates/typescript/service/tests/setup-tests.ts diff --git a/application-templates/typescript/service/tests/integration/cart.spec.ts b/application-templates/typescript/service/tests/integration/cart.spec.ts new file mode 100644 index 0000000..fb6664c --- /dev/null +++ b/application-templates/typescript/service/tests/integration/cart.spec.ts @@ -0,0 +1,138 @@ +import { describe, expect } from '@jest/globals'; +import { cartController } from '../../src/controllers/cart.controller'; +import { Cart, LineItem } from '@commercetools/platform-sdk'; +import { createApiRoot } from '../../src/client/create.client'; + +jest.mock('../../src/client/create.client', () => { + const mockCreateApiRoot = jest.fn(); + return { + createApiRoot: mockCreateApiRoot, + }; +}); + +const cartId = '643cf548-e12e-4f17-bc7d-c826ecbc35b2'; +const productId = 'fc75c80a-35bb-4414-92b1-d7899534b6f2'; + +const lineItem: LineItem = { + id: '5f409894-85eb-498e-b44e-4f8155d3619a', + productId: 'fc75c80a-35bb-4414-92b1-d7899534b6f2', + productKey: 'ben-pillow-cover', + name: { + 'en-US': 'Ben Pillow Cover', + }, + productType: { + typeId: 'product-type', + id: 'fc4487a6-65cb-4433-916a-f669e094e4d8', + }, + productSlug: { + 'en-US': 'ben-pillow-cover', + }, + variant: { + id: 1, + sku: 'LBPC-09', + key: 'LBPC-09', + }, + price: { + id: 'a1ba8634-68cc-4e68-b318-2284f111bb3d', + value: { + type: 'centPrecision', + currencyCode: 'EUR', + centAmount: 1039, + fractionDigits: 2, + }, + key: 'ben-pillow-cover-LBPC-09-FR-EUR', + country: 'FR', + }, + quantity: 1, + discountedPricePerQuantity: [], + perMethodTaxRate: [], + addedAt: '2023-12-18T18:07:29.685Z', + lastModifiedAt: '2023-12-18T18:07:29.685Z', + state: [], + priceMode: 'Platform', + lineItemMode: 'Standard', + totalPrice: { + type: 'centPrecision', + currencyCode: 'EUR', + centAmount: 1039, + fractionDigits: 2, + }, + taxedPricePortions: [], +}; + +const cart: Cart = { + id: cartId, + version: 1, + createdAt: '', + lastModifiedAt: '', + anonymousId: 'e9e59e5b-6e88-414d-9797-d1a08411db12', + locale: 'de-DE', + lineItems: [lineItem], + cartState: 'Active', + totalPrice: { + type: 'centPrecision', + currencyCode: 'EUR', + centAmount: 0, + fractionDigits: 2, + }, + country: 'DE', + shippingMode: 'Single', + shipping: [], + customLineItems: [], + discountCodes: [], + directDiscounts: [], + inventoryMode: 'ReserveOnOrder', + taxMode: 'Platform', + taxRoundingMode: 'HalfEven', + taxCalculationMode: 'LineItemLevel', + refusedGifts: [], + origin: 'Customer', + itemShippingAddresses: [], +}; + +describe('Testing Event Controller', () => { + it('Cart Created (Mocked)', async () => { + // Define a mock root to be returned + const withId = jest.fn().mockReturnValueOnce({ + get: jest.fn().mockReturnValueOnce({ + execute: jest.fn().mockReturnValueOnce(lineItem), + }), + }); + const mockRoot = { + products: jest.fn().mockReturnValueOnce({ + withId: withId, + }), + }; + + // Set the mock implementation for createApiRoot to return mockRoot + (createApiRoot as jest.Mock).mockReturnValueOnce(mockRoot); + + const response = await cartController('Create', { + ...cart, + lineItems: [lineItem], + }); + + expect(response?.statusCode).toBe(200); + expect(withId).toHaveBeenCalledWith({ ID: productId }); + }); + /* + * This Test really calls the commercetools APIs. To do so, + * - remove the mocking code in line 7-12 + * - configure a "real" cartId + * - remove the skip below + * + * The code below comes in handy to test during development. + */ + it.skip('Cart Created', async () => { + const response = await cartController('Create', cart); + expect(response?.statusCode).toBe(200); + expect(response?.actions).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + action: 'recalculate', + updateProductData: false, + }), + ]) + ); + }); +}); diff --git a/application-templates/typescript/service/tests/integration/routes.spec.ts b/application-templates/typescript/service/tests/integration/routes.spec.ts index 6faef88..83259c7 100644 --- a/application-templates/typescript/service/tests/integration/routes.spec.ts +++ b/application-templates/typescript/service/tests/integration/routes.spec.ts @@ -22,14 +22,14 @@ describe('Testing router', () => { }); expect(response.status).toBe(400); expect(response.body).toEqual({ - message: 'Bad request - Missing body parameters.', + message: 'Bad request - Missing action parameter.', }); }); test('Post empty body', async () => { const response = await request(app).post('/service'); expect(response.status).toBe(400); expect(response.body).toEqual({ - message: 'Bad request - Missing body parameters.', + message: 'Bad request - Missing action parameter.', }); }); }); diff --git a/application-templates/typescript/service/tests/setup-tests.ts b/application-templates/typescript/service/tests/setup-tests.ts new file mode 100644 index 0000000..b7998b4 --- /dev/null +++ b/application-templates/typescript/service/tests/setup-tests.ts @@ -0,0 +1,3 @@ +import { config } from 'dotenv'; +config(); +config({ path: `.env.local`, override: true }); From 51dabfb7eb1b43d98d4bc1ab7779b1772ad30e60 Mon Sep 17 00:00:00 2001 From: Philipp Hofmann Date: Wed, 6 Nov 2024 08:50:27 +0100 Subject: [PATCH 13/14] fix: introduced setup.tests file for service --- application-templates/typescript/service/jest.config.cjs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/application-templates/typescript/service/jest.config.cjs b/application-templates/typescript/service/jest.config.cjs index 2fee1a2..2205a9b 100644 --- a/application-templates/typescript/service/jest.config.cjs +++ b/application-templates/typescript/service/jest.config.cjs @@ -2,6 +2,8 @@ module.exports = { displayName: 'Tests Typescript Application - Service', moduleDirectories: ['node_modules', 'src'], testMatch: ['**/tests/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[tj]s?(x)'], + testPathIgnorePatterns: ['tests/setup-tests.ts'], preset: 'ts-jest', testEnvironment: 'node', + setupFiles: ['/tests/setup-tests.ts'], }; From e6efcae634f7bbf559ce239ec0be6ed20ce063fc Mon Sep 17 00:00:00 2001 From: Philipp Hofmann Date: Wed, 6 Nov 2024 09:21:08 +0100 Subject: [PATCH 14/14] chore: fix mocking for test --- .../typescript/event/tests/integration/event.spec.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/application-templates/typescript/event/tests/integration/event.spec.ts b/application-templates/typescript/event/tests/integration/event.spec.ts index 0c07cb9..180cd47 100644 --- a/application-templates/typescript/event/tests/integration/event.spec.ts +++ b/application-templates/typescript/event/tests/integration/event.spec.ts @@ -3,6 +3,7 @@ import { CustomerCreatedMessage, Customer } from '@commercetools/platform-sdk'; import request from 'supertest'; import app from '../../src/app'; import { createApiRoot } from '../../src/client/create.client'; +import { readConfiguration } from '../../src/utils/config.utils'; jest.mock('../../src/client/create.client', () => { const mockCreateApiRoot = jest.fn(); @@ -10,6 +11,7 @@ jest.mock('../../src/client/create.client', () => { createApiRoot: mockCreateApiRoot, }; }); +jest.mock('../../src/utils/config.utils'); const customerId = 'd93a8493-1bfd-4e2d-8b46-20a765dd0978'; @@ -37,7 +39,10 @@ const orderCreatedMessage: CustomerCreatedMessage = { }; describe('Testing Event Controller', () => { - it('Customer Created (Mocked)', async () => { + beforeEach(() => { + (readConfiguration as jest.Mock).mockClear(); + }); + test('Customer Created (Mocked)', async () => { // Define a mock root to be returned const withId = jest.fn().mockReturnValueOnce({ get: jest.fn().mockReturnValueOnce({ @@ -68,7 +73,7 @@ describe('Testing Event Controller', () => { /* * This Test really calls the commercetools APIs. To do so, - * - remove the mocking code in line 7-12 + * - remove the mocking code in line 8-14 * - configure a "real" customerId * - remove the skip below *