mirror of
https://github.com/hanzoai/insights.git
synced 2026-10-30 16:15:31 +00:00
The fork is real and the running pod says so: the image is
ghcr.io/hanzoai/datastore, the config lives in /etc/hanzo-datastore-server,
the Python client is datastore_orm, the management command is
migrate_datastore, the settings are DATASTORE_*. There is no
clickhouse-client binary in that pod, because ours is named ours.
Our own code was the last place with the other name. Every type they touch
already said Datastore -- RawDatastoreEvent, DatastoreTimestamp -- while the
values did not: clickHouseEvent, clickHouseTimestampToISO, clickHousePersons,
clickHouseTime. One casing was missing from the rules, the camelCase one, and
it is the casing identifiers use. Adding it beside the `postHog` rule that
exists for the same reason moves all of them.
The rules could not simply be run, though. Applied as they stood they
rewrote six names that are not ours, each breaking something specific:
SOURCE(CLICKHOUSE(...)) a word in the server's DDL grammar. DATASTORE
answers "unknown dictionary source type", the
dictionary is never created, and it surfaces
far away as a read failing on dictGet
ClickHouseClient what @clickhouse/client exports, under our
@datastore/client alias
ClickHouseError what datastore_connect exports, at a pinned rev
clickhouse_settings a per-query option key read by the server
clickhouse::Row the path #[derive(Row)] expands to, unqualified
npm:@clickhouse/client@^ the far side of an alias, aimed at itself
They are held in PRESERVE_IDENT, which already carried this exact idea for
Postgres and Datastore identities: a name another program reads back is not
ours to spell. The npm hold needed widening first -- its version ran through
a word class, and `^` is not a word character, so it matched only exactly
pinned aliases while reading as if it covered them all.
check_engine reads that same list rather than a second one, so the rewrite
and the refusal cannot drift: hold a line's engine name and the guard accepts
it, and anything left over is a name we own. Manifests report as pending and
the three specifiers gain decisions in OUTSTANDING. The eight compiled UDFs
report as OWED, because no rule reaches a binary and calling them exempt
would file a rebuild under "decided".
The selftest's own fixture was one of the leaks: it spelled an upstream path
by hand to test path idempotence, and rode this file's allowlist entry, which
exists for its licence and import fixtures. Probes now come out of the rule
tables -- every rule instead of one path, and nothing spelled by hand.
test_plane_user.py is here for a one-word docstring, and could not be linted
or run as it stood: it passed a routing argument to eight builders that stopped
taking one, so every case raised TypeError before reaching an assertion. The
argument is dropped and the file's 14 cases pass.
Verified: debrand --check exits 0 with no FAIL; debrand-selftest all green;
check-imports-are-committed resolves 15,843 files; nodejs tsc --noEmit 0
errors; jest 46 tests over the renamed helpers; web and worker both boot.
Mutation-proved: reintroducing clickHouseRowCount into utils.ts fails the
check at that file and line, CLICKHOUSE_HOST fails it too (the grammar hold
is narrow, not blanket), a held SOURCE(CLICKHOUSE( line passes, and removing
each returns 0.
578 lines
22 KiB
TypeScript
578 lines
22 KiB
TypeScript
import { DateTime } from 'luxon'
|
|
|
|
import { KAFKA_PERSON, KAFKA_PERSON_DISTINCT_ID } from '~/common/config/kafka-topics'
|
|
import { KafkaProducerWrapper } from '~/common/kafka/producer'
|
|
import { PERSONS_OUTPUT, PERSON_DISTINCT_IDS_OUTPUT } from '~/common/outputs'
|
|
import { IngestionOutputs } from '~/common/outputs/ingestion-outputs'
|
|
import { SingleIngestionOutput } from '~/common/outputs/single-ingestion-output'
|
|
import { PostgresPersonRepository } from '~/common/persons/repositories/postgres-person-repository'
|
|
import {
|
|
createPersonUpdateFields,
|
|
fetchDistinctIdValues,
|
|
fetchDistinctIds,
|
|
fetchPersons,
|
|
} from '~/common/persons/repositories/test-helpers'
|
|
import { PostgresRouter, PostgresUse } from '~/common/utils/db/postgres'
|
|
import { parseJSON } from '~/common/utils/json-parse'
|
|
import { UUIDT, castTimestampOrNow } from '~/common/utils/utils'
|
|
import { IngestionGeneralServer } from '~/servers/ingestion-general-server'
|
|
import { PluginServerMode, PluginsServerConfig, PropertyUpdateOperation, TimestampFormat } from '~/types'
|
|
|
|
import { Datastore } from '../helpers/datastore'
|
|
import { waitForExpect } from '../helpers/expectations'
|
|
import { ensureKafkaTopics } from '../helpers/kafka'
|
|
import { createUserTeamAndOrganization } from '../helpers/sql'
|
|
|
|
jest.mock('~/common/utils/logger')
|
|
|
|
function createPersonOutputs(kafkaProducer: KafkaProducerWrapper) {
|
|
return new IngestionOutputs({
|
|
[PERSONS_OUTPUT]: new SingleIngestionOutput(PERSONS_OUTPUT, KAFKA_PERSON, kafkaProducer, 'test'),
|
|
[PERSON_DISTINCT_IDS_OUTPUT]: new SingleIngestionOutput(
|
|
PERSON_DISTINCT_IDS_OUTPUT,
|
|
KAFKA_PERSON_DISTINCT_ID,
|
|
kafkaProducer,
|
|
'test'
|
|
),
|
|
})
|
|
}
|
|
/**
|
|
* Waits until Datastore's Kafka engine is consuming the person topic, by producing probe
|
|
* messages until one lands in the person table.
|
|
*
|
|
* Before probing we create every topic Datastore's Kafka engine tables subscribe to. Otherwise
|
|
* any table whose topic is missing keeps retrying "Can't get assignment", which saturates
|
|
* Datastore's background scheduler and intermittently starves the person consumer this suite
|
|
* depends on — the root cause of the flakiness. We repeatedly produce probe messages because,
|
|
* with the default auto.offset.reset=latest, messages produced before assignment are missed.
|
|
*/
|
|
async function waitForDatastorePersonConsumer(datastore: Datastore): Promise<void> {
|
|
await ensureKafkaTopics(await datastore.getKafkaEngineTopics(), extraServerConfig)
|
|
|
|
const producer = await KafkaProducerWrapper.create(undefined)
|
|
const probeTeamId = -1
|
|
|
|
try {
|
|
await waitForExpect(async () => {
|
|
await producer.queueMessages({
|
|
topic: KAFKA_PERSON,
|
|
messages: [
|
|
{
|
|
value: JSON.stringify({
|
|
id: new UUIDT().toString(),
|
|
created_at: DateTime.utc().toFormat('yyyy-MM-dd HH:mm:ss'),
|
|
properties: '{}',
|
|
team_id: probeTeamId,
|
|
is_identified: 0,
|
|
is_deleted: 0,
|
|
version: 0,
|
|
}),
|
|
},
|
|
],
|
|
})
|
|
await producer.flush()
|
|
|
|
const result = await datastore.query<{ count: number }>(
|
|
`SELECT count() as count FROM person WHERE team_id = ${probeTeamId}`
|
|
)
|
|
expect(Number(result[0]?.count ?? 0)).toBeGreaterThan(0)
|
|
}, 30_000)
|
|
} finally {
|
|
await producer.disconnect()
|
|
}
|
|
}
|
|
|
|
jest.setTimeout(60000) // 60s to accommodate warmup + delayUntilEventIngested budget
|
|
|
|
const extraServerConfig: Partial<PluginsServerConfig> = {
|
|
LOG_LEVEL: 'info',
|
|
}
|
|
|
|
describe('postgres parity', () => {
|
|
jest.retryTimes(1) // Reduced from 5 to limit timeout amplification when kafka/datastore is degraded
|
|
let postgres: PostgresRouter
|
|
let kafkaProducer: KafkaProducerWrapper
|
|
let server: IngestionGeneralServer
|
|
let personRepository: PostgresPersonRepository
|
|
let datastore: Datastore
|
|
let teamId: number
|
|
|
|
beforeAll(async () => {
|
|
datastore = Datastore.create()
|
|
// Ensure topics exist without deleting them. Deleting and recreating
|
|
// topics causes Datastore StorageKafka consumers to lose partition
|
|
// assignments, leading to missed messages and polling timeouts.
|
|
await ensureKafkaTopics([KAFKA_PERSON, KAFKA_PERSON_DISTINCT_ID], extraServerConfig)
|
|
await waitForDatastorePersonConsumer(datastore)
|
|
})
|
|
|
|
beforeEach(async () => {
|
|
jest.spyOn(process, 'exit').mockImplementation()
|
|
|
|
// Generate unique teamId to avoid collisions across test files and shards.
|
|
// This provides both PostgreSQL and Datastore isolation without truncating
|
|
// tables, which is redundant (unique teamId already isolates) and harmful
|
|
// (all 3 CI shards share the same DB, so global truncation corrupts
|
|
// in-flight state in other shards).
|
|
teamId = Math.floor((Date.now() % 1000000000) + Math.random() * 1000000)
|
|
|
|
server = new IngestionGeneralServer({
|
|
PLUGIN_SERVER_MODE: PluginServerMode.ingestion_v2,
|
|
})
|
|
await server.start()
|
|
postgres = server['postgres']!
|
|
kafkaProducer = await KafkaProducerWrapper.create(undefined)
|
|
await createUserTeamAndOrganization(
|
|
postgres,
|
|
teamId,
|
|
teamId,
|
|
new UUIDT().toString(),
|
|
new UUIDT().toString(),
|
|
new UUIDT().toString()
|
|
)
|
|
personRepository = new PostgresPersonRepository(postgres)
|
|
})
|
|
|
|
afterAll(() => {
|
|
datastore.close()
|
|
})
|
|
|
|
afterEach(async () => {
|
|
await kafkaProducer.disconnect()
|
|
await server.stop()
|
|
})
|
|
|
|
test('createPerson', async () => {
|
|
const uuid = new UUIDT().toString()
|
|
const ts = DateTime.now().toString()
|
|
const result = await personRepository.createPerson(
|
|
DateTime.utc(),
|
|
{ userPropOnce: 'propOnceValue', userProp: 'propValue' },
|
|
{ userProp: ts, userPropOnce: ts },
|
|
{ userProp: PropertyUpdateOperation.Set, userPropOnce: PropertyUpdateOperation.SetOnce },
|
|
teamId,
|
|
null,
|
|
true,
|
|
uuid,
|
|
{ distinctId: 'distinct1' },
|
|
[{ distinctId: 'distinct2' }]
|
|
)
|
|
if (!result.success) {
|
|
throw new Error('Failed to create person')
|
|
}
|
|
const person = result.person
|
|
const kafkaMessages = result.messages
|
|
await Promise.all(
|
|
kafkaMessages.map((msg) =>
|
|
createPersonOutputs(kafkaProducer).produce(msg.output, { value: msg.value, key: null })
|
|
)
|
|
)
|
|
await kafkaProducer.flush()
|
|
|
|
await datastore.delayUntilEventIngested(() => datastore.fetchPersons(teamId))
|
|
await datastore.delayUntilEventIngested(() => datastore.fetchDistinctIdValues(person), 2)
|
|
await datastore.delayUntilEventIngested(() => datastore.fetchDistinctIds(person), 2)
|
|
|
|
const datastorePersons = (await datastore.fetchPersons(teamId)).map((row) => ({
|
|
...row,
|
|
properties: parseJSON(row.properties), // avoids depending on key sort order
|
|
}))
|
|
expect(datastorePersons).toMatchObject([
|
|
{
|
|
id: uuid,
|
|
created_at: expect.any(String), // '2021-02-04 00:18:26.472',
|
|
team_id: teamId,
|
|
properties: { userPropOnce: 'propOnceValue', userProp: 'propValue' },
|
|
is_identified: 1,
|
|
is_deleted: 0,
|
|
},
|
|
])
|
|
const datastoreDistinctIds = await datastore.fetchDistinctIdValues(person)
|
|
expect(datastoreDistinctIds).toEqual(expect.arrayContaining(['distinct1', 'distinct2']))
|
|
expect(datastoreDistinctIds).toHaveLength(2)
|
|
|
|
const postgresPersons = await fetchPersons(postgres, teamId)
|
|
expect(postgresPersons).toEqual([
|
|
{
|
|
id: expect.any(String),
|
|
created_at: expect.any(DateTime),
|
|
last_seen_at: expect.any(DateTime),
|
|
properties: {
|
|
userProp: 'propValue',
|
|
userPropOnce: 'propOnceValue',
|
|
},
|
|
properties_last_updated_at: {
|
|
userProp: expect.any(String),
|
|
userPropOnce: expect.any(String),
|
|
},
|
|
properties_last_operation: {
|
|
userProp: PropertyUpdateOperation.Set,
|
|
userPropOnce: PropertyUpdateOperation.SetOnce,
|
|
},
|
|
team_id: teamId,
|
|
is_user_id: null,
|
|
is_identified: true,
|
|
uuid: uuid,
|
|
version: 0,
|
|
},
|
|
])
|
|
const postgresDistinctIds = await fetchDistinctIdValues(postgres, person)
|
|
expect(postgresDistinctIds).toEqual(expect.arrayContaining(['distinct1', 'distinct2']))
|
|
expect(postgresDistinctIds).toHaveLength(2)
|
|
|
|
const newDatastoreDistinctIdValues = await datastore.fetchDistinctIds(person)
|
|
expect(newDatastoreDistinctIdValues).toMatchObject([
|
|
{
|
|
distinct_id: 'distinct1',
|
|
person_id: person.uuid,
|
|
team_id: teamId,
|
|
version: 0,
|
|
is_deleted: 0,
|
|
},
|
|
{
|
|
distinct_id: 'distinct2',
|
|
person_id: person.uuid,
|
|
team_id: teamId,
|
|
version: 0,
|
|
is_deleted: 0,
|
|
},
|
|
])
|
|
|
|
expect(person).toEqual(postgresPersons[0])
|
|
})
|
|
|
|
test('updatePerson', async () => {
|
|
const uuid = new UUIDT().toString()
|
|
const result = await personRepository.createPerson(
|
|
DateTime.utc(),
|
|
{ userProp: 'propValue' },
|
|
{ userProp: PropertyUpdateOperation.Set },
|
|
{},
|
|
teamId,
|
|
null,
|
|
false,
|
|
uuid,
|
|
{ distinctId: 'distinct1' },
|
|
[{ distinctId: 'distinct2' }]
|
|
)
|
|
if (!result.success) {
|
|
throw new Error('Failed to create person')
|
|
}
|
|
const person = result.person
|
|
const kafkaMessages = result.messages
|
|
|
|
await Promise.all(
|
|
kafkaMessages.map((msg) =>
|
|
createPersonOutputs(kafkaProducer).produce(msg.output, { value: msg.value, key: null })
|
|
)
|
|
)
|
|
|
|
await datastore.delayUntilEventIngested(() => datastore.fetchPersons(teamId))
|
|
await datastore.delayUntilEventIngested(() => datastore.fetchDistinctIdValues(person), 2)
|
|
|
|
// update properties and set is_identified to true
|
|
const [_p, kafkaMessagesUpdate] = await personRepository.updatePerson(
|
|
person,
|
|
createPersonUpdateFields(person, {
|
|
properties: { replacedUserProp: 'propValue' },
|
|
is_identified: true,
|
|
})
|
|
)
|
|
await Promise.all(
|
|
kafkaMessagesUpdate.map((msg) =>
|
|
createPersonOutputs(kafkaProducer).produce(msg.output, { value: msg.value, key: null })
|
|
)
|
|
)
|
|
|
|
await datastore.delayUntilEventIngested(async () =>
|
|
(await datastore.fetchPersons(teamId)).filter((p) => p.is_identified)
|
|
)
|
|
|
|
const datastorePersons = await datastore.fetchPersons(teamId)
|
|
const postgresPersons = await fetchPersons(postgres, teamId)
|
|
|
|
expect(datastorePersons.filter((p) => p.team_id.toString() === teamId.toString()).length).toEqual(1)
|
|
expect(postgresPersons.filter((p) => p.team_id.toString() === teamId.toString()).length).toEqual(1)
|
|
|
|
expect(postgresPersons[0].is_identified).toEqual(true)
|
|
expect(postgresPersons[0].version).toEqual(1)
|
|
expect(postgresPersons[0].properties).toEqual({ replacedUserProp: 'propValue' })
|
|
|
|
expect(datastorePersons[0].is_identified).toEqual(1)
|
|
expect(datastorePersons[0].is_deleted).toEqual(0)
|
|
expect(datastorePersons[0].properties).toEqual('{"replacedUserProp":"propValue"}')
|
|
|
|
// update date and boolean to false
|
|
|
|
const randomDate = DateTime.utc().minus(100000).setZone('UTC')
|
|
const [updatedPerson, kafkaMessages2] = await personRepository.updatePerson(
|
|
person,
|
|
createPersonUpdateFields(person, {
|
|
created_at: randomDate,
|
|
is_identified: false,
|
|
})
|
|
)
|
|
|
|
await Promise.all(
|
|
kafkaMessages2.map((msg) =>
|
|
createPersonOutputs(kafkaProducer).produce(msg.output, { value: msg.value, key: null })
|
|
)
|
|
)
|
|
|
|
expect(updatedPerson.version).toEqual(2)
|
|
|
|
await datastore.delayUntilEventIngested(async () =>
|
|
(await datastore.fetchPersons(teamId)).filter((p) => !p.is_identified)
|
|
)
|
|
|
|
const datastorePersons2 = await datastore.fetchPersons(teamId)
|
|
const postgresPersons2 = await fetchPersons(postgres, teamId)
|
|
|
|
expect(datastorePersons2.length).toEqual(1)
|
|
expect(postgresPersons2.length).toEqual(1)
|
|
|
|
expect(postgresPersons2[0].is_identified).toEqual(false)
|
|
expect(postgresPersons2[0].created_at.toISO()).toEqual(randomDate.toISO())
|
|
|
|
expect(datastorePersons2[0].is_identified).toEqual(0)
|
|
expect(datastorePersons2[0].created_at).toEqual(
|
|
// TODO: get rid of `+ '.000'` by removing the need for DatastoreSecondPrecision on CH persons
|
|
castTimestampOrNow(randomDate, TimestampFormat.DatastoreSecondPrecision) + '.000'
|
|
)
|
|
})
|
|
|
|
test('addDistinctId', async () => {
|
|
const uuid = new UUIDT().toString()
|
|
const uuid2 = new UUIDT().toString()
|
|
const result = await personRepository.createPerson(
|
|
DateTime.utc(),
|
|
{ userProp: 'propValue' },
|
|
{ userProp: PropertyUpdateOperation.Set },
|
|
{},
|
|
teamId,
|
|
null,
|
|
true,
|
|
uuid,
|
|
{ distinctId: 'distinct1' }
|
|
)
|
|
if (!result.success) {
|
|
throw new Error('Failed to create person')
|
|
}
|
|
const person = result.person
|
|
|
|
await Promise.all(
|
|
result.messages.map((msg) =>
|
|
createPersonOutputs(kafkaProducer).produce(msg.output, { value: msg.value, key: null })
|
|
)
|
|
)
|
|
await kafkaProducer.flush()
|
|
|
|
const result2 = await personRepository.createPerson(
|
|
DateTime.utc(),
|
|
{ userProp: 'propValue' },
|
|
{ userProp: PropertyUpdateOperation.Set },
|
|
{},
|
|
teamId,
|
|
null,
|
|
true,
|
|
uuid2,
|
|
{ distinctId: 'another_distinct_id' }
|
|
)
|
|
if (!result2.success) {
|
|
throw new Error('Failed to create person')
|
|
}
|
|
const anotherPerson = result2.person
|
|
const anotherPersonKafkaMessages = result2.messages
|
|
|
|
await Promise.all(
|
|
anotherPersonKafkaMessages.map((msg) =>
|
|
createPersonOutputs(kafkaProducer).produce(msg.output, { value: msg.value, key: null })
|
|
)
|
|
)
|
|
await kafkaProducer.flush()
|
|
|
|
await datastore.delayUntilEventIngested(() => datastore.fetchPersons(teamId))
|
|
const [postgresPerson] = await fetchPersons(postgres, teamId)
|
|
|
|
await datastore.delayUntilEventIngested(() => datastore.fetchDistinctIds(postgresPerson), 1)
|
|
const datastoreDistinctIdValues = await datastore.fetchDistinctIdValues(postgresPerson)
|
|
const postgresDistinctIdValues = await fetchDistinctIdValues(postgres, postgresPerson)
|
|
|
|
// check that all is in the right format
|
|
|
|
expect(datastoreDistinctIdValues).toEqual(['distinct1'])
|
|
expect(postgresDistinctIdValues).toEqual(['distinct1'])
|
|
|
|
const postgresDistinctIds = await fetchDistinctIds(postgres, postgresPerson)
|
|
const newDatastoreDistinctIdValues = await datastore.fetchDistinctIds(postgresPerson)
|
|
|
|
expect(postgresDistinctIds).toEqual([
|
|
expect.objectContaining({
|
|
distinct_id: 'distinct1',
|
|
person_id: person.id,
|
|
team_id: teamId,
|
|
version: '0',
|
|
}),
|
|
])
|
|
expect(newDatastoreDistinctIdValues).toMatchObject([
|
|
{
|
|
distinct_id: 'distinct1',
|
|
person_id: person.uuid,
|
|
team_id: teamId,
|
|
version: 0,
|
|
is_deleted: 0,
|
|
},
|
|
])
|
|
|
|
// add 'anotherOne' to person
|
|
|
|
const kafkaMessagesAddDistinctId = await personRepository.addDistinctId(postgresPerson, 'anotherOne', 0)
|
|
await Promise.all(
|
|
kafkaMessagesAddDistinctId.map((msg) =>
|
|
createPersonOutputs(kafkaProducer).produce(msg.output, { value: msg.value, key: null })
|
|
)
|
|
)
|
|
|
|
await datastore.delayUntilEventIngested(() => datastore.fetchDistinctIdValues(postgresPerson), 2)
|
|
|
|
const datastoreDistinctIdValues2 = await datastore.fetchDistinctIdValues(postgresPerson)
|
|
const postgresDistinctIdValues2 = await fetchDistinctIdValues(postgres, postgresPerson)
|
|
|
|
expect(datastoreDistinctIdValues2).toEqual(['distinct1', 'anotherOne'])
|
|
expect(postgresDistinctIdValues2).toEqual(['distinct1', 'anotherOne'])
|
|
|
|
// check anotherPerson for their initial distinct id
|
|
|
|
const datastoreDistinctIdValuesOther = await datastore.fetchDistinctIdValues(anotherPerson)
|
|
const postgresDistinctIdValuesOther = await fetchDistinctIdValues(postgres, anotherPerson)
|
|
|
|
expect(datastoreDistinctIdValuesOther).toEqual(['another_distinct_id'])
|
|
expect(postgresDistinctIdValuesOther).toEqual(['another_distinct_id'])
|
|
})
|
|
|
|
test('moveDistinctIds & deletePerson', async () => {
|
|
const uuid = new UUIDT().toString()
|
|
const uuid2 = new UUIDT().toString()
|
|
const result = await personRepository.createPerson(
|
|
DateTime.utc(),
|
|
{ userProp: 'propValue' },
|
|
{ userProp: PropertyUpdateOperation.Set },
|
|
{},
|
|
teamId,
|
|
null,
|
|
false,
|
|
uuid,
|
|
{ distinctId: 'distinct1' }
|
|
)
|
|
if (!result.success) {
|
|
throw new Error('Failed to create person')
|
|
}
|
|
const person = result.person
|
|
await Promise.all(
|
|
result.messages.map((msg) =>
|
|
createPersonOutputs(kafkaProducer).produce(msg.output, { value: msg.value, key: null })
|
|
)
|
|
)
|
|
await kafkaProducer.flush()
|
|
|
|
const result2 = await personRepository.createPerson(
|
|
DateTime.utc(),
|
|
{ userProp: 'propValue' },
|
|
{ userProp: PropertyUpdateOperation.Set },
|
|
{},
|
|
teamId,
|
|
null,
|
|
true,
|
|
uuid2,
|
|
{ distinctId: 'another_distinct_id' }
|
|
)
|
|
if (!result2.success) {
|
|
throw new Error('Failed to create person')
|
|
}
|
|
const anotherPerson = result2.person
|
|
const kafkaMessagesAnotherPerson = result2.messages
|
|
|
|
await Promise.all(
|
|
kafkaMessagesAnotherPerson.map((msg) =>
|
|
createPersonOutputs(kafkaProducer).produce(msg.output, { value: msg.value, key: null })
|
|
)
|
|
)
|
|
await kafkaProducer.flush()
|
|
|
|
await datastore.delayUntilEventIngested(() => datastore.fetchPersons(teamId))
|
|
const [postgresPerson] = await fetchPersons(postgres, teamId)
|
|
|
|
await datastore.delayUntilEventIngested(() => datastore.fetchDistinctIdValues(postgresPerson), 1)
|
|
|
|
// move distinct ids from person to to anotherPerson
|
|
const moveDistinctIdsResult = await personRepository.moveDistinctIds(person, anotherPerson, undefined)
|
|
expect(moveDistinctIdsResult.success).toEqual(true)
|
|
|
|
if (moveDistinctIdsResult.success) {
|
|
await Promise.all(
|
|
moveDistinctIdsResult.messages.map((msg) =>
|
|
createPersonOutputs(kafkaProducer!).produce(msg.output, { value: msg.value, key: null })
|
|
)
|
|
)
|
|
}
|
|
await datastore.delayUntilEventIngested(() => datastore.fetchDistinctIdValues(anotherPerson), 2)
|
|
|
|
// it got added
|
|
|
|
// :TODO: Update version
|
|
const datastoreDistinctIdValuesMoved = await datastore.fetchDistinctIdValues(anotherPerson)
|
|
const postgresDistinctIdValuesMoved = await fetchDistinctIdValues(postgres, anotherPerson)
|
|
const newDatastoreDistinctIdValues = await datastore.delayUntilEventIngested(
|
|
() => datastore.fetchDistinctIds(anotherPerson),
|
|
2
|
|
)
|
|
|
|
expect(postgresDistinctIdValuesMoved).toEqual(expect.arrayContaining(['distinct1', 'another_distinct_id']))
|
|
expect(datastoreDistinctIdValuesMoved).toEqual(expect.arrayContaining(['distinct1', 'another_distinct_id']))
|
|
expect(newDatastoreDistinctIdValues).toMatchObject([
|
|
{
|
|
distinct_id: 'another_distinct_id',
|
|
person_id: anotherPerson.uuid,
|
|
team_id: teamId,
|
|
version: 0,
|
|
is_deleted: 0,
|
|
},
|
|
{
|
|
distinct_id: 'distinct1',
|
|
person_id: anotherPerson.uuid,
|
|
team_id: teamId,
|
|
version: 1,
|
|
is_deleted: 0,
|
|
},
|
|
])
|
|
|
|
// it got removed
|
|
|
|
const datastoreDistinctIdValuesRemoved = await datastore.fetchDistinctIdValues(postgresPerson)
|
|
const postgresDistinctIdValuesRemoved = await fetchDistinctIdValues(postgres, postgresPerson)
|
|
const newDatastoreDistinctIdRemoved = await datastore.fetchDistinctIds(postgresPerson)
|
|
|
|
expect(datastoreDistinctIdValuesRemoved).toEqual([])
|
|
expect(postgresDistinctIdValuesRemoved).toEqual([])
|
|
expect(newDatastoreDistinctIdRemoved).toEqual([])
|
|
|
|
// delete person
|
|
await postgres.transaction(PostgresUse.PERSONS_WRITE, '', async (client) => {
|
|
const deletePersonMessage = await personRepository.deletePerson(person, client)
|
|
await createPersonOutputs(kafkaProducer!).produce(deletePersonMessage[0].output, {
|
|
value: deletePersonMessage[0].value,
|
|
key: null,
|
|
})
|
|
})
|
|
|
|
await datastore.delayUntilEventIngested(async () =>
|
|
(await datastore.fetchPersons(teamId)).length === 1 ? ['deleted!'] : []
|
|
)
|
|
const datastorePersons = await datastore.fetchPersons(teamId)
|
|
const postgresPersons = await fetchPersons(postgres, teamId)
|
|
|
|
expect(datastorePersons.length).toEqual(1)
|
|
expect(postgresPersons.length).toEqual(1)
|
|
})
|
|
})
|