Skip to content

Commit

Permalink
add network tests, expose signals (#1103)
Browse files Browse the repository at this point in the history
  • Loading branch information
silesky authored Jul 10, 2024
1 parent e60f625 commit bca376b
Show file tree
Hide file tree
Showing 7 changed files with 77 additions and 13 deletions.
1 change: 0 additions & 1 deletion packages/signals/signals-example/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{
"name": "@internal/signals-example",
"version": "0.0.1",
"private": true,
"scripts": {
".": "yarn run -T turbo run --filter=@segment/signals-example...",
Expand Down
2 changes: 1 addition & 1 deletion packages/signals/signals/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@segment/analytics-signals",
"version": "0.0.1",
"version": "0.0.0",
"main": "./dist/cjs/index.js",
"license": "MIT",
"repository": {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,73 @@
import { addFetchInterceptor, NetworkGenerator } from '../network'
import {
addFetchInterceptor,
matchHostname,
NetworkGenerator,
containsJSONContent,
} from '../network-gen'
import { SignalEmitter } from '../../emitter'
import { Response } from 'node-fetch'
import { sleep } from '@segment/analytics-core'

describe('addFetchInterceptor', () => {
describe(containsJSONContent, () => {
it('should return true if headers contain application/json', () => {
const headers = new Headers({ 'content-type': 'application/json' })
expect(containsJSONContent(headers)).toBe(true)
})
it('should be case insensitive', () => {
expect(containsJSONContent([['Content-Type', 'application/json']])).toBe(
true
)
expect(
containsJSONContent(new Headers({ 'Content-Type': 'application/json' }))
).toBe(true)
})

it('should return false if headers do not contain application/json', () => {
const headers = new Headers({ 'content-type': 'text/html' })
expect(containsJSONContent(headers)).toBe(false)
expect(containsJSONContent(new Headers())).toBe(false)
expect(containsJSONContent(undefined)).toBe(false)
})
})

describe(matchHostname, () => {
const setHostname = (hostname: string) => {
Object.defineProperty(window, 'location', {
value: {
...window.location,
hostname: hostname,
},
writable: true,
})
}

beforeEach(() => {
setHostname('example.com')
})
it('should only match first party domains', () => {
expect(matchHostname('https://www.example.com')).toBe(true)
expect(matchHostname('https://www.example.com/api/foo')).toBe(true)
expect(matchHostname('https://www.foo.com')).toBe(false)
expect(
matchHostname('https://cdn.segment.com/v1/projects/1234/versions/1')
).toBe(false)
})

it('should work with subdomains', () => {
setHostname('api.example.com')
expect(matchHostname('https://api.example.com/foo')).toBe(true)
expect(matchHostname('https://foo.com/foo')).toBe(false)
expect(matchHostname('https://example.com/foo')).toBe(false)
})

it('should always allow relative domains', () => {
expect(matchHostname('/foo/bar')).toBe(true)
expect(matchHostname('foo/bar')).toBe(true)
expect(matchHostname('foo')).toBe(true)
})
})

describe(addFetchInterceptor, () => {
let origFetch: typeof window.fetch

beforeEach(() => {
Expand Down Expand Up @@ -32,7 +96,7 @@ describe('addFetchInterceptor', () => {
})
})

describe('NetworkGenerator', () => {
describe(NetworkGenerator, () => {
it('should register and emit signals on fetch requests and responses', async () => {
const mockResponse = new Response(JSON.stringify({ data: 'test' }), {
headers: { 'content-type': 'application/json' },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,27 +26,27 @@ export function addFetchInterceptor(
}
}

const matchHostname = (url: string): boolean => {
export const matchHostname = (url: string): boolean => {
const rIsAbs = new RegExp('^(?:[a-z+]+:)?//', 'i')
if (!rIsAbs.test(url)) {
// Relative URL will go to this host
return true
}
return new URL(url).hostname?.includes(window.location.hostname) || false
return new URL(url).hostname.includes(window.location.hostname)
}

const normalizeHeaders = (headers: HeadersInit): Headers => {
return headers instanceof Headers ? headers : new Headers(headers)
}

const containsJSONContent = (headers: HeadersInit | undefined): boolean => {
export const containsJSONContent = (
headers: HeadersInit | undefined
): boolean => {
if (!headers) {
return false
}
const normalizedHeaders = normalizeHeaders(headers)
return (
normalizedHeaders.get('content-type')?.includes('application/json') || false
)
return normalizedHeaders.get('content-type') === 'application/json'
}

export class NetworkGenerator implements SignalGenerator {
Expand Down
4 changes: 2 additions & 2 deletions packages/signals/signals/src/core/signals/signals.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { SignalsIngestClient } from '../client'
import { getSignalBuffer, SignalBuffer } from '../buffer'
import { SignalEmitter } from '../emitter'
import { domGenerators } from '../signal-generators/dom-generators'
import { NetworkGenerator } from '../signal-generators/network'
import { domGenerators } from '../signal-generators/dom-gen'
import { NetworkGenerator } from '../signal-generators/network-gen'
import {
SignalGenerator,
SignalGeneratorClass,
Expand Down
1 change: 1 addition & 0 deletions packages/signals/signals/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ export type {
ProcessSignal,
AnalyticsRuntimePublicApi,
SignalsPluginSettingsConfig,
Signal,
} from './types'

0 comments on commit bca376b

Please sign in to comment.