Skip to content

Commit

Permalink
test: add unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
alexey-yarmosh committed Dec 4, 2023
1 parent da5c840 commit 82f20a9
Show file tree
Hide file tree
Showing 8 changed files with 408 additions and 124 deletions.
2 changes: 1 addition & 1 deletion src/lib/ratelimiter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
7 changes: 4 additions & 3 deletions src/measurement/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
) {}

Expand All @@ -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);

Expand Down Expand Up @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions src/measurement/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ export class MeasurementStore {
return this.redis.sendCommand([ 'JSON.GET', getMeasurementKey(id) ]);
}

async getMeasurement (id: string): Promise<MeasurementRecord> {
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<string[]> {
Expand Down
2 changes: 1 addition & 1 deletion src/measurement/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ type PingTiming = {
ttl: number;
};

type PingResult = TestResult & {
export type PingResult = TestResult & {
timings: PingTiming[];
stats: {
min: number;
Expand Down
3 changes: 2 additions & 1 deletion src/probe/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<number, Probe> = new Map();
Expand All @@ -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);
Expand Down
92 changes: 86 additions & 6 deletions test/tests/unit/measurement/runner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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;
});

Expand Down Expand Up @@ -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' ]);
});
});
Loading

0 comments on commit 82f20a9

Please sign in to comment.