diff --git a/src/lib/ratelimiter.ts b/src/lib/ratelimiter.ts index fa5e9db1..560561cd 100644 --- a/src/lib/ratelimiter.ts +++ b/src/lib/ratelimiter.ts @@ -21,7 +21,7 @@ const setRateLimitHeaders = (ctx: Context, result: RateLimiterRes) => { ctx.set('X-RateLimit-Remaining', `${result.remainingPoints}`); }; -export const checkRateLimit = async (ctx: Context, numberOfProbes: number) => { +export const rateLimit = async (ctx: Context, numberOfProbes: number) => { if (ctx['isAdmin']) { return; } diff --git a/src/measurement/runner.ts b/src/measurement/runner.ts index f5a615f5..34d134f6 100644 --- a/src/measurement/runner.ts +++ b/src/measurement/runner.ts @@ -9,13 +9,14 @@ 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 { checkRateLimit } from '../lib/ratelimiter.js'; +import { rateLimit } from '../lib/ratelimiter.js'; export class MeasurementRunner { constructor ( private readonly io: Server, private readonly store: MeasurementStore, private readonly router: ProbeRouter, + private readonly checkRateLimit: typeof rateLimit, private readonly metrics: MetricsAgent, ) {} @@ -27,7 +28,7 @@ export class MeasurementRunner { throw createHttpError(422, 'No suitable probes found.', { type: 'no_probes_found' }); } - await checkRateLimit(ctx, onlineProbesMap.size); + await this.checkRateLimit(ctx, onlineProbesMap.size); const measurementId = await this.store.createMeasurement(request, onlineProbesMap, allProbes); @@ -80,7 +81,7 @@ let runner: MeasurementRunner; export const getMeasurementRunner = () => { if (!runner) { - runner = new MeasurementRunner(getWsServer(), getMeasurementStore(), getProbeRouter(), getMetricsAgent()); + runner = new MeasurementRunner(getWsServer(), getMeasurementStore(), getProbeRouter(), rateLimit, getMetricsAgent()); } return runner; diff --git a/src/measurement/store.ts b/src/measurement/store.ts index df9b719d..b5887687 100644 --- a/src/measurement/store.ts +++ b/src/measurement/store.ts @@ -48,8 +48,8 @@ export class MeasurementStore { return this.redis.sendCommand([ 'JSON.GET', getMeasurementKey(id) ]); } - async getMeasurement (id: string): Promise { - return await this.redis.json.get(getMeasurementKey(id)) as MeasurementRecord; + async getMeasurement (id: string) { + return await this.redis.json.get(getMeasurementKey(id)) as MeasurementRecord | null; } async getIpsByMeasurementId (id: string): Promise { diff --git a/src/measurement/types.ts b/src/measurement/types.ts index 6e1fbd8e..815228de 100644 --- a/src/measurement/types.ts +++ b/src/measurement/types.ts @@ -18,7 +18,7 @@ type PingTiming = { ttl: number; }; -type PingResult = TestResult & { +export type PingResult = TestResult & { timings: PingTiming[]; stats: { min: number; diff --git a/src/probe/router.ts b/src/probe/router.ts index 5ba8a120..f70bca04 100644 --- a/src/probe/router.ts +++ b/src/probe/router.ts @@ -83,7 +83,6 @@ export class ProbeRouter { private async findWithMeasurementId (connectedProbes: Probe[], measurementId: string) { const ipToConnectedProbe = new Map(connectedProbes.map(probe => [ probe.ipAddress, probe ])); - let prevMeasurement: MeasurementRecord | undefined; const prevIps = await this.store.getIpsByMeasurementId(measurementId); const onlineProbesMap: Map = new Map(); @@ -94,6 +93,8 @@ export class ProbeRouter { allProbes: (Probe | OfflineProbe)[]; }; + let prevMeasurement: MeasurementRecord | null = null; + for (let i = 0; i < prevIps.length; i++) { const ip = prevIps[i]!; const connectedProbe = ipToConnectedProbe.get(ip); diff --git a/test/tests/unit/measurement/runner.test.ts b/test/tests/unit/measurement/runner.test.ts index b00f3678..2021ab13 100644 --- a/test/tests/unit/measurement/runner.test.ts +++ b/test/tests/unit/measurement/runner.test.ts @@ -9,6 +9,7 @@ import { MetricsAgent } from '../../../../src/lib/metrics.js'; import type { Probe } from '../../../../src/probe/types.js'; import type { MeasurementRunner } from '../../../../src/measurement/runner.js'; import type { MeasurementRecord, MeasurementResultMessage } from '../../../../src/measurement/types.js'; +import createHttpError from 'http-errors'; const getProbe = (id: number) => ({ client: id } as unknown as Probe); @@ -26,24 +27,21 @@ describe('MeasurementRunner', () => { const store = sinon.createStubInstance(MeasurementStore); const router = sinon.createStubInstance(ProbeRouter); const metrics = sinon.createStubInstance(MetricsAgent); + const rateLimit = sinon.stub(); let runner: MeasurementRunner; let testId: number; before(async () => { td.replaceEsm('crypto-random-string', null, () => testId++); const { MeasurementRunner } = await import('../../../../src/measurement/runner.js'); - runner = new MeasurementRunner(io, store, router, metrics); + runner = new MeasurementRunner(io, store, router, rateLimit, metrics); }); beforeEach(() => { - emit.reset(); - to.reset(); + sinon.resetHistory(); to.returns({ emit }); io.of.returns({ to } as any); - router.findMatchingProbes.reset(); - store.createMeasurement.reset(); store.createMeasurement.resolves('measurementid'); - metrics.recordMeasurement.reset(); testId = 0; }); @@ -257,4 +255,86 @@ describe('MeasurementRunner', () => { expect(metrics.recordMeasurementTime.args[0]).to.deep.equal([ 'ping', 25000 ]); sandbox.restore(); }); + + it('should call ratelimiter with the number of online probes', async () => { + router.findMatchingProbes.resolves({ + onlineProbesMap: new Map([ getProbe(0) ].entries()), + allProbes: [ getProbe(0), getProbe(1) ], + }); + + const ctx = { + set, + req, + request: { + body: { + type: 'ping', + target: 'jsdelivr.com', + measurementOptions: { + packets: 3, + }, + locations: [], + limit: 10, + inProgressUpdates: false, + }, + }, + } as unknown as Context; + + await runner.run(ctx); + + expect(rateLimit.callCount).to.equal(1); + expect(rateLimit.args[0]).to.deep.equal([ ctx, 1 ]); + }); + + it('should throw 422 error if no probes found', async () => { + router.findMatchingProbes.resolves({ + onlineProbesMap: new Map([].entries()), + allProbes: [], + }); + + const err = await runner.run({ + set, + req, + request: { + body: { + type: 'ping', + target: 'jsdelivr.com', + measurementOptions: { + packets: 3, + }, + locations: [], + limit: 10, + inProgressUpdates: false, + }, + }, + } as unknown as Context).catch((err: unknown) => err); + expect(err).to.deep.equal(createHttpError(422, 'No suitable probes found.', { type: 'no_probes_found' })); + expect(store.markFinished.callCount).to.equal(0); + }); + + it('should immideately call store.markFinished if there are no online probes', async () => { + router.findMatchingProbes.resolves({ + onlineProbesMap: new Map([].entries()), + allProbes: [ getProbe(0) ], + }); + + await runner.run({ + set, + req, + request: { + body: { + type: 'ping', + target: 'jsdelivr.com', + measurementOptions: { + packets: 3, + }, + locations: [], + limit: 10, + inProgressUpdates: false, + }, + }, + } as unknown as Context); + + expect(store.markFinished.callCount).to.equal(1); + expect(store.markFinished.args[0]).to.deep.equal([ 'measurementid' ]); + }); }); diff --git a/test/tests/unit/measurement/store.test.ts b/test/tests/unit/measurement/store.test.ts index c85c0249..b6585a9e 100644 --- a/test/tests/unit/measurement/store.test.ts +++ b/test/tests/unit/measurement/store.test.ts @@ -2,9 +2,11 @@ import * as td from 'testdouble'; import { expect } from 'chai'; import * as sinon from 'sinon'; import type { MeasurementStore } from '../../../../src/measurement/store.js'; -import type { Probe } from '../../../../src/probe/types.js'; +import type { OfflineProbe, Probe } from '../../../../src/probe/types.js'; +import type { PingResult } from '../../../../src/measurement/types.js'; -const getProbe = (id: string) => ({ +const getProbe = (id: string, ip: string) => ({ + ipAddress: ip, location: { network: id, continent: 'continent', @@ -20,6 +22,11 @@ const getProbe = (id: string) => ({ resolvers: [], } as unknown as Probe); +const getOfflineProbe = (id: string, ip: string) => ({ + ...getProbe(id, ip), + status: 'offline', +} as unknown as OfflineProbe); + describe('measurement store', () => { let getMeasurementStore: () => MeasurementStore; let sandbox: sinon.SinonSandbox; @@ -35,6 +42,8 @@ describe('measurement store', () => { set: sinon.stub(), strAppend: sinon.stub(), }, + recordResult: sinon.stub(), + markFinished: sinon.stub(), }; before(async () => { @@ -46,14 +55,8 @@ describe('measurement store', () => { beforeEach(() => { sandbox = sinon.createSandbox({ useFakeTimers: { now: 1_678_000_000_000 } }); sandbox.stub(Math, 'random').returns(0.8); - redisMock.hScan.reset(); - redisMock.hDel.reset(); - redisMock.hSet.reset(); - redisMock.set.reset(); - redisMock.expire.reset(); - redisMock.json.mGet.reset(); - redisMock.json.set.reset(); - redisMock.json.strAppend.reset(); + redisMock.recordResult.reset(); + sinon.resetHistory(); }); afterEach(() => { @@ -116,6 +119,109 @@ describe('measurement store', () => { }]); }); + it('should store measurement probes in the same order as in arguments', async () => { + const store = getMeasurementStore(); + await store.createMeasurement( + { + type: 'ping', + measurementOptions: { packets: 3 }, + target: 'jsdelivr.com', + locations: [], + limit: 4, + inProgressUpdates: false, + }, + new Map([ getProbe('z', '1.1.1.1'), getProbe('10', '2.2.2.2'), getProbe('x', '3.3.3.3'), getProbe('0', '4.4.4.4') ].entries()), + [ getProbe('z', '1.1.1.1'), getProbe('10', '2.2.2.2'), getProbe('x', '3.3.3.3'), getProbe('0', '4.4.4.4') ], + ); + + expect(redisMock.hSet.callCount).to.equal(1); + expect(redisMock.hSet.args[0]).to.deep.equal([ 'gp:in-progress', 'measurementid', 1678000000000 ]); + expect(redisMock.set.callCount).to.equal(1); + expect(redisMock.set.args[0]).to.deep.equal([ 'gp:measurement:measurementid:probes_awaiting', 4, { EX: 35 }]); + expect(redisMock.json.set.callCount).to.equal(2); + + expect(redisMock.json.set.args[0]).to.deep.equal([ 'gp:measurement:measurementid', '$', { + id: 'measurementid', + type: 'ping', + status: 'in-progress', + createdAt: '2023-03-05T07:06:40.000Z', + updatedAt: '2023-03-05T07:06:40.000Z', + target: 'jsdelivr.com', + limit: 4, + probesCount: 4, + results: [{ + probe: { + continent: 'continent', + region: 'region', + country: 'country', + state: 'state', + city: 'city', + asn: 'asn', + longitude: 'longitude', + latitude: 'latitude', + network: 'z', + tags: [], + resolvers: [], + }, + result: { status: 'in-progress', rawOutput: '' }, + }, + { + probe: { + continent: 'continent', + region: 'region', + country: 'country', + state: 'state', + city: 'city', + asn: 'asn', + longitude: 'longitude', + latitude: 'latitude', + network: '10', + tags: [], + resolvers: [], + }, + result: { status: 'in-progress', rawOutput: '' }, + }, + { + probe: { + continent: 'continent', + region: 'region', + country: 'country', + state: 'state', + city: 'city', + asn: 'asn', + longitude: 'longitude', + latitude: 'latitude', + network: 'x', + tags: [], + resolvers: [], + }, + result: { status: 'in-progress', rawOutput: '' }, + }, + { + probe: { + continent: 'continent', + region: 'region', + country: 'country', + state: 'state', + city: 'city', + asn: 'asn', + longitude: 'longitude', + latitude: 'latitude', + network: '0', + tags: [], + resolvers: [], + }, + result: { status: 'in-progress', rawOutput: '' }, + }], + }]); + + expect(redisMock.expire.args[0]).to.deep.equal([ 'gp:measurement:measurementid', 604800 ]); + + expect(redisMock.json.set.args[1]).to.deep.equal([ 'gp:measurement:measurementid:ips', '$', [ '1.1.1.1', '2.2.2.2', '3.3.3.3', '4.4.4.4' ] ]); + + expect(redisMock.expire.args[1]).to.deep.equal([ 'gp:measurement:measurementid:ips', 604800 ]); + }); + it('should initialize measurement object with the proper default values', async () => { const store = getMeasurementStore(); await store.createMeasurement( @@ -127,8 +233,8 @@ describe('measurement store', () => { limit: 1, inProgressUpdates: false, }, - new Map([ [ 0, getProbe('id') ] ]), - [ getProbe('id') ], + new Map([ [ 0, getProbe('id', '1.1.1.1') ] ]), + [ getProbe('id', '1.1.1.1') ], ); expect(redisMock.json.set.firstCall.args).to.deep.equal([ @@ -183,8 +289,8 @@ describe('measurement store', () => { limit: 1, inProgressUpdates: false, }, - new Map([ [ 0, getProbe('id') ] ]), - [ getProbe('id') ], + new Map([ [ 0, getProbe('id', '1.1.1.1') ] ]), + [ getProbe('id', '1.1.1.1') ], ); expect(redisMock.json.set.firstCall.args).to.deep.equal([ @@ -225,7 +331,7 @@ describe('measurement store', () => { ]); }); - it('should store measurement probes in the same order as in arguments', async () => { + it('should initialize measurement object with the proper default in case of offline probes', async () => { const store = getMeasurementStore(); await store.createMeasurement( { @@ -233,96 +339,49 @@ describe('measurement store', () => { measurementOptions: { packets: 3 }, target: 'jsdelivr.com', locations: [], - limit: 4, + limit: 1, inProgressUpdates: false, }, - new Map([ getProbe('z'), getProbe('10'), getProbe('x'), getProbe('0') ].entries()), - [ getProbe('z'), getProbe('10'), getProbe('x'), getProbe('0') ], + new Map(), + [ getOfflineProbe('id', '1.1.1.1') ], ); - expect(redisMock.hSet.callCount).to.equal(1); - expect(redisMock.hSet.args[0]).to.deep.equal([ 'gp:in-progress', 'measurementid', 1678000000000 ]); - expect(redisMock.set.callCount).to.equal(1); - expect(redisMock.set.args[0]).to.deep.equal([ 'gp:measurement:measurementid:probes_awaiting', 4, { EX: 35 }]); - expect(redisMock.json.set.callCount).to.equal(2); - - expect(redisMock.json.set.args[0]).to.deep.equal([ 'gp:measurement:measurementid', '$', { - id: 'measurementid', - type: 'ping', - status: 'in-progress', - createdAt: '2023-03-05T07:06:40.000Z', - updatedAt: '2023-03-05T07:06:40.000Z', - target: 'jsdelivr.com', - limit: 4, - probesCount: 4, - results: [{ - probe: { - continent: 'continent', - region: 'region', - country: 'country', - state: 'state', - city: 'city', - asn: 'asn', - longitude: 'longitude', - latitude: 'latitude', - network: 'z', - tags: [], - resolvers: [], - }, - result: { status: 'in-progress', rawOutput: '' }, - }, - { - probe: { - continent: 'continent', - region: 'region', - country: 'country', - state: 'state', - city: 'city', - asn: 'asn', - longitude: 'longitude', - latitude: 'latitude', - network: '10', - tags: [], - resolvers: [], - }, - result: { status: 'in-progress', rawOutput: '' }, - }, + expect(redisMock.json.set.firstCall.args).to.deep.equal([ + 'gp:measurement:measurementid', + '$', { - probe: { - continent: 'continent', - region: 'region', - country: 'country', - state: 'state', - city: 'city', - asn: 'asn', - longitude: 'longitude', - latitude: 'latitude', - network: 'x', - tags: [], - resolvers: [], - }, - result: { status: 'in-progress', rawOutput: '' }, + id: 'measurementid', + type: 'ping', + status: 'in-progress', + createdAt: '2023-03-05T07:06:40.000Z', + updatedAt: '2023-03-05T07:06:40.000Z', + target: 'jsdelivr.com', + probesCount: 1, + results: [ + { + probe: { + continent: 'continent', + region: 'region', + country: 'country', + state: 'state', + city: 'city', + asn: 'asn', + longitude: 'longitude', + latitude: 'latitude', + network: 'id', + tags: [], + resolvers: [], + }, + result: { + status: 'failed', + rawOutput: 'This probe is currently offline. Please try again later.', + }, + }, + ], }, - { - probe: { - continent: 'continent', - region: 'region', - country: 'country', - state: 'state', - city: 'city', - asn: 'asn', - longitude: 'longitude', - latitude: 'latitude', - network: '0', - tags: [], - resolvers: [], - }, - result: { status: 'in-progress', rawOutput: '' }, - }], - }]); + ]); - expect(redisMock.expire.callCount).to.equal(2); - expect(redisMock.expire.args[0]).to.deep.equal([ 'gp:measurement:measurementid', 604800 ]); + expect(redisMock.set.args[0]).to.deep.equal([ 'gp:measurement:measurementid:probes_awaiting', 0, { EX: 35 }]); }); it('should store non-default fields of the measurement request', async () => { @@ -352,8 +411,8 @@ describe('measurement store', () => { limit: 2, inProgressUpdates: false, }, - new Map([ [ 0, getProbe('id') ] ]), - [ getProbe('id') ], + new Map([ [ 0, getProbe('id', '1.1.1.1') ] ]), + [ getProbe('id', '1.1.1.1') ], ); expect(redisMock.json.set.args[0]).to.deep.equal([ 'gp:measurement:measurementid', '$', { @@ -396,9 +455,6 @@ describe('measurement store', () => { }, }], }]); - - expect(redisMock.expire.callCount).to.equal(2); - expect(redisMock.expire.args[0]).to.deep.equal([ 'gp:measurement:measurementid', 604800 ]); }); it('shouldn\'t store fields of the measurement request which are equal to the default', async () => { @@ -420,8 +476,8 @@ describe('measurement store', () => { locations: [], inProgressUpdates: false, }, - new Map([ [ 0, getProbe('id') ] ]), - [ getProbe('id') ], + new Map([ [ 0, getProbe('id', '1.1.1.1') ] ]), + [ getProbe('id', '1.1.1.1') ], ); expect(redisMock.json.set.args[0]).to.deep.equal([ 'gp:measurement:measurementid', '$', { @@ -454,9 +510,6 @@ describe('measurement store', () => { }, }], }]); - - expect(redisMock.expire.callCount).to.equal(2); - expect(redisMock.expire.args[0]).to.deep.equal([ 'gp:measurement:measurementid', 604800 ]); }); it('should store rawHeaders and rawBody fields for the http in-progress updates', async () => { @@ -514,4 +567,51 @@ describe('measurement store', () => { expect(redisMock.json.strAppend.callCount).to.equal(0); expect(redisMock.json.set.callCount).to.equal(2); }); + + it('should mark measurement as finished if storeMeasurementResult returned record', async () => { + redisMock.recordResult.resolves({}); + + const store = getMeasurementStore(); + await store.storeMeasurementResult({ + testId: 'testid', + measurementId: 'measurementid', + result: { + status: 'finished', + rawOutput: 'output', + } as PingResult, + }); + + expect(redisMock.recordResult.callCount).to.equal(1); + + expect(redisMock.recordResult.args[0]).to.deep.equal([ + 'measurementid', + 'testid', + { status: 'finished', rawOutput: 'output' }, + ]); + + expect(redisMock.markFinished.callCount).to.equal(1); + expect(redisMock.markFinished.args[0]).to.deep.equal([ 'measurementid' ]); + }); + + it('should not mark measurement as finished if storeMeasurementResult didn\'t return record', async () => { + const store = getMeasurementStore(); + await store.storeMeasurementResult({ + testId: 'testid', + measurementId: 'measurementid', + result: { + status: 'finished', + rawOutput: 'output', + } as PingResult, + }); + + expect(redisMock.recordResult.callCount).to.equal(1); + + expect(redisMock.recordResult.args[0]).to.deep.equal([ + 'measurementid', + 'testid', + { status: 'finished', rawOutput: 'output' }, + ]); + + expect(redisMock.markFinished.callCount).to.equal(0); + }); }); diff --git a/test/tests/unit/probe/router.test.ts b/test/tests/unit/probe/router.test.ts index d66bae1f..89eecb97 100644 --- a/test/tests/unit/probe/router.test.ts +++ b/test/tests/unit/probe/router.test.ts @@ -32,8 +32,8 @@ describe('probe router', () => { const store = { getIpsByMeasurementId: sinon.stub(), getMeasurement: sinon.stub(), - } as unknown as MeasurementStore; - const router = new ProbeRouter(fetchSocketsMock, store); + }; + const router = new ProbeRouter(fetchSocketsMock, store as unknown as MeasurementStore); let buildProbe: (socket: RemoteProbeSocket) => Promise; let sandbox: sinon.SinonSandbox; @@ -853,4 +853,106 @@ describe('probe router', () => { expect(allProbes[0]!.location.country).to.equal('GB'); }); }); + + describe('route with measurement id string', async () => { + it('should find probes by prev measurement id', async () => { + const sockets: Array> = [ + await buildSocket('socket-1', { continent: 'EU', country: 'PL' }), + ]; + fetchSocketsMock.resolves(sockets as never); + store.getIpsByMeasurementId.resolves([ '1.2.3.4' ]); + + store.getMeasurement.resolves({ + results: [{ + probe: { + continent: 'EU', + country: 'PL', + city: 'Warsaw', + network: 'Liberty Global B.V.', + tags: [], + }, + }], + }); + + const { onlineProbesMap, allProbes } = await router.findMatchingProbes('measurementid'); + + expect(store.getIpsByMeasurementId.args[0]).to.deep.equal([ 'measurementid' ]); + expect(store.getMeasurement.callCount).to.equal(0); + expect(allProbes[0]!.location.country).to.equal('PL'); + expect(allProbes[0]!.status).to.equal('ready'); + expect(onlineProbesMap.get(0)?.location.country).to.equal('PL'); + }); + + it('should replace non-connected probes with offline probe data', async () => { + const sockets: Array> = [ + await buildSocket('socket-1', { continent: 'EU', country: 'PL' }), + ]; + fetchSocketsMock.resolves(sockets as never); + store.getIpsByMeasurementId.resolves([ '9.9.9.9' ]); + + store.getMeasurement.resolves({ + results: [{ + probe: { + continent: 'EU', + country: 'PL', + city: 'Warsaw', + network: 'Liberty Global B.V.', + tags: [], + }, + }], + }); + + const { onlineProbesMap, allProbes } = await router.findMatchingProbes('measurementid'); + + expect(store.getIpsByMeasurementId.args[0]).to.deep.equal([ 'measurementid' ]); + expect(store.getMeasurement.args[0]).to.deep.equal([ 'measurementid' ]); + expect(allProbes.length).to.equal(1); + expect(allProbes[0]!.location.country).to.equal('PL'); + expect(allProbes[0]!.status).to.equal('offline'); + expect(onlineProbesMap.size).to.equal(0); + }); + + it('should return empty data if measurement ips wasn\'t found', async () => { + const sockets: Array> = [ + await buildSocket('socket-1', { continent: 'EU', country: 'PL' }), + ]; + fetchSocketsMock.resolves(sockets as never); + store.getIpsByMeasurementId.resolves([]); + + store.getMeasurement.resolves({ + results: [{ + probe: { + continent: 'EU', + country: 'PL', + city: 'Warsaw', + network: 'Liberty Global B.V.', + tags: [], + }, + }], + }); + + const { onlineProbesMap, allProbes } = await router.findMatchingProbes('measurementid'); + + expect(store.getIpsByMeasurementId.args[0]).to.deep.equal([ 'measurementid' ]); + expect(allProbes.length).to.equal(0); + expect(onlineProbesMap.size).to.equal(0); + }); + + it('should return empty data if measurement itself wasn\'t found', async () => { + const sockets: Array> = [ + await buildSocket('socket-1', { continent: 'EU', country: 'PL' }), + ]; + fetchSocketsMock.resolves(sockets as never); + store.getIpsByMeasurementId.resolves([ '9.9.9.9' ]); + + store.getMeasurement.resolves(null); + + const { onlineProbesMap, allProbes } = await router.findMatchingProbes('measurementid'); + + expect(store.getIpsByMeasurementId.args[0]).to.deep.equal([ 'measurementid' ]); + expect(store.getMeasurement.args[0]).to.deep.equal([ 'measurementid' ]); + expect(allProbes.length).to.equal(0); + expect(onlineProbesMap.size).to.equal(0); + }); + }); });