diff --git a/packages/event-processor/__tests__/pendingEventsDispatcher.spec.ts b/packages/event-processor/__tests__/pendingEventsDispatcher.spec.ts index 415afdf11..08c517687 100644 --- a/packages/event-processor/__tests__/pendingEventsDispatcher.spec.ts +++ b/packages/event-processor/__tests__/pendingEventsDispatcher.spec.ts @@ -51,7 +51,7 @@ describe('LocalStoragePendingEventsDispatcher', () => { localStorage.clear() }) - it('should properly send the events to the passed in eventDispatcher, when callback statusCode=200', () => { + it('should properly send the events to the passed in eventDispatcher, when callback statusCode=200', (done) => { const callback = jest.fn() const eventV1Request: EventV1Request = { url: 'http://cdn.com', @@ -61,22 +61,27 @@ describe('LocalStoragePendingEventsDispatcher', () => { pendingEventsDispatcher.dispatchEvent(eventV1Request, callback) - expect(callback).not.toHaveBeenCalled() - // manually invoke original eventDispatcher callback - const internalDispatchCall = ((originalEventDispatcher.dispatchEvent as unknown) as jest.Mock) - .mock.calls[0] - internalDispatchCall[1]({ statusCode: 200 }) + setTimeout(() => { + expect(callback).not.toHaveBeenCalled() + // manually invoke original eventDispatcher callback + const internalDispatchCall = ((originalEventDispatcher.dispatchEvent as unknown) as jest.Mock) + .mock.calls[0] + internalDispatchCall[1]({ statusCode: 200 }) - // assert that the original dispatch function was called with the request - expect((originalEventDispatcher.dispatchEvent as unknown) as jest.Mock).toBeCalledTimes(1) - expect(internalDispatchCall[0]).toEqual(eventV1Request) + // assert that the original dispatch function was called with the request + expect((originalEventDispatcher.dispatchEvent as unknown) as jest.Mock).toBeCalledTimes(1) + expect(internalDispatchCall[0]).toEqual(eventV1Request) - // assert that the passed in callback to pendingEventsDispatcher was called - expect(callback).toHaveBeenCalledTimes(1) - expect(callback).toHaveBeenCalledWith({ statusCode: 200 }) + setTimeout(() => { + // assert that the passed in callback to pendingEventsDispatcher was called + expect(callback).toHaveBeenCalledTimes(1) + expect(callback).toHaveBeenCalledWith({ statusCode: 200 }) + done() + }) + }) }) - it('should properly send the events to the passed in eventDispatcher, when callback statusCode=400', () => { + it('should properly send the events to the passed in eventDispatcher, when callback statusCode=400', (done) => { const callback = jest.fn() const eventV1Request: EventV1Request = { url: 'http://cdn.com', @@ -86,19 +91,24 @@ describe('LocalStoragePendingEventsDispatcher', () => { pendingEventsDispatcher.dispatchEvent(eventV1Request, callback) - expect(callback).not.toHaveBeenCalled() - // manually invoke original eventDispatcher callback - const internalDispatchCall = ((originalEventDispatcher.dispatchEvent as unknown) as jest.Mock) - .mock.calls[0] - internalDispatchCall[1]({ statusCode: 400 }) + setTimeout(() => { + expect(callback).not.toHaveBeenCalled() + // manually invoke original eventDispatcher callback + const internalDispatchCall = ((originalEventDispatcher.dispatchEvent as unknown) as jest.Mock) + .mock.calls[0] + internalDispatchCall[1]({ statusCode: 400 }) - // assert that the original dispatch function was called with the request - expect((originalEventDispatcher.dispatchEvent as unknown) as jest.Mock).toBeCalledTimes(1) - expect(internalDispatchCall[0]).toEqual(eventV1Request) + // assert that the original dispatch function was called with the request + expect((originalEventDispatcher.dispatchEvent as unknown) as jest.Mock).toBeCalledTimes(1) + expect(internalDispatchCall[0]).toEqual(eventV1Request) - // assert that the passed in callback to pendingEventsDispatcher was called - expect(callback).toHaveBeenCalledTimes(1) - expect(callback).toHaveBeenCalledWith({ statusCode: 400}) + setTimeout(() => { + // assert that the passed in callback to pendingEventsDispatcher was called + expect(callback).toHaveBeenCalledTimes(1) + expect(callback).toHaveBeenCalledWith({ statusCode: 400}) + done() + }) + }) }) }) @@ -129,7 +139,7 @@ describe('PendingEventsDispatcher', () => { describe('dispatch', () => { describe('when the dispatch is successful', () => { - it('should save the pendingEvent to the store and remove it once dispatch is completed', () => { + it('should save the pendingEvent to the store and remove it once dispatch is completed', async (done) => { const callback = jest.fn() const eventV1Request: EventV1Request = { url: 'http://cdn.com', @@ -139,8 +149,8 @@ describe('PendingEventsDispatcher', () => { pendingEventsDispatcher.dispatchEvent(eventV1Request, callback) - expect(store.values()).toHaveLength(1) - expect(store.get('uuid')).toEqual({ + expect(await store.values()).toHaveLength(1) + expect(await store.get('uuid')).toEqual({ uuid: 'uuid', timestamp: 1, request: eventV1Request, @@ -158,16 +168,19 @@ describe('PendingEventsDispatcher', () => { ).toBeCalledTimes(1) expect(internalDispatchCall[0]).toEqual(eventV1Request) - // assert that the passed in callback to pendingEventsDispatcher was called - expect(callback).toHaveBeenCalledTimes(1) - expect(callback).toHaveBeenCalledWith({ statusCode: 200 }) + setTimeout(async () => { + // assert that the passed in callback to pendingEventsDispatcher was called + expect(callback).toHaveBeenCalledTimes(1) + expect(callback).toHaveBeenCalledWith({ statusCode: 200 }) - expect(store.values()).toHaveLength(0) + expect(await store.values()).toHaveLength(0) + done() + }) }) }) describe('when the dispatch is unsuccessful', () => { - it('should save the pendingEvent to the store and remove it once dispatch is completed', () => { + it('should save the pendingEvent to the store and remove it once dispatch is completed', async (done) => { const callback = jest.fn() const eventV1Request: EventV1Request = { url: 'http://cdn.com', @@ -177,8 +190,8 @@ describe('PendingEventsDispatcher', () => { pendingEventsDispatcher.dispatchEvent(eventV1Request, callback) - expect(store.values()).toHaveLength(1) - expect(store.get('uuid')).toEqual({ + expect(await store.values()).toHaveLength(1) + expect(await store.get('uuid')).toEqual({ uuid: 'uuid', timestamp: 1, request: eventV1Request, @@ -196,19 +209,22 @@ describe('PendingEventsDispatcher', () => { ).toBeCalledTimes(1) expect(internalDispatchCall[0]).toEqual(eventV1Request) - // assert that the passed in callback to pendingEventsDispatcher was called - expect(callback).toHaveBeenCalledTimes(1) - expect(callback).toHaveBeenCalledWith({ statusCode: 400 }) + setTimeout(async () => { + // assert that the passed in callback to pendingEventsDispatcher was called + expect(callback).toHaveBeenCalledTimes(1) + expect(callback).toHaveBeenCalledWith({ statusCode: 400 }) - expect(store.values()).toHaveLength(0) + expect(await store.values()).toHaveLength(0) + done() + }) }) }) }) describe('sendPendingEvents', () => { describe('when no pending events are in the store', () => { - it('should not invoked dispatch', () => { - expect(store.values()).toHaveLength(0) + it('should not invoked dispatch', async () => { + expect(await store.values()).toHaveLength(0) pendingEventsDispatcher.sendPendingEvents() expect(originalEventDispatcher.dispatchEvent).not.toHaveBeenCalled() @@ -216,8 +232,8 @@ describe('PendingEventsDispatcher', () => { }) describe('when there are multiple pending events in the store', () => { - it('should dispatch all of the pending events, and remove them from store', () => { - expect(store.values()).toHaveLength(0) + it('should dispatch all of the pending events, and remove them from store', async (done) => { + expect(await store.values()).toHaveLength(0) const callback = jest.fn() const eventV1Request1: EventV1Request = { @@ -232,29 +248,33 @@ describe('PendingEventsDispatcher', () => { params: ({ id: 'event2' } as unknown) as EventV1, } - store.set('uuid1', { + await store.set('uuid1', { uuid: 'uuid1', timestamp: 1, request: eventV1Request1, }) - store.set('uuid2', { + await store.set('uuid2', { uuid: 'uuid2', timestamp: 2, request: eventV1Request2, }) - expect(store.values()).toHaveLength(2) + expect(await store.values()).toHaveLength(2) pendingEventsDispatcher.sendPendingEvents() - expect(originalEventDispatcher.dispatchEvent).toHaveBeenCalledTimes(2) - // manually invoke original eventDispatcher callback - const internalDispatchCalls = ((originalEventDispatcher.dispatchEvent as unknown) as jest.Mock) - .mock.calls - internalDispatchCalls[0][1]({ statusCode: 200 }) - internalDispatchCalls[1][1]({ statusCode: 200 }) + setTimeout(async () => { + expect(originalEventDispatcher.dispatchEvent).toHaveBeenCalledTimes(2) - expect(store.values()).toHaveLength(0) + // manually invoke original eventDispatcher callback + const internalDispatchCalls = ((originalEventDispatcher.dispatchEvent as unknown) as jest.Mock) + .mock.calls + internalDispatchCalls[0][1]({ statusCode: 200 }) + internalDispatchCalls[1][1]({ statusCode: 200 }) + + expect(await store.values()).toHaveLength(0) + done() + }) }) }) }) diff --git a/packages/event-processor/__tests__/pendingEventsStore.spec.ts b/packages/event-processor/__tests__/pendingEventsStore.spec.ts index 23a8b4871..98ca93380 100644 --- a/packages/event-processor/__tests__/pendingEventsStore.spec.ts +++ b/packages/event-processor/__tests__/pendingEventsStore.spec.ts @@ -36,105 +36,106 @@ describe('LocalStorageStore', () => { localStorage.clear() }) - it('should get, set and remove items', () => { - store.set('1', { + it('should get, set and remove items', async () => { + await store.set('1', { uuid: '1', timestamp: 1, value: 'first', }) - expect(store.get('1')).toEqual({ + expect(await store.get('1')).toEqual({ uuid: '1', timestamp: 1, value: 'first', }) - store.set('1', { + await store.set('1', { uuid: '1', timestamp: 2, value: 'second', }) - expect(store.get('1')).toEqual({ + expect(await store.get('1')).toEqual({ uuid: '1', timestamp: 2, value: 'second', }) - expect(store.values()).toHaveLength(1) + expect(await store.values()).toHaveLength(1) - store.remove('1') + await store.remove('1') - expect(store.values()).toHaveLength(0) + expect(await store.values()).toHaveLength(0) }) - it('should allow replacement of the entire map', () => { - store.set('1', { + it('should allow replacement of the entire map', async () => { + await store.set('1', { uuid: '1', timestamp: 1, value: 'first', }) - store.set('2', { + await store.set('2', { uuid: '2', timestamp: 2, value: 'second', }) - store.set('3', { + await store.set('3', { uuid: '3', timestamp: 3, value: 'third', }) - expect(store.values()).toEqual([ + expect(await store.values()).toEqual([ { uuid: '1', timestamp: 1, value: 'first' }, { uuid: '2', timestamp: 2, value: 'second' }, { uuid: '3', timestamp: 3, value: 'third' }, ]) const newMap = {} - store.values().forEach(item => { + const theItems = await store.values() + theItems.forEach(item => { newMap[item.uuid] = { ...item, value: 'new', } }) - store.replace(newMap) + await store.replace(newMap) - expect(store.values()).toEqual([ + expect(await store.values()).toEqual([ { uuid: '1', timestamp: 1, value: 'new' }, { uuid: '2', timestamp: 2, value: 'new' }, { uuid: '3', timestamp: 3, value: 'new' }, ]) }) - it(`shouldn't allow more than the configured maxValues, using timestamp to remove the oldest entries`, () => { - store.set('2', { + it(`shouldn't allow more than the configured maxValues, using timestamp to remove the oldest entries`, async () => { + await store.set('2', { uuid: '2', timestamp: 2, value: 'second', }) - store.set('3', { + await store.set('3', { uuid: '3', timestamp: 3, value: 'third', }) - store.set('1', { + await store.set('1', { uuid: '1', timestamp: 1, value: 'first', }) - store.set('4', { + await store.set('4', { uuid: '4', timestamp: 4, value: 'fourth', }) - expect(store.values()).toEqual([ + expect(await store.values()).toEqual([ { uuid: '2', timestamp: 2, value: 'second' }, { uuid: '3', timestamp: 3, value: 'third' }, { uuid: '4', timestamp: 4, value: 'fourth' }, diff --git a/packages/event-processor/src/pendingEventsDispatcher.ts b/packages/event-processor/src/pendingEventsDispatcher.ts index f94f9a253..c9799f96f 100644 --- a/packages/event-processor/src/pendingEventsDispatcher.ts +++ b/packages/event-processor/src/pendingEventsDispatcher.ts @@ -53,23 +53,21 @@ export class PendingEventsDispatcher implements EventDispatcher { } sendPendingEvents(): void { - const pendingEvents = this.store.values() - - logger.debug('Sending %s pending events from previous page', pendingEvents.length) - - pendingEvents.forEach(item => { - try { - this.send(item, () => {}) - } catch (e) {} + this.store.values().then((pendingEvents) => { + logger.debug('Sending %s pending events from previous page', pendingEvents.length) + pendingEvents.forEach(item => { + try { + this.send(item, () => {}) + } catch (e) {} + }) }) } protected send(entry: DispatcherEntry, callback: EventDispatcherCallback): void { - this.store.set(entry.uuid, entry) - - this.dispatcher.dispatchEvent(entry.request, response => { - this.store.remove(entry.uuid) - callback(response) + this.store.set(entry.uuid, entry).then(() => { + this.dispatcher.dispatchEvent(entry.request, response => { + this.store.remove(entry.uuid).then(() => callback(response)) + }) }) } } diff --git a/packages/event-processor/src/pendingEventsStore.ts b/packages/event-processor/src/pendingEventsStore.ts index a04c1512d..44528741d 100644 --- a/packages/event-processor/src/pendingEventsStore.ts +++ b/packages/event-processor/src/pendingEventsStore.ts @@ -19,17 +19,17 @@ import { getLogger } from '@optimizely/js-sdk-logging'; const logger = getLogger('EventProcessor') export interface PendingEventsStore { - get(key: string): K | null + get(key: string): Promise - set(key: string, value: K): void + set(key: string, value: K): Promise - remove(key: string): void + remove(key: string): Promise - values(): K[] + values(): Promise - clear(): void + clear(): Promise - replace(newMap: { [key: string]: K }): void + replace(newMap: { [key: string]: K }): Promise } interface StoreEntry { @@ -46,31 +46,31 @@ export class LocalStorageStore implements PendingEventsSto this.maxValues = maxValues } - get(key: string): K | null { + async get(key: string): Promise { return this.getMap()[key] || null } - set(key: string, value: K): void { + async set(key: string, value: K): Promise { const map = this.getMap() map[key] = value this.replace(map) } - remove(key: string): void { + async remove(key: string): Promise { const map = this.getMap() delete map[key] this.replace(map) } - values(): K[] { + async values(): Promise { return objectValues(this.getMap()) } - clear(): void { + async clear(): Promise { this.replace({}) } - replace(map: { [key: string]: K }): void { + async replace(map: { [key: string]: K }): Promise { try { // This is a temporary fix to support React Native which does not have localStorage. window.localStorage && localStorage.setItem(this.LS_KEY, JSON.stringify(map))