Sending events to POST /events endpoint (#15796)
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
const request = require('supertest')
|
||||
const Airtable = require('airtable')
|
||||
const nock = require('nock')
|
||||
const app = require('../../server')
|
||||
|
||||
jest.mock('airtable')
|
||||
@@ -19,17 +20,33 @@ describe('POST /events', () => {
|
||||
beforeEach(async () => {
|
||||
process.env.AIRTABLE_API_KEY = '$AIRTABLE_API_KEY$'
|
||||
process.env.AIRTABLE_BASE_KEY = '$AIRTABLE_BASE_KEY$'
|
||||
process.env.HYDRO_SECRET = '$HYDRO_SECRET$'
|
||||
process.env.HYDRO_ENDPOINT = 'http://example.com/hydro'
|
||||
agent = request.agent(app)
|
||||
const csrfRes = await agent.get('/csrf')
|
||||
csrfToken = csrfRes.body.token
|
||||
nock('http://example.com')
|
||||
.post('/hydro')
|
||||
.reply(200, {})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
delete process.env.AIRTABLE_API_KEY
|
||||
delete process.env.AIRTABLE_BASE_KEY
|
||||
delete process.env.HYDRO_SECRET
|
||||
delete process.env.HYDRO_ENDPOINT
|
||||
csrfToken = ''
|
||||
})
|
||||
|
||||
async function checkEvent (data, code) {
|
||||
return agent
|
||||
.post('/events')
|
||||
.send(data)
|
||||
.set('Accept', 'application/json')
|
||||
.set('csrf-token', csrfToken)
|
||||
.expect(code)
|
||||
}
|
||||
|
||||
describe('HELPFULNESS', () => {
|
||||
const example = {
|
||||
type: 'HELPFULNESS',
|
||||
@@ -41,94 +58,43 @@ describe('POST /events', () => {
|
||||
}
|
||||
|
||||
it('should accept a valid object', () =>
|
||||
agent
|
||||
.post('/events')
|
||||
.send(example)
|
||||
.set('Accept', 'application/json')
|
||||
.set('csrf-token', csrfToken)
|
||||
.expect(201)
|
||||
checkEvent(example, 201)
|
||||
)
|
||||
|
||||
it('should reject extra properties', () =>
|
||||
agent
|
||||
.post('/events')
|
||||
.send({ ...example, toothpaste: false })
|
||||
.set('Accept', 'application/json')
|
||||
|
||||
.set('csrf-token', csrfToken)
|
||||
.expect(400)
|
||||
checkEvent({ ...example, toothpaste: false }, 400)
|
||||
)
|
||||
|
||||
it('should not accept if type is missing', () =>
|
||||
agent
|
||||
.post('/events')
|
||||
.send({ ...example, type: undefined })
|
||||
.set('Accept', 'application/json')
|
||||
.set('csrf-token', csrfToken)
|
||||
.expect(400)
|
||||
checkEvent({ ...example, type: undefined }, 400)
|
||||
)
|
||||
|
||||
it('should not accept if url is missing', () =>
|
||||
agent
|
||||
.post('/events')
|
||||
.send({ ...example, url: undefined })
|
||||
.set('Accept', 'application/json')
|
||||
.set('csrf-token', csrfToken)
|
||||
.expect(400)
|
||||
checkEvent({ ...example, url: undefined }, 400)
|
||||
)
|
||||
|
||||
it('should not accept if url is misformatted', () =>
|
||||
agent
|
||||
.post('/events')
|
||||
.send({ ...example, url: 'examplecom' })
|
||||
.set('Accept', 'application/json')
|
||||
.set('csrf-token', csrfToken)
|
||||
.expect(400)
|
||||
checkEvent({ ...example, url: 'examplecom' }, 400)
|
||||
)
|
||||
|
||||
it('should not accept if vote is missing', () =>
|
||||
agent
|
||||
.post('/events')
|
||||
.send({ ...example, vote: undefined })
|
||||
.set('Accept', 'application/json')
|
||||
.set('csrf-token', csrfToken)
|
||||
.expect(400)
|
||||
checkEvent({ ...example, vote: undefined }, 400)
|
||||
)
|
||||
|
||||
it('should not accept if vote is not boolean', () =>
|
||||
agent
|
||||
.post('/events')
|
||||
.send({ ...example, vote: 'true' })
|
||||
.set('Accept', 'application/json')
|
||||
.set('csrf-token', csrfToken)
|
||||
.expect(400)
|
||||
checkEvent({ ...example, vote: 'true' }, 400)
|
||||
)
|
||||
|
||||
it('should not accept if email is misformatted', () =>
|
||||
agent
|
||||
.post('/events')
|
||||
.send({ ...example, email: 'testexample.com' })
|
||||
.set('Accept', 'application/json')
|
||||
.set('csrf-token', csrfToken)
|
||||
.expect(400)
|
||||
checkEvent({ ...example, email: 'testexample.com' }, 400)
|
||||
)
|
||||
|
||||
it('should not accept if comment is not string', () =>
|
||||
agent
|
||||
.post('/events')
|
||||
.send({ ...example, comment: [] })
|
||||
.set('Accept', 'application/json')
|
||||
.set('csrf-token', csrfToken)
|
||||
.expect(400)
|
||||
checkEvent({ ...example, comment: [] }, 400)
|
||||
)
|
||||
|
||||
it('should not accept if category is not an option', () =>
|
||||
agent
|
||||
.post('/events')
|
||||
.send({ ...example, category: 'Fabulous' })
|
||||
.set('Accept', 'application/json')
|
||||
.set('csrf-token', csrfToken)
|
||||
.expect(400)
|
||||
checkEvent({ ...example, category: 'Fabulous' }, 400)
|
||||
)
|
||||
})
|
||||
|
||||
@@ -142,57 +108,401 @@ describe('POST /events', () => {
|
||||
}
|
||||
|
||||
it('should accept a valid object', () =>
|
||||
agent
|
||||
.post('/events')
|
||||
.send(example)
|
||||
.set('Accept', 'application/json')
|
||||
.set('csrf-token', csrfToken)
|
||||
.expect(201)
|
||||
checkEvent(example, 201)
|
||||
)
|
||||
|
||||
it('should reject extra fields', () =>
|
||||
agent
|
||||
.post('/events')
|
||||
.send({ ...example, toothpaste: false })
|
||||
.set('Accept', 'application/json')
|
||||
.set('csrf-token', csrfToken)
|
||||
.expect(400)
|
||||
checkEvent({ ...example, toothpaste: false }, 400)
|
||||
)
|
||||
|
||||
it('should require a long unique user-id', () =>
|
||||
agent
|
||||
.post('/events')
|
||||
.send({ ...example, 'user-id': 'short' })
|
||||
.set('Accept', 'application/json')
|
||||
.set('csrf-token', csrfToken)
|
||||
.expect(400)
|
||||
checkEvent({ ...example, 'user-id': 'short' }, 400)
|
||||
)
|
||||
|
||||
it('should require a test', () =>
|
||||
agent
|
||||
.post('/events')
|
||||
.send({ ...example, test: undefined })
|
||||
.set('Accept', 'application/json')
|
||||
.set('csrf-token', csrfToken)
|
||||
.expect(400)
|
||||
checkEvent({ ...example, test: undefined }, 400)
|
||||
)
|
||||
|
||||
it('should require a valid group', () =>
|
||||
agent
|
||||
.post('/events')
|
||||
.send({ ...example, group: 'revolution' })
|
||||
.set('Accept', 'application/json')
|
||||
.set('csrf-token', csrfToken)
|
||||
.expect(400)
|
||||
checkEvent({ ...example, group: 'revolution' }, 400)
|
||||
)
|
||||
|
||||
it('should default the success field', () =>
|
||||
agent
|
||||
.post('/events')
|
||||
.send({ ...example, success: undefined })
|
||||
.set('Accept', 'application/json')
|
||||
.set('csrf-token', csrfToken)
|
||||
.expect(201)
|
||||
checkEvent({ ...example, success: undefined }, 201)
|
||||
)
|
||||
})
|
||||
|
||||
const baseExample = {
|
||||
context: {
|
||||
// Primitives
|
||||
event_id: 'a35d7f88-3f48-4f36-ad89-5e3c8ebc3df7',
|
||||
user: '703d32a8-ed0f-45f9-8d78-a913d4dc6f19',
|
||||
version: '1.0.0',
|
||||
created: '2020-10-02T17:12:18.620Z',
|
||||
|
||||
// Content information
|
||||
path: '/github/docs/issues',
|
||||
referrer: 'https://github.com/github/docs',
|
||||
search: '?q=is%3Aissue+is%3Aopen+example+',
|
||||
href: 'https://github.com/github/docs/issues?q=is%3Aissue+is%3Aopen+example+',
|
||||
site_language: 'en',
|
||||
|
||||
// Device information
|
||||
os: 'linux',
|
||||
os_version: '18.04',
|
||||
browser: 'chrome',
|
||||
browser_version: '85.0.4183.121',
|
||||
viewport_width: 1418,
|
||||
viewport_height: 501,
|
||||
|
||||
// Location information
|
||||
timezone: -7,
|
||||
user_language: 'en-US'
|
||||
}
|
||||
}
|
||||
|
||||
describe('page', () => {
|
||||
const pageExample = { ...baseExample, type: 'page' }
|
||||
|
||||
it('should record a page event', () =>
|
||||
checkEvent(pageExample, 201)
|
||||
)
|
||||
|
||||
it('should require a type', () =>
|
||||
checkEvent(baseExample, 400)
|
||||
)
|
||||
|
||||
it('should require an event_id in uuid', () =>
|
||||
checkEvent({
|
||||
...pageExample,
|
||||
context: {
|
||||
...pageExample.context,
|
||||
event_id: 'asdfghjkl'
|
||||
}
|
||||
}, 400)
|
||||
)
|
||||
|
||||
it('should require a user in uuid', () =>
|
||||
checkEvent({
|
||||
...pageExample,
|
||||
context: {
|
||||
...pageExample.context,
|
||||
user: 'asdfghjkl'
|
||||
}
|
||||
}, 400)
|
||||
)
|
||||
|
||||
it('should require a version', () =>
|
||||
checkEvent({
|
||||
...pageExample,
|
||||
context: {
|
||||
...pageExample.context,
|
||||
version: undefined
|
||||
}
|
||||
}, 400)
|
||||
)
|
||||
|
||||
it('should require created timestamp', () =>
|
||||
checkEvent({
|
||||
...pageExample,
|
||||
context: {
|
||||
...pageExample.context,
|
||||
timestamp: 1234
|
||||
}
|
||||
}, 400)
|
||||
)
|
||||
|
||||
it('should not allow a honeypot token', () =>
|
||||
checkEvent({
|
||||
...pageExample,
|
||||
context: {
|
||||
...pageExample.context,
|
||||
token: 'zxcv'
|
||||
}
|
||||
}, 400)
|
||||
)
|
||||
|
||||
it('should path be uri-reference', () =>
|
||||
checkEvent({
|
||||
...pageExample,
|
||||
context: {
|
||||
...pageExample.context,
|
||||
path: ' '
|
||||
}
|
||||
}, 400)
|
||||
)
|
||||
|
||||
it('should referrer be uri-reference', () =>
|
||||
checkEvent({
|
||||
...pageExample,
|
||||
context: {
|
||||
...pageExample.context,
|
||||
referrer: ' '
|
||||
}
|
||||
}, 400)
|
||||
)
|
||||
|
||||
it('should search a string', () =>
|
||||
checkEvent({
|
||||
...pageExample,
|
||||
context: {
|
||||
...pageExample.context,
|
||||
search: 1234
|
||||
}
|
||||
}, 400)
|
||||
)
|
||||
|
||||
it('should href be uri', () =>
|
||||
checkEvent({
|
||||
...pageExample,
|
||||
context: {
|
||||
...pageExample.context,
|
||||
href: '/example'
|
||||
}
|
||||
}, 400)
|
||||
)
|
||||
|
||||
it('should site_language is a valid option', () =>
|
||||
checkEvent({
|
||||
...pageExample,
|
||||
context: {
|
||||
...pageExample.context,
|
||||
site_language: 'nl'
|
||||
}
|
||||
}, 400)
|
||||
)
|
||||
|
||||
it('should a valid os option', () =>
|
||||
checkEvent({
|
||||
...pageExample,
|
||||
context: {
|
||||
...pageExample.context,
|
||||
os: 'ubuntu'
|
||||
}
|
||||
}, 400)
|
||||
)
|
||||
|
||||
it('should os_version a string', () =>
|
||||
checkEvent({
|
||||
...pageExample,
|
||||
context: {
|
||||
...pageExample.context,
|
||||
os_version: 25
|
||||
}
|
||||
}, 400)
|
||||
)
|
||||
|
||||
it('should browser a valid option', () =>
|
||||
checkEvent({
|
||||
...pageExample,
|
||||
context: {
|
||||
...pageExample.context,
|
||||
browser: 'opera'
|
||||
}
|
||||
}, 400)
|
||||
)
|
||||
|
||||
it('should browser_version a string', () =>
|
||||
checkEvent({
|
||||
...pageExample,
|
||||
context: {
|
||||
...pageExample.context,
|
||||
browser_version: 25
|
||||
}
|
||||
}, 400)
|
||||
)
|
||||
|
||||
it('should viewport_width a number', () =>
|
||||
checkEvent({
|
||||
...pageExample,
|
||||
context: {
|
||||
...pageExample.context,
|
||||
viewport_width: -500
|
||||
}
|
||||
}, 400)
|
||||
)
|
||||
|
||||
it('should viewport_height a number', () =>
|
||||
checkEvent({
|
||||
...pageExample,
|
||||
context: {
|
||||
...pageExample.context,
|
||||
viewport_height: '53px'
|
||||
}
|
||||
}, 400)
|
||||
)
|
||||
|
||||
it('should timezone in number', () =>
|
||||
checkEvent({
|
||||
...pageExample,
|
||||
context: {
|
||||
...pageExample.context,
|
||||
timezone: 'GMT-0700'
|
||||
}
|
||||
}, 400)
|
||||
)
|
||||
|
||||
it('should user_language is a string', () =>
|
||||
checkEvent({
|
||||
...pageExample,
|
||||
context: {
|
||||
...pageExample.context,
|
||||
user_language: true
|
||||
}
|
||||
}, 400)
|
||||
)
|
||||
|
||||
it('should page_render_duration is a positive number', () =>
|
||||
checkEvent({
|
||||
...pageExample,
|
||||
page_render_duration: -0.5
|
||||
}, 400)
|
||||
)
|
||||
})
|
||||
|
||||
describe('exit', () => {
|
||||
const exitExample = {
|
||||
...baseExample,
|
||||
type: 'exit',
|
||||
exit_page_id: 'c93c2d16-8e07-43d5-bc3c-eacc999c184d',
|
||||
exit_first_paint: 0.1,
|
||||
exit_dom_interactive: 0.2,
|
||||
exit_dom_complete: 0.3,
|
||||
exit_visit_duration: 5,
|
||||
exit_scroll_length: 0.5
|
||||
}
|
||||
|
||||
it('should record an exit event', () =>
|
||||
checkEvent(exitExample, 201)
|
||||
)
|
||||
|
||||
it('should require exit_page_id', () =>
|
||||
checkEvent({ ...exitExample, exit_page_id: undefined }, 400)
|
||||
)
|
||||
|
||||
it('should require exit_page_id is a uuid', () =>
|
||||
checkEvent({ ...exitExample, exit_page_id: 'afjdskalj' }, 400)
|
||||
)
|
||||
|
||||
it('exit_first_paint is a number', () =>
|
||||
checkEvent({ ...exitExample, exit_first_paint: 'afjdkl' }, 400)
|
||||
)
|
||||
|
||||
it('exit_dom_interactive is a number', () =>
|
||||
checkEvent({ ...exitExample, exit_dom_interactive: '202' }, 400)
|
||||
)
|
||||
|
||||
it('exit_visit_duration is a number', () =>
|
||||
checkEvent({ ...exitExample, exit_visit_duration: '75' }, 400)
|
||||
)
|
||||
|
||||
it('exit_scroll_length is a number between 0 and 1', () =>
|
||||
checkEvent({ ...exitExample, exit_scroll_length: 1.1 }, 400)
|
||||
)
|
||||
})
|
||||
|
||||
describe('link', () => {
|
||||
const linkExample = {
|
||||
...baseExample,
|
||||
type: 'link',
|
||||
link_url: 'https://example.com'
|
||||
}
|
||||
|
||||
it('should send a link event', () =>
|
||||
checkEvent(linkExample, 201)
|
||||
)
|
||||
|
||||
it('link_url is a required uri formatted string', () =>
|
||||
checkEvent({ ...linkExample, link_url: 'foo' }, 400)
|
||||
)
|
||||
})
|
||||
|
||||
describe('search', () => {
|
||||
const searchExample = {
|
||||
...baseExample,
|
||||
type: 'search',
|
||||
search_query: 'github private instances',
|
||||
search_context: 'private'
|
||||
}
|
||||
|
||||
it('should record a search event', () =>
|
||||
checkEvent(searchExample, 201)
|
||||
)
|
||||
|
||||
it('search_query is required string', () =>
|
||||
checkEvent({ ...searchExample, search_query: undefined }, 400)
|
||||
)
|
||||
|
||||
it('search_context is optional string', () =>
|
||||
checkEvent({ ...searchExample, search_context: undefined }, 201)
|
||||
)
|
||||
})
|
||||
|
||||
describe('navigate', () => {
|
||||
const navigateExample = {
|
||||
...baseExample,
|
||||
type: 'navigate',
|
||||
navigate_label: 'drop down'
|
||||
}
|
||||
|
||||
it('should record a navigate event', () =>
|
||||
checkEvent(navigateExample, 201)
|
||||
)
|
||||
|
||||
it('navigate_label is optional string', () =>
|
||||
checkEvent({ ...navigateExample, navigate_label: undefined }, 201)
|
||||
)
|
||||
})
|
||||
|
||||
describe('survey', () => {
|
||||
const surveyExample = {
|
||||
...baseExample,
|
||||
type: 'survey',
|
||||
survey_vote: true,
|
||||
survey_comment: 'I love this site.',
|
||||
survey_email: 'daisy@example.com'
|
||||
}
|
||||
|
||||
it('should record a survey event', () =>
|
||||
checkEvent(surveyExample, 201)
|
||||
)
|
||||
|
||||
it('survey_vote is boolean', () =>
|
||||
checkEvent({ ...surveyExample, survey_vote: undefined }, 400)
|
||||
)
|
||||
|
||||
it('survey_comment is string', () => {
|
||||
checkEvent({ ...surveyExample, survey_comment: 1234 }, 400)
|
||||
})
|
||||
|
||||
it('survey_email is email', () => {
|
||||
checkEvent({ ...surveyExample, survey_email: 'daisy' }, 400)
|
||||
})
|
||||
})
|
||||
|
||||
describe('experiment', () => {
|
||||
const experimentExample = {
|
||||
...baseExample,
|
||||
type: 'experiment',
|
||||
experiment_name: 'change-button-copy',
|
||||
experiment_variation: 'treatment',
|
||||
experiment_success: true
|
||||
}
|
||||
|
||||
it('should record an experiment event', () =>
|
||||
checkEvent(experimentExample, 201)
|
||||
)
|
||||
|
||||
it('experiment_name is required string', () =>
|
||||
checkEvent({ ...experimentExample, experiment_name: undefined }, 400)
|
||||
)
|
||||
|
||||
it('experiment_variation is required string', () =>
|
||||
checkEvent({ ...experimentExample, experiment_variation: undefined }, 400)
|
||||
)
|
||||
|
||||
it('experiment_success is optional boolean', () =>
|
||||
checkEvent({ ...experimentExample, experiment_success: undefined }, 201)
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user