Skip to content

Commit

Permalink
Seth - example refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaelGHSeg authored and silesky committed Jul 5, 2023
1 parent 40745ff commit c12e1f7
Show file tree
Hide file tree
Showing 17 changed files with 180 additions and 154 deletions.
20 changes: 9 additions & 11 deletions packages/node/src/__tests__/callback.test.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
const fetcher = jest.fn()
jest.mock('../lib/fetch', () => ({ fetch: fetcher }))

import { createError, createSuccess } from './test-helpers/factories'
import { createTestAnalytics } from './test-helpers/create-test-analytics'
import { Context } from '../app/context'

describe('Callback behavior', () => {
beforeEach(() => {
fetcher.mockReturnValue(createSuccess())
})

it('should handle success', async () => {
const ajs = createTestAnalytics({ maxEventsInBatch: 1 })
const ajs = createTestAnalytics({
maxEventsInBatch: 1,
})
const ctx = await new Promise<Context>((resolve, reject) =>
ajs.track(
{
Expand All @@ -29,8 +23,12 @@ describe('Callback behavior', () => {
})

it('should handle errors', async () => {
fetcher.mockReturnValue(createError())
const ajs = createTestAnalytics({ maxEventsInBatch: 1 })
const ajs = createTestAnalytics(
{
maxEventsInBatch: 1,
},
{ withError: true }
)
const [err, ctx] = await new Promise<[any, Context]>((resolve) =>
ajs.track(
{
Expand Down
13 changes: 8 additions & 5 deletions packages/node/src/__tests__/disable.integration.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
const fetcher = jest.fn()
jest.mock('../lib/fetch', () => ({ fetch: fetcher }))

import { createTestAnalytics } from './test-helpers/create-test-analytics'
import { TestFetchClient } from './test-helpers/test-fetch-client'

describe('disable', () => {
const customClient = new TestFetchClient()
const mockSend = jest.spyOn(customClient, 'send')

it('should dispatch callbacks and emit an http request, even if disabled', async () => {
const analytics = createTestAnalytics({
disable: true,
Expand All @@ -19,19 +20,21 @@ describe('disable', () => {
it('should call fetch if disabled is false', async () => {
const analytics = createTestAnalytics({
disable: false,
httpClient: customClient,
})
await new Promise((resolve) =>
analytics.track({ anonymousId: 'foo', event: 'bar' }, resolve)
)
expect(fetcher).toBeCalled()
expect(mockSend).toBeCalledTimes(1)
})
it('should not call fetch if disabled is true', async () => {
const analytics = createTestAnalytics({
disable: true,
httpClient: customClient,
})
await new Promise((resolve) =>
analytics.track({ anonymousId: 'foo', event: 'bar' }, resolve)
)
expect(fetcher).not.toBeCalled()
expect(mockSend).toBeCalledTimes(0)
})
})
21 changes: 12 additions & 9 deletions packages/node/src/__tests__/emitter.integration.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
const fetcher = jest.fn()
jest.mock('../lib/fetch', () => ({ fetch: fetcher }))

import { createError, createSuccess } from './test-helpers/factories'
import { createTestAnalytics } from './test-helpers/create-test-analytics'
import { assertHttpRequestEmittedEvent } from './test-helpers/assert-shape'

describe('http_request', () => {
it('emits an http_request event if success', async () => {
fetcher.mockReturnValue(createSuccess())
const analytics = createTestAnalytics()
const fn = jest.fn()
analytics.on('http_request', fn)
Expand All @@ -19,8 +14,12 @@ describe('http_request', () => {
})

it('emits an http_request event if error', async () => {
fetcher.mockReturnValue(createError())
const analytics = createTestAnalytics({ maxRetries: 0 })
const analytics = createTestAnalytics(
{
maxRetries: 0,
},
{ withError: true }
)
const fn = jest.fn()
analytics.on('http_request', fn)
await new Promise((resolve) =>
Expand All @@ -30,8 +29,12 @@ describe('http_request', () => {
})

it('if error, emits an http_request event on every retry', async () => {
fetcher.mockReturnValue(createError())
const analytics = createTestAnalytics({ maxRetries: 2 })
const analytics = createTestAnalytics(
{
maxRetries: 2,
},
{ withError: true }
)
const fn = jest.fn()
analytics.on('http_request', fn)
await new Promise((resolve) =>
Expand Down
18 changes: 10 additions & 8 deletions packages/node/src/__tests__/graceful-shutdown-integration.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import { createSuccess } from './test-helpers/factories'
import { TestFetchClient } from './test-helpers/test-fetch-client'
import { performance as perf } from 'perf_hooks'

const fetcher = jest.fn()
jest.mock('../lib/fetch', () => ({ fetch: fetcher }))

import { Analytics } from '../app/analytics-node'
import { sleep } from './test-helpers/sleep'
import { Plugin, SegmentEvent } from '../app/types'
Expand All @@ -17,18 +13,21 @@ const testPlugin: Plugin = {
isLoaded: () => true,
}

const testClient = new TestFetchClient()
const sendSpy = jest.spyOn(testClient, 'send')

describe('Ability for users to exit without losing events', () => {
let ajs!: Analytics
beforeEach(async () => {
fetcher.mockReturnValue(createSuccess())
ajs = new Analytics({
writeKey: 'abc123',
maxEventsInBatch: 1,
httpClient: testClient,
})
})
const _helpers = {
getFetchCalls: (mockedFetchFn = fetcher) =>
mockedFetchFn.mock.calls.map(([url, request]) => ({
getFetchCalls: () =>
sendSpy.mock.calls.map(([url, request]) => ({
url,
method: request.method,
headers: request.headers,
Expand Down Expand Up @@ -89,6 +88,7 @@ describe('Ability for users to exit without losing events', () => {
ajs = new Analytics({
writeKey: 'abc123',
flushInterval,
httpClient: testClient,
})
const closeAndFlushTimeout = ajs['_closeAndFlushDefaultTimeout']
expect(closeAndFlushTimeout).toBe(flushInterval * 1.25)
Expand Down Expand Up @@ -190,6 +190,7 @@ describe('Ability for users to exit without losing events', () => {
writeKey: 'foo',
flushInterval: 10000,
maxEventsInBatch: 15,
httpClient: testClient,
})
_helpers.makeTrackCall(analytics)
_helpers.makeTrackCall(analytics)
Expand Down Expand Up @@ -220,6 +221,7 @@ describe('Ability for users to exit without losing events', () => {
writeKey: 'foo',
flushInterval: 10000,
maxEventsInBatch: 15,
httpClient: testClient,
})
await analytics.register(_testPlugin)
_helpers.makeTrackCall(analytics)
Expand Down
13 changes: 8 additions & 5 deletions packages/node/src/__tests__/http-integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ describe('Method Smoke Tests', () => {
let scope: nock.Scope
let ajs: Analytics
beforeEach(async () => {
ajs = createTestAnalytics()
ajs = createTestAnalytics({}, { useRealHTTPClient: true })
})

describe('Metadata', () => {
Expand Down Expand Up @@ -333,10 +333,13 @@ describe('Client: requestTimeout', () => {
})
it('should timeout immediately if request timeout is set to 0', async () => {
jest.useRealTimers()
const ajs = createTestAnalytics({
maxEventsInBatch: 1,
httpRequestTimeout: 0,
})
const ajs = createTestAnalytics(
{
maxEventsInBatch: 1,
httpRequestTimeout: 0,
},
{ useRealHTTPClient: true }
)
ajs.track({ event: 'foo', userId: 'foo', properties: { hello: 'world' } })
try {
await resolveCtx(ajs, 'track')
Expand Down
26 changes: 14 additions & 12 deletions packages/node/src/__tests__/integration.test.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
const fetcher = jest.fn()
jest.mock('../lib/fetch', () => ({ fetch: fetcher }))

import { Plugin } from '../app/types'
import { resolveCtx } from './test-helpers/resolve-ctx'
import { testPlugin } from './test-helpers/test-plugin'
import { createSuccess, createError } from './test-helpers/factories'
import { createError } from './test-helpers/factories'
import { createTestAnalytics } from './test-helpers/create-test-analytics'
import { TestFetchClient } from './test-helpers/test-fetch-client'

const writeKey = 'foo'
jest.setTimeout(10000)
const timestamp = new Date()

beforeEach(() => {
fetcher.mockReturnValue(createSuccess())
})
const testClient = new TestFetchClient()
const sendSpy = jest.spyOn(testClient, 'send')

describe('Settings / Configuration Init', () => {
it('throws if no writeKey', () => {
Expand All @@ -28,11 +25,12 @@ describe('Settings / Configuration Init', () => {
const analytics = createTestAnalytics({
host: 'http://foo.com',
path: '/bar',
httpClient: testClient,
})
const track = resolveCtx(analytics, 'track')
analytics.track({ event: 'foo', userId: 'sup' })
await track
expect(fetcher.mock.calls[0][0]).toBe('http://foo.com/bar')
expect(sendSpy.mock.calls[0][0]).toBe('http://foo.com/bar')
})

it('throws if host / path is bad', async () => {
Expand All @@ -53,10 +51,14 @@ describe('Error handling', () => {
})

it('should emit on an error', async () => {
const analytics = createTestAnalytics({ maxRetries: 0 })
fetcher.mockReturnValue(
createError({ statusText: 'Service Unavailable', status: 503 })
)
const err = createError({
statusText: 'Service Unavailable',
status: 503,
})
const analytics = createTestAnalytics({
maxRetries: 0,
httpClient: new TestFetchClient({ response: err }),
})
try {
const promise = resolveCtx(analytics, 'track')
analytics.track({ event: 'foo', userId: 'sup' })
Expand Down
8 changes: 0 additions & 8 deletions packages/node/src/__tests__/plugins.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
const fetcher = jest.fn()
jest.mock('../lib/fetch', () => ({ fetch: fetcher }))

import { createSuccess } from './test-helpers/factories'
import { createTestAnalytics } from './test-helpers/create-test-analytics'

describe('Plugins', () => {
beforeEach(() => {
fetcher.mockReturnValue(createSuccess())
})

describe('Initialize', () => {
it('loads analytics-node-next plugin', async () => {
const analytics = createTestAnalytics()
Expand Down
16 changes: 14 additions & 2 deletions packages/node/src/__tests__/test-helpers/create-test-analytics.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
import { Analytics } from '../../app/analytics-node'
import { AnalyticsSettings } from '../../app/settings'
import { TestFetchClient, TestFetchClientOptions } from './test-fetch-client'

export const createTestAnalytics = (
settings: Partial<AnalyticsSettings> = {}
settings: Partial<AnalyticsSettings> = {},
{
withError,
useRealHTTPClient,
}: TestFetchClientOptions & { useRealHTTPClient?: boolean } = {}
) => {
return new Analytics({ writeKey: 'foo', flushInterval: 100, ...settings })
return new Analytics({
writeKey: 'foo',
flushInterval: 100,
...(useRealHTTPClient
? {}
: { httpClient: new TestFetchClient({ withError }) }),
...settings,
})
}
26 changes: 26 additions & 0 deletions packages/node/src/__tests__/test-helpers/test-fetch-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { AnalyticsHTTPClient } from '../../lib/default-http-client'
import { createError, createSuccess } from './factories'

export type TestFetchClientOptions = {
withError?: boolean
/** override response (if needed) */
response?: Response | Promise<Response>
}
/**
* Test client.
* Try not to use this directly -- use createTestAnalytics instead.
*/
export class TestFetchClient implements AnalyticsHTTPClient {
private withError?: TestFetchClientOptions['withError']
private response?: TestFetchClientOptions['response']
constructor({ withError, response }: TestFetchClientOptions = {}) {
this.withError = withError
this.response = response
}
send(..._args: Parameters<AnalyticsHTTPClient['send']>) {
if (this.response) {
return Promise.resolve(this.response)
}
return Promise.resolve(this.withError ? createError() : createSuccess())
}
}
2 changes: 2 additions & 0 deletions packages/node/src/app/analytics-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
} from './types'
import { Context } from './context'
import { NodeEventQueue } from './event-queue'
import { DefaultHTTPClient } from '../lib/default-http-client'

export class Analytics extends NodeEmitter implements CoreAnalytics {
private readonly _eventFactory: NodeEventFactory
Expand Down Expand Up @@ -51,6 +52,7 @@ export class Analytics extends NodeEmitter implements CoreAnalytics {
httpRequestTimeout: settings.httpRequestTimeout,
disable: settings.disable,
flushInterval,
httpClient: settings.httpClient ?? new DefaultHTTPClient(),
},
this as NodeEmitter
)
Expand Down
8 changes: 8 additions & 0 deletions packages/node/src/app/settings.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ValidationError } from '@segment/analytics-core'
import { AnalyticsHTTPClient } from '../lib/default-http-client'

export interface AnalyticsSettings {
/**
Expand Down Expand Up @@ -34,6 +35,13 @@ export interface AnalyticsSettings {
* Disable the analytics library. All calls will be a noop. Default: false.
*/
disable?: boolean

/**
* Supply a default http client implementation (such as one supporting proxy)
* Default: an http client that uses that value of globalThis.fetch, or
* node-fetch if it doesn't exist
*/
httpClient?: AnalyticsHTTPClient
}

export const validateSettings = (settings: AnalyticsSettings) => {
Expand Down
8 changes: 5 additions & 3 deletions packages/node/src/lib/__tests__/abort.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { abortSignalAfterTimeout } from '../abort'
import nock from 'nock'
import { fetch } from '../fetch'
import { sleep } from '@segment/analytics-core'
import { DefaultHTTPClient } from '../default-http-client'

describe(abortSignalAfterTimeout, () => {
const HOST = 'https://foo.com'
Expand Down Expand Up @@ -30,7 +30,8 @@ describe(abortSignalAfterTimeout, () => {
try {
const [signal] = abortSignalAfterTimeout(2000)
jest.advanceTimersByTime(6000)
await fetch(HOST, { signal })
const client = new DefaultHTTPClient()
await client.send(HOST, { signal })
throw Error('fail test.')
} catch (err: any) {
expect(err.name).toMatch('AbortError')
Expand All @@ -42,7 +43,8 @@ describe(abortSignalAfterTimeout, () => {
nock(HOST).get('/').reply(201)
const [signal] = abortSignalAfterTimeout(0)
try {
await fetch(HOST, { signal })
const client = new DefaultHTTPClient()
await client.send(HOST, { signal })
throw Error('fail test.')
} catch (err: any) {
expect(err.name).toMatch('AbortError')
Expand Down
Loading

0 comments on commit c12e1f7

Please sign in to comment.