diff --git a/src/shadowbox/server/manager_service.spec.ts b/src/shadowbox/server/manager_service.spec.ts index a0ad4cd28..fcbbbe97d 100644 --- a/src/shadowbox/server/manager_service.spec.ts +++ b/src/shadowbox/server/manager_service.spec.ts @@ -19,7 +19,7 @@ import * as restify from 'restify'; import {InMemoryConfig, JsonConfig} from '../infrastructure/json_config'; import {AccessKey, AccessKeyRepository, DataLimit} from '../model/access_key'; import {ManagerMetrics} from './manager_metrics'; -import {bindService, ShadowsocksManagerService} from './manager_service'; +import {bindService, ShadowsocksManagerService, convertTimeRangeToHours} from './manager_service'; import {FakePrometheusClient, FakeShadowsocksServer} from './mocks/mocks'; import {AccessKeyConfigJson, ServerAccessKeyRepository} from './server_access_key'; import {ServerConfigJson} from './server_config'; @@ -1200,6 +1200,20 @@ describe('bindService', () => { }); }); +describe('convertTimeRangeToHours', () => { + it('properly parses time ranges', () => { + expect(convertTimeRangeToHours('30d')).toEqual(30 * 24); + expect(convertTimeRangeToHours('20h')).toEqual(20); + expect(convertTimeRangeToHours('3w')).toEqual(7 * 3 * 24); + }); + + it('throws when an invalid time range is provided', () => { + expect(() => convertTimeRangeToHours('30dd')).toThrow(); + expect(() => convertTimeRangeToHours('hi mom')).toThrow(); + expect(() => convertTimeRangeToHours('1j')).toThrow(); + }); +}); + class ShadowsocksManagerServiceBuilder { private defaultServerName_ = 'default name'; private serverConfig_: JsonConfig = null; diff --git a/src/shadowbox/server/manager_service.ts b/src/shadowbox/server/manager_service.ts index 2abd69ab1..19e6e7496 100644 --- a/src/shadowbox/server/manager_service.ts +++ b/src/shadowbox/server/manager_service.ts @@ -190,6 +190,23 @@ function redirect(url: string): restify.RequestHandlerType { }; } +export function convertTimeRangeToHours(timeRange: string): number { + const TIME_RANGE_UNIT_TO_HOURS_MULTIPLYER = { + h: 1, + d: 24, + w: 7 * 24, + }; + + const timeRangeValue = Number(timeRange.slice(0, -1)); + const timeRangeUnit = timeRange.slice(-1); + + if (isNaN(timeRangeValue) || !TIME_RANGE_UNIT_TO_HOURS_MULTIPLYER[timeRangeUnit]) { + throw new TypeError(`Invalid time range: ${timeRange}`); + } + + return timeRangeValue * TIME_RANGE_UNIT_TO_HOURS_MULTIPLYER[timeRangeUnit]; +} + function validateAccessKeyId(accessKeyId: unknown): string { if (!accessKeyId) { throw new restifyErrors.MissingParameterError({statusCode: 400}, 'Parameter `id` is missing'); @@ -610,47 +627,24 @@ export class ShadowsocksManagerService { } async getServerMetrics(req: RequestType, res: ResponseType, next: restify.Next) { - const TIME_RANGE_UNIT_TO_HOURS_MULTIPLYER = { - h: 1, - d: 24, - w: 7 * 24, - }; + logging.debug(`getServerMetrics request ${JSON.stringify(req.params)}`); + let hours; try { - logging.debug(`getServerMetrics request ${JSON.stringify(req.params)}`); - if (!req.query?.since) { return next( new restifyErrors.MissingParameterError({statusCode: 400}, 'Parameter `since` is missing') ); } - const timeRange = req.query.since as string; - const timeRangeValue = Number(timeRange.slice(0, -1)); - - if (isNaN(timeRangeValue)) { - return next( - new restifyErrors.InvalidArgumentError( - {statusCode: 400}, - `'since' parameter must be a time range in format. Got ${req.query.since}` - ) - ); - } - - const timeRangeUnit = timeRange.slice(-1); - - if (!(timeRangeUnit in TIME_RANGE_UNIT_TO_HOURS_MULTIPLYER)) { - return next( - new restifyErrors.InvalidArgumentError( - {statusCode: 400}, - `'since' parameter must be a time range with units in hours, days or weeks. Got ${req.query.since}` - ) - ); - } + hours = convertTimeRangeToHours(req.query.since as string); + } catch (error) { + logging.error(error); + return next(new restifyErrors.InvalidArgumentError({statusCode: 400}, error.message)); + } - const response = await this.managerMetrics.getServerMetrics({ - hours: timeRangeValue * TIME_RANGE_UNIT_TO_HOURS_MULTIPLYER[timeRangeUnit], - }); + try { + const response = await this.managerMetrics.getServerMetrics({hours}); res.send(HttpSuccess.OK, response); logging.debug(`getServerMetrics response ${JSON.stringify(response)}`); return next();