@benshi.ai/js-sdk
Version:
Benshi SDK
714 lines (601 loc) • 24.4 kB
text/typescript
/**
* @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()
})
})