Skip to content

Commit

Permalink
test: add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
alexey-yarmosh committed Oct 4, 2023
1 parent 26ee471 commit 31ec3ec
Show file tree
Hide file tree
Showing 11 changed files with 221 additions and 9 deletions.
2 changes: 1 addition & 1 deletion config/init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ CREATE TABLE IF NOT EXISTS adopted_probes (
uuid VARCHAR(255)
);

INSERT IGNORE INTO adopted_probes (id, userId, ip) VALUES ('1', '6191378', '65.19.141.130');
INSERT IGNORE INTO adopted_probes (id, userId, ip) VALUES ('1', '6191378', '79.205.97.254');
4 changes: 2 additions & 2 deletions src/adoption-code/route/adoption-code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { AdoptionCodeRequest } from '../types.js';
import { bodyParser } from '../../lib/http/middleware/body-parser.js';
import { validate } from '../../lib/http/middleware/validate.js';
import { schema } from '../schema.js';
import { onlyAdminMiddleware } from '../../lib/http/middleware/only-admin.js';
import { onlyAdmin } from '../../lib/http/middleware/only-admin.js';
import { codeSender } from '../sender.js';

const handle = async (ctx: Context): Promise<void> => {
Expand All @@ -17,5 +17,5 @@ const handle = async (ctx: Context): Promise<void> => {
};

export const registerSendCodeRoute = (router: Router): void => {
router.post('/adoption-code', '/adoption-code', onlyAdminMiddleware(), bodyParser(), validate(schema), handle);
router.post('/adoption-code', '/adoption-code', onlyAdmin(), bodyParser(), validate(schema), handle);
};
2 changes: 1 addition & 1 deletion src/adoption-code/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ import Joi from 'joi';

export const schema = Joi.object({
ip: Joi.string().ip().required(),
code: Joi.string().required().length(6),
code: Joi.string().length(6).required(),
});
4 changes: 2 additions & 2 deletions src/lib/adopted-probes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export class AdoptedProbes {
async syncProbeIds (probeIp: string, probeUuid: string) {
const adoptedProbeByIp = this.adoptedProbesByIp.get(probeIp);

if (adoptedProbeByIp && adoptedProbeByIp.uuid === probeUuid) { // Probe is synced
if (adoptedProbeByIp && adoptedProbeByIp.uuid === probeUuid) { // Probe ids are synced
return;
} else if (adoptedProbeByIp) { // Uuid is wrong
await this.updateUuid(probeIp, probeUuid);
Expand All @@ -35,7 +35,7 @@ export class AdoptedProbes {

const adoptedProbeByUuid = this.adoptedProbesByUuid.get(probeUuid);

if (adoptedProbeByUuid) { // Probe not found by ip but found by uuid, ip is wrong
if (adoptedProbeByUuid) { // Probe not found by ip but found by uuid => ip is wrong
await this.updateIp(probeIp, probeUuid);
}
}
Expand Down
5 changes: 4 additions & 1 deletion src/lib/get-probe-ip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import requestIp from 'request-ip';
import type { Socket } from 'socket.io';

const getProbeIp = (socket: Socket) => {
if (process.env['NODE_ENV'] === 'test') {
return '1.2.3.4';
}

// Use random ip assigned by the API
if (process.env['FAKE_PROBE_IP'] === 'api') {
return _.sample([
Expand All @@ -16,7 +20,6 @@ const getProbeIp = (socket: Socket) => {
'213.136.174.80',
'94.214.253.78',
'79.205.97.254',
'65.19.141.130',
]);
}

Expand Down
2 changes: 1 addition & 1 deletion src/lib/http/middleware/only-admin.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Middleware } from 'koa';
import createHttpError from 'http-errors';

export const onlyAdminMiddleware = (): Middleware => {
export const onlyAdmin = (): Middleware => {
return async (ctx, next) => {
if (!ctx['isAdmin']) {
throw createHttpError(403, 'Forbidden', { type: 'access_forbidden' });
Expand Down
2 changes: 1 addition & 1 deletion src/probe/route/get-probes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const handle = async (ctx: ParameterizedContext<DefaultState, DefaultContext & R
status: isAdmin ? socket.data.probe.status : undefined,
version: socket.data.probe.version,
nodeVersion: isAdmin ? socket.data.probe.nodeVersion : undefined,
id: isAdmin ? socket.data.probe.id : undefined,
uuid: isAdmin ? socket.data.probe.uuid : undefined,
location: {
continent: socket.data.probe.location.continent,
region: socket.data.probe.location.region,
Expand Down
73 changes: 73 additions & 0 deletions test/tests/integration/adoption-code/adoption-code.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import nock from 'nock';
import { expect } from 'chai';
import * as sinon from 'sinon';
import type { Socket } from 'socket.io-client';
import request from 'supertest';
import { getTestServer, addFakeProbe, deleteFakeProbe } from '../../../utils/server.js';
import nockGeoIpProviders from '../../../utils/nock-geo-ip.js';

let probe: Socket;
const app = await getTestServer();
const requestAgent = request(app);
const adoptionCodeStub = sinon.stub();

describe('Adoption code', () => {
before(async () => {
nockGeoIpProviders();

probe = await addFakeProbe({
'probe:adoption:code': adoptionCodeStub,
});
});

afterEach(async () => {
sinon.resetHistory();
});

after(async () => {
nock.cleanAll();
await deleteFakeProbe(probe);
});

it('should send code to the requested probe', async () => {
await requestAgent.post('/v1/adoption-code?adminkey=admin')
.send({
ip: '1.2.3.4',
code: '123456',
})
.expect(200).expect((response) => {
expect(response.body).to.deep.equal({
result: 'Code was sent to the probe',
});
});

expect(adoptionCodeStub.callCount).to.equal(1);
expect(adoptionCodeStub.args[0]).to.deep.equal([{ code: '123456' }]);
});

it('should return 403 for non-admins', async () => {
await requestAgent.post('/v1/adoption-code?adminkey=wrongkey')
.send({
ip: '1.2.3.4',
code: '123456',
})
.expect(403).expect((response) => {
expect(response.body.error.message).to.equal('Forbidden');
});

expect(adoptionCodeStub.callCount).to.equal(0);
});

it('should return 422 if probe not found', async () => {
await requestAgent.post('/v1/adoption-code?adminkey=admin')
.send({
ip: '9.9.9.9',
code: '123456',
})
.expect(422).expect((response) => {
expect(response.body.error.message).to.equal('No suitable probes found.');
});

expect(adoptionCodeStub.callCount).to.equal(0);
});
});
108 changes: 108 additions & 0 deletions test/tests/unit/adopted-probes.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { expect } from 'chai';
import { Knex } from 'knex';
import * as sinon from 'sinon';
import { AdoptedProbes } from '../../../src/lib/adopted-probes.js';

const selectStub = sinon.stub();
const updateStub = sinon.stub();
const whereStub = sinon.stub().returns({
update: updateStub,
});
const sqlStub = sinon.stub().returns({
select: selectStub,
where: whereStub,
});
let clock: sinon.SinonSandbox['clock'];

describe('AdoptedProbes', () => {
before(() => {
clock = sinon.useFakeTimers();
});

beforeEach(() => {
sinon.resetHistory();
});

afterEach(() => {
clock.restore();
});

it('startSync method should sync the data and start regular syncs', async () => {
const adoptedProbes = new AdoptedProbes(sqlStub as unknown as Knex);
selectStub.resolves([{
ip: '1.1.1.1',
uuid: '1-1-1-1-1',
}]);

await adoptedProbes.startSync();

expect(sqlStub.callCount).to.equal(1);
expect(sqlStub.args[0]).deep.equal([ 'adopted_probes' ]);
expect(selectStub.callCount).to.equal(1);
expect(selectStub.args[0]).deep.equal([ 'ip', 'uuid' ]);

await clock.tickAsync(5500);
expect(sqlStub.callCount).to.equal(2);
expect(selectStub.callCount).to.equal(2);
});

it('syncProbeIds method should do nothing if probe was not found either by ip or uuid', async () => {
const adoptedProbes = new AdoptedProbes(sqlStub as unknown as Knex);
selectStub.resolves([{
ip: '1.1.1.1',
uuid: '1-1-1-1-1',
}]);

await adoptedProbes.startSync();
await adoptedProbes.syncProbeIds('2.2.2.2', '2-2-2-2-2');

expect(whereStub.callCount).to.equal(0);
expect(updateStub.callCount).to.equal(0);
});

it('syncProbeIds method should do nothing if probe data is actual', async () => {
const adoptedProbes = new AdoptedProbes(sqlStub as unknown as Knex);
selectStub.resolves([{
ip: '1.1.1.1',
uuid: '1-1-1-1-1',
}]);

await adoptedProbes.startSync();
await adoptedProbes.syncProbeIds('1.1.1.1', '1-1-1-1-1');

expect(whereStub.callCount).to.equal(0);
expect(updateStub.callCount).to.equal(0);
});

it('syncProbeIds method should update uuid if it is wrong', async () => {
const adoptedProbes = new AdoptedProbes(sqlStub as unknown as Knex);
selectStub.resolves([{
ip: '1.1.1.1',
uuid: '1-1-1-1-1',
}]);

await adoptedProbes.startSync();
await adoptedProbes.syncProbeIds('1.1.1.1', '2-2-2-2-2');

expect(whereStub.callCount).to.equal(1);
expect(whereStub.args[0]).to.deep.equal([{ ip: '1.1.1.1' }]);
expect(updateStub.callCount).to.equal(1);
expect(updateStub.args[0]).to.deep.equal([{ uuid: '2-2-2-2-2' }]);
});

it('syncProbeIds method should update ip if it is wrong', async () => {
const adoptedProbes = new AdoptedProbes(sqlStub as unknown as Knex);
selectStub.resolves([{
ip: '1.1.1.1',
uuid: '1-1-1-1-1',
}]);

await adoptedProbes.startSync();
await adoptedProbes.syncProbeIds('2.2.2.2', '1-1-1-1-1');

expect(whereStub.callCount).to.equal(1);
expect(whereStub.args[0]).to.deep.equal([{ uuid: '1-1-1-1-1' }]);
expect(updateStub.callCount).to.equal(1);
expect(updateStub.args[0]).to.deep.equal([{ ip: '2.2.2.2' }]);
});
});
26 changes: 26 additions & 0 deletions test/tests/unit/middleware/only-admin.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { Context } from 'koa';
import * as sinon from 'sinon';
import { expect } from 'chai';
import createHttpError from 'http-errors';
import { onlyAdmin } from '../../../../src/lib/http/middleware/only-admin.js';

const next = sinon.stub();

beforeEach(() => {
sinon.resetHistory();
});

describe('rate limit middleware', () => {
it('should reject requests with "isAdmin" false', async () => {
const ctx = { isAdmin: false } as unknown as Context;
const err = await onlyAdmin()(ctx, next).catch(err => err);
expect(err).to.deep.equal(createHttpError(403, 'Forbidden', { type: 'access_forbidden' }));
expect(next.callCount).to.equal(0);
});

it('should accept requests with "isAdmin" true', async () => {
const ctx = { isAdmin: true } as unknown as Context;
await onlyAdmin()(ctx, next);
expect(next.callCount).to.equal(1);
});
});
2 changes: 2 additions & 0 deletions wallaby.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ export default function wallaby () {
'public/v1/*',
'src/**/*.ts',
'src/**/*.cjs',
'src/**/*.json',
'config/*',
'public/**/*.yaml',
'test/utils/**/*.ts',
'test/mocks/**/*',
'test/plugins/**/*',
Expand Down

0 comments on commit 31ec3ec

Please sign in to comment.