From 54a04e010bdfef3003db7f9fe1730065a8a6a815 Mon Sep 17 00:00:00 2001 From: Evan Greer Date: Wed, 4 Jun 2025 09:57:35 -0600 Subject: [PATCH 1/2] feat: creates IterableEmbeddedPlacement and IterableEmbeddedMessage classes --- .../classes/IterableEmbeddedMessage.ts | 58 +++++++++++++++++++ .../classes/IterableEmbeddedPlacement.ts | 47 ++++++++++++++- 2 files changed, 102 insertions(+), 3 deletions(-) create mode 100644 src/embedded/classes/IterableEmbeddedMessage.ts diff --git a/src/embedded/classes/IterableEmbeddedMessage.ts b/src/embedded/classes/IterableEmbeddedMessage.ts new file mode 100644 index 000000000..47adb916a --- /dev/null +++ b/src/embedded/classes/IterableEmbeddedMessage.ts @@ -0,0 +1,58 @@ +import { IterableEmbeddedMessageMetadata } from './IterableEmbeddedMessageMetadata'; +import { IterableEmbeddedMessageElements } from './IterableEmbeddedMessageElements'; + +/** + * IterableEmbeddedMessage represents an embedded message. + */ +export class IterableEmbeddedMessage { + /** The metadata of the embedded message */ + metadata: IterableEmbeddedMessageMetadata; + /** The elements of the embedded message */ + elements?: IterableEmbeddedMessageElements; + /** The custom payload of the embedded message */ + payload?: Record; + + /** + * Creates an instance of `IterableEmbeddedMessage`. + * + * @param metadata - The metadata of the embedded message. + * @param elements - The elements of the embedded message. + * @param payload - The custom payload of the embedded message. + */ + constructor( + metadata: IterableEmbeddedMessageMetadata, + elements?: IterableEmbeddedMessageElements, + payload?: Record + ) { + this.metadata = metadata; + this.elements = elements; + this.payload = payload; + } + + /** + * Creates an instance of `IterableEmbeddedMessage` from a dictionary object. + * + * @param dict - The dictionary object containing the properties to initialize the `IterableEmbeddedMessage` instance. + * @returns A new instance of `IterableEmbeddedMessage` initialized with the provided dictionary properties. + */ + static fromDict(dict: Partial): IterableEmbeddedMessage { + if (!dict.metadata) { + throw new Error('metadata is required'); + } + const metadata = IterableEmbeddedMessageMetadata.fromDict(dict.metadata); + const elements = dict.elements + ? IterableEmbeddedMessageElements.fromDict(dict.elements) + : undefined; + const payload = dict.payload; + return new IterableEmbeddedMessage(metadata, elements, payload); + } +} + +/** + * An interface defining the dictionary object containing the properties for the embedded message. + */ +interface EmbeddedMessageDict { + metadata: IterableEmbeddedMessageMetadata; + elements: IterableEmbeddedMessageElements; + payload: Record; +} diff --git a/src/embedded/classes/IterableEmbeddedPlacement.ts b/src/embedded/classes/IterableEmbeddedPlacement.ts index 6a02aabfe..1fc37db6b 100644 --- a/src/embedded/classes/IterableEmbeddedPlacement.ts +++ b/src/embedded/classes/IterableEmbeddedPlacement.ts @@ -1,11 +1,52 @@ +import { IterableEmbeddedMessage } from './IterableEmbeddedMessage'; + /** - * Iterable embedded placement - * Contains placement id and the associated embedded messages + * IterableEmbeddedPlacement represents an embedded placement. */ export class IterableEmbeddedPlacement { + /** The placement id of the embedded placement */ readonly placementId: number; + /** The messages associated with the embedded placement */ + readonly messages?: IterableEmbeddedMessage[]; - constructor(placementId: number) { + /** + * Creates an instance of `IterableEmbeddedPlacement`. + * + * @param placementId - The placement id of the embedded placement. + * @param messages - The messages associated with the embedded placement. + */ + constructor(placementId: number, messages?: IterableEmbeddedMessage[]) { this.placementId = placementId; + this.messages = messages; + } + + /** + * Creates an instance of `IterableEmbeddedPlacement` from a dictionary object. + * + * @param dict - The dictionary object containing the properties to initialize the `IterableEmbeddedPlacement` instance. + * @returns A new instance of `IterableEmbeddedPlacement` initialized with the provided dictionary properties. + */ + static fromDict( + dict: Partial + ): IterableEmbeddedPlacement { + if (!dict.placementId) { + throw new Error('placementId is required'); + } + + const placementId = dict.placementId; + const messages = dict.messages + ? dict.messages?.map((message) => + IterableEmbeddedMessage.fromDict(message) + ) + : undefined; + return new IterableEmbeddedPlacement(placementId, messages); } } + +/** + * An interface defining the dictionary object containing the properties for the embedded placement. + */ +export interface EmbeddedPlacementDict { + placementId: number; + messages?: IterableEmbeddedMessage[]; +} From d8d1fe8d0dca2ee0c96ac98f0436cb80cdfb8213 Mon Sep 17 00:00:00 2001 From: Evan Greer Date: Wed, 4 Jun 2025 10:15:25 -0600 Subject: [PATCH 2/2] feat: adds unit tests --- src/__tests__/IterableEmbeddedMessage.test.ts | 163 ++++++++++++++++++ .../IterableEmbeddedPlacement.test.ts | 104 +++++++++++ 2 files changed, 267 insertions(+) create mode 100644 src/__tests__/IterableEmbeddedMessage.test.ts create mode 100644 src/__tests__/IterableEmbeddedPlacement.test.ts diff --git a/src/__tests__/IterableEmbeddedMessage.test.ts b/src/__tests__/IterableEmbeddedMessage.test.ts new file mode 100644 index 000000000..64385e99f --- /dev/null +++ b/src/__tests__/IterableEmbeddedMessage.test.ts @@ -0,0 +1,163 @@ +import { IterableEmbeddedMessage } from '../embedded/classes/IterableEmbeddedMessage'; +import { IterableEmbeddedMessageMetadata } from '../embedded/classes/IterableEmbeddedMessageMetadata'; +import { IterableEmbeddedMessageElements } from '../embedded/classes/IterableEmbeddedMessageElements'; +import { Iterable } from '../core/classes/Iterable'; + +describe('IterableEmbeddedMessage', () => { + it('should create an instance with all properties', () => { + Iterable.logger.log('iterableEmbeddedMessage_fromDict_all_properties'); + + const dict = { + metadata: { + messageId: 'msg-123', + placementId: 1, + campaignId: 456, + isProof: false, + }, + elements: { + title: 'Awesome Title', + body: 'Radical Body Text', + mediaUrl: 'https://example.com/image.jpg', + mediaUrlCaption: 'Check out this sick image!', + defaultAction: { + type: 'openUrl', + data: 'https://example.com', + }, + buttons: [ + { + id: 'button-1', + title: 'Click Me!', + action: { + type: 'openUrl', + data: 'https://example.com/button1', + }, + }, + ], + text: [ + { + id: 'text-1', + text: 'Some cool text', + type: 'body', + }, + ], + }, + payload: { + customKey: 'customValue', + anotherKey: 123, + }, + }; + + const message = IterableEmbeddedMessage.fromDict(dict); + + expect(message).toBeInstanceOf(IterableEmbeddedMessage); + + // Check metadata + expect(message.metadata).toBeInstanceOf(IterableEmbeddedMessageMetadata); + expect(message.metadata.messageId).toBe('msg-123'); + expect(message.metadata.placementId).toBe(1); + expect(message.metadata.campaignId).toBe(456); + expect(message.metadata.isProof).toBe(false); + + // Check elements + expect(message.elements).toBeInstanceOf(IterableEmbeddedMessageElements); + expect(message.elements?.title).toBe('Awesome Title'); + expect(message.elements?.body).toBe('Radical Body Text'); + expect(message.elements?.mediaUrl).toBe('https://example.com/image.jpg'); + expect(message.elements?.mediaUrlCaption).toBe( + 'Check out this sick image!' + ); + + // Check payload + expect(message.payload).toEqual({ + customKey: 'customValue', + anotherKey: 123, + }); + }); + + it('should create an instance with only required metadata', () => { + Iterable.logger.log('iterableEmbeddedMessage_fromDict_required_only'); + + const dict = { + metadata: { + messageId: 'msg-123', + placementId: 1, + isProof: false, + }, + }; + + const message = IterableEmbeddedMessage.fromDict(dict); + + expect(message).toBeInstanceOf(IterableEmbeddedMessage); + expect(message.metadata).toBeInstanceOf(IterableEmbeddedMessageMetadata); + expect(message.metadata.messageId).toBe('msg-123'); + expect(message.metadata.placementId).toBe(1); + expect(message.metadata.campaignId).toBeUndefined(); + expect(message.metadata.isProof).toBe(false); + expect(message.elements).toBeUndefined(); + expect(message.payload).toBeUndefined(); + }); + + it('should throw an error if metadata is missing', () => { + Iterable.logger.log('iterableEmbeddedMessage_fromDict_missing_metadata'); + + const dict = { + elements: { + title: 'Some Title', + body: 'Some Body', + }, + }; + + expect(() => IterableEmbeddedMessage.fromDict(dict)).toThrow( + 'metadata is required' + ); + }); + + it('should create an instance with elements but no payload', () => { + Iterable.logger.log('iterableEmbeddedMessage_fromDict_elements_only'); + + const dict = { + metadata: { + messageId: 'msg-123', + placementId: 1, + isProof: false, + }, + elements: { + title: 'Elements Only', + body: 'No payload here', + }, + }; + + const message = IterableEmbeddedMessage.fromDict(dict); + + expect(message).toBeInstanceOf(IterableEmbeddedMessage); + expect(message.metadata).toBeInstanceOf(IterableEmbeddedMessageMetadata); + expect(message.elements).toBeInstanceOf(IterableEmbeddedMessageElements); + expect(message.elements?.title).toBe('Elements Only'); + expect(message.elements?.body).toBe('No payload here'); + expect(message.payload).toBeUndefined(); + }); + + it('should create an instance with payload but no elements', () => { + Iterable.logger.log('iterableEmbeddedMessage_fromDict_payload_only'); + + const dict = { + metadata: { + messageId: 'msg-123', + placementId: 1, + isProof: false, + }, + payload: { + someData: 'someValue', + }, + }; + + const message = IterableEmbeddedMessage.fromDict(dict); + + expect(message).toBeInstanceOf(IterableEmbeddedMessage); + expect(message.metadata).toBeInstanceOf(IterableEmbeddedMessageMetadata); + expect(message.elements).toBeUndefined(); + expect(message.payload).toEqual({ + someData: 'someValue', + }); + }); +}); diff --git a/src/__tests__/IterableEmbeddedPlacement.test.ts b/src/__tests__/IterableEmbeddedPlacement.test.ts new file mode 100644 index 000000000..65c9b110a --- /dev/null +++ b/src/__tests__/IterableEmbeddedPlacement.test.ts @@ -0,0 +1,104 @@ +import { IterableEmbeddedPlacement } from '../embedded/classes/IterableEmbeddedPlacement'; +import { IterableEmbeddedMessage } from '../embedded/classes/IterableEmbeddedMessage'; +import { Iterable } from '../core/classes/Iterable'; + +describe('IterableEmbeddedPlacement', () => { + it('should create an instance with placementId and messages', () => { + Iterable.logger.log('iterableEmbeddedPlacement_fromDict_with_messages'); + + const dict = { + placementId: 123, + messages: [ + { + metadata: { + messageId: 'msg-1', + placementId: 123, + isProof: false, + }, + elements: { + title: 'First Message', + body: 'Body of first message', + }, + }, + { + metadata: { + messageId: 'msg-2', + placementId: 123, + isProof: false, + }, + elements: { + title: 'Second Message', + body: 'Body of second message', + }, + }, + ], + }; + + const placement = IterableEmbeddedPlacement.fromDict(dict); + + expect(placement).toBeInstanceOf(IterableEmbeddedPlacement); + expect(placement.placementId).toBe(123); + expect(placement.messages).toBeDefined(); + expect(placement.messages!.length).toBe(2); + const messages = placement.messages as [ + IterableEmbeddedMessage, + IterableEmbeddedMessage, + ]; + expect(messages[0]).toBeInstanceOf(IterableEmbeddedMessage); + expect(messages[1]).toBeInstanceOf(IterableEmbeddedMessage); + expect(messages[0].metadata.messageId).toBe('msg-1'); + expect(messages[1].metadata.messageId).toBe('msg-2'); + }); + + it('should create an instance with only placementId', () => { + Iterable.logger.log('iterableEmbeddedPlacement_fromDict_placementId_only'); + + const dict = { + placementId: 456, + }; + + const placement = IterableEmbeddedPlacement.fromDict(dict); + + expect(placement).toBeInstanceOf(IterableEmbeddedPlacement); + expect(placement.placementId).toBe(456); + expect(placement.messages).toBeUndefined(); + }); + + it('should throw an error if placementId is missing', () => { + Iterable.logger.log( + 'iterableEmbeddedPlacement_fromDict_missing_placementId' + ); + + const dict = { + messages: [ + { + metadata: { + messageId: 'msg-1', + placementId: 123, + isProof: false, + }, + }, + ], + }; + + expect(() => IterableEmbeddedPlacement.fromDict(dict)).toThrow( + 'placementId is required' + ); + }); + + it('should handle empty messages array', () => { + Iterable.logger.log('iterableEmbeddedPlacement_fromDict_empty_messages'); + + const dict = { + placementId: 789, + messages: [], + }; + + const placement = IterableEmbeddedPlacement.fromDict(dict); + + expect(placement).toBeInstanceOf(IterableEmbeddedPlacement); + expect(placement.placementId).toBe(789); + expect(placement.messages).toBeDefined(); + expect(placement.messages!.length).toBe(0); + }); +});