From 95266f6cb3d9d013c1cee8ebb0e4da9ff52bb234 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Wed, 18 Jun 2025 18:26:39 +0200 Subject: [PATCH] improve test coverage --- test/api/unit/middlewares/blocker.test.js | 148 ++++++++++++++++++++-- website/server/models/blocker.js | 2 +- 2 files changed, 139 insertions(+), 11 deletions(-) diff --git a/test/api/unit/middlewares/blocker.test.js b/test/api/unit/middlewares/blocker.test.js index 2236eb5d58..bd5c5c443f 100644 --- a/test/api/unit/middlewares/blocker.test.js +++ b/test/api/unit/middlewares/blocker.test.js @@ -7,6 +7,7 @@ import { } from '../../../helpers/api-unit.helper'; import { Forbidden } from '../../../../website/server/libs/errors'; import { apiError } from '../../../../website/server/libs/apiError'; +import { model as Blocker } from '../../../../website/server/models/blocker'; function checkIPBlockedErrorThrown (next) { expect(next).to.have.been.calledOnce; @@ -15,6 +16,13 @@ function checkIPBlockedErrorThrown (next) { expect(calledWith[0] instanceof Forbidden).to.equal(true); } +function checkClientBlockedErrorThrown (next) { + expect(next).to.have.been.calledOnce; + const calledWith = next.getCall(0).args; + expect(calledWith[0].message).to.equal(apiError('clientBlocked')); + expect(calledWith[0] instanceof Forbidden).to.equal(true); +} + function checkErrorNotThrown (next) { expect(next).to.have.been.calledOnce; const calledWith = next.getCall(0).args; @@ -35,24 +43,24 @@ describe('Blocker middleware', () => { describe('Blocking IPs', () => { it('is disabled when the env var is not defined', () => { sandbox.stub(nconf, 'get').withArgs('BLOCKED_IPS').returns(undefined); - const attachIpBlocker = requireAgain(pathToBlocker).default; - attachIpBlocker(req, res, next); + const attachBlocker = requireAgain(pathToBlocker).default; + attachBlocker(req, res, next); checkErrorNotThrown(next); }); it('is disabled when the env var is an empty string', () => { sandbox.stub(nconf, 'get').withArgs('BLOCKED_IPS').returns(''); - const attachIpBlocker = requireAgain(pathToBlocker).default; - attachIpBlocker(req, res, next); + const attachBlocker = requireAgain(pathToBlocker).default; + attachBlocker(req, res, next); checkErrorNotThrown(next); }); it('is disabled when the env var contains comma separated empty strings', () => { sandbox.stub(nconf, 'get').withArgs('BLOCKED_IPS').returns(' , , '); - const attachIpBlocker = requireAgain(pathToBlocker).default; - attachIpBlocker(req, res, next); + const attachBlocker = requireAgain(pathToBlocker).default; + attachBlocker(req, res, next); checkErrorNotThrown(next); }); @@ -60,19 +68,139 @@ describe('Blocker middleware', () => { it('does not throw when the ip does not match', () => { req.ip = '192.168.1.1'; sandbox.stub(nconf, 'get').withArgs('BLOCKED_IPS').returns('192.168.1.2'); - const attachIpBlocker = requireAgain(pathToBlocker).default; - attachIpBlocker(req, res, next); + const attachBlocker = requireAgain(pathToBlocker).default; + attachBlocker(req, res, next); checkErrorNotThrown(next); }); + it('does not throw when the blocker IP does not match', async () => { + req.ip = '192.168.1.1'; + sandbox.stub(Blocker, 'watchBlockers').returns({ + on: (event, callback) => { + if (event === 'change') { + callback({ operation: 'add', blocker: { type: 'ipaddress', area: 'full', value: '192.168.1.2' } }); + } + }, + }); + const attachBlocker = requireAgain(pathToBlocker).default; + attachBlocker(req, res, next); + + checkErrorNotThrown(next); + }); + + it('does not throw when a client is blocked', async () => { + sandbox.stub(Blocker, 'watchBlockers').returns({ + on: (event, callback) => { + if (event === 'change') { + callback({ operation: 'add', blocker: { type: 'client', area: 'full', value: '192.168.1.1' } }); + } + }, + }); + const attachBlocker = requireAgain(pathToBlocker).default; + attachBlocker(req, res, next); + + checkErrorNotThrown(next); + }); + + it('throws when the blocker IP is blocked', async () => { + req.ip = '192.168.1.1'; + sandbox.stub(Blocker, 'watchBlockers').returns({ + on: (event, callback) => { + if (event === 'change') { + callback({ operation: 'add', blocker: { type: 'ipaddress', area: 'full', value: '192.168.1.1' } }); + } + }, + }); + const attachBlocker = requireAgain(pathToBlocker).default; + attachBlocker(req, res, next); + + checkIPBlockedErrorThrown(next); + }); + it('throws when the ip is blocked', () => { req.ip = '192.168.1.1'; sandbox.stub(nconf, 'get').withArgs('BLOCKED_IPS').returns('192.168.1.1'); - const attachIpBlocker = requireAgain(pathToBlocker).default; - attachIpBlocker(req, res, next); + const attachBlocker = requireAgain(pathToBlocker).default; + attachBlocker(req, res, next); checkIPBlockedErrorThrown(next); }); }); + + describe('Blocking clients', () => { + beforeEach(() => { + sandbox.stub(nconf, 'get').withArgs('BLOCKED_IPS').returns(''); + req.headers['x-client'] = 'test-client'; + }); + it('is disabled when no clients are blocked', () => { + const attachBlocker = requireAgain(pathToBlocker).default; + attachBlocker(req, res, next); + + checkErrorNotThrown(next); + }); + + it('does not throw when the client does not match', async () => { + sandbox.stub(Blocker, 'watchBlockers').returns({ + on: (event, callback) => { + if (event === 'change') { + callback({ operation: 'add', blocker: { type: 'client', area: 'full', value: 'another-client' } }); + } + }, + }); + const attachBlocker = requireAgain(pathToBlocker).default; + attachBlocker(req, res, next); + + checkErrorNotThrown(next); + }); + + it('throws when the client is blocked', async () => { + sandbox.stub(Blocker, 'watchBlockers').returns({ + on: (event, callback) => { + if (event === 'change') { + callback({ operation: 'add', blocker: { type: 'client', area: 'full', value: 'test-client' } }); + } + }, + }); + const attachBlocker = requireAgain(pathToBlocker).default; + attachBlocker(req, res, next); + + checkClientBlockedErrorThrown(next); + }); + + it('does not throw when an ip is blocked', async () => { + sandbox.stub(Blocker, 'watchBlockers').returns({ + on: (event, callback) => { + if (event === 'change') { + callback({ operation: 'add', blocker: { type: 'ipaddress', area: 'full', value: 'test-client' } }); + } + }, + }); + const attachBlocker = requireAgain(pathToBlocker).default; + attachBlocker(req, res, next); + + checkErrorNotThrown(next); + }); + + it('updates the list when data changes', async () => { + let blockCallback; + sandbox.stub(Blocker, 'watchBlockers').returns({ + on: (event, callback) => { + blockCallback = callback; + if (event === 'change') { + callback({ operation: 'add', blocker: { type: 'client', area: 'full', value: 'another-client' } }); + } + }, + }); + const attachBlocker = requireAgain(pathToBlocker).default; + attachBlocker(req, res, next); + checkErrorNotThrown(next); + blockCallback({ operation: 'add', blocker: { type: 'client', area: 'full', value: 'test-client' } }); + attachBlocker(req, res, next); + expect(next).to.have.been.calledTwice; + const calledWith = next.getCall(1).args; + expect(calledWith[0].message).to.equal(apiError('clientBlocked')); + expect(calledWith[0] instanceof Forbidden).to.equal(true); + }); + }); }); diff --git a/website/server/models/blocker.js b/website/server/models/blocker.js index 87306300b0..51f1c13529 100644 --- a/website/server/models/blocker.js +++ b/website/server/models/blocker.js @@ -29,7 +29,7 @@ export const schema = new mongoose.Schema({ $type: String, required: true, // e.g. IP address }, blockSource: { - $type: String, enum: ['user', 'system', 'worker'], default: 'user', // who created the block + $type: String, enum: ['administrator', 'system', 'worker'], default: 'administrator', // who created the block }, reason: { $type: String, required: false, // e.g. 'abusive behavior'