-
-
Notifications
You must be signed in to change notification settings - Fork 735
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: improve logic for syncing counts
- Loading branch information
Showing
6 changed files
with
242 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
142 changes: 142 additions & 0 deletions
142
src/lib/features/unique-connection/unique-connection-service.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
import { UniqueConnectionService } from './unique-connection-service'; | ||
import { FakeUniqueConnectionStore } from './fake-unique-connection-store'; | ||
import getLogger from '../../../test/fixtures/no-logger'; | ||
import type { IFlagResolver } from '../../types'; | ||
import { SDK_CONNECTION_ID_RECEIVED } from '../../metric-events'; | ||
import { addHours } from 'date-fns'; | ||
import { EventEmitter } from 'events'; | ||
|
||
const alwaysOnFlagResolver = { | ||
isEnabled() { | ||
return true; | ||
}, | ||
} as unknown as IFlagResolver; | ||
|
||
test('sync first current bucket', async () => { | ||
const eventBus = new EventEmitter(); | ||
const config = { flagResolver: alwaysOnFlagResolver, getLogger, eventBus }; | ||
const uniqueConnectionStore = new FakeUniqueConnectionStore(); | ||
const uniqueConnectionService = new UniqueConnectionService( | ||
{ uniqueConnectionStore }, | ||
config, | ||
); | ||
uniqueConnectionService.listen(); | ||
|
||
eventBus.emit(SDK_CONNECTION_ID_RECEIVED, 'connection1'); | ||
eventBus.emit(SDK_CONNECTION_ID_RECEIVED, 'connection1'); | ||
eventBus.emit(SDK_CONNECTION_ID_RECEIVED, 'connection2'); | ||
eventBus.emit(SDK_CONNECTION_ID_RECEIVED, 'connection2'); | ||
eventBus.emit(SDK_CONNECTION_ID_RECEIVED, 'connection2'); | ||
|
||
await uniqueConnectionService.sync(); | ||
|
||
const stats = await uniqueConnectionService.getStats(); | ||
expect(stats).toEqual({ previous: 0, current: 2 }); | ||
}); | ||
|
||
test('sync first previous bucket', async () => { | ||
const eventBus = new EventEmitter(); | ||
const config = { flagResolver: alwaysOnFlagResolver, getLogger, eventBus }; | ||
const uniqueConnectionStore = new FakeUniqueConnectionStore(); | ||
const uniqueConnectionService = new UniqueConnectionService( | ||
{ uniqueConnectionStore }, | ||
config, | ||
); | ||
uniqueConnectionService.listen(); | ||
|
||
eventBus.emit(SDK_CONNECTION_ID_RECEIVED, 'connection1'); | ||
eventBus.emit(SDK_CONNECTION_ID_RECEIVED, 'connection2'); | ||
|
||
await uniqueConnectionService.sync(); | ||
|
||
eventBus.emit(SDK_CONNECTION_ID_RECEIVED, 'connection3'); | ||
|
||
await uniqueConnectionService.sync(() => addHours(new Date(), 1)); | ||
|
||
const stats = await uniqueConnectionService.getStats(); | ||
expect(stats).toEqual({ previous: 3, current: 0 }); | ||
}); | ||
|
||
test('sync to existing current bucket from the same service', async () => { | ||
const eventBus = new EventEmitter(); | ||
const config = { flagResolver: alwaysOnFlagResolver, getLogger, eventBus }; | ||
const uniqueConnectionStore = new FakeUniqueConnectionStore(); | ||
const uniqueConnectionService = new UniqueConnectionService( | ||
{ uniqueConnectionStore }, | ||
config, | ||
); | ||
uniqueConnectionService.listen(); | ||
|
||
eventBus.emit(SDK_CONNECTION_ID_RECEIVED, 'connection1'); | ||
eventBus.emit(SDK_CONNECTION_ID_RECEIVED, 'connection2'); | ||
|
||
await uniqueConnectionService.sync(); | ||
|
||
eventBus.emit(SDK_CONNECTION_ID_RECEIVED, 'connection1'); | ||
eventBus.emit(SDK_CONNECTION_ID_RECEIVED, 'connection3'); | ||
|
||
const stats = await uniqueConnectionService.getStats(); | ||
expect(stats).toEqual({ previous: 0, current: 3 }); | ||
}); | ||
|
||
test('sync to existing current bucket from another service', async () => { | ||
const eventBus = new EventEmitter(); | ||
const config = { | ||
flagResolver: alwaysOnFlagResolver, | ||
getLogger, | ||
eventBus: eventBus, | ||
}; | ||
const uniqueConnectionStore = new FakeUniqueConnectionStore(); | ||
const uniqueConnectionService1 = new UniqueConnectionService( | ||
{ uniqueConnectionStore }, | ||
config, | ||
); | ||
const uniqueConnectionService2 = new UniqueConnectionService( | ||
{ uniqueConnectionStore }, | ||
config, | ||
); | ||
|
||
uniqueConnectionService1.count('connection1'); | ||
uniqueConnectionService1.count('connection2'); | ||
await uniqueConnectionService1.sync(); | ||
|
||
uniqueConnectionService2.count('connection1'); | ||
uniqueConnectionService2.count('connection3'); | ||
await uniqueConnectionService2.sync(); | ||
|
||
const stats1 = await uniqueConnectionService1.getStats(); | ||
expect(stats1).toEqual({ previous: 0, current: 3 }); | ||
const stats2 = await uniqueConnectionService2.getStats(); | ||
expect(stats2).toEqual({ previous: 0, current: 3 }); | ||
}); | ||
|
||
test('sync to existing previous bucket from another service', async () => { | ||
const eventBus = new EventEmitter(); | ||
const config = { | ||
flagResolver: alwaysOnFlagResolver, | ||
getLogger, | ||
eventBus: eventBus, | ||
}; | ||
const uniqueConnectionStore = new FakeUniqueConnectionStore(); | ||
const uniqueConnectionService1 = new UniqueConnectionService( | ||
{ uniqueConnectionStore }, | ||
config, | ||
); | ||
const uniqueConnectionService2 = new UniqueConnectionService( | ||
{ uniqueConnectionStore }, | ||
config, | ||
); | ||
|
||
uniqueConnectionService1.count('connection1'); | ||
uniqueConnectionService1.count('connection2'); | ||
await uniqueConnectionService1.sync(() => addHours(new Date(), 1)); | ||
|
||
uniqueConnectionService2.count('connection1'); | ||
uniqueConnectionService2.count('connection3'); | ||
await uniqueConnectionService2.sync(() => addHours(new Date(), 1)); | ||
|
||
const stats1 = await uniqueConnectionService1.getStats(); | ||
expect(stats1).toEqual({ previous: 3, current: 0 }); | ||
const stats2 = await uniqueConnectionService2.getStats(); | ||
expect(stats1).toEqual({ previous: 3, current: 0 }); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
58 changes: 58 additions & 0 deletions
58
src/lib/features/unique-connection/unique-connection-store.e2e.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import dbInit, { type ITestDb } from '../../../test/e2e/helpers/database-init'; | ||
import getLogger from '../../../test/fixtures/no-logger'; | ||
import type { | ||
IUniqueConnectionStore, | ||
IUnleashStores, | ||
} from '../../../lib/types'; | ||
import HyperLogLog from 'hyperloglog-lite'; | ||
|
||
let stores: IUnleashStores; | ||
let db: ITestDb; | ||
let uniqueConnectionStore: IUniqueConnectionStore; | ||
|
||
beforeAll(async () => { | ||
db = await dbInit('unique_connections_store', getLogger); | ||
stores = db.stores; | ||
uniqueConnectionStore = stores.uniqueConnectionStore; | ||
}); | ||
|
||
afterAll(async () => { | ||
await db.destroy(); | ||
}); | ||
|
||
beforeEach(async () => { | ||
await uniqueConnectionStore.deleteAll(); | ||
}); | ||
|
||
test('should store empty HyperLogLog buffer', async () => { | ||
const hll = HyperLogLog(12); | ||
await uniqueConnectionStore.insert({ | ||
id: 'current', | ||
hll: hll.output().buckets, | ||
}); | ||
|
||
const fetchedHll = await uniqueConnectionStore.get('current'); | ||
hll.merge({ n: 12, buckets: fetchedHll!.hll }); | ||
expect(hll.count()).toBe(0); | ||
}); | ||
|
||
test('should store non empty HyperLogLog buffer', async () => { | ||
const hll = HyperLogLog(12); | ||
hll.add(HyperLogLog.hash('connection-1')); | ||
hll.add(HyperLogLog.hash('connection-2')); | ||
await uniqueConnectionStore.insert({ | ||
id: 'current', | ||
hll: hll.output().buckets, | ||
}); | ||
|
||
const fetchedHll = await uniqueConnectionStore.get('current'); | ||
const emptyHll = HyperLogLog(12); | ||
emptyHll.merge({ n: 12, buckets: fetchedHll!.hll }); | ||
expect(hll.count()).toBe(2); | ||
}); | ||
|
||
test('should indicate when no entry', async () => { | ||
const fetchedHll = await uniqueConnectionStore.get('current'); | ||
|
||
expect(fetchedHll).toBeNull(); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters