From 216338450516e56a45529903a098167e5d8d8622 Mon Sep 17 00:00:00 2001 From: Seth Silesky <5115498+silesky@users.noreply.github.com> Date: Tue, 6 Dec 2022 13:55:39 -0600 Subject: [PATCH 1/3] make tests a bit less flake --- package.json | 1 + packages/browser/package.json | 1 + .../src/browser/__tests__/integration.test.ts | 177 +---------- .../integrations.integration.test.ts | 227 +++++++++++++ .../browser/src/core/events/interfaces.ts | 1 + .../src/plugins/ajs-destination/loader.ts | 1 - .../src/plugins/routing-middleware/index.ts | 13 +- .../browser/src/test-helpers/factories.ts | 3 +- .../src/test-helpers/fixtures/cdn-settings.ts | 299 ++++++++++++++++++ .../fixtures/classic-destination.ts | 25 ++ .../fixtures/create-fetch-method.ts | 24 ++ packages/node/package.json | 3 +- yarn.lock | 5 +- 13 files changed, 611 insertions(+), 169 deletions(-) create mode 100644 packages/browser/src/browser/__tests__/integrations.integration.test.ts create mode 100644 packages/browser/src/test-helpers/fixtures/cdn-settings.ts create mode 100644 packages/browser/src/test-helpers/fixtures/classic-destination.ts create mode 100644 packages/browser/src/test-helpers/fixtures/create-fetch-method.ts diff --git a/package.json b/package.json index 8ffaa4f7e..5dbe5f45f 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "jest": "^28.1.0", "lint-staged": "^13.0.0", "lodash": "^4.17.21", + "nock": "^13.2.9", "node-gyp": "^9.0.0", "prettier": "^2.6.2", "ts-jest": "^28.0.4", diff --git a/packages/browser/package.json b/packages/browser/package.json index 9983d5c9d..99195b09f 100644 --- a/packages/browser/package.json +++ b/packages/browser/package.json @@ -61,6 +61,7 @@ }, "devDependencies": { "@internal/config": "0.0.0", + "@segment/analytics.js-integration": "^3.3.3", "@segment/analytics.js-integration-amplitude": "^3.3.3", "@segment/inspector-webext": "^2.0.3", "@size-limit/preset-big-lib": "^7.0.8", diff --git a/packages/browser/src/browser/__tests__/integration.test.ts b/packages/browser/src/browser/__tests__/integration.test.ts index 50a5a5698..e05e68419 100644 --- a/packages/browser/src/browser/__tests__/integration.test.ts +++ b/packages/browser/src/browser/__tests__/integration.test.ts @@ -1,4 +1,6 @@ /* eslint-disable @typescript-eslint/no-floating-promises */ +import { cdnSettingsKitchenSink } from '../../test-helpers/fixtures/cdn-settings' +import { createMockFetchImplementation } from '../../test-helpers/fixtures/create-fetch-method' import { Context } from '@/core/context' import { Plugin } from '@/core/plugin' import { JSDOM } from 'jsdom' @@ -11,27 +13,19 @@ import { AnalyticsBrowser, loadLegacySettings } from '..' import { isOffline } from '../../core/connection' import * as SegmentPlugin from '../../plugins/segmentio' import jar from 'js-cookie' -import { - AMPLITUDE_WRITEKEY, - TEST_WRITEKEY, -} from '../../test-helpers/test-writekeys' import { PriorityQueue } from '../../lib/priority-queue' import { getCDN, setGlobalCDNUrl } from '../../lib/parse-cdn' import { clearAjsBrowserStorage } from '../../test-helpers/browser-storage' import { ActionDestination } from '@/plugins/remote-loader' -import { ClassicIntegrationBuilder } from '../../plugins/ajs-destination/types' -// eslint-disable-next-line @typescript-eslint/no-explicit-any let fetchCalls: Array[] = [] -// Mock unfetch so we can record any http requests made + jest.mock('unfetch', () => { - const originalModule = jest.requireActual('unfetch') return { __esModule: true, - ...originalModule, - default: (...args: unknown[]) => { - fetchCalls.push(args) - return originalModule.apply(originalModule, args) + default: (url: RequestInfo, body?: RequestInit) => { + fetchCalls.push([url, body]) + return createMockFetchImplementation(cdnSettingsKitchenSink)(url, body) }, } }) @@ -87,7 +81,8 @@ const enrichBilling: Plugin = { }, } -const writeKey = TEST_WRITEKEY +const writeKey = 'foo' +const amplitudeWriteKey = 'bar' beforeEach(() => { setGlobalCDNUrl(undefined as any) @@ -95,9 +90,9 @@ beforeEach(() => { describe('Initialization', () => { beforeEach(async () => { + fetchCalls = [] jest.resetAllMocks() jest.resetModules() - fetchCalls = [] }) it('loads plugins', async () => { @@ -204,6 +199,7 @@ describe('Initialization', () => { }, ], }) + expect(fetchCalls[0][0]).toContain(overriddenCDNUrl) expect.assertions(3) }) @@ -680,7 +676,7 @@ describe('addDestinationMiddleware', () => { 'amplitude', 'latest', { - apiKey: AMPLITUDE_WRITEKEY, + apiKey: amplitudeWriteKey, }, {} ) @@ -823,7 +819,7 @@ describe('deregister', () => { 'amplitude', 'latest', { - apiKey: AMPLITUDE_WRITEKEY, + apiKey: amplitudeWriteKey, }, {} ) @@ -868,7 +864,7 @@ describe('retries', () => { it('does not retry errored events if retryQueue setting is set to false', async () => { const [ajs] = await AnalyticsBrowser.load( - { writeKey: TEST_WRITEKEY }, + { writeKey: writeKey }, { retryQueue: false } ) @@ -951,147 +947,6 @@ describe('Segment.io overrides', () => { }) }) -describe('.Integrations', () => { - beforeEach(async () => { - jest.restoreAllMocks() - jest.resetAllMocks() - - const html = ` - - - - - - - - `.trim() - - const jsd = new JSDOM(html, { - runScripts: 'dangerously', - resources: 'usable', - url: 'https://localhost', - }) - - const windowSpy = jest.spyOn(global, 'window', 'get') - windowSpy.mockImplementation( - () => jsd.window as unknown as Window & typeof globalThis - ) - - const documentSpy = jest.spyOn(global, 'document', 'get') - documentSpy.mockImplementation( - () => jsd.window.document as unknown as Document - ) - }) - - it('lists all legacy destinations', async () => { - const amplitude = new LegacyDestination( - 'Amplitude', - 'latest', - { - apiKey: AMPLITUDE_WRITEKEY, - }, - {} - ) - - const ga = new LegacyDestination('Google-Analytics', 'latest', {}, {}) - - const [analytics] = await AnalyticsBrowser.load({ - writeKey, - plugins: [amplitude, ga], - }) - - await analytics.ready() - - expect(analytics.Integrations).toMatchInlineSnapshot(` - Object { - "Amplitude": [Function], - "Google-Analytics": [Function], - } - `) - }) - - it('catches destinations with dots in their names', async () => { - const amplitude = new LegacyDestination( - 'Amplitude', - 'latest', - { - apiKey: AMPLITUDE_WRITEKEY, - }, - {} - ) - - const ga = new LegacyDestination('Google-Analytics', 'latest', {}, {}) - const customerIO = new LegacyDestination('Customer.io', 'latest', {}, {}) - - const [analytics] = await AnalyticsBrowser.load({ - writeKey, - plugins: [amplitude, ga, customerIO], - }) - - await analytics.ready() - - expect(analytics.Integrations).toMatchInlineSnapshot(` - Object { - "Amplitude": [Function], - "Customer.io": [Function], - "Google-Analytics": [Function], - } - `) - }) - - it('uses directly provided classic integrations without fetching them from cdn', async () => { - const amplitude = // @ts-ignore - (await import('@segment/analytics.js-integration-amplitude')).default - - const intializeSpy = jest.spyOn(amplitude.prototype, 'initialize') - const trackSpy = jest.spyOn(amplitude.prototype, 'track') - - const [analytics] = await AnalyticsBrowser.load( - { - writeKey, - classicIntegrations: [ - amplitude as unknown as ClassicIntegrationBuilder, - ], - }, - { - integrations: { - Amplitude: { - apiKey: 'abc', - }, - }, - } - ) - - await analytics.ready() - expect(intializeSpy).toHaveBeenCalledTimes(1) - - await analytics.track('test event') - - expect(trackSpy).toHaveBeenCalledTimes(1) - }) - - it('ignores directly provided classic integrations if settings for them are unavailable', async () => { - const amplitude = // @ts-ignore - (await import('@segment/analytics.js-integration-amplitude')).default - - const intializeSpy = jest.spyOn(amplitude.prototype, 'initialize') - const trackSpy = jest.spyOn(amplitude.prototype, 'track') - - const [analytics] = await AnalyticsBrowser.load({ - writeKey, - classicIntegrations: [amplitude as unknown as ClassicIntegrationBuilder], - }) - - await analytics.ready() - - expect(intializeSpy).not.toHaveBeenCalled() - - await analytics.track('test event') - - expect(trackSpy).not.toHaveBeenCalled() - }) -}) - describe('Options', () => { beforeEach(async () => { jest.restoreAllMocks() @@ -1129,7 +984,7 @@ describe('Options', () => { 'amplitude', 'latest', { - apiKey: AMPLITUDE_WRITEKEY, + apiKey: amplitudeWriteKey, }, {} ) @@ -1165,7 +1020,7 @@ describe('Options', () => { 'amplitude', 'latest', { - apiKey: AMPLITUDE_WRITEKEY, + apiKey: amplitudeWriteKey, }, initOptions ) @@ -1201,7 +1056,7 @@ describe('Options', () => { 'amplitude', 'latest', { - apiKey: AMPLITUDE_WRITEKEY, + apiKey: amplitudeWriteKey, }, initOptions ) diff --git a/packages/browser/src/browser/__tests__/integrations.integration.test.ts b/packages/browser/src/browser/__tests__/integrations.integration.test.ts new file mode 100644 index 000000000..3f80931b2 --- /dev/null +++ b/packages/browser/src/browser/__tests__/integrations.integration.test.ts @@ -0,0 +1,227 @@ +import { JSDOM } from 'jsdom' +import { AnalyticsBrowser } from '../..' +import { LegacyDestination } from '../../plugins/ajs-destination' +import { ClassicIntegrationBuilder } from '../../plugins/ajs-destination/types' +import { ActionDestination } from '../../plugins/remote-loader' +import unfetch from 'unfetch' +import { cdnSettingsMinimal } from '../../test-helpers/fixtures/cdn-settings' +import { Fake } from '../../test-helpers/fixtures/classic-destination' +import { Plugin } from '../../core/plugin' +import { createMockFetchImplementation } from '../../test-helpers/fixtures/create-fetch-method' +const amplitudeWriteKey = 'foo' +const writeKey = 'foo' + +jest.mock('unfetch') + +const mockFetchCdnSettings = (cdnSettings: any = {}) => { + return jest + .mocked(unfetch) + .mockImplementation(createMockFetchImplementation(cdnSettings)) +} + +describe('Integrations', () => { + beforeEach(async () => { + mockFetchCdnSettings() + + const html = ` + + + + + + + + `.trim() + + const jsd = new JSDOM(html, { + runScripts: 'dangerously', + resources: 'usable', + url: 'https://localhost', + }) + + const windowSpy = jest.spyOn(global, 'window', 'get') + windowSpy.mockImplementation( + () => jsd.window as unknown as Window & typeof globalThis + ) + + const documentSpy = jest.spyOn(global, 'document', 'get') + documentSpy.mockImplementation( + () => jsd.window.document as unknown as Document + ) + }) + + describe('addDestinationMiddleware', () => { + it('supports registering destination middlewares', async () => { + const [analytics] = await AnalyticsBrowser.load({ + writeKey, + }) + + const amplitude = new LegacyDestination( + 'amplitude', + 'latest', + { + apiKey: amplitudeWriteKey, + }, + {} + ) + + await analytics.register(amplitude) + await amplitude.ready() + + analytics + .addDestinationMiddleware('amplitude', ({ next, payload }) => { + payload.obj.properties!.hello = 'from the other side' + next(payload) + }) + .catch((err) => { + throw err + }) + + const integrationMock = jest.spyOn(amplitude.integration!, 'track') + const ctx = await analytics.track('Hello!') + + // does not modify the event + expect(ctx.event.properties).not.toEqual({ + hello: 'from the other side', + }) + + const calledWith = integrationMock.mock.calls[0][0].properties() + + // only impacted this destination + expect(calledWith).toEqual({ + ...ctx.event.properties, + hello: 'from the other side', + }) + }) + + it('supports registering action destination middlewares', async () => { + const testPlugin: Plugin = { + name: 'test', + type: 'destination', + version: '0.1.0', + load: () => Promise.resolve(), + isLoaded: () => true, + } + + const [analytics] = await AnalyticsBrowser.load({ + writeKey, + }) + + const fullstory = new ActionDestination('fullstory', testPlugin) + + await analytics.register(fullstory) + await fullstory.ready() + + analytics + .addDestinationMiddleware('fullstory', ({ next, payload }) => + next(payload) + ) + .catch((err) => { + throw err + }) + + expect(analytics.queue.plugins).toContain(fullstory) + }) + }) + + describe('Legacy / Classic Destinations', () => { + it('lists all legacy destinations', async () => { + const amplitude = new LegacyDestination( + 'Amplitude', + 'latest', + { + apiKey: amplitudeWriteKey, + }, + {} + ) + + const ga = new LegacyDestination('Google-Analytics', 'latest', {}, {}) + + const [analytics] = await AnalyticsBrowser.load({ + writeKey, + plugins: [amplitude, ga], + }) + + await analytics.ready() + + expect(analytics.Integrations).toMatchInlineSnapshot(` + Object { + "Amplitude": [Function], + "Google-Analytics": [Function], + } + `) + }) + + it('catches destinations with dots in their names', async () => { + const amplitude = new LegacyDestination( + 'Amplitude', + 'latest', + { + apiKey: amplitudeWriteKey, + }, + {} + ) + + const ga = new LegacyDestination('Google-Analytics', 'latest', {}, {}) + const customerIO = new LegacyDestination('Customer.io', 'latest', {}, {}) + + const [analytics] = await AnalyticsBrowser.load({ + writeKey, + plugins: [amplitude, ga, customerIO], + }) + + await analytics.ready() + + expect(analytics.Integrations).toMatchInlineSnapshot(` + Object { + "Amplitude": [Function], + "Customer.io": [Function], + "Google-Analytics": [Function], + } + `) + }) + + it('uses directly provided classic integrations without fetching them from cdn', async () => { + mockFetchCdnSettings({ integrations: cdnSettingsMinimal }) + const intializeSpy = jest.spyOn(Fake.prototype, 'initialize') + const trackSpy = jest.spyOn(Fake.prototype, 'track') + + const [analytics] = await AnalyticsBrowser.load( + { + writeKey, + classicIntegrations: [Fake], + }, + { + integrations: { + Fake: {}, + }, + } + ) + await analytics.ready() + + await analytics.track('test event') + + expect(trackSpy).toHaveBeenCalledTimes(1) + expect(intializeSpy).toHaveBeenCalledTimes(1) + }) + + it('ignores directly provided classic integrations if settings for them are unavailable', async () => { + mockFetchCdnSettings({ integrations: {} }) + const intializeSpy = jest.spyOn(Fake.prototype, 'initialize') + const trackSpy = jest.spyOn(Fake.prototype, 'track') + + const [analytics] = await AnalyticsBrowser.load({ + writeKey, + classicIntegrations: [Fake as unknown as ClassicIntegrationBuilder], + }) + + await analytics.ready() + + expect(intializeSpy).not.toHaveBeenCalled() + + await analytics.track('test event') + + expect(trackSpy).not.toHaveBeenCalled() + }) + }) +}) diff --git a/packages/browser/src/core/events/interfaces.ts b/packages/browser/src/core/events/interfaces.ts index ab786dc44..ad2b289e7 100644 --- a/packages/browser/src/core/events/interfaces.ts +++ b/packages/browser/src/core/events/interfaces.ts @@ -173,6 +173,7 @@ export interface SegmentEvent { */ export interface Plan { track?: TrackPlan + identify?: TrackPlan } export interface TrackPlan { diff --git a/packages/browser/src/plugins/ajs-destination/loader.ts b/packages/browser/src/plugins/ajs-destination/loader.ts index d4a461583..e19d1a1fa 100644 --- a/packages/browser/src/plugins/ajs-destination/loader.ts +++ b/packages/browser/src/plugins/ajs-destination/loader.ts @@ -64,7 +64,6 @@ export function buildIntegration( const integration = new integrationCtr(integrationSettings) integration.analytics = analyticsInstance - return integration } diff --git a/packages/browser/src/plugins/routing-middleware/index.ts b/packages/browser/src/plugins/routing-middleware/index.ts index 657156803..f2dabb966 100644 --- a/packages/browser/src/plugins/routing-middleware/index.ts +++ b/packages/browser/src/plugins/routing-middleware/index.ts @@ -1,8 +1,17 @@ import * as tsub from '@segment/tsub' -import { Rule } from '@segment/tsub/dist/store' +import { Matcher, Rule } from '@segment/tsub/dist/store' import { DestinationMiddlewareFunction } from '../middleware' -export type RoutingRule = Rule +// TODO: update tsub definition +type RoutingRuleMatcher = Matcher & { + config?: { + expr: string + } +} + +export type RoutingRule = Rule & { + matchers: RoutingRuleMatcher[] +} export const tsubMiddleware = (rules: RoutingRule[]): DestinationMiddlewareFunction => diff --git a/packages/browser/src/test-helpers/factories.ts b/packages/browser/src/test-helpers/factories.ts index 8b1abc0ac..a5045018a 100644 --- a/packages/browser/src/test-helpers/factories.ts +++ b/packages/browser/src/test-helpers/factories.ts @@ -1,9 +1,10 @@ -export const createSuccess = (body: any) => { +export const createSuccess = (body: any, overrides: Partial = {}) => { return Promise.resolve({ json: () => Promise.resolve(body), ok: true, status: 200, statusText: 'OK', + ...overrides, }) as Promise } diff --git a/packages/browser/src/test-helpers/fixtures/cdn-settings.ts b/packages/browser/src/test-helpers/fixtures/cdn-settings.ts new file mode 100644 index 000000000..fa22413b7 --- /dev/null +++ b/packages/browser/src/test-helpers/fixtures/cdn-settings.ts @@ -0,0 +1,299 @@ +import { LegacySettings } from '../..' +import { mockIntegrationName } from './classic-destination' + +export const cdnSettingsKitchenSink: LegacySettings = { + integrations: { + [mockIntegrationName]: {}, + 'Customer.io': { + siteId: 'abc123foofixture', // overwritten + versionSettings: { + version: '2.2.3', + componentTypes: ['browser', 'server'], + }, + type: 'browser', + bundlingStatus: 'bundled', + }, + FullStory: { + debug: false, + org: 'se', + trackAllPages: false, + trackCategorizedPages: false, + trackNamedPages: false, + versionSettings: { + version: '3.0.1', + componentTypes: ['browser'], + }, + type: 'browser', + bundlingStatus: 'bundled', + }, + 'Google Analytics': { + anonymizeIp: false, + classic: false, + contentGroupings: {}, + dimensions: {}, + domain: '', + doubleClick: false, + enableServerIdentify: false, + enhancedEcommerce: false, + enhancedLinkAttribution: false, + identifyCategory: '', + identifyEventName: '', + ignoredReferrers: [], + includeSearch: false, + metrics: {}, + mobileTrackingId: '', + nameTracker: false, + nonInteraction: false, + optimize: '', + protocolMappings: {}, + reportUncaughtExceptions: false, + resetCustomDimensionsOnPage: [], + sampleRate: 100, + sendUserId: false, + setAllMappedProps: true, + siteSpeedSampleRate: 1, + trackCategorizedPages: true, + trackNamedPages: true, + trackingId: 'UA-970334309-1', + useGoogleAmpClientId: false, + versionSettings: { + version: '2.18.5', + componentTypes: ['browser', 'ios', 'android', 'server'], + }, + type: 'browser', + bundlingStatus: 'unbundled', + }, + 'Segment.io': { + apiKey: 'D8frB7upBChqDN9PMWksNvZYDaKJIYo6', + unbundledIntegrations: ['Google Analytics'], + addBundledMetadata: true, + maybeBundledConfigIds: { + 'Customer.io': ['60104dde8882b933c2006d1f'], + FullStory: ['6010547f5a1d4d46c418d68e'], + }, + versionSettings: { + version: '4.4.7', + componentTypes: ['browser'], + }, + }, + }, + plan: { + track: { + __default: { + enabled: true, + integrations: {}, + }, + }, + identify: { + __default: { + enabled: true, + }, + address: { + enabled: true, + }, + avatar: { + enabled: true, + }, + bs: { + enabled: true, + }, + bsAdjective: { + enabled: true, + }, + bsBuzz: { + enabled: true, + }, + bsNoun: { + enabled: true, + }, + catchPhrase: { + enabled: true, + }, + catchPhraseAdjective: { + enabled: true, + }, + catchPhraseDescriptor: { + enabled: true, + }, + catchPhraseNoun: { + enabled: true, + }, + color: { + enabled: true, + }, + company: { + enabled: true, + }, + companyName: { + enabled: true, + }, + companySuffix: { + enabled: true, + }, + custom: { + enabled: true, + }, + department: { + enabled: true, + }, + domainName: { + enabled: true, + }, + domainSuffix: { + enabled: true, + }, + domainWord: { + enabled: true, + }, + email: { + enabled: true, + }, + exampleEmail: { + enabled: true, + }, + findName: { + enabled: true, + }, + firstName: { + enabled: true, + }, + gender: { + enabled: true, + }, + id: { + enabled: true, + }, + ip: { + enabled: true, + }, + ipv6: { + enabled: true, + }, + jobArea: { + enabled: true, + }, + jobDescriptor: { + enabled: true, + }, + jobTitle: { + enabled: true, + }, + jobType: { + enabled: true, + }, + lastName: { + enabled: true, + }, + mac: { + enabled: true, + }, + name: { + enabled: true, + }, + person: { + enabled: true, + }, + phone: { + enabled: true, + }, + prefix: { + enabled: true, + }, + price: { + enabled: true, + }, + product: { + enabled: true, + }, + productAdjective: { + enabled: true, + }, + productDescription: { + enabled: true, + }, + productMaterial: { + enabled: true, + }, + productName: { + enabled: true, + }, + protocol: { + enabled: true, + }, + suffix: { + enabled: true, + }, + suffixes: { + enabled: true, + }, + title: { + enabled: true, + }, + url: { + enabled: true, + }, + userAgent: { + enabled: true, + }, + userName: { + enabled: true, + }, + username: { + enabled: true, + }, + website: { + enabled: true, + }, + }, + group: { + __default: { + enabled: true, + }, + coolKids: { + enabled: true, + }, + }, + }, + middlewareSettings: { + routingRules: [ + { + matchers: [ + { + ir: '["and",["=","event",{"value":"munanyo"}],["and",["=","type",{"value":"track"}],["=","properties.referrer",{"value":"munyaaaanyo"}]]]', + type: 'fql', + config: { + expr: 'event = "munanyo" and type = "track" and properties.referrer = "munyaaaanyo"', + }, + }, + ], + scope: 'destinations', + target_type: 'workspace::project::destination::config', + transformers: [ + [ + { + type: 'drop_properties', + config: { + drop: { + properties: ['url'], + }, + }, + }, + ], + ], + destinationName: 'Google Analytics', + }, + ], + }, + enabledMiddleware: {}, + metrics: { + sampleRate: 0.1, + }, + legacyVideoPluginsEnabled: true, + remotePlugins: [], +} + +export const cdnSettingsMinimal: LegacySettings = { + integrations: { + [mockIntegrationName]: {}, + }, +} diff --git a/packages/browser/src/test-helpers/fixtures/classic-destination.ts b/packages/browser/src/test-helpers/fixtures/classic-destination.ts new file mode 100644 index 000000000..cc422326d --- /dev/null +++ b/packages/browser/src/test-helpers/fixtures/classic-destination.ts @@ -0,0 +1,25 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +/** + * This is a classic destination, such as: + * https://github.com/segmentio/analytics.js-integrations/blob/master/integrations/appcues/lib/index.js + */ + +const integration = require('@segment/analytics.js-integration') + +export const mockIntegrationName = 'Fake' +export const Fake = integration(mockIntegrationName) + +Fake.prototype.initialize = function () { + this.load(this.ready) +} + +Fake.prototype.loaded = function () { + return true +} + +Fake.prototype.track = function () {} + +Fake.prototype.load = function (callback: Function) { + // this callback is important to actually initialize. + callback() +} diff --git a/packages/browser/src/test-helpers/fixtures/create-fetch-method.ts b/packages/browser/src/test-helpers/fixtures/create-fetch-method.ts new file mode 100644 index 000000000..b27f041d1 --- /dev/null +++ b/packages/browser/src/test-helpers/fixtures/create-fetch-method.ts @@ -0,0 +1,24 @@ +import { LegacySettings } from '../..' +import { createSuccess } from '../factories' +import { cdnSettingsMinimal } from './cdn-settings' + +export const createMockFetchImplementation = ( + cdnSettings: Partial = {} +) => { + return (url: RequestInfo, req?: RequestInit) => { + const reqUrl = url.toString() + if (!req || (req.method === 'get' && reqUrl.includes('cdn.segment.com'))) { + // GET https://cdn.segment.com/v1/projects/{writeKey} + return createSuccess({ ...cdnSettingsMinimal, ...cdnSettings }) + } + + if (req?.method === 'post' && reqUrl.includes('api.segment.io')) { + // POST https://api.segment.io/v1/{event.type} + return createSuccess({ success: true }, { status: 201 }) + } + + throw new Error( + `no match found for request (url:${url}, req:${JSON.stringify(req)})` + ) + } +} diff --git a/packages/node/package.json b/packages/node/package.json index cfbae3bf9..50961730b 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -40,8 +40,7 @@ }, "devDependencies": { "@internal/config": "0.0.0", - "@types/node": "^14", - "nock": "^13.2.9" + "@types/node": "^14" }, "packageManager": "yarn@3.2.1" } diff --git a/yarn.lock b/yarn.lock index 4a8e6cb69..1bb79ad20 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1932,6 +1932,7 @@ __metadata: "@internal/config": 0.0.0 "@lukeed/uuid": ^2.0.0 "@segment/analytics-core": 1.1.5 + "@segment/analytics.js-integration": ^3.3.3 "@segment/analytics.js-integration-amplitude": ^3.3.3 "@segment/analytics.js-video-plugins": ^0.2.1 "@segment/facade": ^3.4.9 @@ -1990,7 +1991,6 @@ __metadata: "@lukeed/uuid": ^2.0.0 "@segment/analytics-core": 1.1.5 "@types/node": ^14 - nock: ^13.2.9 node-fetch: ^2.6.7 tslib: ^2.4.0 languageName: unknown @@ -2023,7 +2023,7 @@ __metadata: languageName: node linkType: hard -"@segment/analytics.js-integration@npm:^3.3.0": +"@segment/analytics.js-integration@npm:^3.3.0, @segment/analytics.js-integration@npm:^3.3.3": version: 3.3.3 resolution: "@segment/analytics.js-integration@npm:3.3.3" dependencies: @@ -4621,6 +4621,7 @@ __metadata: jest: ^28.1.0 lint-staged: ^13.0.0 lodash: ^4.17.21 + nock: ^13.2.9 node-gyp: ^9.0.0 prettier: ^2.6.2 ts-jest: ^28.0.4 From 92d5d6c36b365ae1049ef13a4ad2ac651ab2e640 Mon Sep 17 00:00:00 2001 From: Seth Silesky <5115498+silesky@users.noreply.github.com> Date: Tue, 6 Dec 2022 17:46:08 -0600 Subject: [PATCH 2/3] integrations can be undefined (according to the sample we were using) for a plan associated with identity or group --- packages/browser/src/core/events/interfaces.ts | 3 ++- packages/browser/src/plugins/schema-filter/index.ts | 8 +++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/browser/src/core/events/interfaces.ts b/packages/browser/src/core/events/interfaces.ts index ad2b289e7..a5c32f377 100644 --- a/packages/browser/src/core/events/interfaces.ts +++ b/packages/browser/src/core/events/interfaces.ts @@ -174,6 +174,7 @@ export interface SegmentEvent { export interface Plan { track?: TrackPlan identify?: TrackPlan + group?: TrackPlan } export interface TrackPlan { @@ -190,7 +191,7 @@ export interface PlanEvent { /** * Which integrations the plan event applies to */ - integrations: { + integrations?: { [key: string]: boolean } } diff --git a/packages/browser/src/plugins/schema-filter/index.ts b/packages/browser/src/plugins/schema-filter/index.ts index d0748f222..7c181502e 100644 --- a/packages/browser/src/plugins/schema-filter/index.ts +++ b/packages/browser/src/plugins/schema-filter/index.ts @@ -13,9 +13,11 @@ function disabledActionDestinations( return {} } - const disabledIntegrations = Object.keys(plan.integrations).filter( - (i) => plan.integrations[i] === false - ) + const disabledIntegrations = plan.integrations + ? Object.keys(plan.integrations).filter( + (i) => plan.integrations![i] === false + ) + : [] // This accounts for cases like Fullstory, where the settings.integrations // contains a "Fullstory" object but settings.remotePlugins contains "Fullstory (Actions)" From 5b11626b3af48c6e1858f8892d5c39bbca90c78e Mon Sep 17 00:00:00 2001 From: Seth Silesky <5115498+silesky@users.noreply.github.com> Date: Tue, 6 Dec 2022 17:47:07 -0600 Subject: [PATCH 3/3] add logic if plan.integration is falsy --- .changeset/real-otters-refuse.md | 5 +++++ packages/browser/src/plugins/ajs-destination/index.ts | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/real-otters-refuse.md diff --git a/.changeset/real-otters-refuse.md b/.changeset/real-otters-refuse.md new file mode 100644 index 000000000..dc9185831 --- /dev/null +++ b/.changeset/real-otters-refuse.md @@ -0,0 +1,5 @@ +--- +'@segment/analytics-next': patch +--- + +add logic if plan.integrations is falsy diff --git a/packages/browser/src/plugins/ajs-destination/index.ts b/packages/browser/src/plugins/ajs-destination/index.ts index 51e8efaf0..621831703 100644 --- a/packages/browser/src/plugins/ajs-destination/index.ts +++ b/packages/browser/src/plugins/ajs-destination/index.ts @@ -227,7 +227,7 @@ export class LegacyDestination implements Plugin { }) } - if (planEvent?.enabled && planEvent?.integrations[this.name] === false) { + if (planEvent?.enabled && planEvent?.integrations![this.name] === false) { ctx.cancel( new ContextCancelation({ retry: false,