Adding some ✨ pretty ✨ stuff
Reorganized code and added some unit tests for reservations and requests
This commit is contained in:
parent
eec0edb76b
commit
743cc08887
16 changed files with 7977 additions and 262 deletions
|
|
@ -7,5 +7,9 @@
|
|||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended"
|
||||
],
|
||||
"ignorePatterns": [
|
||||
"**/*.js",
|
||||
"!src/**/*.ts"
|
||||
]
|
||||
}
|
||||
|
|
@ -1,3 +1,6 @@
|
|||
{
|
||||
"presets": ["@babel/preset-typescript"]
|
||||
"presets": [
|
||||
["@babel/preset-env", { "targets": { "node": "current" } }],
|
||||
"@babel/preset-typescript"
|
||||
]
|
||||
}
|
||||
29
jest.config.js
Normal file
29
jest.config.js
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* For a detailed explanation regarding each configuration property, visit:
|
||||
* https://jestjs.io/docs/configuration
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
clearMocks: true,
|
||||
collectCoverage: true,
|
||||
coverageReporters: [
|
||||
"text"
|
||||
],
|
||||
coveragePathIgnorePatterns: [
|
||||
"/node_modules/"
|
||||
],
|
||||
coverageProvider: "v8",
|
||||
moduleDirectories: [
|
||||
"node_modules"
|
||||
],
|
||||
moduleFileExtensions: [
|
||||
"js",
|
||||
"ts"
|
||||
],
|
||||
roots: [
|
||||
"<rootDir>/tests"
|
||||
],
|
||||
testMatch: [
|
||||
"**/*.test.ts"
|
||||
]
|
||||
};
|
||||
7683
package-lock.json
generated
7683
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -7,6 +7,7 @@
|
|||
"node": "14.x"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "jest",
|
||||
"lint": "eslint src/ --ext ts",
|
||||
"prettier": "prettier src -w",
|
||||
"build": "tsc",
|
||||
|
|
@ -16,18 +17,22 @@
|
|||
"author": "Collin Duncan <cgduncan7@gmail.com>",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"dayjs": "^1.10.7",
|
||||
"puppeteer": "^11.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.16.0",
|
||||
"@babel/preset-env": "^7.16.0",
|
||||
"@babel/preset-env": "^7.16.4",
|
||||
"@babel/preset-typescript": "^7.16.0",
|
||||
"@rollup/plugin-babel": "^5.3.0",
|
||||
"@types/aws-lambda": "^8.10.85",
|
||||
"@types/jest": "^27.0.2",
|
||||
"@types/puppeteer": "^5.4.4",
|
||||
"@typescript-eslint/eslint-plugin": "^5.4.0",
|
||||
"@typescript-eslint/parser": "^5.4.0",
|
||||
"babel-jest": "^27.3.1",
|
||||
"eslint": "^8.2.0",
|
||||
"jest": "^27.3.1",
|
||||
"prettier": "^2.4.1",
|
||||
"typescript": "^4.4.4"
|
||||
}
|
||||
|
|
|
|||
116
src/common/request.ts
Normal file
116
src/common/request.ts
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
import dayjs, { Dayjs } from 'dayjs'
|
||||
import isSameOrBefore from 'dayjs/plugin/isSameOrBefore'
|
||||
dayjs.extend(isSameOrBefore)
|
||||
|
||||
import { DateRange, Opponent } from './reservation'
|
||||
|
||||
export interface ReservationRequest {
|
||||
username: string
|
||||
password: string
|
||||
dateRanges: {
|
||||
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,
|
||||
}
|
||||
|
||||
export class ValidationError extends Error {
|
||||
public readonly code: ValidationErrorCode
|
||||
|
||||
constructor(message: string, code: ValidationErrorCode) {
|
||||
super(message)
|
||||
this.code = code
|
||||
}
|
||||
}
|
||||
|
||||
export const validateRequest = (
|
||||
body: string
|
||||
): ReservationRequest => {
|
||||
const request = validateRequestBody(body)
|
||||
validateRequestDateRanges(request.dateRanges)
|
||||
validateRequestOpponent(request.opponent)
|
||||
return request
|
||||
}
|
||||
|
||||
const validateRequestBody = (body?: string): ReservationRequest => {
|
||||
if (body === undefined) {
|
||||
throw new ValidationError('Invalid request', ValidationErrorCode.UNDEFINED_REQUEST_BODY)
|
||||
}
|
||||
|
||||
const jsonBody = transformRequestBody(body)
|
||||
const { username, password, opponent, dateRanges } = jsonBody
|
||||
|
||||
if (
|
||||
!username ||
|
||||
username.length < 1 ||
|
||||
!password ||
|
||||
password.length < 1 ||
|
||||
!dateRanges ||
|
||||
dateRanges.length < 1 ||
|
||||
(opponent && opponent.id && opponent.id.length < 1) ||
|
||||
(opponent && opponent.name && opponent.name.length < 1)
|
||||
) {
|
||||
throw new ValidationError('Invalid request', ValidationErrorCode.INVALID_REQUEST_BODY)
|
||||
}
|
||||
|
||||
return jsonBody
|
||||
}
|
||||
|
||||
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) }
|
||||
}
|
||||
)
|
||||
return {
|
||||
username: json.username,
|
||||
password: json.password,
|
||||
opponent: json.opponent,
|
||||
dateRanges,
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const validateRequestOpponent = (opponent?: Opponent): void => {
|
||||
if (!opponent) return
|
||||
|
||||
const { id, name } = opponent
|
||||
if (
|
||||
typeof id !== 'string' ||
|
||||
typeof name !== 'string' ||
|
||||
id.length < 1 ||
|
||||
name.length < 1
|
||||
) {
|
||||
throw new ValidationError('Invalid request', ValidationErrorCode.INVALID_OPPONENT)
|
||||
}
|
||||
}
|
||||
40
src/common/reservation.ts
Normal file
40
src/common/reservation.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import dayjs, { Dayjs } from 'dayjs'
|
||||
import isSameOrBefore from 'dayjs/plugin/isSameOrBefore'
|
||||
dayjs.extend(isSameOrBefore)
|
||||
|
||||
export interface Opponent {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
|
||||
export interface DateRange {
|
||||
start: dayjs.Dayjs
|
||||
end: dayjs.Dayjs
|
||||
}
|
||||
|
||||
export class Reservation {
|
||||
public readonly dateRange: DateRange
|
||||
public readonly opponent: Opponent
|
||||
public readonly possibleDates: Dayjs[]
|
||||
public booked = false
|
||||
|
||||
constructor(dateRange: DateRange, opponent: Opponent) {
|
||||
this.dateRange = dateRange
|
||||
this.opponent = opponent
|
||||
this.possibleDates = this.createPossibleDates()
|
||||
}
|
||||
|
||||
private createPossibleDates(): Dayjs[] {
|
||||
const possibleDates: Dayjs[] = []
|
||||
|
||||
const { start, end } = this.dateRange
|
||||
|
||||
let possibleDate = dayjs(start)
|
||||
while (possibleDate.isSameOrBefore(end)) {
|
||||
possibleDates.push(possibleDate)
|
||||
possibleDate = possibleDate.add(15, 'minute')
|
||||
}
|
||||
|
||||
return possibleDates
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
import { Dayjs } from 'dayjs'
|
||||
import puppeteer, {
|
||||
Browser,
|
||||
BrowserConnectOptions,
|
||||
|
|
@ -6,7 +7,8 @@ import puppeteer, {
|
|||
LaunchOptions,
|
||||
Page,
|
||||
} from 'puppeteer'
|
||||
import { DateTime, Opponent, Reservation, timeToString } from './reservation'
|
||||
|
||||
import { Opponent, Reservation } from './reservation'
|
||||
|
||||
export class Runner {
|
||||
private readonly username: string
|
||||
|
|
@ -58,7 +60,7 @@ export class Runner {
|
|||
|
||||
private async makeReservation(reservation: Reservation): Promise<void> {
|
||||
try {
|
||||
await this.navigateToDay(reservation.dateTime)
|
||||
await this.navigateToDay(reservation.dateRange.start)
|
||||
await this.selectAvailableTime(reservation)
|
||||
await this.selectOpponent(reservation.opponent)
|
||||
await this.confirmReservation()
|
||||
|
|
@ -68,21 +70,25 @@ export class Runner {
|
|||
}
|
||||
}
|
||||
|
||||
private async navigateToDay(dt: DateTime): Promise<void> {
|
||||
private async navigateToDay(date: Dayjs): Promise<void> {
|
||||
await this.page
|
||||
?.waitForSelector(`td#cal_${dt.year}_${dt.month}_${dt.day}`)
|
||||
?.waitForSelector(
|
||||
`td#cal_${date.get('year')}_${date.get('month') + 1}_${date.get('day')}`
|
||||
)
|
||||
.then((d) => d?.click())
|
||||
await this.page?.waitForSelector(
|
||||
`td#cal_${dt.year}_${dt.month}_${dt.day}.selected`
|
||||
`td#cal_${date.get('year')}_${date.get('month') + 1}_${date.get(
|
||||
'day'
|
||||
)}.selected`
|
||||
)
|
||||
}
|
||||
|
||||
private async selectAvailableTime(res: Reservation): Promise<void> {
|
||||
let freeCourt: ElementHandle | null | undefined
|
||||
let i = 0
|
||||
while (i < res.possibleTimes.length && !freeCourt) {
|
||||
const possibleTime = res.possibleTimes[i]
|
||||
const timeString = timeToString(possibleTime)
|
||||
while (i < res.possibleDates.length && !freeCourt) {
|
||||
const possibleDate = res.possibleDates[i]
|
||||
const timeString = possibleDate.format('HH:mm')
|
||||
const selector =
|
||||
`tr[data-time='${timeString}']` + `> td.free[rowspan='3'][type='free']`
|
||||
freeCourt = await this.page?.$(selector)
|
||||
18
src/lambda/reservationHandler.ts
Normal file
18
src/lambda/reservationHandler.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
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,8 +1,8 @@
|
|||
import { SQSEvent, SQSHandler } from 'aws-lambda'
|
||||
import { validateRequestEvent } from './request'
|
||||
import { validateRequestEvent } from '../common/request'
|
||||
|
||||
import { Reservation } from './reservation'
|
||||
import { Runner } from './runner'
|
||||
import { Reservation } from '../common/reservation'
|
||||
import { Runner } from '../common/runner'
|
||||
|
||||
export const run: SQSHandler = async (event: SQSEvent): Promise<void> => {
|
||||
const { request, error } = validateRequestEvent(event)
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { IncomingRequest } from './request'
|
||||
import { Reservation } from './reservation'
|
||||
import { Runner } from './runner'
|
||||
import { IncomingRequest } from './common/request'
|
||||
import { Reservation } from './common/reservation'
|
||||
import { Runner } from './common/runner'
|
||||
|
||||
const run = async (request: IncomingRequest) => {
|
||||
const { username, password, dateTimes, opponent } = request
|
||||
|
|
|
|||
114
src/request.ts
114
src/request.ts
|
|
@ -1,114 +0,0 @@
|
|||
import { SQSEvent } from 'aws-lambda'
|
||||
import { DateTime, Opponent } from './reservation'
|
||||
|
||||
export interface IncomingRequest {
|
||||
username: string
|
||||
password: string
|
||||
dateTimes: DateTime[]
|
||||
opponent: Opponent
|
||||
}
|
||||
|
||||
export interface ValidationError {
|
||||
message: string
|
||||
code: number
|
||||
}
|
||||
|
||||
export const validateRequestEvent = (
|
||||
event: SQSEvent
|
||||
): { request?: IncomingRequest; error?: ValidationError } => {
|
||||
try {
|
||||
const request = validateRequestBody(event.Records[0].body)
|
||||
validateRequestDateTimes(request.dateTimes)
|
||||
validateRequestOpponent(request.opponent)
|
||||
return { request }
|
||||
} catch (err: unknown) {
|
||||
return { error: { message: 'Invalid request', code: (err as ValidationError).code ?? 0 } }
|
||||
}
|
||||
}
|
||||
|
||||
const validateRequestBody = (body?: string): IncomingRequest => {
|
||||
if (body === undefined) {
|
||||
throw {
|
||||
message: 'Invalid request',
|
||||
code: 1,
|
||||
}
|
||||
}
|
||||
|
||||
let jsonBody: IncomingRequest
|
||||
try {
|
||||
jsonBody = JSON.parse(body)
|
||||
} catch (err) {
|
||||
throw {
|
||||
message: 'Invalid request',
|
||||
code: 2,
|
||||
}
|
||||
}
|
||||
|
||||
const { username, password, dateTimes } = jsonBody
|
||||
if (
|
||||
!username ||
|
||||
username.length < 1 ||
|
||||
!password ||
|
||||
password.length < 1 ||
|
||||
!dateTimes
|
||||
) {
|
||||
throw {
|
||||
message: 'Invalid request',
|
||||
code: 3,
|
||||
}
|
||||
}
|
||||
|
||||
return jsonBody
|
||||
}
|
||||
|
||||
const validateRequestDateTimes = (dateTimes: DateTime[]): void => {
|
||||
const now = new Date()
|
||||
for (let i = 0; i < dateTimes.length; i++) {
|
||||
const dateTime = dateTimes[i]
|
||||
const { year, month, day, timeRange } = dateTime
|
||||
const { start, end } = timeRange
|
||||
|
||||
if (
|
||||
typeof year !== 'number' ||
|
||||
typeof month !== 'number' ||
|
||||
typeof day !== 'number' ||
|
||||
typeof start.hour !== 'number' ||
|
||||
typeof start.minute !== 'number' ||
|
||||
typeof end.hour !== 'number' ||
|
||||
typeof end.minute !== 'number'
|
||||
) {
|
||||
throw {
|
||||
message: 'Invalid request',
|
||||
code: 4,
|
||||
}
|
||||
}
|
||||
|
||||
const date = new Date()
|
||||
date.setFullYear(year, month - 1, day)
|
||||
date.setHours(start.hour, start.minute)
|
||||
|
||||
if (now.getTime() >= date.getTime()) {
|
||||
throw {
|
||||
message: 'Invalid request',
|
||||
code: 5,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const validateRequestOpponent = (opponent?: Opponent): void => {
|
||||
if (!opponent) return
|
||||
|
||||
const { id, name } = opponent
|
||||
if (
|
||||
typeof id !== 'string' ||
|
||||
typeof name !== 'string' ||
|
||||
id.length < 1 ||
|
||||
name.length < 1
|
||||
) {
|
||||
throw {
|
||||
message: 'Invalid request',
|
||||
code: 6,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
export interface Time {
|
||||
hour: number
|
||||
minute: number
|
||||
}
|
||||
|
||||
export interface Opponent {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
|
||||
export interface DateTime {
|
||||
year: number
|
||||
month: number
|
||||
day: number
|
||||
timeRange: {
|
||||
start: Time
|
||||
end: Time
|
||||
}
|
||||
}
|
||||
|
||||
export const timeToString = ({ hour, minute }: Time): string =>
|
||||
`${`${hour}`.padStart(2, '0')}:${`${minute}`.padStart(2, '0')}`
|
||||
|
||||
export class Reservation {
|
||||
public readonly dateTime: DateTime
|
||||
public readonly opponent: Opponent
|
||||
public readonly possibleTimes: Time[]
|
||||
public booked = false
|
||||
|
||||
constructor(dateTime: DateTime, opponent: Opponent) {
|
||||
this.dateTime = dateTime
|
||||
this.opponent = opponent
|
||||
this.possibleTimes = this.createPossibleTimes()
|
||||
}
|
||||
|
||||
private createPossibleTimes() {
|
||||
const possibleTimes: Time[] = []
|
||||
|
||||
const { start, end } = this.dateTime.timeRange
|
||||
|
||||
let { hour, minute } = start
|
||||
const { hour: endHour, minute: endMinute } = end
|
||||
|
||||
while (hour <= endHour && minute <= endMinute) {
|
||||
possibleTimes.push({ hour, minute })
|
||||
minute = (minute + 15) % 60
|
||||
if (minute === 0) {
|
||||
hour++
|
||||
}
|
||||
}
|
||||
|
||||
return possibleTimes
|
||||
}
|
||||
}
|
||||
112
tests/common/request.test.ts
Normal file
112
tests/common/request.test.ts
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
import dayjs from 'dayjs'
|
||||
|
||||
import {
|
||||
validateRequest,
|
||||
ValidationError,
|
||||
ValidationErrorCode,
|
||||
} from '../../src/common/request'
|
||||
|
||||
describe('request', () => {
|
||||
describe('validateRequest', () => {
|
||||
test('should return ReservationRequest', () => {
|
||||
const body = JSON.stringify({
|
||||
username: 'collin',
|
||||
password: '123abc',
|
||||
dateRanges: [
|
||||
{ start: '2021-12-25T12:34:56Z', end: '2021-12-25T12:45:56Z' }
|
||||
],
|
||||
opponent: {
|
||||
id: '123',
|
||||
name: 'collin',
|
||||
}
|
||||
})
|
||||
|
||||
expect(() => validateRequest(body)).not.toThrow()
|
||||
})
|
||||
|
||||
test('should fail for undefined body', () => {
|
||||
expect(() => validateRequest(undefined)).toThrowError(new ValidationError('Invalid request', ValidationErrorCode.UNDEFINED_REQUEST_BODY))
|
||||
})
|
||||
|
||||
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: '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' } },
|
||||
])('should fail for body missing required values', (body) => {
|
||||
expect(() => validateRequest(JSON.stringify(body))).toThrowError(new ValidationError('Invalid request', ValidationErrorCode.INVALID_REQUEST_BODY))
|
||||
})
|
||||
|
||||
test('should fail for invalid date range', () => {
|
||||
const body = JSON.stringify({
|
||||
username: 'collin',
|
||||
password: '123abc',
|
||||
dateRanges: [
|
||||
{ start: 'monkey', end: '2021-12-25T12:45:56Z' }
|
||||
],
|
||||
opponent: {
|
||||
id: '123',
|
||||
name: 'collin',
|
||||
}
|
||||
})
|
||||
|
||||
expect(() => validateRequest(body)).toThrowError(new ValidationError('Invalid request', ValidationErrorCode.INVALID_DATE_RANGE))
|
||||
})
|
||||
|
||||
test.each([
|
||||
{ start: dayjs().subtract(1, 'hour').toString(), end: dayjs().add(1, 'hour').toString() },
|
||||
{ start: dayjs().add(2, 'hour').toString(), end: dayjs().add(1, 'hour').toString() },
|
||||
{ start: dayjs().toString(), end: dayjs().add(1, 'day').toString() }
|
||||
])('should fail for improper start or end dates', (dateRange) => {
|
||||
const body = JSON.stringify({
|
||||
username: 'collin',
|
||||
password: '123abc',
|
||||
dateRanges: [
|
||||
dateRange
|
||||
],
|
||||
opponent: {
|
||||
id: '123',
|
||||
name: 'collin',
|
||||
}
|
||||
})
|
||||
|
||||
expect(() => validateRequest(body)).toThrowError(new ValidationError('Invalid request', ValidationErrorCode.INVALID_START_OR_END_DATE))
|
||||
})
|
||||
|
||||
test('should not fail if no opponent is provided', () => {
|
||||
const body = JSON.stringify({
|
||||
username: 'collin',
|
||||
password: '123abc',
|
||||
dateRanges: [
|
||||
{ start: '2021-12-25T12:34:56Z', end: '2021-12-25T12:45:56Z' }
|
||||
],
|
||||
})
|
||||
|
||||
expect(() => validateRequest(body)).not.toThrow()
|
||||
})
|
||||
|
||||
test.each([
|
||||
{ id: 123, name: 'collin' },
|
||||
{ id: '', name: 'collin' },
|
||||
{ id: '123', name: true },
|
||||
{ id: '123', name: '' },
|
||||
])('should fail for invalid opponent id', (opponent) => {
|
||||
const body = JSON.stringify({
|
||||
username: 'collin',
|
||||
password: '123abc',
|
||||
dateRanges: [
|
||||
{ start: '2021-12-25T12:34:56Z', end: '2021-12-25T12:45:56Z' }
|
||||
],
|
||||
opponent,
|
||||
})
|
||||
|
||||
expect(() => validateRequest(body)).toThrowError(new ValidationError('Invalid request', ValidationErrorCode.INVALID_OPPONENT))
|
||||
})
|
||||
})
|
||||
})
|
||||
22
tests/common/reservation.test.ts
Normal file
22
tests/common/reservation.test.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import dayjs from 'dayjs'
|
||||
import { DateRange, Reservation } from '../../src/common/reservation'
|
||||
|
||||
describe("Reservation", () => {
|
||||
it("will create correct possible dates", () => {
|
||||
const startDate = dayjs().set("hour", 12).set("minute", 0)
|
||||
const endDate = dayjs().set("hour", 13).set("minute", 0)
|
||||
const dateRange: DateRange = {
|
||||
start: startDate,
|
||||
end: endDate,
|
||||
}
|
||||
const res = new Reservation(dateRange, { id: 'collin', name: 'collin' })
|
||||
|
||||
expect(res.possibleDates).toHaveLength(5)
|
||||
|
||||
expect(res.possibleDates[0]).toEqual(startDate)
|
||||
expect(res.possibleDates[1]).toEqual(startDate.add(15, "minute"))
|
||||
expect(res.possibleDates[2]).toEqual(startDate.add(30, "minute"))
|
||||
expect(res.possibleDates[3]).toEqual(startDate.add(45, "minute"))
|
||||
expect(res.possibleDates[4]).toEqual(startDate.add(60, "minute"))
|
||||
})
|
||||
})
|
||||
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"outDir": "dist/"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue