mirror of
https://github.com/PostHog/posthog.git
synced 2024-11-25 11:17:50 +01:00
1028 lines
33 KiB
TypeScript
1028 lines
33 KiB
TypeScript
import { UUIDT } from '../../src/utils/utils'
|
|
import {
|
|
capture,
|
|
createOrganization,
|
|
createTeam,
|
|
fetchEvents,
|
|
fetchGroups,
|
|
fetchIngestionWarnings,
|
|
fetchPersons,
|
|
getMetric,
|
|
} from '../api'
|
|
import { waitForExpect } from '../expectations'
|
|
|
|
let organizationId: string
|
|
|
|
beforeAll(async () => {
|
|
organizationId = await createOrganization()
|
|
})
|
|
|
|
test.concurrent(`event ingestion: handles $$client_ingestion_warning events`, async () => {
|
|
const teamId = await createTeam(organizationId)
|
|
const distinctId = new UUIDT().toString()
|
|
|
|
await capture({
|
|
teamId,
|
|
distinctId,
|
|
uuid: new UUIDT().toString(),
|
|
event: '$$client_ingestion_warning',
|
|
properties: {
|
|
$$client_ingestion_warning_message: 'test message',
|
|
},
|
|
})
|
|
|
|
await waitForExpect(async () => {
|
|
const events = await fetchIngestionWarnings(teamId)
|
|
expect(events).toEqual([
|
|
expect.objectContaining({
|
|
type: 'client_ingestion_warning',
|
|
team_id: teamId,
|
|
details: expect.objectContaining({ message: 'test message' }),
|
|
}),
|
|
])
|
|
})
|
|
})
|
|
|
|
test.concurrent(`event ingestion: can set and update group properties`, async () => {
|
|
const teamId = await createTeam(organizationId)
|
|
const distinctId = new UUIDT().toString()
|
|
|
|
const groupIdentityUuid = new UUIDT().toString()
|
|
await capture({
|
|
teamId,
|
|
distinctId,
|
|
uuid: groupIdentityUuid,
|
|
event: '$groupidentify',
|
|
properties: {
|
|
distinct_id: distinctId,
|
|
$group_type: 'organization',
|
|
$group_key: 'posthog',
|
|
$group_set: {
|
|
prop: 'value',
|
|
},
|
|
},
|
|
})
|
|
|
|
await waitForExpect(async () => {
|
|
const group = await fetchGroups(teamId)
|
|
expect(group).toEqual([
|
|
expect.objectContaining({
|
|
group_type_index: 0,
|
|
group_key: 'posthog',
|
|
group_properties: { prop: 'value' },
|
|
}),
|
|
])
|
|
})
|
|
|
|
const firstEventUuid = new UUIDT().toString()
|
|
await capture({
|
|
teamId,
|
|
distinctId,
|
|
uuid: firstEventUuid,
|
|
event: 'custom event',
|
|
properties: {
|
|
name: 'haha',
|
|
$group_0: 'posthog',
|
|
},
|
|
})
|
|
|
|
await waitForExpect(async () => {
|
|
const [event] = await fetchEvents(teamId, firstEventUuid)
|
|
expect(event).toEqual(
|
|
expect.objectContaining({
|
|
$group_0: 'posthog',
|
|
})
|
|
)
|
|
})
|
|
|
|
const secondGroupIdentityUuid = new UUIDT().toString()
|
|
await capture({
|
|
teamId,
|
|
distinctId,
|
|
uuid: secondGroupIdentityUuid,
|
|
event: '$groupidentify',
|
|
properties: {
|
|
distinct_id: distinctId,
|
|
$group_type: 'organization',
|
|
$group_key: 'posthog',
|
|
$group_set: {
|
|
prop: 'updated value',
|
|
},
|
|
},
|
|
})
|
|
|
|
await waitForExpect(async () => {
|
|
const group = await fetchGroups(teamId)
|
|
expect(group).toContainEqual(
|
|
expect.objectContaining({
|
|
group_type_index: 0,
|
|
group_key: 'posthog',
|
|
group_properties: { prop: 'updated value' },
|
|
})
|
|
)
|
|
})
|
|
|
|
const secondEventUuid = new UUIDT().toString()
|
|
await capture({
|
|
teamId,
|
|
distinctId,
|
|
uuid: secondEventUuid,
|
|
event: 'custom event',
|
|
properties: {
|
|
name: 'haha',
|
|
$group_0: 'posthog',
|
|
},
|
|
})
|
|
await waitForExpect(async () => {
|
|
const [event] = await fetchEvents(teamId, secondEventUuid)
|
|
expect(event).toEqual(
|
|
expect.objectContaining({
|
|
$group_0: 'posthog',
|
|
})
|
|
)
|
|
})
|
|
})
|
|
|
|
test.concurrent(`liveness check endpoint works`, async () => {
|
|
await waitForExpect(async () => {
|
|
const response = await fetch('http://localhost:6738/_health')
|
|
expect(response.status).toBe(200)
|
|
|
|
const body = await response.json()
|
|
expect(body).toEqual(
|
|
expect.objectContaining({
|
|
checks: expect.objectContaining({ 'analytics-ingestion': 'ok' }),
|
|
})
|
|
)
|
|
})
|
|
})
|
|
|
|
test.concurrent(`event ingestion: handles $groupidentify with no properties`, async () => {
|
|
const teamId = await createTeam(organizationId)
|
|
const distinctId = new UUIDT().toString()
|
|
|
|
const groupIdentityUuid = new UUIDT().toString()
|
|
await capture({
|
|
teamId,
|
|
distinctId,
|
|
uuid: groupIdentityUuid,
|
|
event: '$groupidentify',
|
|
properties: {
|
|
distinct_id: distinctId,
|
|
$group_type: 'organization',
|
|
$group_key: 'posthog',
|
|
},
|
|
})
|
|
|
|
const firstEventUuid = new UUIDT().toString()
|
|
await capture({
|
|
teamId,
|
|
distinctId,
|
|
uuid: firstEventUuid,
|
|
event: 'custom event',
|
|
properties: {
|
|
name: 'haha',
|
|
$group_0: 'posthog',
|
|
},
|
|
})
|
|
|
|
const event = await waitForExpect(async () => {
|
|
const [event] = await fetchEvents(teamId, firstEventUuid)
|
|
expect(event).toBeDefined()
|
|
return event
|
|
})
|
|
|
|
expect(event).toEqual(
|
|
expect.objectContaining({
|
|
$group_0: 'posthog',
|
|
})
|
|
)
|
|
})
|
|
|
|
test.concurrent(`event ingestion: can $set and update person properties`, async () => {
|
|
const teamId = await createTeam(organizationId)
|
|
const distinctId = new UUIDT().toString()
|
|
|
|
await capture({
|
|
teamId,
|
|
distinctId,
|
|
uuid: new UUIDT().toString(),
|
|
event: '$identify',
|
|
properties: {
|
|
distinct_id: distinctId,
|
|
$set: { prop: 'value' },
|
|
},
|
|
})
|
|
|
|
const firstUuid = new UUIDT().toString()
|
|
await capture({ teamId, distinctId, uuid: firstUuid, event: 'custom event', properties: {} })
|
|
await waitForExpect(async () => {
|
|
const [event] = await fetchEvents(teamId, firstUuid)
|
|
expect(event).toEqual(
|
|
expect.objectContaining({
|
|
person_properties: expect.objectContaining({
|
|
prop: 'value',
|
|
}),
|
|
})
|
|
)
|
|
})
|
|
|
|
await capture({
|
|
teamId,
|
|
distinctId,
|
|
uuid: new UUIDT().toString(),
|
|
event: '$identify',
|
|
properties: {
|
|
distinct_id: distinctId,
|
|
$set: { prop: 'updated value' },
|
|
},
|
|
})
|
|
|
|
const secondUuid = new UUIDT().toString()
|
|
await capture({ teamId, distinctId, uuid: secondUuid, event: 'custom event', properties: {} })
|
|
await waitForExpect(async () => {
|
|
const [event] = await fetchEvents(teamId, secondUuid)
|
|
expect(event).toEqual(
|
|
expect.objectContaining({
|
|
person_properties: expect.objectContaining({
|
|
prop: 'updated value',
|
|
}),
|
|
})
|
|
)
|
|
})
|
|
})
|
|
|
|
test.concurrent(
|
|
`event ingestion: $process_person_profile=false drops expected fields, doesn't include person properties`,
|
|
async () => {
|
|
const teamId = await createTeam(organizationId)
|
|
const distinctId = new UUIDT().toString()
|
|
|
|
// Normal ("full") event creates person with a property.
|
|
await capture({
|
|
teamId,
|
|
distinctId,
|
|
uuid: new UUIDT().toString(),
|
|
event: '$identify',
|
|
properties: {
|
|
distinct_id: distinctId,
|
|
$set: { prop: 'value' },
|
|
},
|
|
})
|
|
|
|
// Propertyless event tries to $set, $set_once, $unset and use groups, but none of these
|
|
// should work.
|
|
const properylessUuid = new UUIDT().toString()
|
|
await capture({
|
|
teamId,
|
|
distinctId,
|
|
uuid: properylessUuid,
|
|
event: 'custom event',
|
|
properties: {
|
|
$process_person_profile: false,
|
|
$group_0: 'group_key',
|
|
$set: {
|
|
c: 3,
|
|
},
|
|
$set_once: {
|
|
d: 4,
|
|
},
|
|
$unset: ['prop'],
|
|
},
|
|
$set: {
|
|
a: 1,
|
|
},
|
|
$set_once: {
|
|
b: 2,
|
|
},
|
|
})
|
|
await waitForExpect(async () => {
|
|
const [event] = await fetchEvents(teamId, properylessUuid)
|
|
expect(event).toEqual(
|
|
expect.objectContaining({
|
|
person_properties: {},
|
|
properties: { uuid: properylessUuid, $sent_at: expect.any(String), $process_person_profile: false },
|
|
person_mode: 'propertyless',
|
|
})
|
|
)
|
|
})
|
|
|
|
// Another normal ("full") event sees the existing person property (it wasn't $unset)
|
|
const secondUuid = new UUIDT().toString()
|
|
await capture({ teamId, distinctId, uuid: secondUuid, event: 'custom event', properties: {} })
|
|
await waitForExpect(async () => {
|
|
const [event] = await fetchEvents(teamId, secondUuid)
|
|
expect(event).toEqual(
|
|
expect.objectContaining({
|
|
person_properties: expect.objectContaining({
|
|
prop: 'value',
|
|
}),
|
|
person_mode: 'full',
|
|
})
|
|
)
|
|
})
|
|
}
|
|
)
|
|
|
|
test.concurrent(`event ingestion: can $set and update person properties with top level $set`, async () => {
|
|
// We support $set at the top level. This is as the time of writing how the
|
|
// posthog-js library works.
|
|
const teamId = await createTeam(organizationId)
|
|
const distinctId = new UUIDT().toString()
|
|
|
|
await capture({
|
|
teamId,
|
|
distinctId,
|
|
uuid: new UUIDT().toString(),
|
|
event: '$identify',
|
|
properties: {
|
|
distinct_id: distinctId,
|
|
},
|
|
$set: { prop: 'value' },
|
|
})
|
|
|
|
const firstUuid = new UUIDT().toString()
|
|
await capture({ teamId, distinctId, uuid: firstUuid, event: 'custom event', properties: {} })
|
|
await waitForExpect(async () => {
|
|
const [event] = await fetchEvents(teamId, firstUuid)
|
|
expect(event).toEqual(
|
|
expect.objectContaining({
|
|
person_properties: expect.objectContaining({
|
|
prop: 'value',
|
|
}),
|
|
})
|
|
)
|
|
})
|
|
})
|
|
|
|
test.concurrent(`event ingestion: person properties are point in event time`, async () => {
|
|
const teamId = await createTeam(organizationId)
|
|
const distinctId = new UUIDT().toString()
|
|
|
|
await capture({
|
|
teamId,
|
|
distinctId,
|
|
uuid: new UUIDT().toString(),
|
|
event: '$identify',
|
|
properties: {
|
|
distinct_id: distinctId,
|
|
$set: { prop: 'value' },
|
|
},
|
|
})
|
|
|
|
const firstUuid = new UUIDT().toString()
|
|
await capture({ teamId, distinctId, uuid: firstUuid, event: 'custom event', properties: {} })
|
|
await capture({
|
|
teamId,
|
|
distinctId,
|
|
uuid: new UUIDT().toString(),
|
|
event: 'custom event',
|
|
properties: {
|
|
distinct_id: distinctId,
|
|
$set: {
|
|
prop: 'updated value',
|
|
new_prop: 'new value',
|
|
},
|
|
},
|
|
})
|
|
|
|
await waitForExpect(async () => {
|
|
const [event] = await fetchEvents(teamId, firstUuid)
|
|
expect(event).toEqual(
|
|
expect.objectContaining({
|
|
person_properties: expect.objectContaining({
|
|
prop: 'value',
|
|
}),
|
|
})
|
|
)
|
|
})
|
|
})
|
|
|
|
test.concurrent(`event ingestion: can $set_once person properties but not update`, async () => {
|
|
const teamId = await createTeam(organizationId)
|
|
const distinctId = new UUIDT().toString()
|
|
|
|
const personEventUuid = new UUIDT().toString()
|
|
await capture({
|
|
teamId,
|
|
distinctId,
|
|
uuid: personEventUuid,
|
|
event: '$identify',
|
|
properties: {
|
|
distinct_id: distinctId,
|
|
$set_once: { prop: 'value' },
|
|
},
|
|
})
|
|
|
|
const firstUuid = new UUIDT().toString()
|
|
await capture({ teamId, distinctId, uuid: firstUuid, event: 'custom event', properties: {} })
|
|
await waitForExpect(async () => {
|
|
const [event] = await fetchEvents(teamId, firstUuid)
|
|
expect(event).toEqual(
|
|
expect.objectContaining({
|
|
person_properties: {
|
|
$creator_event_uuid: personEventUuid,
|
|
prop: 'value',
|
|
},
|
|
})
|
|
)
|
|
})
|
|
|
|
await capture({
|
|
teamId,
|
|
distinctId,
|
|
uuid: personEventUuid,
|
|
event: '$identify',
|
|
properties: {
|
|
distinct_id: distinctId,
|
|
$set_once: { prop: 'updated value' },
|
|
},
|
|
})
|
|
|
|
const secondUuid = new UUIDT().toString()
|
|
await capture({ teamId, distinctId, uuid: secondUuid, event: 'custom event', properties: {} })
|
|
await waitForExpect(async () => {
|
|
const [event] = await fetchEvents(teamId, secondUuid)
|
|
expect(event).toEqual(
|
|
expect.objectContaining({
|
|
person_properties: {
|
|
$creator_event_uuid: personEventUuid,
|
|
prop: 'value',
|
|
},
|
|
})
|
|
)
|
|
})
|
|
})
|
|
|
|
test.concurrent(
|
|
`event ingestion: can $set_once person properties but not update, with top level $set_once`,
|
|
async () => {
|
|
// We support $set_once at the top level. This is as the time of writing
|
|
// how the posthog-js library works.
|
|
const teamId = await createTeam(organizationId)
|
|
const distinctId = new UUIDT().toString()
|
|
|
|
const personEventUuid = new UUIDT().toString()
|
|
await capture({
|
|
teamId,
|
|
distinctId,
|
|
uuid: personEventUuid,
|
|
event: '$identify',
|
|
properties: {
|
|
distinct_id: distinctId,
|
|
},
|
|
$set_once: { prop: 'value' },
|
|
})
|
|
|
|
const firstUuid = new UUIDT().toString()
|
|
await capture({ teamId, distinctId, uuid: firstUuid, event: 'custom event', properties: {} })
|
|
await waitForExpect(async () => {
|
|
const [event] = await fetchEvents(teamId, firstUuid)
|
|
expect(event).toEqual(
|
|
expect.objectContaining({
|
|
person_properties: {
|
|
$creator_event_uuid: personEventUuid,
|
|
prop: 'value',
|
|
},
|
|
})
|
|
)
|
|
})
|
|
}
|
|
)
|
|
|
|
test.concurrent(`event ingestion: events without a team_id get processed correctly`, async () => {
|
|
const token = new UUIDT().toString()
|
|
const teamId = await createTeam(organizationId, '', token)
|
|
const personIdentifier = 'test@posthog.com'
|
|
|
|
await capture({
|
|
teamId: null,
|
|
distinctId: personIdentifier,
|
|
uuid: new UUIDT().toString(),
|
|
event: 'test event',
|
|
properties: {
|
|
distinct_id: personIdentifier,
|
|
},
|
|
token,
|
|
})
|
|
|
|
await waitForExpect(async () => {
|
|
const events = await fetchEvents(teamId)
|
|
expect(events.length).toBe(1)
|
|
expect(events[0].team_id).toBe(teamId)
|
|
})
|
|
})
|
|
|
|
test.concurrent('consumer updates timestamp exported to prometheus', async () => {
|
|
// NOTE: it may be another event other than the one we emit here that causes
|
|
// the gauge to increase, but pushing this event through should at least
|
|
// ensure that the gauge is updated.
|
|
const teamId = await createTeam(organizationId)
|
|
const distinctId = new UUIDT().toString()
|
|
|
|
const metricBefore = await getMetric({
|
|
name: 'latest_processed_timestamp_ms',
|
|
type: 'GAUGE',
|
|
labels: { topic: 'events_plugin_ingestion', partition: '0', groupId: 'ingestion' },
|
|
})
|
|
|
|
await capture({ teamId, distinctId, uuid: new UUIDT().toString(), event: 'custom event', properties: {} })
|
|
|
|
await waitForExpect(async () => {
|
|
const metricAfter = await getMetric({
|
|
name: 'latest_processed_timestamp_ms',
|
|
type: 'GAUGE',
|
|
labels: { topic: 'events_plugin_ingestion', partition: '0', groupId: 'ingestion' },
|
|
})
|
|
expect(metricAfter).toBeGreaterThan(metricBefore)
|
|
expect(metricAfter).toBeLessThan(Date.now()) // Make sure, e.g. we're not setting micro seconds
|
|
expect(metricAfter).toBeGreaterThan(Date.now() - 60_000) // Make sure, e.g. we're not setting seconds
|
|
}, 10_000)
|
|
})
|
|
|
|
test.concurrent(`event ingestion: initial login flow keeps the same person_id`, async () => {
|
|
const teamId = await createTeam(organizationId)
|
|
const initialDistinctId = 'initialDistinctId'
|
|
const personIdentifier = 'test@posthog.com'
|
|
|
|
// This simulates initial sign-up flow,
|
|
// where the user has first been browsing the site anonymously for a while
|
|
|
|
// First we emit an anoymous event and wait for the person to be
|
|
// created.
|
|
const initialEventId = new UUIDT().toString()
|
|
await capture({ teamId, distinctId: initialDistinctId, uuid: initialEventId, event: 'custom event' })
|
|
await waitForExpect(async () => {
|
|
const persons = await fetchPersons(teamId)
|
|
expect(persons).toContainEqual(
|
|
expect.objectContaining({
|
|
properties: expect.objectContaining({ $creator_event_uuid: initialEventId }),
|
|
})
|
|
)
|
|
}, 10000)
|
|
|
|
// We then identify the person
|
|
await capture({
|
|
teamId,
|
|
distinctId: personIdentifier,
|
|
uuid: new UUIDT().toString(),
|
|
event: '$identify',
|
|
properties: {
|
|
distinct_id: personIdentifier,
|
|
$anon_distinct_id: initialDistinctId,
|
|
},
|
|
})
|
|
|
|
await waitForExpect(async () => {
|
|
const events = await fetchEvents(teamId)
|
|
expect(events.length).toBe(2)
|
|
expect(events[0].person_id).toBeDefined()
|
|
expect(events[0].person_id).not.toBe('00000000-0000-0000-0000-000000000000')
|
|
expect(new Set(events.map((event) => event.person_id)).size).toBe(1)
|
|
}, 10000)
|
|
})
|
|
|
|
test.concurrent(`events still ingested even if merge fails`, async () => {
|
|
const teamId = await createTeam(organizationId)
|
|
const illegalDistinctId = '0'
|
|
const distinctId = new UUIDT().toString()
|
|
|
|
// First we emit anoymous events and wait for the persons to be created.
|
|
await capture({ teamId, distinctId: illegalDistinctId, uuid: new UUIDT().toString(), event: 'custom event' })
|
|
await capture({ teamId, distinctId: distinctId, uuid: new UUIDT().toString(), event: 'custom event 2' })
|
|
|
|
await waitForExpect(async () => {
|
|
const persons = await fetchPersons(teamId)
|
|
expect(persons.length).toBe(2)
|
|
}, 10000)
|
|
|
|
await capture({
|
|
teamId,
|
|
distinctId: distinctId,
|
|
uuid: new UUIDT().toString(),
|
|
event: '$merge_dangerously',
|
|
properties: {
|
|
distinct_id: distinctId,
|
|
alias: illegalDistinctId,
|
|
$set: { prop: 'value' },
|
|
},
|
|
})
|
|
|
|
await waitForExpect(async () => {
|
|
const events = await fetchEvents(teamId)
|
|
expect(events.length).toBe(3)
|
|
}, 10000)
|
|
|
|
await waitForExpect(async () => {
|
|
const events = await fetchEvents(teamId)
|
|
expect(events.length).toBe(3)
|
|
expect(events[0].person_id).toBeDefined()
|
|
expect(events[0].person_id).not.toBe('00000000-0000-0000-0000-000000000000')
|
|
expect(new Set(events.map((event) => event.person_id)).size).toBe(2)
|
|
}, 10000)
|
|
})
|
|
|
|
test.concurrent(`properties still $set even if merge fails`, async () => {
|
|
const teamId = await createTeam(organizationId)
|
|
const illegalDistinctId = '0'
|
|
const distinctId = new UUIDT().toString()
|
|
|
|
await capture({
|
|
teamId,
|
|
distinctId: distinctId,
|
|
uuid: new UUIDT().toString(),
|
|
event: '$merge_dangerously',
|
|
properties: {
|
|
distinct_id: distinctId,
|
|
alias: illegalDistinctId,
|
|
$set: { prop: 'value' },
|
|
},
|
|
})
|
|
|
|
const firstUuid = new UUIDT().toString()
|
|
await capture({ teamId, distinctId, uuid: firstUuid, event: 'custom event', properties: {} })
|
|
await waitForExpect(async () => {
|
|
const [event] = await fetchEvents(teamId, firstUuid)
|
|
expect(event).toEqual(
|
|
expect.objectContaining({
|
|
person_properties: expect.objectContaining({
|
|
prop: 'value',
|
|
}),
|
|
})
|
|
)
|
|
})
|
|
})
|
|
|
|
test.concurrent(`single merge results in all events resolving to the same person id`, async () => {
|
|
const teamId = await createTeam(organizationId)
|
|
const initialDistinctId = new UUIDT().toString()
|
|
const secondDistinctId = new UUIDT().toString()
|
|
const personIdentifier = new UUIDT().toString()
|
|
|
|
// This simulates sign-up flow with backend events having an anonymous ID in both frontend and backend
|
|
|
|
// First we emit anoymous events and wait for the persons to be created.
|
|
const initialEventId = new UUIDT().toString()
|
|
await capture({ teamId, distinctId: initialDistinctId, uuid: initialEventId, event: 'custom event' })
|
|
const secondEventId = new UUIDT().toString()
|
|
await capture({ teamId, distinctId: secondDistinctId, uuid: secondEventId, event: 'custom event 2' })
|
|
await waitForExpect(async () => {
|
|
const persons = await fetchPersons(teamId)
|
|
expect(persons).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
properties: expect.objectContaining({ $creator_event_uuid: initialEventId }),
|
|
}),
|
|
expect.objectContaining({
|
|
properties: expect.objectContaining({ $creator_event_uuid: secondEventId }),
|
|
}),
|
|
])
|
|
)
|
|
}, 10000)
|
|
|
|
// Then we identify both ids
|
|
const uuidOfFirstIdentifyEvent = new UUIDT().toString()
|
|
await capture({
|
|
teamId,
|
|
distinctId: personIdentifier,
|
|
uuid: uuidOfFirstIdentifyEvent,
|
|
event: '$identify',
|
|
properties: {
|
|
distinct_id: personIdentifier,
|
|
$anon_distinct_id: initialDistinctId,
|
|
},
|
|
})
|
|
const uuidOfSecondIdentifyEvent = new UUIDT().toString()
|
|
await capture({
|
|
teamId,
|
|
distinctId: personIdentifier,
|
|
uuid: uuidOfSecondIdentifyEvent,
|
|
event: '$identify',
|
|
properties: {
|
|
distinct_id: personIdentifier,
|
|
$anon_distinct_id: secondDistinctId,
|
|
},
|
|
})
|
|
|
|
await waitForExpect(async () => {
|
|
const events = await fetchEvents(teamId)
|
|
expect(events.length).toBe(4)
|
|
expect(events[0].person_id).toBeDefined()
|
|
expect(events[0].person_id).not.toBe('00000000-0000-0000-0000-000000000000')
|
|
expect(new Set(events.map((event) => event.person_id)).size).toBe(1)
|
|
}, 10000)
|
|
})
|
|
|
|
test.concurrent(`chained merge results in all events resolving to the same person id`, async () => {
|
|
const teamId = await createTeam(organizationId)
|
|
const initialDistinctId = new UUIDT().toString()
|
|
const secondDistinctId = new UUIDT().toString()
|
|
const thirdDistinctId = new UUIDT().toString()
|
|
|
|
// First we emit anoymous events and wait for the persons to be created.
|
|
await capture({ teamId, distinctId: initialDistinctId, uuid: new UUIDT().toString(), event: 'custom event' })
|
|
await capture({ teamId, distinctId: secondDistinctId, uuid: new UUIDT().toString(), event: 'custom event 2' })
|
|
await capture({ teamId, distinctId: thirdDistinctId, uuid: new UUIDT().toString(), event: 'custom event 3' })
|
|
await waitForExpect(async () => {
|
|
const persons = await fetchPersons(teamId)
|
|
expect(persons.length).toBe(3)
|
|
}, 10000)
|
|
|
|
// Then we identify first two together
|
|
await capture({
|
|
teamId,
|
|
distinctId: initialDistinctId,
|
|
uuid: new UUIDT().toString(),
|
|
event: '$identify',
|
|
properties: {
|
|
distinct_id: initialDistinctId,
|
|
$anon_distinct_id: secondDistinctId,
|
|
},
|
|
})
|
|
|
|
// This guarantees that we process them in order, which verifies the right overrides and
|
|
// makes sure we don't run into Merge refused errors if secondDistinctId is already identified if later completed first
|
|
await waitForExpect(async () => {
|
|
const persons = await fetchEvents(teamId)
|
|
expect(persons.length).toBe(4)
|
|
}, 10000)
|
|
|
|
// Then we merge the third person
|
|
await capture({
|
|
teamId,
|
|
distinctId: secondDistinctId,
|
|
uuid: new UUIDT().toString(),
|
|
event: '$identify',
|
|
properties: {
|
|
distinct_id: secondDistinctId,
|
|
$anon_distinct_id: thirdDistinctId,
|
|
},
|
|
})
|
|
|
|
await waitForExpect(async () => {
|
|
const events = await fetchEvents(teamId)
|
|
expect(events.length).toBe(5)
|
|
expect(events[0].person_id).toBeDefined()
|
|
expect(events[0].person_id).not.toBe('00000000-0000-0000-0000-000000000000')
|
|
expect(new Set(events.map((event) => event.person_id)).size).toBe(1)
|
|
}, 20000)
|
|
})
|
|
|
|
test.concurrent(`complex chained merge adds results in all events resolving to the same person id`, async () => {
|
|
// let's assume we have 4 persons 1234, we'll first merge 1-2 & 3-4, then we'll merge 2-3
|
|
// this should still result in all events having the same person_id or override[person_id]
|
|
|
|
const teamId = await createTeam(organizationId)
|
|
const initialDistinctId = new UUIDT().toString()
|
|
const secondDistinctId = new UUIDT().toString()
|
|
const thirdDistinctId = new UUIDT().toString()
|
|
const forthDistinctId = new UUIDT().toString()
|
|
|
|
// First we emit anoymous events and wait for the persons to be created.
|
|
await capture({ teamId, distinctId: initialDistinctId, uuid: new UUIDT().toString(), event: 'custom event' })
|
|
await capture({ teamId, distinctId: secondDistinctId, uuid: new UUIDT().toString(), event: 'custom event 2' })
|
|
await capture({ teamId, distinctId: thirdDistinctId, uuid: new UUIDT().toString(), event: 'custom event 3' })
|
|
await capture({ teamId, distinctId: forthDistinctId, uuid: new UUIDT().toString(), event: 'custom event 3' })
|
|
await waitForExpect(async () => {
|
|
const persons = await fetchPersons(teamId)
|
|
expect(persons.length).toBe(4)
|
|
}, 10000)
|
|
|
|
// Then we identify 1-2 and 3-4
|
|
await capture({
|
|
teamId,
|
|
distinctId: initialDistinctId,
|
|
uuid: new UUIDT().toString(),
|
|
event: '$identify',
|
|
properties: {
|
|
distinct_id: initialDistinctId,
|
|
$anon_distinct_id: secondDistinctId,
|
|
},
|
|
})
|
|
await capture({
|
|
teamId,
|
|
distinctId: thirdDistinctId,
|
|
uuid: new UUIDT().toString(),
|
|
event: '$identify',
|
|
properties: {
|
|
distinct_id: thirdDistinctId,
|
|
$anon_distinct_id: forthDistinctId,
|
|
},
|
|
})
|
|
|
|
await waitForExpect(async () => {
|
|
const events = await fetchEvents(teamId)
|
|
expect(events.length).toBe(6)
|
|
}, 10000)
|
|
|
|
// Then we merge 2-3
|
|
await capture({
|
|
teamId,
|
|
distinctId: initialDistinctId,
|
|
uuid: new UUIDT().toString(),
|
|
event: '$merge_dangerously',
|
|
properties: {
|
|
distinct_id: secondDistinctId,
|
|
alias: thirdDistinctId,
|
|
},
|
|
})
|
|
|
|
await waitForExpect(async () => {
|
|
const events = await fetchEvents(teamId)
|
|
expect(events.length).toBe(7)
|
|
expect(events[0].person_id).toBeDefined()
|
|
expect(events[0].person_id).not.toBe('00000000-0000-0000-0000-000000000000')
|
|
expect(new Set(events.map((event) => event.person_id)).size).toBe(1)
|
|
}, 20000)
|
|
})
|
|
|
|
// TODO: adjust this test to poEEmbraceJoin
|
|
test.skip(`person properties don't see properties from descendents`, async () => {
|
|
// The only thing that should propagate to an ancestor is the person_id.
|
|
// Person properties should not propagate to ancestors within a branch.
|
|
//
|
|
// P(k: v, set_once_property: value)
|
|
// |
|
|
// |
|
|
// P'(k: v, j: w, set_once_property: value)
|
|
//
|
|
// The person properties of P' should not be assiciated with events tied to
|
|
// P.
|
|
|
|
const teamId = await createTeam(organizationId)
|
|
const firstDistinctId = new UUIDT().toString()
|
|
|
|
const firstUuid = new UUIDT().toString()
|
|
await capture({
|
|
teamId,
|
|
distinctId: firstDistinctId,
|
|
uuid: firstUuid,
|
|
event: 'custom event',
|
|
properties: {
|
|
$set: {
|
|
k: 'v',
|
|
},
|
|
$set_once: {
|
|
set_once_property: 'value',
|
|
},
|
|
},
|
|
})
|
|
|
|
const secondUuid = new UUIDT().toString()
|
|
await capture({
|
|
teamId,
|
|
distinctId: firstDistinctId,
|
|
uuid: secondUuid,
|
|
event: 'custom event',
|
|
properties: {
|
|
$set: {
|
|
j: 'w',
|
|
},
|
|
$set_once: {
|
|
set_once_property: 'second value',
|
|
},
|
|
},
|
|
})
|
|
|
|
await waitForExpect(async () => {
|
|
const [first] = await fetchEvents(teamId, firstUuid)
|
|
const [second] = await fetchEvents(teamId, secondUuid)
|
|
|
|
expect(first).toEqual(
|
|
expect.objectContaining({
|
|
person_id: second.person_id,
|
|
person_properties: {
|
|
$creator_event_uuid: expect.any(String),
|
|
k: 'v',
|
|
set_once_property: 'value',
|
|
},
|
|
})
|
|
)
|
|
|
|
expect(second).toEqual(
|
|
expect.objectContaining({
|
|
person_properties: {
|
|
$creator_event_uuid: expect.any(String),
|
|
k: 'v',
|
|
j: 'w',
|
|
set_once_property: 'value',
|
|
},
|
|
})
|
|
)
|
|
})
|
|
})
|
|
|
|
// Skipping this test as without ording of events across distinct_id we don't
|
|
// know which event will be processed first, and hence this test is flaky. We
|
|
// are at any rate looking at alternatives to the implementation to speed up
|
|
// queries which may make this test obsolete.
|
|
test.skip(`person properties can't see properties from merge descendants`, async () => {
|
|
// This is specifically to test that the merge event doesn't result in
|
|
// properties being picked up on events from it's parents.
|
|
//
|
|
// Alice(k: v)
|
|
// \
|
|
// \ Bob(j: w)
|
|
// \ /
|
|
// \ /
|
|
// AliceAndBob(k: v, j: w, l: x)
|
|
//
|
|
// NOTE: a stronger guarantee would be to ensure that events only pick up
|
|
// properties from their relatives. Instead, if event e1 has a common
|
|
// descendant with e2, they will pick up properties from which ever was
|
|
// _processed_ first.
|
|
// TODO: change the guarantee to be that unrelated branches properties are
|
|
// isolated from each other.
|
|
|
|
const teamId = await createTeam(organizationId)
|
|
const aliceAnonId = new UUIDT().toString()
|
|
const bobAnonId = new UUIDT().toString()
|
|
|
|
const firstUuid = new UUIDT().toString()
|
|
await capture({
|
|
teamId,
|
|
distinctId: aliceAnonId,
|
|
uuid: firstUuid,
|
|
event: 'custom event',
|
|
properties: {
|
|
$set: {
|
|
k: 'v',
|
|
},
|
|
},
|
|
})
|
|
|
|
const secondUuid = new UUIDT().toString()
|
|
await capture({
|
|
teamId,
|
|
distinctId: bobAnonId,
|
|
uuid: secondUuid,
|
|
event: 'custom event',
|
|
properties: {
|
|
$set: {
|
|
j: 'w',
|
|
},
|
|
},
|
|
})
|
|
|
|
const thirdUuid = new UUIDT().toString()
|
|
// NOTE: $create_alias is not symmetric, so we will get different
|
|
// results according to the order of `bobAnonId` and `aliceAnonId`.
|
|
await capture({
|
|
teamId,
|
|
distinctId: bobAnonId,
|
|
uuid: thirdUuid,
|
|
event: '$create_alias',
|
|
properties: {
|
|
alias: aliceAnonId,
|
|
$set: {
|
|
l: 'x',
|
|
},
|
|
},
|
|
})
|
|
|
|
// Now we wait to ensure that these events have been ingested.
|
|
const [first, second, third] = await waitForExpect(async () => {
|
|
const [first] = await fetchEvents(teamId, firstUuid)
|
|
const [second] = await fetchEvents(teamId, secondUuid)
|
|
const [third] = await fetchEvents(teamId, thirdUuid)
|
|
|
|
expect(first).toBeDefined()
|
|
expect(second).toBeDefined()
|
|
expect(third).toBeDefined()
|
|
|
|
return [first, second, third]
|
|
})
|
|
|
|
expect(first).toEqual(
|
|
expect.objectContaining({
|
|
person_id: third.person_id,
|
|
person_properties: {
|
|
$creator_event_uuid: expect.any(String),
|
|
k: 'v',
|
|
},
|
|
})
|
|
)
|
|
|
|
expect(second).toEqual(
|
|
expect.objectContaining({
|
|
person_id: third.person_id,
|
|
person_properties: {
|
|
$creator_event_uuid: expect.any(String),
|
|
k: 'v',
|
|
j: 'w',
|
|
},
|
|
})
|
|
)
|
|
|
|
expect(third).toEqual(
|
|
expect.objectContaining({
|
|
person_properties: {
|
|
$creator_event_uuid: expect.any(String),
|
|
k: 'v',
|
|
j: 'w',
|
|
l: 'x',
|
|
},
|
|
})
|
|
)
|
|
})
|