Add test for CUSTOMER_CREATED webhook in CRM app (#422)

* Add test for CUSTOMER_CREATED webhook in CRM app

* remove log
This commit is contained in:
Lukasz Ostrowski 2023-04-25 18:57:17 +02:00 committed by GitHub
parent fc7a70f598
commit 112bed4a0b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 169 additions and 6 deletions

View file

@ -64,6 +64,7 @@
"eslint-config-next": "13.1.2", "eslint-config-next": "13.1.2",
"eslint-config-prettier": "^8.6.0", "eslint-config-prettier": "^8.6.0",
"eslint-config-saleor": "workspace:*", "eslint-config-saleor": "workspace:*",
"node-mocks-http": "^1.12.2",
"prettier": "^2.8.2", "prettier": "^2.8.2",
"typescript": "4.9.4" "typescript": "4.9.4"
} }

View file

@ -26,13 +26,17 @@ const MetadataSchemaV1 = z.object({
config: ConfigV1, config: ConfigV1,
}); });
export interface IMailchimpConfigSettingsManagerV1 {
getConfig(): Promise<z.infer<typeof ConfigV1> | null>;
}
/** /**
* V1 config. In case of changing config, create another instance and perform migration * V1 config. In case of changing config, create another instance and perform migration
* *
* todo save domain? * todo save domain?
* todo add test * todo add test
*/ */
export class MailchimpConfigSettingsManagerV1 { export class MailchimpConfigSettingsManagerV1 implements IMailchimpConfigSettingsManagerV1 {
private settingsManager: SettingsManager; private settingsManager: SettingsManager;
private readonly metadataKey = "mailchimp_config_v1"; private readonly metadataKey = "mailchimp_config_v1";
private logger = createLogger({ private logger = createLogger({
@ -71,7 +75,7 @@ export class MailchimpConfigSettingsManagerV1 {
} }
async getConfig(): Promise<z.infer<typeof ConfigV1> | null> { async getConfig(): Promise<z.infer<typeof ConfigV1> | null> {
this.logger.debug(`Will fetched metadata key: ${this.metadataKey}`); this.logger.debug(`Will fetch metadata key: ${this.metadataKey}`);
const rawMetadata = await this.settingsManager const rawMetadata = await this.settingsManager
.get(this.metadataKey) .get(this.metadataKey)
.then(this.parseEmptyResponse); .then(this.parseEmptyResponse);
@ -112,3 +116,4 @@ export class MailchimpConfigSettingsManagerV1 {
export const MailchimpConfigSettingsManager = MailchimpConfigSettingsManagerV1; export const MailchimpConfigSettingsManager = MailchimpConfigSettingsManagerV1;
export const MailchimpConfig = ConfigV1; export const MailchimpConfig = ConfigV1;
export type MailchimpConfigType = z.infer<typeof MailchimpConfig>;

View file

@ -18,7 +18,8 @@ export const customerCreatedWebhook = new SaleorAsyncWebhook<CustomerCreatedPayl
query: CustomerCreatedDocument, query: CustomerCreatedDocument,
}); });
const handler: NextWebhookApiHandler<CustomerCreatedPayloadFragment> = async ( // todo - fetch metadata with event
export const customerCreatedHandler: NextWebhookApiHandler<CustomerCreatedPayloadFragment> = async (
req, req,
res, res,
context context
@ -62,7 +63,7 @@ const handler: NextWebhookApiHandler<CustomerCreatedPayloadFragment> = async (
return res.status(200).json({ message: "The event has been handled" }); return res.status(200).json({ message: "The event has been handled" });
}; };
export default customerCreatedWebhook.createHandler(handler); export default customerCreatedWebhook.createHandler(customerCreatedHandler);
export const config = { export const config = {
api: { api: {

View file

@ -0,0 +1,82 @@
import { describe, expect, it, vi } from "vitest";
import { createMocks } from "node-mocks-http";
import { customerCreatedHandler } from "../../pages/api/webhooks/customer-created";
import { AuthData } from "@saleor/app-sdk/APL";
import mailchimp_marketing from "@mailchimp/mailchimp_marketing";
import {
IMailchimpConfigSettingsManagerV1,
MailchimpConfigType,
} from "../../modules/mailchimp/mailchimp-config-settings-manager";
/**
* Mock settings manager. Consider mocking graphQL api instead
*/
vi.mock("../../modules/mailchimp/mailchimp-config-settings-manager", () => {
class MockManager implements IMailchimpConfigSettingsManagerV1 {
async getConfig(): Promise<MailchimpConfigType> {
return {
token: "mailchimpToken",
customerCreateEvent: {
enabled: true,
listId: "saleor",
},
dc: "us41",
};
}
}
return {
MailchimpConfigSettingsManager: MockManager,
};
});
/**
* Spy on mailchimp client
*/
mailchimp_marketing.lists.addListMember = vi.fn();
const mockAuthData: AuthData = {
saleorApiUrl: "https://demo.saleor.io/graphql/",
domain: "demo.saleor.io",
appId: "XYZ",
token: "token-mocked",
};
describe("CUSTOMER_CREATED webhook", () => {
it("Call Mailchimp client to add customer with properly mapped data and tags", async () => {
const { req, res } = createMocks({});
await customerCreatedHandler(req, res, {
authData: mockAuthData,
payload: {
user: {
id: "user-id",
email: "someuser@gmail.com",
firstName: "John",
lastName: "Doe",
privateMetadata: [
{
key: "mailchimp_tags",
value: JSON.stringify(["tag1"]),
},
],
},
},
event: "CUSTOMER_CREATED",
baseUrl: "localhost:3000",
});
return expect(mailchimp_marketing.lists.addListMember).toHaveBeenCalledWith("saleor", {
email_address: "someuser@gmail.com",
merge_fields: {
FNAME: "John",
LNAME: "Doe",
},
status: "transactional",
tags: ["Saleor Import", "tag1"],
});
});
it.todo('Doesnt do anything if configuration "customerCreateEvent" is disabled');
});

View file

@ -312,6 +312,9 @@ importers:
eslint-config-saleor: eslint-config-saleor:
specifier: workspace:* specifier: workspace:*
version: link:../../packages/eslint-config-saleor version: link:../../packages/eslint-config-saleor
node-mocks-http:
specifier: ^1.12.2
version: 1.12.2
prettier: prettier:
specifier: ^2.8.2 specifier: ^2.8.2
version: 2.8.4 version: 2.8.4
@ -7507,6 +7510,14 @@ packages:
dependencies: dependencies:
event-target-shim: 5.0.1 event-target-shim: 5.0.1
/accepts@1.3.8:
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
engines: {node: '>= 0.6'}
dependencies:
mime-types: 2.1.35
negotiator: 0.6.3
dev: true
/acorn-globals@7.0.1: /acorn-globals@7.0.1:
resolution: {integrity: sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==} resolution: {integrity: sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==}
dependencies: dependencies:
@ -8610,6 +8621,13 @@ packages:
upper-case: 2.0.2 upper-case: 2.0.2
dev: true dev: true
/content-disposition@0.5.4:
resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}
engines: {node: '>= 0.6'}
dependencies:
safe-buffer: 5.2.1
dev: true
/convert-source-map@1.9.0: /convert-source-map@1.9.0:
resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==}
@ -8955,6 +8973,11 @@ packages:
resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==}
dev: false dev: false
/depd@1.1.2:
resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==}
engines: {node: '>= 0.6'}
dev: true
/depd@2.0.0: /depd@2.0.0:
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
@ -10690,6 +10713,11 @@ packages:
resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==} resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==}
dev: true dev: true
/fresh@0.5.2:
resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
engines: {node: '>= 0.6'}
dev: true
/fs-constants@1.0.0: /fs-constants@1.0.0:
resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==}
dev: false dev: false
@ -12473,6 +12501,11 @@ packages:
resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==} resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==}
dev: false dev: false
/media-typer@0.3.0:
resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
engines: {node: '>= 0.6'}
dev: true
/memoize-one@5.2.1: /memoize-one@5.2.1:
resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==} resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==}
dev: false dev: false
@ -12497,6 +12530,10 @@ packages:
type-fest: 0.13.1 type-fest: 0.13.1
yargs-parser: 18.1.3 yargs-parser: 18.1.3
/merge-descriptors@1.0.1:
resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==}
dev: true
/merge-stream@2.0.0: /merge-stream@2.0.0:
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
dev: true dev: true
@ -12532,7 +12569,6 @@ packages:
/methods@1.1.2: /methods@1.1.2:
resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
dev: false
/microinvoice@1.0.6: /microinvoice@1.0.6:
resolution: {integrity: sha512-mFzikOGHV4tEEuGMZ4ZawcOQvA3NRRxi9RajGuuZKevIWK8Wjayo+DsUq+kERcK6s89sar/8V9TxoAzLELEeHg==} resolution: {integrity: sha512-mFzikOGHV4tEEuGMZ4ZawcOQvA3NRRxi9RajGuuZKevIWK8Wjayo+DsUq+kERcK6s89sar/8V9TxoAzLELEeHg==}
@ -12744,7 +12780,6 @@ packages:
resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
engines: {node: '>=4'} engines: {node: '>=4'}
hasBin: true hasBin: true
dev: false
/mime@2.6.0: /mime@2.6.0:
resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==}
@ -13266,6 +13301,11 @@ packages:
/natural-compare@1.4.0: /natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
/negotiator@0.6.3:
resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
engines: {node: '>= 0.6'}
dev: true
/neo-async@2.6.2: /neo-async@2.6.2:
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
dev: false dev: false
@ -13397,6 +13437,22 @@ packages:
resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==}
dev: true dev: true
/node-mocks-http@1.12.2:
resolution: {integrity: sha512-xhWwC0dh35R9rf0j3bRZXuISXdHxxtMx0ywZQBwjrg3yl7KpRETzogfeCamUIjltpn0Fxvs/ZhGJul1vPLrdJQ==}
engines: {node: '>=0.6'}
dependencies:
accepts: 1.3.8
content-disposition: 0.5.4
depd: 1.1.2
fresh: 0.5.2
merge-descriptors: 1.0.1
methods: 1.1.2
mime: 1.6.0
parseurl: 1.3.3
range-parser: 1.2.1
type-is: 1.6.18
dev: true
/node-releases@2.0.10: /node-releases@2.0.10:
resolution: {integrity: sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==} resolution: {integrity: sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==}
@ -13813,6 +13869,11 @@ packages:
peberminta: 0.8.0 peberminta: 0.8.0
dev: false dev: false
/parseurl@1.3.3:
resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
engines: {node: '>= 0.8'}
dev: true
/pascal-case@3.1.2: /pascal-case@3.1.2:
resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==}
dependencies: dependencies:
@ -14165,6 +14226,11 @@ packages:
engines: {node: '>=10'} engines: {node: '>=10'}
dev: false dev: false
/range-parser@1.2.1:
resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
engines: {node: '>= 0.6'}
dev: true
/raw-body@2.5.1: /raw-body@2.5.1:
resolution: {integrity: sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==} resolution: {integrity: sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==}
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
@ -15974,6 +16040,14 @@ packages:
resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==}
engines: {node: '>=8'} engines: {node: '>=8'}
/type-is@1.6.18:
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
engines: {node: '>= 0.6'}
dependencies:
media-typer: 0.3.0
mime-types: 2.1.35
dev: true
/typed-array-length@1.0.4: /typed-array-length@1.0.4:
resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==} resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==}
dependencies: dependencies: