From e6497209ec01bedbc74650694a4511171b452454 Mon Sep 17 00:00:00 2001 From: Alexey Yarmosh Date: Thu, 7 Dec 2023 17:47:00 +0100 Subject: [PATCH] feat: allow to pass measurement id in the magic field --- src/measurement/schema/location-schema.ts | 2 +- src/probe/probes-location-filter.ts | 4 +- src/probe/router.ts | 36 ++++++++----- .../measurement/create-measurement.test.ts | 37 +++++++++++++ .../unit/measurement/schema/schema.test.ts | 2 +- test/tests/unit/probe/router.test.ts | 53 ++++++++++++++++--- 6 files changed, 109 insertions(+), 25 deletions(-) diff --git a/src/measurement/schema/location-schema.ts b/src/measurement/schema/location-schema.ts index d4854f5c..11bf8967 100644 --- a/src/measurement/schema/location-schema.ts +++ b/src/measurement/schema/location-schema.ts @@ -25,7 +25,7 @@ export const schema = Joi.alternatives().try( city: Joi.string().min(1).max(128).lowercase().custom(normalizeValue), network: Joi.string().min(1).max(128).lowercase().custom(normalizeValue), asn: Joi.number().integer().positive(), - magic: Joi.string().min(1).lowercase().custom(normalizeValue), + magic: Joi.string().min(1).custom(normalizeValue), tags: Joi.array().items(Joi.string().min(1).max(128).lowercase().custom(normalizeValue)), limit: Joi.number().min(1).max(measurementConfig.limits.location).when(Joi.ref('/limit'), { is: Joi.exist(), diff --git a/src/probe/probes-location-filter.ts b/src/probe/probes-location-filter.ts index ba7e0b28..fe6f3382 100644 --- a/src/probe/probes-location-filter.ts +++ b/src/probe/probes-location-filter.ts @@ -42,11 +42,11 @@ export class ProbesLocationFilter { } static getExactIndexPosition (probe: Probe, value: string) { - return probe.index.findIndex(category => category.some(index => index === value.replaceAll('-', ' ').trim())); + return probe.index.findIndex(category => category.some(index => index === value.toLowerCase().replaceAll('-', ' ').trim())); } static getIndexPosition (probe: Probe, value: string) { - return probe.index.findIndex(category => category.some(index => index.includes(value.replaceAll('-', ' ').trim()))); + return probe.index.findIndex(category => category.some(index => index.includes(value.toLowerCase().replaceAll('-', ' ').trim()))); } static hasTag (probe: Probe, tag: string) { diff --git a/src/probe/router.ts b/src/probe/router.ts index 5b327f2f..de62bcce 100644 --- a/src/probe/router.ts +++ b/src/probe/router.ts @@ -20,26 +20,34 @@ export class ProbeRouter { globalLimit = 1, ) { const connectedProbes = await this.fetchProbes(); - let onlineProbesMap: Map; - let allProbes: (Probe | OfflineProbe)[] = []; if (typeof locations === 'string') { - ({ onlineProbesMap, allProbes } = await this.findWithMeasurementId(connectedProbes, locations)); - } else if (locations.some(l => l.limit)) { + return this.findWithMeasurementId(connectedProbes, locations); + } + + if (locations.some(l => l.limit)) { const filtered = this.findWithLocationLimit(connectedProbes, locations); - allProbes = filtered; - onlineProbesMap = new Map(filtered.entries()); - } else if (locations.length > 0) { + return this.processFiltered(filtered, connectedProbes, locations); + } + + if (locations.length > 0) { const filtered = this.findWithGlobalLimit(connectedProbes, locations, globalLimit); - allProbes = filtered; - onlineProbesMap = new Map(filtered.entries()); - } else { - const filtered = this.findGloballyDistributed(connectedProbes, globalLimit); - allProbes = filtered; - onlineProbesMap = new Map(filtered.entries()); + return this.processFiltered(filtered, connectedProbes, locations); } - return { onlineProbesMap, allProbes }; + const filtered = this.findGloballyDistributed(connectedProbes, globalLimit); + return this.processFiltered(filtered, connectedProbes, locations); + } + + private async processFiltered (filtered: Probe[], connectedProbes: Probe[], locations: LocationWithLimit[]) { + if (filtered.length === 0 && locations.length === 1 && locations[0]?.magic) { + return this.findWithMeasurementId(connectedProbes, locations[0].magic); + } + + return { + allProbes: filtered, + onlineProbesMap: new Map(filtered.entries()), + }; } private async fetchProbes (): Promise { diff --git a/test/tests/integration/measurement/create-measurement.test.ts b/test/tests/integration/measurement/create-measurement.test.ts index e01e596e..d4b7838b 100644 --- a/test/tests/integration/measurement/create-measurement.test.ts +++ b/test/tests/integration/measurement/create-measurement.test.ts @@ -523,6 +523,43 @@ describe('Create measurement', () => { }); }); + it('should create measurement with another measurement id location passed in magic field', async () => { + let id; + await requestAgent.post('/v1/measurements') + .send({ + type: 'ping', + target: 'example.com', + }) + .expect(202) + .expect((response) => { + id = response.body.id; + }); + + let id2; + await requestAgent.post('/v1/measurements') + .send({ + type: 'ping', + target: 'example.com', + locations: [{ magic: id }], + }) + .expect(202) + .expect((response) => { + expect(response.body.id).to.exist; + expect(response.header.location).to.exist; + expect(response.body.probesCount).to.equal(1); + expect(response).to.matchApiSchema(); + id2 = response.body.id; + }); + + 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).to.matchApiSchema(); + }); + }); + it('should respond with error if there is no requested measurement id', async () => { await requestAgent.post('/v1/measurements') .send({ diff --git a/test/tests/unit/measurement/schema/schema.test.ts b/test/tests/unit/measurement/schema/schema.test.ts index 617f0979..4083e1d7 100644 --- a/test/tests/unit/measurement/schema/schema.test.ts +++ b/test/tests/unit/measurement/schema/schema.test.ts @@ -228,7 +228,7 @@ describe('command schema', async () => { const valid = locationSchema.validate(input); - expect(valid.value![0].magic).to.equal('petah tiqva'); + expect(valid.value![0].magic).to.equal('Petah Tiqva'); }); it('should correct region value (lowercase)', () => { diff --git a/test/tests/unit/probe/router.test.ts b/test/tests/unit/probe/router.test.ts index 4dcc8622..d4bb5667 100644 --- a/test/tests/unit/probe/router.test.ts +++ b/test/tests/unit/probe/router.test.ts @@ -30,7 +30,7 @@ describe('probe router', () => { const geoLookupMock = sinon.stub(); const getRegionMock = sinon.stub(); const store = { - getMeasurementIps: sinon.stub(), + getMeasurementIps: sinon.stub().resolves([]), getMeasurement: sinon.stub(), }; const router = new ProbeRouter(fetchSocketsMock, store as unknown as MeasurementStore); @@ -70,12 +70,9 @@ describe('probe router', () => { beforeEach(() => { sandbox.reset(); - }); - - afterEach(() => { - geoLookupMock.reset(); - fetchSocketsMock.reset(); - getRegionMock.reset(); + sinon.resetHistory(); + sinon.resetBehavior(); + store.getMeasurementIps.resolves([]); }); after(() => { @@ -920,6 +917,48 @@ describe('probe router', () => { expect(onlineProbesMap.get(0)?.location.country).to.equal('PL'); }); + it('should find probes by prev measurement id in magic field', async () => { + const sockets: Array> = [ + await buildSocket('socket-1', { continent: 'EU', country: 'PL' }), + ]; + fetchSocketsMock.resolves(sockets as never); + store.getMeasurementIps.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([{ magic: 'measurementid' }]); + + expect(store.getMeasurementIps.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 not find probes without errors by prev measurement id in magic field if no such measurement found', async () => { + const sockets: Array> = [ + await buildSocket('socket-1', { continent: 'EU', country: 'PL' }), + ]; + fetchSocketsMock.resolves(sockets as never); + + const { onlineProbesMap, allProbes } = await router.findMatchingProbes([{ magic: 'measurementid' }]); + + expect(store.getMeasurementIps.args[0]).to.deep.equal([ 'measurementid' ]); + expect(store.getMeasurement.callCount).to.equal(0); + expect(allProbes.length).to.equal(0); + expect(onlineProbesMap.size).to.equal(0); + }); + it('should replace non-connected probes with offline probe data', async () => { const sockets: Array> = [ await buildSocket('socket-1', { continent: 'EU', country: 'PL' }),