diff --git a/packages/core/src/plugin.ts b/packages/core/src/plugin.ts index 17876bfd..d0328d19 100644 --- a/packages/core/src/plugin.ts +++ b/packages/core/src/plugin.ts @@ -1,5 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ - import type { SegmentClient } from './analytics'; import { Timeline } from './timeline'; import { diff --git a/packages/core/src/plugins/ConsentPlugin.ts b/packages/core/src/plugins/ConsentPlugin.ts index f5ab2470..1695a851 100644 --- a/packages/core/src/plugins/ConsentPlugin.ts +++ b/packages/core/src/plugins/ConsentPlugin.ts @@ -57,7 +57,7 @@ export class ConsentPlugin extends Plugin { } async execute(event: SegmentEvent): Promise { - if ((event as TrackEventType).event === CONSENT_PREF_UPDATE_EVENT) { + if (this.isConsentUpdateEvent(event)) { return event; } @@ -77,18 +77,36 @@ export class ConsentPlugin extends Plugin { } private injectConsentFilterIfApplicable = (plugin: Plugin) => { - if ( - this.isDestinationPlugin(plugin) && - plugin.key !== SEGMENT_DESTINATION_KEY - ) { - const settings = this.analytics?.settings.get()?.[plugin.key]; - + if (this.isDestinationPlugin(plugin)) { plugin.add( - new ConsentFilterPlugin( - this.containsConsentSettings(settings) - ? settings.consentSettings.categories - : [] - ) + new ConsentFilterPlugin((event) => { + const settings = this.analytics?.settings.get() || {}; + const preferences = event.context?.consent?.categoryPreferences || {}; + + if (plugin.key === SEGMENT_DESTINATION_KEY) { + return ( + this.isConsentUpdateEvent(event) || + !( + Object.values(preferences).every((consented) => !consented) && + Object.entries(settings) + .filter(([k]) => k !== SEGMENT_DESTINATION_KEY) + .every(([_, v]) => this.containsConsentSettings(v)) + ) + ); + } + + const integrationSettings = settings?.[plugin.key]; + + if (this.containsConsentSettings(integrationSettings)) { + const categories = integrationSettings.consentSettings.categories; + return ( + !this.isConsentUpdateEvent(event) && + categories.every((category) => preferences?.[category]) + ); + } + + return true; + }) ); } }; @@ -105,6 +123,10 @@ export class ConsentPlugin extends Plugin { ?.categories === 'object' ); }; + + private isConsentUpdateEvent(event: SegmentEvent): boolean { + return (event as TrackEventType).event === CONSENT_PREF_UPDATE_EVENT; + } } /** @@ -114,21 +136,11 @@ export class ConsentPlugin extends Plugin { class ConsentFilterPlugin extends Plugin { type = PluginType.before; - constructor(private categories: string[]) { + constructor(private shouldAllowEvent: (event: SegmentEvent) => boolean) { super(); } execute(event: SegmentEvent): SegmentEvent | undefined { - const preferences = event.context?.consent?.categoryPreferences; - - // if consent plugin is active but the setup isn't properly configured - events are blocked by default - if (!preferences || this.categories.length === 0) { - return undefined; - } - - // all categories this destination is tagged with must be present, and allowed in consent preferences - return this.categories.every((category) => preferences?.[category]) - ? event - : undefined; + return this.shouldAllowEvent(event) ? event : undefined; } } diff --git a/packages/core/src/plugins/__tests__/consent/analytics-swift-consent b/packages/core/src/plugins/__tests__/consent/analytics-swift-consent new file mode 160000 index 00000000..8e23248b --- /dev/null +++ b/packages/core/src/plugins/__tests__/consent/analytics-swift-consent @@ -0,0 +1 @@ +Subproject commit 8e23248b87cc8b9d51139abe4a76baad896b99c7 diff --git a/packages/core/src/plugins/__tests__/consent/consentNotEnabledAtSegment.test.ts b/packages/core/src/plugins/__tests__/consent/consentNotEnabledAtSegment.test.ts new file mode 100644 index 00000000..7fcf3ef4 --- /dev/null +++ b/packages/core/src/plugins/__tests__/consent/consentNotEnabledAtSegment.test.ts @@ -0,0 +1,115 @@ +import { createTestClient } from '../../../__tests__/__helpers__/setupSegmentClient'; +import { ConsentPlugin } from '../../ConsentPlugin'; + +import { + setupTestDestinations, + createConsentProvider, + createSegmentWatcher, +} from './utils'; +import consentNotEnabledAtSegment from './mockSettings/ConsentNotEnabledAtSegment.json'; + +describe('Consent not enabled at Segment', () => { + const createClient = () => + createTestClient( + { + settings: consentNotEnabledAtSegment.integrations, + }, + { autoAddSegmentDestination: true } + ); + + test('no to all', async () => { + const { client } = createClient(); + const testDestinations = setupTestDestinations(client); + const mockConsentStatuses = { + C0001: false, + C0002: false, + C0003: false, + C0004: false, + C0005: false, + }; + + client.add({ + plugin: new ConsentPlugin( + createConsentProvider(mockConsentStatuses), + Object.keys(mockConsentStatuses) + ), + }); + + await client.init(); + + const segmentDestination = createSegmentWatcher(client); + + await client.track('test'); + + expect(segmentDestination).toHaveBeenCalled(); + expect(testDestinations.dest1.track).toHaveBeenCalled(); + expect(testDestinations.dest2.track).toHaveBeenCalled(); + expect(testDestinations.dest3.track).toHaveBeenCalled(); + expect(testDestinations.dest4.track).toHaveBeenCalled(); + expect(testDestinations.dest5.track).toHaveBeenCalled(); + }); + + test('yes to some', async () => { + const { client } = createClient(); + const testDestinations = setupTestDestinations(client); + const mockConsentStatuses = { + C0001: false, + C0002: true, + C0003: false, + C0004: true, + C0005: true, + }; + + client.add({ + plugin: new ConsentPlugin( + createConsentProvider(mockConsentStatuses), + Object.keys(mockConsentStatuses) + ), + }); + + await client.init(); + + const segmentDestination = createSegmentWatcher(client); + + await client.track('test'); + + expect(segmentDestination).toHaveBeenCalled(); + expect(testDestinations.dest1.track).toHaveBeenCalled(); + expect(testDestinations.dest2.track).toHaveBeenCalled(); + expect(testDestinations.dest3.track).toHaveBeenCalled(); + expect(testDestinations.dest4.track).toHaveBeenCalled(); + expect(testDestinations.dest5.track).toHaveBeenCalled(); + }); + + test('yes to all', async () => { + const { client } = createClient(); + const testDestinations = setupTestDestinations(client); + const mockConsentStatuses = { + C0001: true, + C0002: true, + C0003: true, + C0004: true, + C0005: true, + }; + + client.add({ + plugin: new ConsentPlugin( + createConsentProvider(mockConsentStatuses), + Object.keys(mockConsentStatuses) + ), + }); + + await client.init(); + + const segmentDestination = createSegmentWatcher(client); + + await client.track('test'); + + expect(segmentDestination).toHaveBeenCalled(); + expect(testDestinations.dest1.track).toHaveBeenCalled(); + expect(testDestinations.dest2.track).toHaveBeenCalled(); + expect(testDestinations.dest3.track).toHaveBeenCalled(); + expect(testDestinations.dest4.track).toHaveBeenCalled(); + expect(testDestinations.dest5.track).toHaveBeenCalled(); + }); +}); diff --git a/packages/core/src/plugins/__tests__/consent/destinationMultipleCategories.test.ts b/packages/core/src/plugins/__tests__/consent/destinationMultipleCategories.test.ts new file mode 100644 index 00000000..6b3aa87a --- /dev/null +++ b/packages/core/src/plugins/__tests__/consent/destinationMultipleCategories.test.ts @@ -0,0 +1,135 @@ +import { createTestClient } from '../../../__tests__/__helpers__/setupSegmentClient'; +import { ConsentPlugin } from '../../ConsentPlugin'; + +import { + setupTestDestinations, + createConsentProvider, + createSegmentWatcher, +} from './utils'; +import destinationsMultipleCategories from './mockSettings/DestinationsMultipleCategories.json'; + +describe('Destinations multiple categories', () => { + const createClient = () => + createTestClient( + { + settings: destinationsMultipleCategories.integrations, + }, + { autoAddSegmentDestination: true } + ); + + test('no to all', async () => { + const { client } = createClient(); + const testDestinations = setupTestDestinations(client); + const mockConsentStatuses = { + C0001: false, + C0002: false, + C0003: false, + C0004: false, + C0005: false, + }; + + client.add({ + plugin: new ConsentPlugin( + createConsentProvider(mockConsentStatuses), + Object.keys(mockConsentStatuses) + ), + }); + + await client.init(); + + const segmentDestination = createSegmentWatcher(client); + + await client.track('test'); + + expect(segmentDestination).not.toHaveBeenCalled(); + expect(testDestinations.dest1.track).not.toHaveBeenCalled(); + expect(testDestinations.dest2.track).not.toHaveBeenCalled(); + }); + + test('yes to 1', async () => { + const { client } = createClient(); + const testDestinations = setupTestDestinations(client); + const mockConsentStatuses = { + C0001: true, + C0002: false, + C0003: false, + C0004: false, + C0005: false, + }; + + client.add({ + plugin: new ConsentPlugin( + createConsentProvider(mockConsentStatuses), + Object.keys(mockConsentStatuses) + ), + }); + + await client.init(); + + const segmentDestination = createSegmentWatcher(client); + + await client.track('test'); + + expect(segmentDestination).toHaveBeenCalled(); + expect(testDestinations.dest1.track).not.toHaveBeenCalled(); + expect(testDestinations.dest2.track).toHaveBeenCalled(); + }); + + test('yes to 2', async () => { + const { client } = createClient(); + const testDestinations = setupTestDestinations(client); + const mockConsentStatuses = { + C0001: false, + C0002: true, + C0003: false, + C0004: false, + C0005: false, + }; + + client.add({ + plugin: new ConsentPlugin( + createConsentProvider(mockConsentStatuses), + Object.keys(mockConsentStatuses) + ), + }); + + await client.init(); + + const segmentDestination = createSegmentWatcher(client); + + await client.track('test'); + + expect(segmentDestination).toHaveBeenCalled(); + expect(testDestinations.dest1.track).not.toHaveBeenCalled(); + expect(testDestinations.dest2.track).not.toHaveBeenCalled(); + }); + + test('yes to all', async () => { + const { client } = createClient(); + const testDestinations = setupTestDestinations(client); + const mockConsentStatuses = { + C0001: true, + C0002: true, + C0003: true, + C0004: true, + C0005: true, + }; + + client.add({ + plugin: new ConsentPlugin( + createConsentProvider(mockConsentStatuses), + Object.keys(mockConsentStatuses) + ), + }); + + await client.init(); + + const segmentDestination = createSegmentWatcher(client); + + await client.track('test'); + + expect(segmentDestination).toHaveBeenCalled(); + expect(testDestinations.dest1.track).toHaveBeenCalled(); + expect(testDestinations.dest2.track).toHaveBeenCalled(); + }); +}); diff --git a/packages/core/src/plugins/__tests__/consent/mockSettings/ConsentNotEnabledAtSegment.json b/packages/core/src/plugins/__tests__/consent/mockSettings/ConsentNotEnabledAtSegment.json new file mode 100644 index 00000000..343d06c4 --- /dev/null +++ b/packages/core/src/plugins/__tests__/consent/mockSettings/ConsentNotEnabledAtSegment.json @@ -0,0 +1,71 @@ +{ + "integrations": { + "DummyDest1": { + "versionSettings": { + "componentTypes": [] + } + }, + "DummyDest2": { + "versionSettings": { + "componentTypes": [] + } + }, + "DummyDest3": { + "versionSettings": { + "componentTypes": [] + } + }, + "DummyDest4": { + "versionSettings": { + "componentTypes": [] + } + }, + "DummyDest5": { + "versionSettings": { + "componentTypes": [] + } + }, + "Segment.io": { + "apiKey": "test", + "unbundledIntegrations": [], + "addBundledMetadata": true, + "maybeBundledConfigIds": {}, + "versionSettings": { + "version": "4.4.7", + "componentTypes": [ + "browser" + ] + }, + "apiHost": "api.segment.io/v1" + } + }, + "plan": { + "track": { + "__default": { + "enabled": true, + "integrations": {} + } + }, + "identify": { + "__default": { + "enabled": true + } + }, + "group": { + "__default": { + "enabled": true + } + } + }, + "edgeFunction": {}, + "analyticsNextEnabled": true, + "middlewareSettings": {}, + "enabledMiddleware": {}, + "metrics": { + "sampleRate": 0.1, + "host": "api.segment.io/v1" + }, + "legacyVideoPluginsEnabled": false, + "remotePlugins": [] +} + diff --git a/packages/core/src/plugins/__tests__/consent/mockSettings/DestinationsMultipleCategories.json b/packages/core/src/plugins/__tests__/consent/mockSettings/DestinationsMultipleCategories.json new file mode 100644 index 00000000..e7244523 --- /dev/null +++ b/packages/core/src/plugins/__tests__/consent/mockSettings/DestinationsMultipleCategories.json @@ -0,0 +1,73 @@ +{ + "integrations": { + "DummyDest1": { + "versionSettings": { + "componentTypes": [] + }, + "consentSettings": { + "categories": [ + "C0001", + "C0002" + ] + } + }, + "DummyDest2": { + "versionSettings": { + "componentTypes": [] + }, + "consentSettings": { + "categories": [ + "C0001" + ] + } + }, + "Segment.io": { + "apiKey": "test", + "unbundledIntegrations": [], + "addBundledMetadata": true, + "maybeBundledConfigIds": {}, + "versionSettings": { + "version": "4.4.7", + "componentTypes": [ + "browser" + ] + }, + "apiHost": "api.segment.io/v1" + } + }, + "plan": { + "track": { + "__default": { + "enabled": true, + "integrations": {} + } + }, + "identify": { + "__default": { + "enabled": true + } + }, + "group": { + "__default": { + "enabled": true + } + } + }, + "edgeFunction": {}, + "analyticsNextEnabled": true, + "middlewareSettings": {}, + "enabledMiddleware": {}, + "metrics": { + "sampleRate": 0.1, + "host": "api.segment.io/v1" + }, + "legacyVideoPluginsEnabled": false, + "remotePlugins": [], + "consentSettings": { + "allCategories": [ + "C0001", + "C0002" + ] + } +} + diff --git a/packages/core/src/plugins/__tests__/consent/mockSettings/NoUnmappedDestinations.json b/packages/core/src/plugins/__tests__/consent/mockSettings/NoUnmappedDestinations.json new file mode 100644 index 00000000..6d190cd4 --- /dev/null +++ b/packages/core/src/plugins/__tests__/consent/mockSettings/NoUnmappedDestinations.json @@ -0,0 +1,105 @@ +{ + "integrations": { + "DummyDest1": { + "versionSettings": { + "componentTypes": [] + }, + "consentSettings": { + "categories": [ + "C0001" + ] + } + }, + "DummyDest2": { + "versionSettings": { + "componentTypes": [] + }, + "consentSettings": { + "categories": [ + "C0002" + ] + } + }, + "DummyDest3": { + "versionSettings": { + "componentTypes": [] + }, + "consentSettings": { + "categories": [ + "C0003" + ] + } + }, + "DummyDest4": { + "versionSettings": { + "componentTypes": [] + }, + "consentSettings": { + "categories": [ + "C0004" + ] + } + }, + "DummyDest5": { + "versionSettings": { + "componentTypes": [] + }, + "consentSettings": { + "categories": [ + "C0005" + ] + } + }, + "Segment.io": { + "apiKey": "test", + "unbundledIntegrations": [], + "addBundledMetadata": true, + "maybeBundledConfigIds": {}, + "versionSettings": { + "version": "4.4.7", + "componentTypes": [ + "browser" + ] + }, + "apiHost": "api.segment.io/v1" + } + }, + "plan": { + "track": { + "__default": { + "enabled": true, + "integrations": {} + } + }, + "identify": { + "__default": { + "enabled": true + } + }, + "group": { + "__default": { + "enabled": true + } + } + }, + "edgeFunction": {}, + "analyticsNextEnabled": true, + "middlewareSettings": {}, + "enabledMiddleware": {}, + "metrics": { + "sampleRate": 0.1, + "host": "api.segment.io/v1" + }, + "legacyVideoPluginsEnabled": false, + "remotePlugins": [], + "consentSettings": { + "allCategories": [ + "C0001", + "C0002", + "C0003", + "C0004", + "C0005" + ] + } +} + diff --git a/packages/core/src/plugins/__tests__/consent/mockSettings/UnmappedDestinations.json b/packages/core/src/plugins/__tests__/consent/mockSettings/UnmappedDestinations.json new file mode 100644 index 00000000..2e3a6dcf --- /dev/null +++ b/packages/core/src/plugins/__tests__/consent/mockSettings/UnmappedDestinations.json @@ -0,0 +1,101 @@ +{ + "integrations": { + "DummyDest1": { + "versionSettings": { + "componentTypes": [] + }, + "consentSettings": { + "categories": [ + "C0001", + "C0002" + ] + } + }, + "DummyDest2": { + "versionSettings": { + "componentTypes": [] + }, + "consentSettings": { + "categories": [ + "C0003" + ] + } + }, + "DummyDest3": { + "versionSettings": { + "componentTypes": [] + }, + "consentSettings": { + "categories": [ + "C0004" + ] + } + }, + "DummyDest4": { + "versionSettings": { + "componentTypes": [] + }, + "consentSettings": { + "categories": [ + "C0005" + ] + } + }, + "DummyDest5": { + "versionSettings": { + "componentTypes": [] + } + }, + "Segment.io": { + "apiKey": "test", + "unbundledIntegrations": [], + "addBundledMetadata": true, + "maybeBundledConfigIds": {}, + "versionSettings": { + "version": "4.4.7", + "componentTypes": [ + "browser" + ] + }, + "apiHost": "api.segment.io/v1" + } + }, + "plan": { + "track": { + "__default": { + "enabled": true, + "integrations": {} + } + }, + "identify": { + "__default": { + "enabled": true + } + }, + "group": { + "__default": { + "enabled": true + } + } + }, + "edgeFunction": {}, + "analyticsNextEnabled": true, + "middlewareSettings": {}, + "enabledMiddleware": {}, + "metrics": { + "sampleRate": 0.1, + "host": "api.segment.io/v1" + }, + "legacyVideoPluginsEnabled": false, + "remotePlugins": [], + "consentSettings": { + "allCategories": [ + "C0001", + "C0002", + "C0003", + "C0004", + "C0005" + ] + } +} + diff --git a/packages/core/src/plugins/__tests__/consent/noUnmapped.test.ts b/packages/core/src/plugins/__tests__/consent/noUnmapped.test.ts new file mode 100644 index 00000000..17f86fb8 --- /dev/null +++ b/packages/core/src/plugins/__tests__/consent/noUnmapped.test.ts @@ -0,0 +1,213 @@ +import { createTestClient } from '../../../__tests__/__helpers__/setupSegmentClient'; +import { ConsentPlugin } from '../../ConsentPlugin'; + +import { setupTestDestinations, createConsentProvider } from './utils'; +import noUnmappedDestinations from './mockSettings/NoUnmappedDestinations.json'; + +describe('No unmapped destinations', () => { + const createClient = () => + createTestClient({ + settings: noUnmappedDestinations.integrations, + }); + + test('no to all', async () => { + const { client } = createClient(); + const testDestinations = setupTestDestinations(client); + const mockConsentStatuses = { + C0001: false, + C0002: false, + C0003: false, + C0004: false, + C0005: false, + }; + + client.add({ + plugin: new ConsentPlugin( + createConsentProvider(mockConsentStatuses), + Object.keys(mockConsentStatuses) + ), + }); + + await client.init(); + + await client.track('test'); + + Object.values(testDestinations).forEach((testDestination) => { + expect(testDestination.track).not.toHaveBeenCalled(); + }); + }); + + test('yes to 1', async () => { + const { client } = createClient(); + const testDestinations = setupTestDestinations(client); + const mockConsentStatuses = { + C0001: true, + C0002: false, + C0003: false, + C0004: false, + C0005: false, + }; + + client.add({ + plugin: new ConsentPlugin( + createConsentProvider(mockConsentStatuses), + Object.keys(mockConsentStatuses) + ), + }); + + await client.init(); + + await client.track('test'); + + expect(testDestinations.dest1.track).toHaveBeenCalled(); + expect(testDestinations.dest2.track).not.toHaveBeenCalled(); + expect(testDestinations.dest3.track).not.toHaveBeenCalled(); + expect(testDestinations.dest4.track).not.toHaveBeenCalled(); + expect(testDestinations.dest5.track).not.toHaveBeenCalled(); + }); + + test('yes to 2', async () => { + const { client } = createClient(); + const testDestinations = setupTestDestinations(client); + const mockConsentStatuses = { + C0001: false, + C0002: true, + C0003: false, + C0004: false, + C0005: false, + }; + + client.add({ + plugin: new ConsentPlugin( + createConsentProvider(mockConsentStatuses), + Object.keys(mockConsentStatuses) + ), + }); + + await client.init(); + + await client.track('test'); + + expect(testDestinations.dest1.track).not.toHaveBeenCalled(); + expect(testDestinations.dest2.track).toHaveBeenCalled(); + expect(testDestinations.dest3.track).not.toHaveBeenCalled(); + expect(testDestinations.dest4.track).not.toHaveBeenCalled(); + expect(testDestinations.dest5.track).not.toHaveBeenCalled(); + }); + + test('yes to 3', async () => { + const { client } = createClient(); + const testDestinations = setupTestDestinations(client); + const mockConsentStatuses = { + C0001: false, + C0002: false, + C0003: true, + C0004: false, + C0005: false, + }; + + client.add({ + plugin: new ConsentPlugin( + createConsentProvider(mockConsentStatuses), + Object.keys(mockConsentStatuses) + ), + }); + + await client.init(); + + await client.track('test'); + + expect(testDestinations.dest1.track).not.toHaveBeenCalled(); + expect(testDestinations.dest2.track).not.toHaveBeenCalled(); + expect(testDestinations.dest3.track).toHaveBeenCalled(); + expect(testDestinations.dest4.track).not.toHaveBeenCalled(); + expect(testDestinations.dest5.track).not.toHaveBeenCalled(); + }); + + test('yes to 4', async () => { + const { client } = createClient(); + const testDestinations = setupTestDestinations(client); + const mockConsentStatuses = { + C0001: false, + C0002: false, + C0003: false, + C0004: true, + C0005: false, + }; + + client.add({ + plugin: new ConsentPlugin( + createConsentProvider(mockConsentStatuses), + Object.keys(mockConsentStatuses) + ), + }); + + await client.init(); + + await client.track('test'); + + expect(testDestinations.dest1.track).not.toHaveBeenCalled(); + expect(testDestinations.dest2.track).not.toHaveBeenCalled(); + expect(testDestinations.dest3.track).not.toHaveBeenCalled(); + expect(testDestinations.dest4.track).toHaveBeenCalled(); + expect(testDestinations.dest5.track).not.toHaveBeenCalled(); + }); + + test('yes to 1 and 3', async () => { + const { client } = createClient(); + const testDestinations = setupTestDestinations(client); + const mockConsentStatuses = { + C0001: true, + C0002: false, + C0003: true, + C0004: false, + C0005: false, + }; + + client.add({ + plugin: new ConsentPlugin( + createConsentProvider(mockConsentStatuses), + Object.keys(mockConsentStatuses) + ), + }); + + await client.init(); + + await client.track('test'); + + expect(testDestinations.dest1.track).toHaveBeenCalled(); + expect(testDestinations.dest2.track).not.toHaveBeenCalled(); + expect(testDestinations.dest3.track).toHaveBeenCalled(); + expect(testDestinations.dest4.track).not.toHaveBeenCalled(); + expect(testDestinations.dest5.track).not.toHaveBeenCalled(); + }); + + test('yes to all', async () => { + const { client } = createClient(); + const testDestinations = setupTestDestinations(client); + const mockConsentStatuses = { + C0001: true, + C0002: true, + C0003: true, + C0004: true, + C0005: true, + }; + + client.add({ + plugin: new ConsentPlugin( + createConsentProvider(mockConsentStatuses), + Object.keys(mockConsentStatuses) + ), + }); + + await client.init(); + + await client.track('test'); + + expect(testDestinations.dest1.track).toHaveBeenCalled(); + expect(testDestinations.dest2.track).toHaveBeenCalled(); + expect(testDestinations.dest3.track).toHaveBeenCalled(); + expect(testDestinations.dest4.track).toHaveBeenCalled(); + expect(testDestinations.dest5.track).toHaveBeenCalled(); + }); +}); diff --git a/packages/core/src/plugins/__tests__/consent/unmapped.test.ts b/packages/core/src/plugins/__tests__/consent/unmapped.test.ts new file mode 100644 index 00000000..1678e77a --- /dev/null +++ b/packages/core/src/plugins/__tests__/consent/unmapped.test.ts @@ -0,0 +1,243 @@ +import { createTestClient } from '../../../__tests__/__helpers__/setupSegmentClient'; +import { ConsentPlugin } from '../../ConsentPlugin'; + +import { + setupTestDestinations, + createConsentProvider, + createSegmentWatcher, +} from './utils'; +import unmappedDestinations from './mockSettings/UnmappedDestinations.json'; + +describe('Unmapped destinations', () => { + const createClient = () => + createTestClient( + { + settings: unmappedDestinations.integrations, + }, + { autoAddSegmentDestination: true } + ); + + test('no to all', async () => { + const { client } = createClient(); + const testDestinations = setupTestDestinations(client); + const mockConsentStatuses = { + C0001: false, + C0002: false, + C0003: false, + C0004: false, + C0005: false, + }; + + client.add({ + plugin: new ConsentPlugin( + createConsentProvider(mockConsentStatuses), + Object.keys(mockConsentStatuses) + ), + }); + + await client.init(); + + const segmentDestination = createSegmentWatcher(client); + + await client.track('test'); + + expect(segmentDestination).toHaveBeenCalled(); + expect(testDestinations.dest1.track).not.toHaveBeenCalled(); + expect(testDestinations.dest2.track).not.toHaveBeenCalled(); + expect(testDestinations.dest3.track).not.toHaveBeenCalled(); + expect(testDestinations.dest4.track).not.toHaveBeenCalled(); + expect(testDestinations.dest5.track).toHaveBeenCalled(); + }); + + test('yes to 1', async () => { + const { client } = createClient(); + const testDestinations = setupTestDestinations(client); + const mockConsentStatuses = { + C0001: true, + C0002: false, + C0003: false, + C0004: false, + C0005: false, + }; + + client.add({ + plugin: new ConsentPlugin( + createConsentProvider(mockConsentStatuses), + Object.keys(mockConsentStatuses) + ), + }); + + await client.init(); + + const segmentDestination = createSegmentWatcher(client); + + await client.track('test'); + + expect(segmentDestination).toHaveBeenCalled(); + expect(testDestinations.dest1.track).not.toHaveBeenCalled(); + expect(testDestinations.dest2.track).not.toHaveBeenCalled(); + expect(testDestinations.dest3.track).not.toHaveBeenCalled(); + expect(testDestinations.dest4.track).not.toHaveBeenCalled(); + expect(testDestinations.dest5.track).toHaveBeenCalled(); + }); + + test('yes to 2', async () => { + const { client } = createClient(); + const testDestinations = setupTestDestinations(client); + const mockConsentStatuses = { + C0001: false, + C0002: true, + C0003: false, + C0004: false, + C0005: false, + }; + + client.add({ + plugin: new ConsentPlugin( + createConsentProvider(mockConsentStatuses), + Object.keys(mockConsentStatuses) + ), + }); + + await client.init(); + + const segmentDestination = createSegmentWatcher(client); + + await client.track('test'); + + expect(segmentDestination).toHaveBeenCalled(); + expect(testDestinations.dest1.track).not.toHaveBeenCalled(); + expect(testDestinations.dest2.track).not.toHaveBeenCalled(); + expect(testDestinations.dest3.track).not.toHaveBeenCalled(); + expect(testDestinations.dest4.track).not.toHaveBeenCalled(); + expect(testDestinations.dest5.track).toHaveBeenCalled(); + }); + + test('yes to 3', async () => { + const { client } = createClient(); + const testDestinations = setupTestDestinations(client); + const mockConsentStatuses = { + C0001: false, + C0002: false, + C0003: true, + C0004: false, + C0005: false, + }; + + client.add({ + plugin: new ConsentPlugin( + createConsentProvider(mockConsentStatuses), + Object.keys(mockConsentStatuses) + ), + }); + + await client.init(); + + const segmentDestination = createSegmentWatcher(client); + + await client.track('test'); + + expect(segmentDestination).toHaveBeenCalled(); + expect(testDestinations.dest1.track).not.toHaveBeenCalled(); + expect(testDestinations.dest2.track).toHaveBeenCalled(); + expect(testDestinations.dest3.track).not.toHaveBeenCalled(); + expect(testDestinations.dest4.track).not.toHaveBeenCalled(); + expect(testDestinations.dest5.track).toHaveBeenCalled(); + }); + + test('yes to 4', async () => { + const { client } = createClient(); + const testDestinations = setupTestDestinations(client); + const mockConsentStatuses = { + C0001: false, + C0002: false, + C0003: false, + C0004: true, + C0005: false, + }; + + client.add({ + plugin: new ConsentPlugin( + createConsentProvider(mockConsentStatuses), + Object.keys(mockConsentStatuses) + ), + }); + + await client.init(); + + const segmentDestination = createSegmentWatcher(client); + + await client.track('test'); + + expect(segmentDestination).toHaveBeenCalled(); + expect(testDestinations.dest1.track).not.toHaveBeenCalled(); + expect(testDestinations.dest2.track).not.toHaveBeenCalled(); + expect(testDestinations.dest3.track).toHaveBeenCalled(); + expect(testDestinations.dest4.track).not.toHaveBeenCalled(); + expect(testDestinations.dest5.track).toHaveBeenCalled(); + }); + + test('yes to 1 and 2', async () => { + const { client } = createClient(); + const testDestinations = setupTestDestinations(client); + const mockConsentStatuses = { + C0001: true, + C0002: true, + C0003: false, + C0004: false, + C0005: false, + }; + + client.add({ + plugin: new ConsentPlugin( + createConsentProvider(mockConsentStatuses), + Object.keys(mockConsentStatuses) + ), + }); + + await client.init(); + + const segmentDestination = createSegmentWatcher(client); + + await client.track('test'); + + expect(segmentDestination).toHaveBeenCalled(); + expect(testDestinations.dest1.track).toHaveBeenCalled(); + expect(testDestinations.dest2.track).not.toHaveBeenCalled(); + expect(testDestinations.dest3.track).not.toHaveBeenCalled(); + expect(testDestinations.dest4.track).not.toHaveBeenCalled(); + expect(testDestinations.dest5.track).toHaveBeenCalled(); + }); + + test('yes to all', async () => { + const { client } = createClient(); + const testDestinations = setupTestDestinations(client); + const mockConsentStatuses = { + C0001: true, + C0002: true, + C0003: true, + C0004: true, + C0005: true, + }; + + client.add({ + plugin: new ConsentPlugin( + createConsentProvider(mockConsentStatuses), + Object.keys(mockConsentStatuses) + ), + }); + + await client.init(); + + const segmentDestination = createSegmentWatcher(client); + + await client.track('test'); + + expect(segmentDestination).toHaveBeenCalled(); + expect(testDestinations.dest1.track).toHaveBeenCalled(); + expect(testDestinations.dest2.track).toHaveBeenCalled(); + expect(testDestinations.dest3.track).toHaveBeenCalled(); + expect(testDestinations.dest4.track).toHaveBeenCalled(); + expect(testDestinations.dest5.track).toHaveBeenCalled(); + }); +}); diff --git a/packages/core/src/plugins/__tests__/consent/utils.ts b/packages/core/src/plugins/__tests__/consent/utils.ts new file mode 100644 index 00000000..c3997968 --- /dev/null +++ b/packages/core/src/plugins/__tests__/consent/utils.ts @@ -0,0 +1,78 @@ +import { + CategoryConsentStatusProvider, + DestinationPlugin, + PluginType, + SegmentClient, + UtilityPlugin, +} from '@segment/analytics-react-native'; +import { SegmentDestination } from '../../SegmentDestination'; + +beforeEach(() => { + jest.spyOn(SegmentDestination.prototype, 'execute'); +}); + +class SegmentWatcherPlugin extends UtilityPlugin { + type = PluginType.after; + execute = jest.fn(); +} + +class MockDestination extends DestinationPlugin { + track = jest.fn(); + + constructor(public readonly key: string) { + super(); + } +} + +export const setupTestDestinations = (client: SegmentClient) => { + const dest1 = new MockDestination('DummyDest1'); + const dest2 = new MockDestination('DummyDest2'); + const dest3 = new MockDestination('DummyDest3'); + const dest4 = new MockDestination('DummyDest4'); + const dest5 = new MockDestination('DummyDest5'); + + client.add({ plugin: dest1 }); + client.add({ plugin: dest2 }); + client.add({ plugin: dest3 }); + client.add({ plugin: dest4 }); + client.add({ plugin: dest5 }); + + return { + dest1, + dest2, + dest3, + dest4, + dest5, + }; +}; + +export const createSegmentWatcher = (client: SegmentClient) => { + const segmentDestination = client + .getPlugins() + .find( + (p) => (p as DestinationPlugin).key === 'Segment.io' + ) as SegmentDestination; + + const segmentWatcher = new SegmentWatcherPlugin(); + segmentDestination.add(segmentWatcher); + + return segmentWatcher.execute; +}; + +export const createConsentProvider = ( + statuses: Record +): CategoryConsentStatusProvider => ({ + getConsentStatus: () => Promise.resolve(statuses), + setApplicableCategories: () => { + /** no op */ + }, + onConsentChange: () => { + /** no op */ + }, +}); + +describe('Consent test utils', () => { + it('works', () => { + // this is just to suppress jest error - "must have at least one test" + }); +}); diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index 75b8e39d..fb8dbb41 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -5,6 +5,6 @@ "rootDir": ".", "baseUrl": "." }, - "include": ["src/**/*", "types.d.ts"], - "references": [{ "path": "../sovran" }] // Add refereneces to any packages you require + "references": [{ "path": "../sovran" }], + "include": ["src/**/*", "src/**/*.json", "types.d.ts"] }