UNPKG

@benshi.ai/js-sdk

Version:

Benshi SDK

714 lines (601 loc) 24.4 kB
/** * @jest-environment jsdom */ import BsCore from "../../../core/BsCore" import ECommerce from "../../ECommerce" import Navigation from "../../Navigation" import { EventEmitter } from 'events' import { CartAction, DeliveryAction, DrugProperties, ECommerceTypes, ItemAction, ListAction, ListType, ItemType, StockStatus, ScheduleDeliveryAction, CancelType } from "../typings"; import { BloodProperties } from "../blood.typings" import { CurrencyCode } from "../../../core/commonTypes" import { ICurrencyRepository } from "../../../core/repositories/currency/CurrencyRepository" import { ICatalogRepository } from "../../../core/repositories/catalog/CatalogRepository"; import { AudioVideoType, ContentBlock, MediaData, UserProperties } from "../../Navigation/typings"; import { toISOLocal } from "../../../utils"; import { ImpressionEventType } from "../../../core/impressions/typings"; import { MedicalEquipmentProperties } from "../medicalEquipment.typings"; import { OxygenProperties } from "../oxygen.typings"; let windowSpy let mockSender; let mockImpressionManager = { on: () => { } }; let mockCatalogRepository: ICatalogRepository; const device_id = 'asdf' describe('ECommerce', () => { beforeAll(() => { mockSender = { add() { return false } } const mockCurrencyRepository: ICurrencyRepository = { convertCurrencyToUSD: (currency: CurrencyCode) => new Promise(r => r({ date: '2022-10-02', usd: 2 })) } mockCatalogRepository = { injectBlood: (itemId: string, properties: BloodProperties) => new Promise(r => r()), injectDrug: (itemId: string, properties: DrugProperties) => new Promise(r => r()), injectMedia: (id: string, type: AudioVideoType, properties: MediaData | {}) => { }, injectMedicalEquipment: (itemId: string, properties: MedicalEquipmentProperties) => new Promise(r => r()), injectOxygen: (itemId: string, properties: OxygenProperties) => new Promise(r => r()), injectUser: (userId: string, properties: UserProperties) => new Promise(r => r()), } ECommerce.init(mockCurrencyRepository, mockCatalogRepository) }) beforeEach(() => { windowSpy = jest.spyOn(window, "window", "get"); }) afterEach(() => { // to remove the singleton instance (BsCore as any).instance = null }) it('Should deliver a "Checkout" event', async () => { const senderSpy = jest.spyOn(mockSender, 'add') const bsCore = BsCore.createInstance(mockSender as any, mockImpressionManager as any, false, ContentBlock.Core, device_id) const event = { id: "1", cart_price: 10, currency: CurrencyCode.EUR, tax: 0, items: [{ id: '111', price: 90, type: ItemType.Drug, quantity: 1, promo_id: '11111', currency: CurrencyCode.EUR, stock_status: StockStatus.InStock }], cart_id: '11', is_successful: true } await ECommerce.logCheckoutEvent(event) expect(senderSpy).toBeCalledWith( expect.stringMatching(device_id), expect.stringMatching(device_id), expect.objectContaining( { type: ECommerceTypes.Checkout, props: { ...event, usd_rate: 1 } }), expect.anything()) }) it('Should thrown an exception on "Checkout" event with repeated items', async () => { const senderSpy = jest.spyOn(mockSender, 'add') const bsCore = BsCore.createInstance(mockSender as any, mockImpressionManager as any, false, ContentBlock.Core, device_id) const event = { id: "1", cart_price: 10, currency: CurrencyCode.EUR, tax: 0, items: [{ id: '111', price: 90, type: ItemType.Drug, quantity: 1, promo_id: '11111', currency: CurrencyCode.EUR, stock_status: StockStatus.InStock },{ id: '111', price: 89, type: ItemType.Drug, quantity: 1, promo_id: '11111', currency: CurrencyCode.EUR, stock_status: StockStatus.InStock }, ], cart_id: '11', is_successful: true } expect(async () => await ECommerce.logCheckoutEvent(event)).rejects.toThrowError() }) it('Should trigger an exception delivering a "Checkout" event with blood without meta', async () => { const senderSpy = jest.spyOn(mockSender, 'add') const bsCore = BsCore.createInstance(mockSender as any, mockImpressionManager as any, false, ContentBlock.Core, device_id) const event = { id: "1", cart_price: 10, currency: CurrencyCode.EUR, tax: 0, items: [{ id: '111', price: 90, type: ItemType.Drug, quantity: 1, promo_id: '11111', currency: CurrencyCode.EUR, stock_status: StockStatus.InStock }, { id: '222', price: 90, type: ItemType.Blood, quantity: 1, promo_id: '11111', currency: CurrencyCode.EUR, stock_status: StockStatus.InStock },], cart_id: '11', is_successful: true } await expect(() => ECommerce.logCheckoutEvent(event)).rejects.toThrowError() }) it('Should not trigger an exception delivering a "Checkout" event with blood with meta', async () => { const senderSpy = jest.spyOn(mockSender, 'add') const bsCore = BsCore.createInstance(mockSender as any, mockImpressionManager as any, false, ContentBlock.Core, device_id) const event = { id: "1", cart_price: 10, currency: CurrencyCode.EUR, tax: 0, items: [{ id: '111', price: 90, type: ItemType.Drug, quantity: 1, promo_id: '11111', currency: CurrencyCode.EUR, stock_status: StockStatus.InStock }, { id: '222', price: 90, type: ItemType.Blood, quantity: 1, promo_id: '11111', currency: CurrencyCode.EUR, stock_status: StockStatus.InStock, meta: { cross_matching: true, temperature_strips: true, extra_tests: true, reason: 'asdf' } },], cart_id: '11', is_successful: true } await expect(() => ECommerce.logCheckoutEvent(event)).not.toThrowError() }) it('Should trigger an Exception due currency missmatch in a "Checkout" event', async () => { const senderSpy = jest.spyOn(mockSender, 'add') const bsCore = BsCore.createInstance(mockSender as any, mockImpressionManager as any, false, ContentBlock.Core, device_id) const event = { id: "1", cart_price: 10, currency: CurrencyCode.EUR, tax: 0, items: [{ id: '111', price: 90, type: ItemType.Drug, quantity: 1, promo_id: '11111', currency: CurrencyCode.EUR, stock_status: StockStatus.InStock }, { id: '111', type: ItemType.Drug, price: 90, quantity: 1, promo_id: '11111', currency: CurrencyCode.GBP, stock_status: StockStatus.InStock }, ], cart_id: '11', is_successful: true } await expect(ECommerce.logCheckoutEvent(event)).rejects.toThrow('currency-missmatch') }) it('Should deliver a "ScheduleDelivery" marked as non urgent', () => { const senderSpy = jest.spyOn(mockSender, 'add') BsCore.createInstance(mockSender as any, mockImpressionManager as any, false, ContentBlock.Core, device_id) const event = { order_id: "123", is_urgent: false, // true = emergency, false = schedule action: ScheduleDeliveryAction.Schedule, ts: toISOLocal(new Date()) } ECommerce.logScheduleDeliveryEvent(event) expect(senderSpy).toBeCalledWith( expect.stringMatching(device_id), expect.stringMatching(device_id), expect.objectContaining( { type: ECommerceTypes.ScheduleDelivery, props: event }), expect.anything()) }) it('Should thrown an exception when delivering a "ScheduleDelivery" marked as non urgent', () => { const senderSpy = jest.spyOn(mockSender, 'add') BsCore.createInstance(mockSender as any, mockImpressionManager as any, false, ContentBlock.Core, device_id) const event = { order_id: "123", is_urgent: false, // true = emergency, false = schedule action: ScheduleDeliveryAction.Schedule, ts: '1985-04-12T23:20:50.52Z' } expect(() => ECommerce.logScheduleDeliveryEvent(event)).toThrowError() }) it('Should deliver a "ScheduleDelivery" marked as urgent', () => { const mockSender = { add() { return false } } const senderSpy = jest.spyOn(mockSender, 'add') BsCore.createInstance(mockSender as any, mockImpressionManager as any, false, ContentBlock.Core, device_id) const event = { order_id: "123", is_urgent: true, // true = emergency, false = schedule action: ScheduleDeliveryAction.Schedule, } ECommerce.logScheduleDeliveryEvent(event) // check that datetime have been set automatically jut now const diffTime = new Date() - new Date(senderSpy.mock.calls[0][2].props.ts) expect(diffTime < 100).toBeTruthy() }) it('Should deliver a "Delivery" event', () => { const senderSpy = jest.spyOn(mockSender, 'add') const bsCore = BsCore.createInstance(mockSender as any, mockImpressionManager as any, false, ContentBlock.Core, device_id) const event = { id: "1", action: DeliveryAction.Delivered, order_id: '11', } ECommerce.logDeliveryEvent(event) expect(senderSpy).toBeCalledWith( expect.stringMatching(device_id), expect.stringMatching(device_id), expect.objectContaining( { type: ECommerceTypes.Delivery, props: event }), expect.anything()) }) it('Should deliver a "Cancel" event', () => { const senderSpy = jest.spyOn(mockSender, 'add') BsCore.createInstance(mockSender as any, mockImpressionManager as any, false, ContentBlock.Core, device_id) const event = { id: "123", type: CancelType.Order, items: [{ id: '111', type: ItemType.Drug }], reason: "some text", } ECommerce.logCancelCheckoutEvent(event) expect(senderSpy).toBeCalledWith( expect.stringMatching(device_id), expect.stringMatching(device_id), expect.objectContaining( { type: ECommerceTypes.Cancel, props: event }), expect.anything()) }) it('Should thrown an exception on "Cancel" event due repeated ids', () => { const senderSpy = jest.spyOn(mockSender, 'add') BsCore.createInstance(mockSender as any, mockImpressionManager as any, false, ContentBlock.Core, device_id) const event = { id: "123", type: CancelType.Order, items: [{ id: '111', type: ItemType.Drug }, { id: '111', type: ItemType.Drug }], reason: "some text", } expect(async () => ECommerce.logCancelCheckoutEvent(event)).rejects.toThrowError() }) it('Should throw an error delivering "Cancel" event with invalid types', () => { const senderSpy = jest.spyOn(mockSender, 'add') BsCore.createInstance(mockSender as any, mockImpressionManager as any, false, ContentBlock.Core, device_id) const event = { order_id: "123", types: [ItemType.Blood, ItemType.Electronics, "non-existing-type"], reason: "some text", } expect(() => ECommerce.logCancelOrderEvent(event)).toThrowError() }) it('Should deliver a "Cart" event', async () => { const senderSpy = jest.spyOn(mockSender, 'add') const bsCore = BsCore.createInstance( mockSender as any, mockImpressionManager as any, false, ContentBlock.Core, device_id) const event = { id: "1", action: CartAction.AddItem, cart_price: 10, currency: CurrencyCode.EUR, item: { id: '111', type: ItemType.Drug, price: 90, quantity: 1, promo_id: '11111', currency: CurrencyCode.EUR, stock_status: StockStatus.InStock }, } await ECommerce.logCartEvent(event) expect(senderSpy).toBeCalledWith( expect.stringMatching(device_id), expect.stringMatching(device_id), expect.objectContaining( { type: ECommerceTypes.Cart, props: { ...event, usd_rate: 1 } }), expect.anything()) }) it('Should trigger an Exception when currencies missmatch in "Cart" event', async () => { const senderSpy = jest.spyOn(mockSender, 'add') const bsCore = BsCore.createInstance( mockSender as any, mockImpressionManager as any, false, ContentBlock.Core, device_id) const event = { id: "1", action: CartAction.AddItem, cart_price: 10, currency: CurrencyCode.BDT, item: { id: '111', price: 90, type: ItemType.Drug, quantity: 1, promo_id: '11111', currency: CurrencyCode.EUR, stock_status: StockStatus.InStock }, } await expect(ECommerce.logCartEvent(event)).rejects.toThrow('currency-missmatch') }) it('Should deliver an "Item" event', async () => { const senderSpy = jest.spyOn(mockSender, 'add') const bsCore = BsCore.createInstance(mockSender as any, mockImpressionManager as any, false, ContentBlock.Core, device_id) const event = { action: ItemAction.View, item: { id: '111', type: ItemType.Drug, quantity: 2, price: 100, currency: CurrencyCode.FKP, stock_status: StockStatus.InStock, promo_id: 'asdf' }, search_id: "1" } await ECommerce.logItemEvent(event, {}) expect(senderSpy).toBeCalledWith( expect.stringMatching(device_id), expect.stringMatching(device_id), expect.objectContaining( { type: ECommerceTypes.Item, props: { ...event, usd_rate: 1 } }), expect.anything()) }) it('Should not inject the drug when the action is not item_view', async () => { const senderSpy = jest.spyOn(mockSender, 'add') const catalogSpy = jest.spyOn(mockCatalogRepository, 'injectDrug') const bsCore = BsCore.createInstance(mockSender as any, mockImpressionManager as any, false, ContentBlock.Core, device_id) const event = { action: ItemAction.Detail, item: { id: '111', quantity: 2, type: ItemType.Drug, price: 100, currency: CurrencyCode.FKP, stock_status: StockStatus.InStock, promo_id: 'asdf' }, search_id: "1" } await ECommerce.logItemEvent(event, {}) expect(catalogSpy).not.toBeCalled() }) it('Should inject the drug when the action is item_view', async () => { const senderSpy = jest.spyOn(mockSender, 'add') const catalogSpy = jest.spyOn(mockCatalogRepository, 'injectDrug') const bsCore = BsCore.createInstance(mockSender as any, mockImpressionManager as any, false, ContentBlock.Core, device_id) const event = { action: ItemAction.View, item: { id: '111', quantity: 2, type: ItemType.Drug, price: 100, currency: CurrencyCode.FKP, stock_status: StockStatus.InStock, promo_id: 'asdf' }, search_id: "1" } await ECommerce.logItemEvent(event, { name: 'drugX', supplier: 'supplierY' }) expect(catalogSpy).toBeCalled() }) it('Should inject blood without meta', async () => { const senderSpy = jest.spyOn(mockSender, 'add') const catalogSpy = jest.spyOn(mockCatalogRepository, 'injectBlood') const bsCore = BsCore.createInstance(mockSender as any, mockImpressionManager as any, false, ContentBlock.Core, device_id) const event = { action: ItemAction.View, item: { id: '111', quantity: 2, type: ItemType.Blood, price: 100, currency: CurrencyCode.FKP, stock_status: StockStatus.InStock, promo_id: 'asdf' }, search_id: "1" } const catalogProperties = { market_id: "", blood_component: 'aa', blood_group: 'bb', packaging_size: 50, packaging_units: 'units' } await expect(() => ECommerce.logItemEvent(event, catalogProperties)).not.toThrowError() }) it('Should inject medicalEquipment data into catalog', async () => { const senderSpy = jest.spyOn(mockSender, 'add') const catalogSpy = jest.spyOn(mockCatalogRepository, 'injectMedicalEquipment') const bsCore = BsCore.createInstance(mockSender as any, mockImpressionManager as any, false, ContentBlock.Core, device_id) const event = { action: ItemAction.View, item: { id: '111', quantity: 2, type: ItemType.MedicalEquipment, price: 100, currency: CurrencyCode.FKP, stock_status: StockStatus.InStock, promo_id: 'asdf' }, search_id: "1" } const catalogProperties = { name: 'medical equip. name', // medical consumable name (e.g. 2-Way Catheter 16FR) market_id: '123', supplier_id: '123', supplier_name: '123-name', packaging_size: 50, packaging_units: 'units' } await ECommerce.logItemEvent(event, catalogProperties) expect(catalogSpy).toBeCalled() }) it('Should inject the drug when the action is item_view - itemId must be inserted automatically', async () => { const senderSpy = jest.spyOn(mockSender, 'add') const catalogSpy = jest.spyOn(mockCatalogRepository, 'injectDrug') const bsCore = BsCore.createInstance(mockSender as any, mockImpressionManager as any, false, ContentBlock.Core, device_id) const itemId = "111" const drugProperties = { name: 'drugX', supplier: 'supplierY' } const event = { action: ItemAction.View, item: { id: '111', quantity: 2, type: ItemType.Drug, price: 100, currency: CurrencyCode.FKP, stock_status: StockStatus.InStock, promo_id: 'asdf' }, search_id: "1" } await ECommerce.logItemEvent(event, drugProperties) expect(catalogSpy).toBeCalledWith(itemId, drugProperties) }) it('Should trigger an Exception when the item info is malformed', async () => { const senderSpy = jest.spyOn(mockSender, 'add') const bsCore = BsCore.createInstance(mockSender as any, mockImpressionManager as any, false, ContentBlock.Core, device_id) const event = { action: ItemAction.View, item: { id: '111', quantity: 2, price: 100, stock_status: StockStatus.InStock, promo_id: 'asdf' }, search_id: "1" } await expect(ECommerce.logItemEvent(event as any, {})).rejects.toThrowError() }) it('Should ingest data into item catalog when an item is impressed in the screen', async () => { const senderSpy = jest.spyOn(mockSender, 'add') const catalogSpy = jest.spyOn(mockCatalogRepository, 'injectDrug') class ImpressionsDetectorMock extends EventEmitter { constructor() { super() } start() { setTimeout(() => { console.log('timeout impression detector!') const dataForCatalog = { market_id: '2222', name: 'drugname', description: 'description', supplier_id: 'supplier_id', supplier_name: 'supplier_name', packaging_size: 50, packaging_units: 'units' } const data = { dataset: { logId: '1222', logCurrency: 'USD', logPrice: 123, logQuantity: 2, logStockStatus: 'in_stock', logPromoId: '', logDrugCatalogObject: JSON.stringify(dataForCatalog) }, appData: {} } this.emit(ImpressionEventType.Impression, data) }, 20) } } const bsCore = BsCore.createInstance(mockSender as any, ImpressionsDetectorMock as any, false, ContentBlock.Core, device_id) ECommerce.startTrackingImpressions('container-classname', 'item-classname', '111') await new Promise((resolve) => setTimeout(() => resolve(''), 100)) expect(catalogSpy).toBeCalled() }) it('Should throw an exception when trying to restart impression with an unknown container classname', async () => { const senderSpy = jest.spyOn(mockSender, 'add') const catalogSpy = jest.spyOn(mockCatalogRepository, 'injectDrug') class ImpressionsDetectorMock extends EventEmitter { constructor() { super() } start() { } } const bsCore = BsCore.createInstance(mockSender as any, ImpressionsDetectorMock as any, false, ContentBlock.Core, device_id) ECommerce.startTrackingImpressions('container-classname', 'item-classname', '111') expect(() => ECommerce.restartTrackingImpressions('unknown-container', '111')).toThrowError() }) })