From 66b57da8863bb8248de830df0dff874de45250bd Mon Sep 17 00:00:00 2001 From: Alexey Yarmosh Date: Fri, 8 Dec 2023 16:37:37 +0100 Subject: [PATCH] feat: replace magic with id in locations field with actual location --- src/measurement/runner.ts | 6 +- src/measurement/schema/utils.ts | 8 +- src/measurement/store.ts | 48 ++--- src/measurement/types.ts | 9 +- src/probe/router.ts | 58 +++--- .../measurement/create-measurement.test.ts | 6 +- test/tests/unit/measurement/runner.test.ts | 123 ++++++----- test/tests/unit/probe/router.test.ts | 195 ++++++++++++------ 8 files changed, 265 insertions(+), 188 deletions(-) diff --git a/src/measurement/runner.ts b/src/measurement/runner.ts index 34d134f6..56a3ffb1 100644 --- a/src/measurement/runner.ts +++ b/src/measurement/runner.ts @@ -8,7 +8,7 @@ import type { Probe } from '../probe/types.js'; import { getMetricsAgent, type MetricsAgent } from '../lib/metrics.js'; import type { MeasurementStore } from './store.js'; import { getMeasurementStore } from './store.js'; -import type { MeasurementRequest, MeasurementResultMessage, MeasurementProgressMessage } from './types.js'; +import type { MeasurementRequest, MeasurementResultMessage, MeasurementProgressMessage, UserRequest } from './types.js'; import { rateLimit } from '../lib/ratelimiter.js'; export class MeasurementRunner { @@ -21,8 +21,8 @@ export class MeasurementRunner { ) {} async run (ctx: Context): Promise<{measurementId: string; probesCount: number;}> { - const request = ctx.request.body as MeasurementRequest; - const { onlineProbesMap, allProbes } = await this.router.findMatchingProbes(request.locations, request.limit); + const userRequest = ctx.request.body as UserRequest; + const { onlineProbesMap, allProbes, request } = await this.router.findMatchingProbes(userRequest); if (allProbes.length === 0) { throw createHttpError(422, 'No suitable probes found.', { type: 'no_probes_found' }); diff --git a/src/measurement/schema/utils.ts b/src/measurement/schema/utils.ts index 59ff09a1..42ef12f5 100644 --- a/src/measurement/schema/utils.ts +++ b/src/measurement/schema/utils.ts @@ -52,7 +52,13 @@ export const globalIpOptions: {version: string[]; cidr: PresenceMode} = { versio export const GLOBAL_DEFAULTS = { locations: [], - limit: (request: MeasurementRequest) => request.locations.length || 1, + limit: (request: MeasurementRequest) => { + if (typeof request.locations === 'string') { + return 1; + } + + return request.locations?.length || 1; + }, inProgressUpdates: false, }; diff --git a/src/measurement/store.ts b/src/measurement/store.ts index c1892c06..58c0616d 100644 --- a/src/measurement/store.ts +++ b/src/measurement/store.ts @@ -5,7 +5,7 @@ import type { OfflineProbe, Probe } from '../probe/types.js'; import type { RedisClient } from '../lib/redis/client.js'; import { getRedisClient } from '../lib/redis/client.js'; import { scopedLogger } from '../lib/logger.js'; -import type { MeasurementRecord, MeasurementResult, MeasurementRequest, MeasurementProgressMessage, RequestType, MeasurementResultMessage, LocationWithLimit } from './types.js'; +import type { MeasurementRecord, MeasurementResult, MeasurementRequest, MeasurementProgressMessage, RequestType, MeasurementResultMessage } from './types.js'; import { getDefaults } from './schema/utils.js'; const logger = scopedLogger('store'); @@ -57,45 +57,14 @@ export class MeasurementStore { return ips || []; } - async getLimitAndLocations (id: string) { - const response = await this.redis.json.get(getMeasurementKey(id), { path: [ '$.limit', '$.locations' ] }) as { - '$.limit': number[]; - '$.locations': LocationWithLimit[][]; - } | null; - - return { - limit: response ? response['$.limit'][0] : undefined, - locations: response ? response['$.locations'][0] : undefined, - }; - } - async createMeasurement (request: MeasurementRequest, onlineProbesMap: Map, allProbes: (Probe | OfflineProbe)[]): Promise { const id = cryptoRandomString({ length: 16, type: 'alphanumeric' }); const key = getMeasurementKey(id); const probesAwaitingTtl = config.get('measurement.timeout') + 5; const startTime = new Date(); - const measurement = await this.getInitialMeasurement(id, request, allProbes, startTime); - - await Promise.all([ - this.redis.hSet('gp:in-progress', id, startTime.getTime()), - this.redis.set(getMeasurementKey(id, 'probes_awaiting'), onlineProbesMap.size, { EX: probesAwaitingTtl }), - this.redis.json.set(key, '$', measurement), - this.redis.expire(key, config.get('measurement.resultTTL')), - this.redis.json.set(getMeasurementKey(id, 'ips'), '$', allProbes.map(probe => probe.ipAddress)), - this.redis.expire(getMeasurementKey(id, 'ips'), config.get('measurement.resultTTL')), - ]); - - return id; - } - - async getInitialMeasurement (id: string, request: MeasurementRequest, allProbes: (Probe | OfflineProbe)[], startTime: Date) { const results = this.probesToResults(allProbes, request.type); - const { limit, locations } = typeof request.locations === 'string' - ? await this.getLimitAndLocations(request.locations) - : { limit: request.limit, locations: request.locations }; - const measurement: Partial = { id, type: request.type, @@ -103,15 +72,24 @@ export class MeasurementStore { createdAt: startTime.toISOString(), updatedAt: startTime.toISOString(), target: request.target, - ...(limit && { limit }), + ...(request.limit && { limit: request.limit }), probesCount: allProbes.length, - ...(locations && { locations }), + ...(request.locations && { locations: request.locations }), measurementOptions: request.measurementOptions, results, }; const measurementWithoutDefaults = this.removeDefaults(measurement, request); - return measurementWithoutDefaults; + await Promise.all([ + this.redis.hSet('gp:in-progress', id, startTime.getTime()), + this.redis.set(getMeasurementKey(id, 'probes_awaiting'), onlineProbesMap.size, { EX: probesAwaitingTtl }), + this.redis.json.set(key, '$', measurementWithoutDefaults), + this.redis.expire(key, config.get('measurement.resultTTL')), + this.redis.json.set(getMeasurementKey(id, 'ips'), '$', allProbes.map(probe => probe.ipAddress)), + this.redis.expire(getMeasurementKey(id, 'ips'), config.get('measurement.resultTTL')), + ]); + + return id; } async storeMeasurementProgress (data: MeasurementProgressMessage): Promise { diff --git a/src/measurement/types.ts b/src/measurement/types.ts index 0def41ab..90fa3997 100644 --- a/src/measurement/types.ts +++ b/src/measurement/types.ts @@ -170,12 +170,17 @@ export type LocationWithLimit = Location & {limit?: number}; * Measurement Objects */ +export type UserRequest = Omit & { + locations: LocationWithLimit[] | string; + limit: number; +} + export type MeasurementRequest = { type: 'ping' | 'traceroute' | 'dns' | 'http' | 'mtr'; target: string; measurementOptions: MeasurementOptions; - locations: LocationWithLimit[] | string; - limit: number; + locations: LocationWithLimit[] | undefined; + limit: number | undefined; inProgressUpdates: boolean; }; diff --git a/src/probe/router.ts b/src/probe/router.ts index de62bcce..7a8bf64b 100644 --- a/src/probe/router.ts +++ b/src/probe/router.ts @@ -1,6 +1,6 @@ import _ from 'lodash'; import { fetchSockets } from '../lib/ws/fetch-sockets.js'; -import type { LocationWithLimit, MeasurementRecord, MeasurementResult } from '../measurement/types.js'; +import type { LocationWithLimit, MeasurementRequest, MeasurementResult, UserRequest } from '../measurement/types.js'; import type { Location } from '../lib/location/types.js'; import type { OfflineProbe, Probe } from './types.js'; import { ProbesLocationFilter } from './probes-location-filter.js'; @@ -15,38 +15,43 @@ export class ProbeRouter { private readonly store: MeasurementStore, ) {} - public async findMatchingProbes ( - locations: LocationWithLimit[] | string = [], - globalLimit = 1, - ) { + public async findMatchingProbes (userRequest: UserRequest): Promise<{ + onlineProbesMap: Map; + allProbes: (Probe | OfflineProbe)[]; + request: MeasurementRequest; + }> { + const locations = userRequest.locations ?? []; + const globalLimit = userRequest.limit ?? 1; + const connectedProbes = await this.fetchProbes(); if (typeof locations === 'string') { - return this.findWithMeasurementId(connectedProbes, locations); + return this.findWithMeasurementId(connectedProbes, locations, userRequest); } if (locations.some(l => l.limit)) { const filtered = this.findWithLocationLimit(connectedProbes, locations); - return this.processFiltered(filtered, connectedProbes, locations); + return this.processFiltered(filtered, connectedProbes, locations, userRequest); } if (locations.length > 0) { const filtered = this.findWithGlobalLimit(connectedProbes, locations, globalLimit); - return this.processFiltered(filtered, connectedProbes, locations); + return this.processFiltered(filtered, connectedProbes, locations, userRequest); } const filtered = this.findGloballyDistributed(connectedProbes, globalLimit); - return this.processFiltered(filtered, connectedProbes, locations); + return this.processFiltered(filtered, connectedProbes, locations, userRequest); } - private async processFiltered (filtered: Probe[], connectedProbes: Probe[], locations: LocationWithLimit[]) { + private async processFiltered (filtered: Probe[], connectedProbes: Probe[], locations: LocationWithLimit[], request: UserRequest) { if (filtered.length === 0 && locations.length === 1 && locations[0]?.magic) { - return this.findWithMeasurementId(connectedProbes, locations[0].magic); + return this.findWithMeasurementId(connectedProbes, locations[0].magic, request); } return { allProbes: filtered, onlineProbesMap: new Map(filtered.entries()), + request: request as MeasurementRequest, }; } @@ -89,19 +94,28 @@ export class ProbeRouter { return [ ...picked ]; } - private async findWithMeasurementId (connectedProbes: Probe[], measurementId: string) { + private async findWithMeasurementId (connectedProbes: Probe[], measurementId: string, userRequest: UserRequest): Promise<{ + onlineProbesMap: Map; + allProbes: (Probe | OfflineProbe)[]; + request: MeasurementRequest; + }> { const ipToConnectedProbe = new Map(connectedProbes.map(probe => [ probe.ipAddress, probe ])); const prevIps = await this.store.getMeasurementIps(measurementId); + const prevMeasurement = await this.store.getMeasurement(measurementId); - const onlineProbesMap: Map = new Map(); - const allProbes: (Probe | OfflineProbe)[] = []; - - const emptyResult = { onlineProbesMap: new Map(), allProbes: [] } as { + const emptyResult = { onlineProbesMap: new Map(), allProbes: [], request: userRequest } as { onlineProbesMap: Map; allProbes: (Probe | OfflineProbe)[]; + request: MeasurementRequest; }; - let prevMeasurement: MeasurementRecord | null = null; + if (!prevMeasurement || prevIps.length === 0) { + return emptyResult; + } + + const request: MeasurementRequest = { ...userRequest, limit: prevMeasurement.limit, locations: prevMeasurement.locations }; + const onlineProbesMap: Map = new Map(); + const allProbes: (Probe | OfflineProbe)[] = []; for (let i = 0; i < prevIps.length; i++) { const ip = prevIps[i]!; @@ -111,14 +125,6 @@ export class ProbeRouter { onlineProbesMap.set(i, connectedProbe); allProbes.push(connectedProbe); } else { - if (!prevMeasurement) { - prevMeasurement = await this.store.getMeasurement(measurementId); - - if (!prevMeasurement) { - return emptyResult; - } - } - const prevTest = prevMeasurement.results[i]; if (!prevTest) { @@ -130,7 +136,7 @@ export class ProbeRouter { } } - return { onlineProbesMap, allProbes }; + return { onlineProbesMap, allProbes, request }; } private testToOfflineProbe = (test: MeasurementResult, ip: string): OfflineProbe => ({ diff --git a/test/tests/integration/measurement/create-measurement.test.ts b/test/tests/integration/measurement/create-measurement.test.ts index d4b7838b..6dcd2512 100644 --- a/test/tests/integration/measurement/create-measurement.test.ts +++ b/test/tests/integration/measurement/create-measurement.test.ts @@ -529,6 +529,8 @@ describe('Create measurement', () => { .send({ type: 'ping', target: 'example.com', + limit: 2, + locations: [{ country: 'US' }], }) .expect(202) .expect((response) => { @@ -554,8 +556,8 @@ describe('Create measurement', () => { await requestAgent.get(`/v1/measurements/${id2}`) .expect(200) .expect((response) => { - expect(response.body.limit).to.not.exist; - expect(response.body.locations).to.not.exist; + expect(response.body.limit).to.equal(2); + expect(response.body.locations).to.deep.equal([{ country: 'US' }]); expect(response).to.matchApiSchema(); }); }); diff --git a/test/tests/unit/measurement/runner.test.ts b/test/tests/unit/measurement/runner.test.ts index 2021ab13..60954e77 100644 --- a/test/tests/unit/measurement/runner.test.ts +++ b/test/tests/unit/measurement/runner.test.ts @@ -45,32 +45,39 @@ describe('MeasurementRunner', () => { testId = 0; }); + after(() => { + td.reset(); + }); + it('should run measurement for the required amount of probes', async () => { + const request = { + type: 'ping' as const, + target: 'jsdelivr.com', + measurementOptions: { + packets: 3, + }, + locations: [], + limit: 10, + inProgressUpdates: false, + }; + router.findMatchingProbes.resolves({ onlineProbesMap: new Map([ getProbe(0), getProbe(1), getProbe(2), getProbe(3) ].entries()), allProbes: [ getProbe(0), getProbe(1), getProbe(2), getProbe(3) ], + request, }); await runner.run({ set, req, request: { - body: { - type: 'ping', - target: 'jsdelivr.com', - measurementOptions: { - packets: 3, - }, - locations: [], - limit: 10, - inProgressUpdates: false, - }, + body: request, }, } as unknown as Context); expect(router.findMatchingProbes.callCount).to.equal(1); - expect(router.findMatchingProbes.args[0]).to.deep.equal([ [], 10 ]); + expect(router.findMatchingProbes.args[0]).to.deep.equal([ request ]); expect(store.createMeasurement.callCount).to.equal(1); expect(store.createMeasurement.args[0]).to.deep.equal([ @@ -145,31 +152,34 @@ describe('MeasurementRunner', () => { }); it('should send `inProgressUpdates: true` to the first N probes if requested', async () => { + const request = { + type: 'ping'as const, + target: 'jsdelivr.com', + measurementOptions: { + packets: 3, + }, + locations: [], + limit: 10, + inProgressUpdates: true, + }; + router.findMatchingProbes.resolves({ onlineProbesMap: new Map([ getProbe(0), getProbe(1), getProbe(2), getProbe(3) ].entries()), allProbes: [ getProbe(0), getProbe(1), getProbe(2), getProbe(3) ], + request, }); await runner.run({ set, req, request: { - body: { - type: 'ping', - target: 'jsdelivr.com', - measurementOptions: { - packets: 3, - }, - locations: [], - limit: 10, - inProgressUpdates: true, - }, + body: request, }, } as unknown as Context); expect(router.findMatchingProbes.callCount).to.equal(1); - expect(router.findMatchingProbes.args[0]).to.deep.equal([ [], 10 ]); + expect(router.findMatchingProbes.args[0]).to.deep.equal([ request ]); expect(store.createMeasurement.callCount).to.equal(1); expect(store.createMeasurement.args[0]).to.deep.equal([ @@ -257,25 +267,28 @@ describe('MeasurementRunner', () => { }); it('should call ratelimiter with the number of online probes', async () => { + const request = { + type: 'ping' as const, + target: 'jsdelivr.com', + measurementOptions: { + packets: 3, + }, + locations: [], + limit: 10, + inProgressUpdates: false, + }; + router.findMatchingProbes.resolves({ onlineProbesMap: new Map([ getProbe(0) ].entries()), allProbes: [ getProbe(0), getProbe(1) ], + request, }); const ctx = { set, req, request: { - body: { - type: 'ping', - target: 'jsdelivr.com', - measurementOptions: { - packets: 3, - }, - locations: [], - limit: 10, - inProgressUpdates: false, - }, + body: request, }, } as unknown as Context; @@ -286,25 +299,28 @@ describe('MeasurementRunner', () => { }); it('should throw 422 error if no probes found', async () => { + const request = { + type: 'ping' as const, + target: 'jsdelivr.com', + measurementOptions: { + packets: 3, + }, + locations: [], + limit: 10, + inProgressUpdates: false, + }; + router.findMatchingProbes.resolves({ onlineProbesMap: new Map([].entries()), allProbes: [], + request, }); const err = await runner.run({ set, req, request: { - body: { - type: 'ping', - target: 'jsdelivr.com', - measurementOptions: { - packets: 3, - }, - locations: [], - limit: 10, - inProgressUpdates: false, - }, + body: request, }, } as unknown as Context).catch((err: unknown) => err); expect(err).to.deep.equal(createHttpError(422, 'No suitable probes found.', { type: 'no_probes_found' })); @@ -312,25 +328,28 @@ describe('MeasurementRunner', () => { }); it('should immideately call store.markFinished if there are no online probes', async () => { + const request = { + type: 'ping' as const, + target: 'jsdelivr.com', + measurementOptions: { + packets: 3, + }, + locations: [], + limit: 10, + inProgressUpdates: false, + }; + router.findMatchingProbes.resolves({ onlineProbesMap: new Map([].entries()), allProbes: [ getProbe(0) ], + request, }); await runner.run({ set, req, request: { - body: { - type: 'ping', - target: 'jsdelivr.com', - measurementOptions: { - packets: 3, - }, - locations: [], - limit: 10, - inProgressUpdates: false, - }, + body: request, }, } as unknown as Context); diff --git a/test/tests/unit/probe/router.test.ts b/test/tests/unit/probe/router.test.ts index d4bb5667..c14b78b8 100644 --- a/test/tests/unit/probe/router.test.ts +++ b/test/tests/unit/probe/router.test.ts @@ -10,6 +10,7 @@ import type { DeepPartial } from '../../../types.js'; import type { Probe, ProbeLocation } from '../../../../src/probe/types.js'; import type { Location } from '../../../../src/lib/location/types.js'; import type { MeasurementStore } from '../../../../src/measurement/store.js'; +import type { UserRequest } from '../../../../src/measurement/types.js'; const defaultLocation = { continent: '', @@ -71,7 +72,6 @@ describe('probe router', () => { beforeEach(() => { sandbox.reset(); sinon.resetHistory(); - sinon.resetBehavior(); store.getMeasurementIps.resolves([]); }); @@ -91,11 +91,14 @@ describe('probe router', () => { ]; fetchSocketsMock.resolves(sockets as never); - const { onlineProbesMap, allProbes } = await router.findMatchingProbes([ + const userRequest = { locations: [ { country: 'UA', limit: 2 }, { country: 'PL', limit: 2 }, - ]); + ] } as UserRequest; + + const { onlineProbesMap, allProbes, request } = await router.findMatchingProbes(userRequest); + expect(request).to.equal(userRequest); expect(onlineProbesMap.size).to.equal(4); expect(fetchSocketsMock.calledOnce).to.be.true; expect(fetchSocketsMock.firstCall.args).to.deep.equal([]); @@ -116,10 +119,10 @@ describe('probe router', () => { fetchSocketsMock.resolves(sockets as never); - const { onlineProbesMap, allProbes } = await router.findMatchingProbes([ + const { onlineProbesMap, allProbes } = await router.findMatchingProbes({ locations: [ { country: 'UA', limit: 2 }, { country: 'PL' }, - ]); + ] } as UserRequest); expect(fetchSocketsMock.calledOnce).to.be.true; expect(fetchSocketsMock.firstCall.args).to.deep.equal([]); @@ -130,6 +133,28 @@ describe('probe router', () => { expect(allProbes.filter(p => p.location.country === 'PL').length).to.equal(1); }); + it('should return 1 probe if global limit is not set', async () => { + const sockets: Array> = [ + await buildSocket('socket-1', { continent: 'EU', country: 'UA' }), + await buildSocket('socket-2', { continent: 'EU', country: 'PL' }), + await buildSocket('socket-3', { continent: 'EU', country: 'PL' }), + await buildSocket('socket-4', { continent: 'NA', country: 'UA' }), + await buildSocket('socket-5', { continent: 'EU', country: 'PL' }), + ]; + + fetchSocketsMock.resolves(sockets as never); + + const userRequest = { locations: [] } as unknown as UserRequest; + const { onlineProbesMap, allProbes, request } = await router.findMatchingProbes(userRequest); + + expect(request).to.equal(userRequest); + expect(fetchSocketsMock.calledOnce).to.be.true; + expect(fetchSocketsMock.firstCall.args).to.deep.equal([]); + + expect(onlineProbesMap.size).to.equal(1); + expect(allProbes.length).to.equal(1); + }); + it('should shuffle result probes', async () => { const sockets: Array> = [ await buildSocket('socket-1', { continent: 'EU', country: 'UA' }), @@ -145,12 +170,12 @@ describe('probe router', () => { ]; fetchSocketsMock.resolves(sockets as never); - const { allProbes: probes1, onlineProbesMap: online1 } = await router.findMatchingProbes([ + const { allProbes: probes1, onlineProbesMap: online1 } = await router.findMatchingProbes({ locations: [ { continent: 'EU', limit: 10 }, - ]); - const { allProbes: probes2, onlineProbesMap: online2 } = await router.findMatchingProbes([ + ] } as UserRequest); + const { allProbes: probes2, onlineProbesMap: online2 } = await router.findMatchingProbes({ locations: [ { continent: 'EU', limit: 10 }, - ]); + ] } as UserRequest); expect(fetchSocketsMock.calledTwice).to.be.true; expect(online1.size).to.equal(10); @@ -174,10 +199,10 @@ describe('probe router', () => { fetchSocketsMock.resolves(sockets as never); - const { onlineProbesMap, allProbes } = await router.findMatchingProbes([ + const { onlineProbesMap, allProbes } = await router.findMatchingProbes({ locations: [ { country: 'GB', limit: 2 }, { country: 'PL', limit: 2 }, - ]); + ] } as UserRequest); expect(fetchSocketsMock.calledOnce).to.be.true; expect(fetchSocketsMock.firstCall.args).to.deep.equal([]); @@ -196,7 +221,7 @@ describe('probe router', () => { fetchSocketsMock.resolves(sockets as never); - const { onlineProbesMap, allProbes } = await router.findMatchingProbes([], 100); + const { onlineProbesMap, allProbes } = await router.findMatchingProbes({ locations: [], limit: 100 } as unknown as UserRequest); const grouped = _.groupBy(allProbes, 'location.continent'); expect(onlineProbesMap.size).to.equal(100); @@ -221,7 +246,7 @@ describe('probe router', () => { fetchSocketsMock.resolves(sockets as never); - const { onlineProbesMap, allProbes } = await router.findMatchingProbes([], 100); + const { onlineProbesMap, allProbes } = await router.findMatchingProbes({ locations: [], limit: 100 } as unknown as UserRequest); const grouped = _.groupBy(allProbes, 'location.continent'); expect(allProbes.length).to.equal(100); @@ -243,7 +268,7 @@ describe('probe router', () => { fetchSocketsMock.resolves(sockets as never); - const { onlineProbesMap, allProbes } = await router.findMatchingProbes([{ continent: 'AF' }, { continent: 'NA' }, { continent: 'SA' }], 7); + const { onlineProbesMap, allProbes } = await router.findMatchingProbes({ locations: [{ continent: 'AF' }, { continent: 'NA' }, { continent: 'SA' }], limit: 7 } as UserRequest); expect(allProbes.length).to.equal(7); expect(onlineProbesMap.size).to.equal(7); }); @@ -258,7 +283,7 @@ describe('probe router', () => { fetchSocketsMock.resolves(sockets as never); - const { onlineProbesMap, allProbes } = await router.findMatchingProbes([], 100); + const { onlineProbesMap, allProbes } = await router.findMatchingProbes({ locations: [], limit: 100 } as unknown as UserRequest); const grouped = _.groupBy(allProbes, 'location.continent'); expect(allProbes.length).to.equal(65); @@ -284,8 +309,8 @@ describe('probe router', () => { ]; fetchSocketsMock.resolves(sockets as never); - const { allProbes: probes1 } = await router.findMatchingProbes([], 100); - const { allProbes: probes2 } = await router.findMatchingProbes([], 100); + const { allProbes: probes1 } = await router.findMatchingProbes({ locations: [], limit: 100 } as unknown as UserRequest); + const { allProbes: probes2 } = await router.findMatchingProbes({ locations: [], limit: 100 } as unknown as UserRequest); expect(fetchSocketsMock.calledTwice).to.be.true; expect(probes1.length).to.equal(10); @@ -337,7 +362,7 @@ describe('probe router', () => { fetchSocketsMock.resolves(sockets as never); - const { onlineProbesMap, allProbes } = await router.findMatchingProbes(locations, 100); + const { onlineProbesMap, allProbes } = await router.findMatchingProbes({ locations, limit: 100 } as unknown as UserRequest); const grouped = _.groupBy(allProbes, 'location.country'); expect(allProbes.length).to.equal(100); @@ -360,7 +385,7 @@ describe('probe router', () => { fetchSocketsMock.resolves(sockets as never); - const { onlineProbesMap, allProbes } = await router.findMatchingProbes(locations, 100); + const { onlineProbesMap, allProbes } = await router.findMatchingProbes({ locations, limit: 100 } as unknown as UserRequest); const grouped = _.groupBy(allProbes, 'location.country'); expect(allProbes.length).to.equal(100); @@ -385,12 +410,8 @@ describe('probe router', () => { ]; fetchSocketsMock.resolves(sockets as never); - const { allProbes: probes1, onlineProbesMap: online1 } = await router.findMatchingProbes([ - { continent: 'EU' }, - ], 100); - const { allProbes: probes2, onlineProbesMap: online2 } = await router.findMatchingProbes([ - { continent: 'EU' }, - ], 100); + const { allProbes: probes1, onlineProbesMap: online1 } = await router.findMatchingProbes({ locations: [{ continent: 'EU' }], limit: 100 } as unknown as UserRequest); + const { allProbes: probes2, onlineProbesMap: online2 } = await router.findMatchingProbes({ locations: [{ continent: 'EU' }], limit: 100 } as unknown as UserRequest); expect(fetchSocketsMock.calledTwice).to.be.true; expect(probes1.length).to.equal(10); @@ -426,7 +447,7 @@ describe('probe router', () => { fetchSocketsMock.resolves(sockets as never); - const { onlineProbesMap, allProbes } = await router.findMatchingProbes(locations, 100); + const { onlineProbesMap, allProbes } = await router.findMatchingProbes({ locations, limit: 100 } as unknown as UserRequest); expect(allProbes.length).to.equal(1); expect(onlineProbesMap.size).to.equal(1); @@ -439,10 +460,10 @@ describe('probe router', () => { ]; fetchSocketsMock.resolves(sockets as never); - const { onlineProbesMap, allProbes } = await router.findMatchingProbes([{ continent: 'NA' }], 100); + const { onlineProbesMap, allProbes } = await router.findMatchingProbes({ locations: [{ continent: 'NA' }], limit: 100 } as unknown as UserRequest); expect(allProbes.length).to.equal(1); expect(onlineProbesMap.size).to.equal(1); - const { allProbes: probes2 } = await router.findMatchingProbes([{ continent: 'North America' }], 100); + const { allProbes: probes2 } = await router.findMatchingProbes({ locations: [{ continent: 'North America' }], limit: 100 } as unknown as UserRequest); expect(probes2.length).to.equal(0); }); @@ -453,10 +474,10 @@ describe('probe router', () => { fetchSocketsMock.resolves(sockets as never); - const { onlineProbesMap, allProbes } = await router.findMatchingProbes([{ region: 'Northern Africa' }], 100); + const { onlineProbesMap, allProbes } = await router.findMatchingProbes({ locations: [{ region: 'Northern Africa' }], limit: 100 } as unknown as UserRequest); expect(allProbes.length).to.equal(1); expect(onlineProbesMap.size).to.equal(1); - const { allProbes: probes2 } = await router.findMatchingProbes([{ region: 'North Africa' }], 100); + const { allProbes: probes2 } = await router.findMatchingProbes({ locations: [{ region: 'North Africa' }], limit: 100 } as unknown as UserRequest); expect(probes2.length).to.equal(0); }); }); @@ -480,7 +501,7 @@ describe('probe router', () => { fetchSocketsMock.resolves(sockets as never); - const { onlineProbesMap, allProbes } = await router.findMatchingProbes([{ magic: 'europe' }], 100); + const { onlineProbesMap, allProbes } = await router.findMatchingProbes({ locations: [{ magic: 'europe' }], limit: 100 } as unknown as UserRequest); expect(allProbes.length).to.equal(1); expect(onlineProbesMap.size).to.equal(1); @@ -494,7 +515,7 @@ describe('probe router', () => { fetchSocketsMock.resolves(sockets as never); - const { onlineProbesMap, allProbes } = await router.findMatchingProbes([{ magic: 'north africa' }], 100); + const { onlineProbesMap, allProbes } = await router.findMatchingProbes({ locations: [{ magic: 'north africa' }], limit: 100 } as unknown as UserRequest); expect(allProbes.length).to.equal(1); expect(onlineProbesMap.size).to.equal(1); @@ -508,7 +529,7 @@ describe('probe router', () => { fetchSocketsMock.resolves(sockets as never); - const { onlineProbesMap, allProbes } = await router.findMatchingProbes([{ magic: 'south africa' }], 100); + const { onlineProbesMap, allProbes } = await router.findMatchingProbes({ locations: [{ magic: 'south africa' }], limit: 100 } as unknown as UserRequest); expect(allProbes.length).to.equal(0); expect(onlineProbesMap.size).to.equal(0); @@ -525,7 +546,7 @@ describe('probe router', () => { fetchSocketsMock.resolves(sockets as never); - const { onlineProbesMap, allProbes } = await router.findMatchingProbes(locations, 100); + const { onlineProbesMap, allProbes } = await router.findMatchingProbes({ locations, limit: 100 } as unknown as UserRequest); expect(allProbes.length).to.equal(1); expect(onlineProbesMap.size).to.equal(1); @@ -543,7 +564,7 @@ describe('probe router', () => { fetchSocketsMock.resolves(sockets as never); - const { onlineProbesMap, allProbes } = await router.findMatchingProbes(locations, 100); + const { onlineProbesMap, allProbes } = await router.findMatchingProbes({ locations, limit: 100 } as unknown as UserRequest); expect(allProbes.length).to.equal(1); expect(onlineProbesMap.size).to.equal(1); @@ -558,9 +579,9 @@ describe('probe router', () => { ]; fetchSocketsMock.resolves(sockets as never); - const { onlineProbesMap, allProbes } = await router.findMatchingProbes([ + const { onlineProbesMap, allProbes } = await router.findMatchingProbes({ locations: [ { magic: 'd' }, - ], 100); + ], limit: 100 } as unknown as UserRequest); expect(fetchSocketsMock.calledOnce).to.be.true; expect(allProbes.length).to.equal(3); @@ -578,9 +599,9 @@ describe('probe router', () => { ]; fetchSocketsMock.resolves(sockets as never); - const { onlineProbesMap, allProbes } = await router.findMatchingProbes([ + const { onlineProbesMap, allProbes } = await router.findMatchingProbes({ locations: [ { magic: 'vn' }, - ], 100); + ], limit: 100 } as unknown as UserRequest); expect(fetchSocketsMock.calledOnce).to.be.true; expect(allProbes.length).to.equal(1); @@ -596,9 +617,9 @@ describe('probe router', () => { ]; fetchSocketsMock.resolves(sockets as never); - const { onlineProbesMap, allProbes } = await router.findMatchingProbes([ + const { onlineProbesMap, allProbes } = await router.findMatchingProbes({ locations: [ { magic: 'wars' }, - ], 100); + ], limit: 100 } as unknown as UserRequest); expect(fetchSocketsMock.calledOnce).to.be.true; expect(allProbes.length).to.equal(1); @@ -613,9 +634,9 @@ describe('probe router', () => { ]; fetchSocketsMock.resolves(sockets as never); - const { onlineProbesMap, allProbes } = await router.findMatchingProbes([ + const { onlineProbesMap, allProbes } = await router.findMatchingProbes({ locations: [ { magic: 'york' }, - ], 100); + ], limit: 100 } as unknown as UserRequest); expect(fetchSocketsMock.calledOnce).to.be.true; expect(allProbes.length).to.equal(1); @@ -658,12 +679,12 @@ describe('probe router', () => { ]; fetchSocketsMock.resolves(sockets as never); - const { allProbes: probes1, onlineProbesMap: online1 } = await router.findMatchingProbes([ + const { allProbes: probes1, onlineProbesMap: online1 } = await router.findMatchingProbes({ locations: [ { magic: 'd' }, - ], 100); - const { allProbes: probes2, onlineProbesMap: online2 } = await router.findMatchingProbes([ + ], limit: 100 } as unknown as UserRequest); + const { allProbes: probes2, onlineProbesMap: online2 } = await router.findMatchingProbes({ locations: [ { magic: 'd' }, - ], 100); + ], limit: 100 } as unknown as UserRequest); expect(fetchSocketsMock.calledTwice).to.be.true; expect(probes1.length).to.equal(30); @@ -702,12 +723,12 @@ describe('probe router', () => { ]; fetchSocketsMock.resolves(sockets as never); - const { allProbes: probes1, onlineProbesMap: online1 } = await router.findMatchingProbes([ + const { allProbes: probes1, onlineProbesMap: online1 } = await router.findMatchingProbes({ locations: [ { magic: 'de' }, - ], 100); - const { allProbes: probes2, onlineProbesMap: online2 } = await router.findMatchingProbes([ + ], limit: 100 } as unknown as UserRequest); + const { allProbes: probes2, onlineProbesMap: online2 } = await router.findMatchingProbes({ locations: [ { magic: 'de' }, - ], 100); + ], limit: 100 } as unknown as UserRequest); expect(fetchSocketsMock.calledTwice).to.be.true; expect(probes1.length).to.equal(10); @@ -732,7 +753,7 @@ describe('probe router', () => { fetchSocketsMock.resolves(sockets as never); - const { onlineProbesMap, allProbes } = await router.findMatchingProbes(locations, 100); + const { onlineProbesMap, allProbes } = await router.findMatchingProbes({ locations, limit: 100 } as unknown as UserRequest); expect(allProbes.length).to.equal(1); expect(onlineProbesMap.size).to.equal(1); @@ -754,7 +775,7 @@ describe('probe router', () => { fetchSocketsMock.resolves(sockets as never); - const { onlineProbesMap, allProbes } = await router.findMatchingProbes(locations, 100); + const { onlineProbesMap, allProbes } = await router.findMatchingProbes({ locations, limit: 100 } as unknown as UserRequest); expect(allProbes.length).to.equal(1); expect(onlineProbesMap.size).to.equal(1); @@ -786,7 +807,7 @@ describe('probe router', () => { fetchSocketsMock.resolves(sockets as never); - const { onlineProbesMap, allProbes } = await router.findMatchingProbes(locations, 100); + const { onlineProbesMap, allProbes } = await router.findMatchingProbes({ locations, limit: 100 } as unknown as UserRequest); expect(allProbes.length).to.equal(1); expect(onlineProbesMap.size).to.equal(1); @@ -809,7 +830,7 @@ describe('probe router', () => { fetchSocketsMock.resolves(sockets as never); - const { onlineProbesMap, allProbes } = await router.findMatchingProbes(locations, 100); + const { onlineProbesMap, allProbes } = await router.findMatchingProbes({ locations, limit: 100 } as unknown as UserRequest); expect(allProbes.length).to.equal(1); expect(onlineProbesMap.size).to.equal(1); @@ -842,7 +863,7 @@ describe('probe router', () => { fetchSocketsMock.resolves(sockets as never); - const { onlineProbesMap, allProbes } = await router.findMatchingProbes(locations, 100); + const { onlineProbesMap, allProbes } = await router.findMatchingProbes({ locations, limit: 100 } as unknown as UserRequest); expect(allProbes.length).to.equal(1); expect(onlineProbesMap.size).to.equal(1); @@ -861,7 +882,7 @@ describe('probe router', () => { fetchSocketsMock.resolves(sockets as never); - const { onlineProbesMap, allProbes } = await router.findMatchingProbes(locations, 100); + const { onlineProbesMap, allProbes } = await router.findMatchingProbes({ locations, limit: 100 } as unknown as UserRequest); expect(allProbes.length).to.equal(0); expect(onlineProbesMap.size).to.equal(0); }); @@ -881,7 +902,7 @@ describe('probe router', () => { fetchSocketsMock.resolves(sockets as never); - const { onlineProbesMap, allProbes } = await router.findMatchingProbes(locations, 100); + const { onlineProbesMap, allProbes } = await router.findMatchingProbes({ locations, limit: 100 } as unknown as UserRequest); expect(allProbes.length).to.equal(1); expect(onlineProbesMap.size).to.equal(1); expect(allProbes[0]!.location.country).to.equal('GB'); @@ -908,10 +929,11 @@ describe('probe router', () => { }], }); - const { onlineProbesMap, allProbes } = await router.findMatchingProbes('measurementid'); + const { onlineProbesMap, allProbes, request } = await router.findMatchingProbes({ locations: 'measurementid' } as UserRequest); + expect(request).to.deep.equal({ limit: undefined, locations: undefined }); expect(store.getMeasurementIps.args[0]).to.deep.equal([ 'measurementid' ]); - expect(store.getMeasurement.callCount).to.equal(0); + expect(store.getMeasurement.args[0]).to.deep.equal([ 'measurementid' ]); expect(allProbes[0]!.location.country).to.equal('PL'); expect(allProbes[0]!.status).to.equal('ready'); expect(onlineProbesMap.get(0)?.location.country).to.equal('PL'); @@ -936,10 +958,11 @@ describe('probe router', () => { }], }); - const { onlineProbesMap, allProbes } = await router.findMatchingProbes([{ magic: 'measurementid' }]); + const { onlineProbesMap, allProbes, request } = await router.findMatchingProbes({ locations: [{ magic: 'measurementid' }] } as UserRequest); + expect(request).to.deep.equal({ limit: undefined, locations: undefined }); expect(store.getMeasurementIps.args[0]).to.deep.equal([ 'measurementid' ]); - expect(store.getMeasurement.callCount).to.equal(0); + expect(store.getMeasurement.args[0]).to.deep.equal([ 'measurementid' ]); expect(allProbes[0]!.location.country).to.equal('PL'); expect(allProbes[0]!.status).to.equal('ready'); expect(onlineProbesMap.get(0)?.location.country).to.equal('PL'); @@ -951,14 +974,51 @@ describe('probe router', () => { ]; fetchSocketsMock.resolves(sockets as never); - const { onlineProbesMap, allProbes } = await router.findMatchingProbes([{ magic: 'measurementid' }]); + const { onlineProbesMap, allProbes, request } = await router.findMatchingProbes({ locations: [{ magic: 'measurementid' }] } as UserRequest); + expect(request).to.deep.equal({ locations: [{ magic: 'measurementid' }] }); expect(store.getMeasurementIps.args[0]).to.deep.equal([ 'measurementid' ]); - expect(store.getMeasurement.callCount).to.equal(0); + expect(store.getMeasurement.args[0]).to.deep.equal([ 'measurementid' ]); expect(allProbes.length).to.equal(0); expect(onlineProbesMap.size).to.equal(0); }); + it('should return proper values for locations and limit in request object', async () => { + const sockets: Array> = [ + await buildSocket('socket-1', { continent: 'EU', country: 'PL' }), + ]; + fetchSocketsMock.resolves(sockets as never); + store.getMeasurementIps.resolves([ '1.2.3.4', '1.2.3.4' ]); + + store.getMeasurement.resolves({ + limit: 2, + locations: [{ + continent: 'EU', + }], + results: [{ + probe: { + continent: 'EU', + country: 'PL', + city: 'Warsaw', + network: 'Liberty Global B.V.', + tags: [], + }, + }, { + probe: { + continent: 'EU', + country: 'PL', + city: 'Warsaw', + network: 'Liberty Global B.V.', + tags: [], + }, + }], + }); + + const { request } = await router.findMatchingProbes({ locations: [{ magic: 'measurementid' }] } as UserRequest); + + expect(request).to.deep.equal({ locations: [{ continent: 'EU' }], limit: 2 }); + }); + it('should replace non-connected probes with offline probe data', async () => { const sockets: Array> = [ await buildSocket('socket-1', { continent: 'EU', country: 'PL' }), @@ -978,8 +1038,9 @@ describe('probe router', () => { }], }); - const { onlineProbesMap, allProbes } = await router.findMatchingProbes('measurementid'); + const { onlineProbesMap, allProbes, request } = await router.findMatchingProbes({ locations: 'measurementid' } as UserRequest); + expect(request).to.deep.equal({ locations: undefined, limit: undefined }); expect(store.getMeasurementIps.args[0]).to.deep.equal([ 'measurementid' ]); expect(store.getMeasurement.args[0]).to.deep.equal([ 'measurementid' ]); expect(allProbes.length).to.equal(1); @@ -1007,7 +1068,7 @@ describe('probe router', () => { }], }); - const { onlineProbesMap, allProbes } = await router.findMatchingProbes('measurementid'); + const { onlineProbesMap, allProbes } = await router.findMatchingProbes({ locations: 'measurementid' } as UserRequest); expect(store.getMeasurementIps.args[0]).to.deep.equal([ 'measurementid' ]); expect(allProbes.length).to.equal(0); @@ -1023,7 +1084,7 @@ describe('probe router', () => { store.getMeasurement.resolves(null); - const { onlineProbesMap, allProbes } = await router.findMatchingProbes('measurementid'); + const { onlineProbesMap, allProbes } = await router.findMatchingProbes({ locations: 'measurementid' } as UserRequest); expect(store.getMeasurementIps.args[0]).to.deep.equal([ 'measurementid' ]); expect(store.getMeasurement.args[0]).to.deep.equal([ 'measurementid' ]);