Updating request to focus on single request, started working on reservationScheduler
This commit is contained in:
parent
743cc08887
commit
7246ecabf2
7 changed files with 127 additions and 94 deletions
|
|
@ -7,19 +7,20 @@ import { DateRange, Opponent } from './reservation'
|
|||
export interface ReservationRequest {
|
||||
username: string
|
||||
password: string
|
||||
dateRanges: {
|
||||
dateRange: {
|
||||
start: Dayjs
|
||||
end: Dayjs
|
||||
}[]
|
||||
}
|
||||
opponent: Opponent
|
||||
}
|
||||
|
||||
export enum ValidationErrorCode {
|
||||
UNDEFINED_REQUEST_BODY = 1,
|
||||
INVALID_REQUEST_BODY = 2,
|
||||
INVALID_DATE_RANGE = 3,
|
||||
INVALID_START_OR_END_DATE = 4,
|
||||
INVALID_OPPONENT = 5,
|
||||
UNDEFINED_REQUEST_BODY,
|
||||
INVALID_JSON,
|
||||
INVALID_REQUEST_BODY,
|
||||
INVALID_DATE_RANGE,
|
||||
INVALID_START_OR_END_DATE,
|
||||
INVALID_OPPONENT,
|
||||
}
|
||||
|
||||
export class ValidationError extends Error {
|
||||
|
|
@ -31,11 +32,16 @@ export class ValidationError extends Error {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates an incoming request body and converts to ReservationRequest
|
||||
* @param body String of request body
|
||||
* @returns ReservationRequest
|
||||
*/
|
||||
export const validateRequest = (
|
||||
body: string
|
||||
): ReservationRequest => {
|
||||
const request = validateRequestBody(body)
|
||||
validateRequestDateRanges(request.dateRanges)
|
||||
validateRequestDateRange(request.dateRange)
|
||||
validateRequestOpponent(request.opponent)
|
||||
return request
|
||||
}
|
||||
|
|
@ -46,15 +52,16 @@ const validateRequestBody = (body?: string): ReservationRequest => {
|
|||
}
|
||||
|
||||
const jsonBody = transformRequestBody(body)
|
||||
const { username, password, opponent, dateRanges } = jsonBody
|
||||
const { username, password, opponent, dateRange } = jsonBody
|
||||
|
||||
if (
|
||||
!username ||
|
||||
username.length < 1 ||
|
||||
!password ||
|
||||
password.length < 1 ||
|
||||
!dateRanges ||
|
||||
dateRanges.length < 1 ||
|
||||
!dateRange ||
|
||||
!dateRange.start ||
|
||||
!dateRange.end ||
|
||||
(opponent && opponent.id && opponent.id.length < 1) ||
|
||||
(opponent && opponent.name && opponent.name.length < 1)
|
||||
) {
|
||||
|
|
@ -65,39 +72,40 @@ const validateRequestBody = (body?: string): ReservationRequest => {
|
|||
}
|
||||
|
||||
const transformRequestBody = (body: string): ReservationRequest => {
|
||||
const json = JSON.parse(body)
|
||||
const dateRanges: DateRange[] = json.dateRanges?.map(
|
||||
({ start, end }: { start: string; end: string }): DateRange => {
|
||||
return { start: dayjs(start), end: dayjs(end) }
|
||||
}
|
||||
)
|
||||
let json
|
||||
try {
|
||||
json = JSON.parse(body)
|
||||
} catch (err) {
|
||||
throw new ValidationError('Invalid request', ValidationErrorCode.INVALID_JSON)
|
||||
}
|
||||
const startTime = json.dateRange?.start ?? 'invalid'
|
||||
const endTime = json.dateRange?.end ?? 'invalid'
|
||||
const dateRange: DateRange = { start: dayjs(startTime), end: dayjs(endTime) }
|
||||
return {
|
||||
username: json.username,
|
||||
password: json.password,
|
||||
opponent: json.opponent,
|
||||
dateRanges,
|
||||
dateRange,
|
||||
}
|
||||
}
|
||||
|
||||
const validateRequestDateRanges = (dateRanges: DateRange[]): void => {
|
||||
for (let i = 0; i < dateRanges.length; i++) {
|
||||
// checking that both dates are valid
|
||||
const { start, end } = dateRanges[i]
|
||||
if (!start.isValid() || !end.isValid()) {
|
||||
throw new ValidationError('Invalid request', ValidationErrorCode.INVALID_DATE_RANGE)
|
||||
}
|
||||
const validateRequestDateRange = (dateRange: DateRange): void => {
|
||||
// checking that both dates are valid
|
||||
const { start, end } = dateRange
|
||||
if (!start.isValid() || !end.isValid()) {
|
||||
throw new ValidationError('Invalid request', ValidationErrorCode.INVALID_DATE_RANGE)
|
||||
}
|
||||
|
||||
// checking that:
|
||||
// 1. start occurs after now
|
||||
// 2. start occurs before or same as end
|
||||
// 3. start and end fall on same YYYY/MM/DD
|
||||
if (
|
||||
!start.isAfter(dayjs()) ||
|
||||
!start.isSameOrBefore(end) ||
|
||||
start.format('YYYY MM DD') !== end.format('YYYY MM DD')
|
||||
) {
|
||||
throw new ValidationError('Invalid request', ValidationErrorCode.INVALID_START_OR_END_DATE)
|
||||
}
|
||||
// checking that:
|
||||
// 1. start occurs after now
|
||||
// 2. start occurs before or same as end
|
||||
// 3. start and end fall on same YYYY/MM/DD
|
||||
if (
|
||||
!start.isAfter(dayjs()) ||
|
||||
!start.isSameOrBefore(end) ||
|
||||
start.format('YYYY MM DD') !== end.format('YYYY MM DD')
|
||||
) {
|
||||
throw new ValidationError('Invalid request', ValidationErrorCode.INVALID_START_OR_END_DATE)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
import { SQSEvent, SQSHandler } from 'aws-lambda'
|
||||
import { validateRequest } from '../common/request'
|
||||
|
||||
import { Reservation } from '../common/reservation'
|
||||
import { Runner } from '../common/runner'
|
||||
|
||||
export const run: SQSHandler = async (event: SQSEvent): Promise<void> => {
|
||||
const { request, error } = validateRequest(event.Records[0].body)
|
||||
if (error || !request) {
|
||||
throw new Error(error?.message)
|
||||
}
|
||||
|
||||
const { username, password, dateRanges, opponent } = request
|
||||
const reservations = dateRanges.map((dr) => new Reservation(dr, opponent))
|
||||
|
||||
const runner = new Runner(username, password, reservations)
|
||||
await runner.run({ headless: false })
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
import { SQSEvent, SQSHandler } from 'aws-lambda'
|
||||
import { validateRequestEvent } from '../common/request'
|
||||
|
||||
import { Reservation } from '../common/reservation'
|
||||
import { Runner } from '../common/runner'
|
||||
|
||||
export const run: SQSHandler = async (event: SQSEvent): Promise<void> => {
|
||||
const { request, error } = validateRequestEvent(event)
|
||||
if (error || !request) {
|
||||
throw new Error(error?.message)
|
||||
}
|
||||
|
||||
const { username, password, dateTimes, opponent } = request
|
||||
const reservations = dateTimes.map((dt) => new Reservation(dt, opponent))
|
||||
|
||||
const runner = new Runner(username, password, reservations)
|
||||
await runner.run({ headless: false })
|
||||
}
|
||||
7
src/lambdas/reservationHandler.ts
Normal file
7
src/lambdas/reservationHandler.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import { Handler } from 'aws-lambda'
|
||||
|
||||
import { Reservation } from '../common/reservation'
|
||||
import { Runner } from '../common/runner'
|
||||
|
||||
export const run: Handler = async (payload: string): Promise<void> => {
|
||||
}
|
||||
22
src/lambdas/reservationScheduler.ts
Normal file
22
src/lambdas/reservationScheduler.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import { Handler } from 'aws-lambda'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
import { InputEvent } from '../stepFunctions/event'
|
||||
import { Reservation } from '../common/reservation'
|
||||
import { validateRequest } from '../common/request'
|
||||
import { Runner } from '../common/runner'
|
||||
|
||||
export const run: Handler<InputEvent, void> = async (input: InputEvent): Promise<void> => {
|
||||
console.log(`Handling event: ${input}`)
|
||||
const { username, password, dateRange, opponent } = validateRequest(JSON.stringify(input.reservationRequest))
|
||||
console.log('Successfully validated request')
|
||||
|
||||
console.log('Creating reservation')
|
||||
const reservation = new Reservation(dateRange, opponent)
|
||||
console.log('Created reservation')
|
||||
|
||||
console.log('Runner starting')
|
||||
const runner = new Runner(username, password, [reservation])
|
||||
await runner.run()
|
||||
console.log('Runner finished')
|
||||
}
|
||||
9
src/stepFunctions/event.ts
Normal file
9
src/stepFunctions/event.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { ReservationRequest } from "../common/request";
|
||||
|
||||
export interface RawReservationRequest extends Omit<ReservationRequest, 'dateRanges'> {
|
||||
dateRanges: { start: string, end: string }[]
|
||||
}
|
||||
|
||||
export interface InputEvent {
|
||||
reservationRequest: RawReservationRequest
|
||||
}
|
||||
|
|
@ -12,9 +12,10 @@ describe('request', () => {
|
|||
const body = JSON.stringify({
|
||||
username: 'collin',
|
||||
password: '123abc',
|
||||
dateRanges: [
|
||||
{ start: '2021-12-25T12:34:56Z', end: '2021-12-25T12:45:56Z' }
|
||||
],
|
||||
dateRange: {
|
||||
start: '2021-12-25T12:34:56Z',
|
||||
end: '2021-12-25T12:45:56Z'
|
||||
},
|
||||
opponent: {
|
||||
id: '123',
|
||||
name: 'collin',
|
||||
|
|
@ -28,17 +29,36 @@ describe('request', () => {
|
|||
expect(() => validateRequest(undefined)).toThrowError(new ValidationError('Invalid request', ValidationErrorCode.UNDEFINED_REQUEST_BODY))
|
||||
})
|
||||
|
||||
test('should fail for invalid json', () => {
|
||||
const body = `A{
|
||||
username: 'collin',
|
||||
password: '123abc',
|
||||
dateRange: {
|
||||
start: '2021-12-25T12:34:56Z',
|
||||
end: '2021-12-25T12:45:56Z'
|
||||
},
|
||||
opponent: {
|
||||
id: '123',
|
||||
name: 'collin',
|
||||
}
|
||||
}`
|
||||
|
||||
expect(() => validateRequest(body)).toThrowError(new ValidationError('Invalid request', ValidationErrorCode.INVALID_JSON))
|
||||
})
|
||||
|
||||
test.each([
|
||||
{ username: '', password: '1qaz2wsx', dateRanges: [{ start: '1', end: '1' }], opponent: { id: '123', name: 'abc' } },
|
||||
{ password: '1qaz2wsx', dateRanges: [{ start: '1', end: '1' }], opponent: { id: '123', name: 'abc' } },
|
||||
{ username: 'collin', password: '', dateRanges: [{ start: '1', end: '1' }], opponent: { id: '123', name: 'abc' } },
|
||||
{ username: 'collin', dateRanges: [{ start: '1', end: '1' }], opponent: { id: '123', name: 'abc' } },
|
||||
{ username: 'collin', password: '1qaz2wsx', dateRanges: [], opponent: { id: '123', name: 'abc' } },
|
||||
{ username: '', password: '1qaz2wsx', dateRange: { start: '1', end: '1' }, opponent: { id: '123', name: 'abc' } },
|
||||
{ password: '1qaz2wsx', dateRange: { start: '1', end: '1' }, opponent: { id: '123', name: 'abc' } },
|
||||
{ username: 'collin', password: '', dateRange: { start: '1', end: '1' }, opponent: { id: '123', name: 'abc' } },
|
||||
{ username: 'collin', dateRange: { start: '1', end: '1' }, opponent: { id: '123', name: 'abc' } },
|
||||
{ username: 'collin', password: '1qaz2wsx', dateRange: {}, opponent: { id: '123', name: 'abc' } },
|
||||
{ username: 'collin', password: '1qaz2wsx', dateRange: { start: '1' }, opponent: { id: '123', name: 'abc' } },
|
||||
{ username: 'collin', password: '1qaz2wsx', dateRange: { end: '1' }, opponent: { id: '123', name: 'abc' } },
|
||||
{ username: 'collin', password: '1qaz2wsx', opponent: { id: '123', name: 'abc' } },
|
||||
{ username: 'collin', password: '1qaz2wsx', dateRanges: [{ start: '1', end: '1' }], opponent: { id: '', name: 'abc' } },
|
||||
{ username: 'collin', password: '1qaz2wsx', dateRanges: [{ start: '1', end: '1' }], opponent: { name: 'abc' } },
|
||||
{ username: 'collin', password: '1qaz2wsx', dateRanges: [{ start: '1', end: '1' }], opponent: { id: '123', name: '' } },
|
||||
{ username: 'collin', password: '1qaz2wsx', dateRanges: [{ start: '1', end: '1' }], opponent: { id: '123' } },
|
||||
{ username: 'collin', password: '1qaz2wsx', dateRange: { start: '1', end: '1' }, opponent: { id: '', name: 'abc' } },
|
||||
{ username: 'collin', password: '1qaz2wsx', dateRange: { start: '1', end: '1' }, opponent: { name: 'abc' } },
|
||||
{ username: 'collin', password: '1qaz2wsx', dateRange: { start: '1', end: '1' }, opponent: { id: '123', name: '' } },
|
||||
{ username: 'collin', password: '1qaz2wsx', dateRange: { start: '1', end: '1' }, opponent: { id: '123' } },
|
||||
])('should fail for body missing required values', (body) => {
|
||||
expect(() => validateRequest(JSON.stringify(body))).toThrowError(new ValidationError('Invalid request', ValidationErrorCode.INVALID_REQUEST_BODY))
|
||||
})
|
||||
|
|
@ -47,9 +67,10 @@ describe('request', () => {
|
|||
const body = JSON.stringify({
|
||||
username: 'collin',
|
||||
password: '123abc',
|
||||
dateRanges: [
|
||||
{ start: 'monkey', end: '2021-12-25T12:45:56Z' }
|
||||
],
|
||||
dateRange: {
|
||||
start: 'monkey',
|
||||
end: '2021-12-25T12:45:56Z'
|
||||
},
|
||||
opponent: {
|
||||
id: '123',
|
||||
name: 'collin',
|
||||
|
|
@ -67,7 +88,7 @@ describe('request', () => {
|
|||
const body = JSON.stringify({
|
||||
username: 'collin',
|
||||
password: '123abc',
|
||||
dateRanges: [
|
||||
dateRange: [
|
||||
dateRange
|
||||
],
|
||||
opponent: {
|
||||
|
|
@ -83,9 +104,10 @@ describe('request', () => {
|
|||
const body = JSON.stringify({
|
||||
username: 'collin',
|
||||
password: '123abc',
|
||||
dateRanges: [
|
||||
{ start: '2021-12-25T12:34:56Z', end: '2021-12-25T12:45:56Z' }
|
||||
],
|
||||
dateRange: {
|
||||
start: '2021-12-25T12:34:56Z',
|
||||
end: '2021-12-25T12:45:56Z'
|
||||
},
|
||||
})
|
||||
|
||||
expect(() => validateRequest(body)).not.toThrow()
|
||||
|
|
@ -100,9 +122,10 @@ describe('request', () => {
|
|||
const body = JSON.stringify({
|
||||
username: 'collin',
|
||||
password: '123abc',
|
||||
dateRanges: [
|
||||
{ start: '2021-12-25T12:34:56Z', end: '2021-12-25T12:45:56Z' }
|
||||
],
|
||||
dateRange: {
|
||||
start: '2021-12-25T12:34:56Z',
|
||||
end: '2021-12-25T12:45:56Z'
|
||||
},
|
||||
opponent,
|
||||
})
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue