diff --git a/package-lock.json b/package-lock.json index ab017c8..2bdd785 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1807,12 +1807,6 @@ "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", "dev": true }, - "@types/aws-lambda": { - "version": "8.10.85", - "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.85.tgz", - "integrity": "sha512-cMRXVxb+NMb6EekKel1fPBfz2ZqE5cGhIS14G7FVUM4Bqilx0lHKnZbsDLWLSeckDpkvlp5six2F7UWyEEJSoQ==", - "dev": true - }, "@types/babel__core": { "version": "7.1.16", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.16.tgz", @@ -1944,6 +1938,12 @@ "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "dev": true }, + "@types/uuid": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", + "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==", + "dev": true + }, "@types/yargs": { "version": "16.0.4", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", @@ -6143,6 +6143,11 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + }, "v8-compile-cache": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", diff --git a/package.json b/package.json index f1d32d4..99689c1 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,8 @@ "license": "ISC", "dependencies": { "dayjs": "^1.10.7", - "puppeteer": "^11.0.0" + "puppeteer": "^11.0.0", + "uuid": "^8.3.2" }, "devDependencies": { "@babel/core": "^7.16.0", @@ -32,9 +33,9 @@ "@rollup/plugin-commonjs": "^21.0.1", "@rollup/plugin-node-resolve": "^13.0.6", "@rollup/plugin-typescript": "^8.3.0", - "@types/aws-lambda": "^8.10.85", "@types/jest": "^27.0.2", "@types/puppeteer": "^5.4.4", + "@types/uuid": "^8.3.4", "@typescript-eslint/eslint-plugin": "^5.4.0", "@typescript-eslint/parser": "^5.4.0", "babel-jest": "^27.3.1", diff --git a/src/common/logger.ts b/src/common/logger.ts index d8542f5..251a335 100644 --- a/src/common/logger.ts +++ b/src/common/logger.ts @@ -8,10 +8,11 @@ export class Logger { private static instance: LoggerInstance public static instantiate( + tag: string, correlationId: string, level = LogLevel.ERROR ): LoggerInstance { - Logger.instance = new LoggerInstance(correlationId, level) + Logger.instance = new LoggerInstance(tag, correlationId, level) return Logger.instance } @@ -33,10 +34,12 @@ export class Logger { } export class LoggerInstance { + private readonly tag: string private readonly correlationId: string private readonly level: LogLevel - public constructor(correlationId: string, level = LogLevel.ERROR) { + public constructor(tag: string, correlationId: string, level = LogLevel.ERROR) { + this.tag = tag this.correlationId = correlationId this.level = level } @@ -60,8 +63,8 @@ export class LoggerInstance { break } - let fmtString = '[%s] %s: %s' - const params: Array = [this.correlationId, levelString, message] + let fmtString = '<%s> [%s] %s: %s' + const params: Array = [this.tag, this.correlationId, levelString, message] if (details) { params.push(details) fmtString += ' - %O' diff --git a/src/common/request.ts b/src/common/request.ts index 0a81079..cd0fc66 100644 --- a/src/common/request.ts +++ b/src/common/request.ts @@ -101,7 +101,6 @@ const transformRequestBody = (body: string): ReservationRequest => { try { json = JSON.parse(body) } catch (err) { - console.error(err) throw new ValidationError( 'Invalid request', ValidationErrorCode.INVALID_JSON @@ -155,12 +154,12 @@ const validateRequestDateRange = (dateRange: DateRange): void => { const validateRequestOpponent = (opponent?: Opponent): void => { if (!opponent) return + const idRegex = /^-1$|^[^-]\d+$/ + const nameRegex = /^[A-Za-z0-9 -.'()]+$/ const { id, name } = opponent if ( - typeof id !== 'string' || - typeof name !== 'string' || - id.length < 1 || - name.length < 1 + !idRegex.test(id) || + !nameRegex.test(name) ) { throw new ValidationError( 'Invalid request', diff --git a/src/lambdas/reservationHandler/index.ts b/src/lambdas/reservationHandler/index.ts deleted file mode 100644 index 386c590..0000000 --- a/src/lambdas/reservationHandler/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Handler } from 'aws-lambda' - -export const run: Handler = async (): Promise => { - return -} diff --git a/src/workers/reservationRequestor/index.ts b/src/workers/reservationRequestor/index.ts new file mode 100644 index 0000000..ff2094c --- /dev/null +++ b/src/workers/reservationRequestor/index.ts @@ -0,0 +1,5 @@ +import { Worker } from "../types" + +export const run: Worker = async (): Promise => { + return +} diff --git a/src/lambdas/reservationHandler/rollup.config.js b/src/workers/reservationRequestor/rollup.config.js similarity index 89% rename from src/lambdas/reservationHandler/rollup.config.js rename to src/workers/reservationRequestor/rollup.config.js index ed853c5..5234929 100644 --- a/src/lambdas/reservationHandler/rollup.config.js +++ b/src/workers/reservationRequestor/rollup.config.js @@ -8,7 +8,7 @@ import commonjs from '@rollup/plugin-commonjs' export default { input: path.join(__dirname, 'index.ts'), output: { - file: './dist/reservationHandler/index.js', + file: './dist/reservationRequestor/index.js', format: 'cjs', sourcemap: true, }, diff --git a/src/lambdas/reservationScheduler/index.ts b/src/workers/reservationScheduler/index.ts similarity index 88% rename from src/lambdas/reservationScheduler/index.ts rename to src/workers/reservationScheduler/index.ts index 865e49b..c2fa735 100644 --- a/src/lambdas/reservationScheduler/index.ts +++ b/src/workers/reservationScheduler/index.ts @@ -1,10 +1,11 @@ -import { Context, Handler } from 'aws-lambda' import { Dayjs } from 'dayjs' +import { v4 } from 'uuid' import { Logger, LogLevel } from '../../common/logger' import { Reservation } from '../../common/reservation' import { ReservationRequest, validateJSONRequest } from '../../common/request' import { scheduleDateToRequestReservation } from '../../common/schedule' +import { Worker } from '../types' export interface ScheduledReservationRequest { reservationRequest: ReservationRequest @@ -20,14 +21,10 @@ export interface ReservationSchedulerInput dateRange: { start: string; end: string } } -export const handler: Handler< - ReservationSchedulerInput, - ReservationSchedulerResult -> = async ( +export const run: Worker = async ( payload: ReservationSchedulerInput, - context: Context ): Promise => { - Logger.instantiate(context.awsRequestId, LogLevel.DEBUG) + Logger.instantiate('reservationScheduler', v4(), LogLevel.DEBUG) Logger.debug('Handling event', { payload }) let reservationRequest: ReservationRequest try { diff --git a/src/lambdas/reservationScheduler/rollup.config.js b/src/workers/reservationScheduler/rollup.config.js similarity index 100% rename from src/lambdas/reservationScheduler/rollup.config.js rename to src/workers/reservationScheduler/rollup.config.js diff --git a/src/workers/types.ts b/src/workers/types.ts new file mode 100644 index 0000000..3c35b49 --- /dev/null +++ b/src/workers/types.ts @@ -0,0 +1 @@ +export type Worker = (payload: I) => O | Promise \ No newline at end of file diff --git a/tests/common/logger.test.ts b/tests/common/logger.test.ts index 9fa1f4e..87883ed 100644 --- a/tests/common/logger.test.ts +++ b/tests/common/logger.test.ts @@ -2,7 +2,7 @@ import { Logger, LogLevel } from '../../src/common/logger' describe('Logger', () => { test('should create a single instance of LoggerInstance', () => { - const a = Logger.instantiate('abc', LogLevel.DEBUG) + const a = Logger.instantiate('tag', 'abc', LogLevel.DEBUG) const b = Logger.getInstance() expect(a).toStrictEqual(b) @@ -14,21 +14,21 @@ describe('Logger', () => { jest.spyOn(console, 'log').mockImplementation(consoleLogSpy) jest.spyOn(console, 'error').mockImplementation(consoleErrorSpy) - Logger.instantiate('abc', LogLevel.DEBUG) + Logger.instantiate('tag', 'abc', LogLevel.DEBUG) Logger.debug('first') Logger.info('second') Logger.error('third', { errorMessage: 'test' }) expect(consoleLogSpy).toHaveBeenCalledTimes(2) expect(consoleLogSpy).toHaveBeenNthCalledWith( - 1, '[%s] %s: %s', 'abc', 'DEBUG', 'first' + 1, '<%s> [%s] %s: %s', 'tag', 'abc', 'DEBUG', 'first' ) expect(consoleLogSpy).toHaveBeenNthCalledWith( - 2, '[%s] %s: %s', 'abc', 'INFO', 'second' + 2, '<%s> [%s] %s: %s', 'tag', 'abc', 'INFO', 'second' ) expect(consoleErrorSpy).toHaveBeenCalledTimes(1) expect(consoleErrorSpy).toHaveBeenCalledWith( - '[%s] %s: %s - %O', 'abc', 'ERROR', 'third', { "errorMessage": "test" } + '<%s> [%s] %s: %s - %O', 'tag', 'abc', 'ERROR', 'third', { "errorMessage": "test" } ) }) @@ -36,7 +36,7 @@ describe('Logger', () => { const consoleLogSpy = jest.fn() jest.spyOn(console, 'log').mockImplementationOnce(consoleLogSpy) - Logger.instantiate('abc', LogLevel.INFO) + Logger.instantiate('tag', 'abc', LogLevel.INFO) Logger.debug('should\'t appear') expect(consoleLogSpy).not.toHaveBeenCalled() diff --git a/tests/common/request.test.ts b/tests/common/request.test.ts index 57285e9..b0f4059 100644 --- a/tests/common/request.test.ts +++ b/tests/common/request.test.ts @@ -8,14 +8,17 @@ import { } from '../../src/common/request' describe('request', () => { + + const testDate = dayjs().add(1, 'day') + describe('validateStringRequest', () => { test('should return ReservationRequest', () => { const body = JSON.stringify({ username: 'collin', password: '123abc', dateRange: { - start: '2021-12-25T12:34:56Z', - end: '2021-12-25T12:45:56Z' + start: testDate.clone().toISOString(), + end: testDate.add(15, 'minutes').toISOString(), }, opponent: { id: '123', @@ -70,7 +73,7 @@ describe('request', () => { password: '123abc', dateRange: { start: 'monkey', - end: '2021-12-25T12:45:56Z' + end: testDate.add(15, 'minutes').toISOString(), }, opponent: { id: '123', @@ -106,8 +109,8 @@ describe('request', () => { username: 'collin', password: '123abc', dateRange: { - start: '2021-12-25T12:34:56Z', - end: '2021-12-25T12:45:56Z' + start: testDate.clone().toISOString(), + end: testDate.add(15, 'minutes').toISOString() }, }) @@ -115,17 +118,17 @@ describe('request', () => { }) test.each([ - { id: 123, name: 'collin' }, - { id: '', name: 'collin' }, - { id: '123', name: true }, - { id: '123', name: '' }, - ])('should fail for invalid opponent id', (opponent) => { + { id: '-50', name: 'collin' }, + { id: 'abc', name: 'collin' }, + { id: '-1', name: '*!@#' }, + { id: '123', name: '!@#' }, + ])('should fail for invalid opponent $id, $name', (opponent) => { const body = JSON.stringify({ username: 'collin', password: '123abc', dateRange: { - start: '2021-12-25T12:34:56Z', - end: '2021-12-25T12:45:56Z' + start: testDate.clone().toISOString(), + end: testDate.add(15, 'minutes').toISOString(), }, opponent, }) @@ -140,8 +143,8 @@ describe('request', () => { username: 'collin', password: '123abc', dateRange: { - start: '2021-12-25T12:34:56Z', - end: '2021-12-25T12:45:56Z' + start: testDate.clone().toISOString(), + end: testDate.add(15, 'minutes').toISOString() }, opponent: { id: '123', diff --git a/tests/lambdas/reservationScheduler.test.ts b/tests/workers/reservationScheduler.test.ts similarity index 79% rename from tests/lambdas/reservationScheduler.test.ts rename to tests/workers/reservationScheduler.test.ts index 1f95dbe..d45b1cb 100644 --- a/tests/lambdas/reservationScheduler.test.ts +++ b/tests/workers/reservationScheduler.test.ts @@ -1,6 +1,6 @@ import dayjs from 'dayjs' import { ValidationError, ValidationErrorCode } from '../../src/common/request' -import { handler, ReservationSchedulerInput, ReservationSchedulerResult } from '../../src/lambdas/reservationScheduler' +import { run, ReservationSchedulerInput, ReservationSchedulerResult } from '../../src/workers/reservationScheduler' jest.mock('../../src/common/logger') @@ -16,8 +16,7 @@ describe('reservationScheduler', () => { opponent: { id: "123", name: "collin" } } - // @ts-expect-error - Stubbing AWS context - await expect(handler(payload, { awsRequestId: '1234' }, undefined)).resolves + await expect(run(payload)).resolves .toMatchObject({ scheduledReservationRequest: { reservationRequest: { @@ -39,8 +38,7 @@ describe('reservationScheduler', () => { opponent: { id: "123", name: "collin" } } - // @ts-expect-error - Stubbing AWS context - await expect(handler(payload, { awsRequestId: '1234' }, undefined)).resolves.toMatchObject({ + await expect(run(payload)).resolves.toMatchObject({ scheduledReservationRequest: { reservationRequest: { username: 'collin', @@ -63,8 +61,7 @@ describe('reservationScheduler', () => { opponent: { id: "123", name: "collin" } } - // @ts-expect-error - Stubbing AWS context - await expect(handler(payload, { awsRequestId: '1234' }, undefined)) + await expect(run(payload)) .rejects .toThrowError(new ValidationError('Invalid request', ValidationErrorCode.INVALID_REQUEST_BODY)) })