Compare commits

..

3 commits

Author SHA1 Message Date
fc5f8b2f11 Removing github workflow
All checks were successful
ci/woodpecker/push/test Pipeline was successful
2025-02-02 14:02:59 +01:00
d5ccc4e570 Updating image of woodpecker test to node/hydrogen
Some checks failed
ci/woodpecker/push/test Pipeline was successful
Push to main / test (push) Has been cancelled
Push to main / build-image (push) Has been cancelled
2025-02-02 12:18:16 +01:00
3e0ba829d3
Adding woodpecker-cicd workflow file
Some checks failed
Push to main / test (push) Waiting to run
Push to main / build-image (push) Blocked by required conditions
ci/woodpecker/push/test Pipeline failed
Signed-off-by: Collin Duncan <github@collinduncan.com>
2025-02-02 11:25:02 +01:00
13 changed files with 114 additions and 230 deletions

View file

@ -1,43 +0,0 @@
name: Push to main
on:
push:
branches:
- main
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: lts/hydrogen
- run: npm ci
- run: npm run test:unit
build-image:
needs:
- test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ secrets.GHCR_USERNAME }}
password: ${{ secrets.GHCR_PASSWORD }}
- name: Declare GIT_COMMIT
shell: bash
run: |
echo "GIT_COMMIT=$(git rev-parse --short "$GITHUB_SHA")" >> "$GITHUB_ENV"
- name: Build docker image
uses: docker/build-push-action@v5
with:
context: .
file: ./docker/server/Dockerfile
push: true
tags: ghcr.io/cgduncan7/autobaan:latest
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: GIT_COMMIT=${{ env.GIT_COMMIT }}

10
.woodpecker/test.yaml Normal file
View file

@ -0,0 +1,10 @@
when:
- branch: main
event: push
steps:
- name: test
image: docker.io/node:hydrogen-slim
commands:
- npm ci
- npm run test:unit

View file

@ -1,31 +0,0 @@
import { MigrationInterface, QueryRunner } from 'typeorm'
export class ReservationStatuses1739366336570 implements MigrationInterface {
name = 'ReservationStatuses1739366336570'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE "temporary_reservations" ("id" varchar PRIMARY KEY NOT NULL, "dateRangeStart" datetime NOT NULL, "dateRangeEnd" datetime NOT NULL, "status" varchar(32) NOT NULL DEFAULT ('pending'), "waitingListId" integer, "ownerId" varchar(32) NOT NULL, "opponents" json NOT NULL)`,
)
await queryRunner.query(
`INSERT INTO "temporary_reservations"("id", "dateRangeStart", "dateRangeEnd", "status", "waitingListId", "ownerId", "opponents") SELECT "id", "dateRangeStart", "dateRangeEnd", CASE "waitListed" WHEN true THEN "on_waiting_list" ELSE "pending" END, "waitingListId", "ownerId", "opponents" FROM "reservations"`,
)
await queryRunner.query(`DROP TABLE "reservations"`)
await queryRunner.query(
`ALTER TABLE "temporary_reservations" RENAME TO "reservations"`,
)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "reservations" RENAME TO "temporary_reservations"`,
)
await queryRunner.query(
`CREATE TABLE "reservations" ("id" varchar PRIMARY KEY NOT NULL, "dateRangeStart" datetime NOT NULL, "dateRangeEnd" datetime NOT NULL, "waitListed" boolean NOT NULL DEFAULT (0), "waitingListId" integer, "ownerId" varchar(32) NOT NULL, "opponents" json NOT NULL)`,
)
await queryRunner.query(
`INSERT INTO "reservations"("id", "dateRangeStart", "dateRangeEnd", "waitListed", "waitingListId", "ownerId", "opponents") SELECT "id", "dateRangeStart", "dateRangeEnd", CASE "status" WHEN "on_waiting_list" THEN true ELSE false, "waitingListId", "ownerId", "opponents" FROM "temporary_reservations"`,
)
await queryRunner.query(`DROP TABLE "temporary_reservations"`)
}
}

View file

@ -1,6 +1,5 @@
import 'dayjs/locale/nl' import 'dayjs/locale/nl'
import { TransformationType, TransformFnParams } from 'class-transformer'
import * as dayjs from 'dayjs' import * as dayjs from 'dayjs'
import * as customParseFormat from 'dayjs/plugin/customParseFormat' import * as customParseFormat from 'dayjs/plugin/customParseFormat'
import * as isSameOrBefore from 'dayjs/plugin/isSameOrBefore' import * as isSameOrBefore from 'dayjs/plugin/isSameOrBefore'
@ -42,20 +41,4 @@ const dayjsTz = (
return dayjs(date, format).tz('Europe/Amsterdam') return dayjs(date, format).tz('Europe/Amsterdam')
} }
export const DayjsTransformer = ({ value, type }: TransformFnParams) => {
switch (type) {
case TransformationType.PLAIN_TO_CLASS:
return dayjsTz(value)
case TransformationType.CLASS_TO_PLAIN:
return value.format()
default:
return value
}
}
export const DayjsColumnTransformer = {
to: (value: dayjs.Dayjs) => value.format(),
from: (value: Date) => dayjsTz(value),
}
export default dayjsTz export default dayjsTz

View file

@ -19,6 +19,7 @@ export class Monitor {
@Column('datetime', { @Column('datetime', {
nullable: false, nullable: false,
default: dayjs(),
transformer: { transformer: {
to: (value?: Dayjs) => (value ?? dayjs()).format(), to: (value?: Dayjs) => (value ?? dayjs()).format(),
from: (value: Date) => dayjs(value), from: (value: Date) => dayjs(value),

View file

@ -2,7 +2,6 @@ import { Injectable } from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm' import { InjectRepository } from '@nestjs/typeorm'
import { Repository } from 'typeorm' import { Repository } from 'typeorm'
import dayjsTz from '../common/dayjs'
import { Monitor, MonitorType } from './entity' import { Monitor, MonitorType } from './entity'
@Injectable() @Injectable()
@ -14,7 +13,7 @@ export class MonitorsService {
async performMonitor(type: MonitorType, data: string) { async performMonitor(type: MonitorType, data: string) {
await this.monitorsRepository.save( await this.monitorsRepository.save(
this.monitorsRepository.create({ type, data, createdAt: dayjsTz() }), this.monitorsRepository.create({ type, data }),
) )
} }
} }

View file

@ -121,14 +121,14 @@ export class NtfyProvider implements OnApplicationBootstrap {
) )
} }
async sendReservationOnWaitingListNotification( async sendReservationWaitlistedNotification(
reservationId: string, reservationId: string,
startTime: Dayjs, startTime: Dayjs,
endTime: Dayjs, endTime: Dayjs,
) { ) {
await this.publishQueue.add( await this.publishQueue.add(
...NtfyProvider.defaultJob({ ...NtfyProvider.defaultJob({
title: 'Reservation added to waiting list', title: 'Reservation waitlisted',
message: `${reservationId} - ${startTime.format()} to ${endTime.format()}`, message: `${reservationId} - ${startTime.format()} to ${endTime.format()}`,
tags: [MessageTags.badminton, MessageTags.hourglass], tags: [MessageTags.badminton, MessageTags.hourglass],
}), }),
@ -138,7 +138,7 @@ export class NtfyProvider implements OnApplicationBootstrap {
async sendWaitListEmailReceivedNotification(subject: string) { async sendWaitListEmailReceivedNotification(subject: string) {
await this.publishQueue.add( await this.publishQueue.add(
...NtfyProvider.defaultJob({ ...NtfyProvider.defaultJob({
title: 'Reservation on waiting list has become available', title: 'Wait listed reservation available',
message: `${subject}`, message: `${subject}`,
tags: [MessageTags.badminton, MessageTags.hourglass], tags: [MessageTags.badminton, MessageTags.hourglass],
}), }),

View file

@ -12,61 +12,73 @@ import {
Query, Query,
UseInterceptors, UseInterceptors,
} from '@nestjs/common' } from '@nestjs/common'
import { Transform } from 'class-transformer' import { Transform, TransformationType } from 'class-transformer'
import { import {
IsArray, IsArray,
IsBoolean, IsBoolean,
IsEnum,
IsOptional, IsOptional,
IsString, IsString,
ValidateNested, ValidateNested,
} from 'class-validator' } from 'class-validator'
import { Dayjs } from 'dayjs' import { Dayjs } from 'dayjs'
import { DayjsTransformer } from '../common/dayjs' import dayjs from '../common/dayjs'
import { LoggerService } from '../logger/service.logger' import { LoggerService } from '../logger/service.logger'
import { RESERVATIONS_QUEUE_NAME, ReservationsQueue } from './config' import { RESERVATIONS_QUEUE_NAME, ReservationsQueue } from './config'
import { ReservationStatus } from './entity'
import { ReservationsService } from './service' import { ReservationsService } from './service'
export class GetReservationsQueryParams { export class GetReservationsQueryParams {
@IsOptional() @IsOptional()
@Transform(() => Dayjs) @Transform(() => Dayjs)
readonly date?: Dayjs date?: Dayjs
@IsOptional() @IsOptional()
@IsBoolean() @IsBoolean()
@Transform(({ value }) => value === 'true') @Transform(({ value }) => value === 'true')
readonly schedulable?: boolean readonly schedulable?: boolean
@IsOptional()
@IsEnum(ReservationStatus)
readonly status?: ReservationStatus
} }
export class CreateReservationOpponent { export class CreateReservationOpponent {
@IsString() @IsString()
readonly id: string id: string
@IsString() @IsString()
readonly name: string name: string
} }
export class CreateReservationRequest { export class CreateReservationRequest {
@IsString() @IsString()
readonly ownerId: string ownerId: string
@Transform(DayjsTransformer) @Transform(({ value, type }) => {
readonly dateRangeStart: Dayjs switch (type) {
case TransformationType.PLAIN_TO_CLASS:
return dayjs(value)
case TransformationType.CLASS_TO_PLAIN:
return value.format()
default:
return value
}
})
dateRangeStart: Dayjs
@IsOptional() @IsOptional()
@Transform(DayjsTransformer) @Transform(({ value, type }) => {
readonly dateRangeEnd?: Dayjs switch (type) {
case TransformationType.PLAIN_TO_CLASS:
return dayjs(value)
case TransformationType.CLASS_TO_PLAIN:
return value.format()
default:
return value
}
})
dateRangeEnd?: Dayjs
@IsOptional() @IsOptional()
@IsArray() @IsArray()
@ValidateNested() @ValidateNested()
readonly opponents?: CreateReservationOpponent[] opponents?: CreateReservationOpponent[]
} }
@Controller('reservations') @Controller('reservations')
@ -85,14 +97,14 @@ export class ReservationsController {
@Get() @Get()
getReservations(@Query() params: GetReservationsQueryParams) { getReservations(@Query() params: GetReservationsQueryParams) {
const { schedulable, date, status } = params const { schedulable, date } = params
if (schedulable) { if (schedulable) {
return this.reservationsService.getSchedulable() return this.reservationsService.getSchedulable()
} }
if (date) { if (date) {
return this.reservationsService.getByDate(date, status) return this.reservationsService.getByDate(date)
} }
return this.reservationsService.getAll(status) return this.reservationsService.getAll()
} }
@Get(':id') @Get(':id')

View file

@ -1,24 +1,15 @@
import { Exclude, Transform, Type } from 'class-transformer' import { Exclude, Transform, Type } from 'class-transformer'
import { IsEnum } from 'class-validator' import { TransformationType } from 'class-transformer'
import { Dayjs } from 'dayjs' import { Dayjs } from 'dayjs'
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm' import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'
import dayjs, { import dayjs from '../common/dayjs'
DayjsColumnTransformer,
DayjsTransformer,
} from '../common/dayjs'
export interface Opponent { export interface Opponent {
id: string id: string
name: string name: string
} }
export enum ReservationStatus {
Pending = 'pending',
OnWaitingList = 'on_waiting_list',
Booked = 'booked',
}
@Entity({ name: 'reservations' }) @Entity({ name: 'reservations' })
export class Reservation { export class Reservation {
@PrimaryGeneratedColumn('uuid') @PrimaryGeneratedColumn('uuid')
@ -29,30 +20,49 @@ export class Reservation {
@Column('datetime', { @Column('datetime', {
nullable: false, nullable: false,
transformer: DayjsColumnTransformer, transformer: {
to: (value: Dayjs) => value.format(),
from: (value: Date) => dayjs(value),
},
}) })
@Type(() => Dayjs) @Type(() => Dayjs)
@Transform(DayjsTransformer) @Transform(({ value, type }) => {
switch (type) {
case TransformationType.PLAIN_TO_CLASS:
return dayjs(value)
case TransformationType.CLASS_TO_PLAIN:
return value.format()
default:
return value
}
})
dateRangeStart: Dayjs dateRangeStart: Dayjs
@Column('datetime', { @Column('datetime', {
nullable: false, nullable: false,
transformer: DayjsColumnTransformer, transformer: {
to: (value: Dayjs) => value.format(),
from: (value: Date) => dayjs(value),
},
}) })
@Type(() => Dayjs) @Type(() => Dayjs)
@Transform(DayjsTransformer) @Transform(({ value, type }) => {
switch (type) {
case TransformationType.PLAIN_TO_CLASS:
return dayjs(value)
case TransformationType.CLASS_TO_PLAIN:
return value.format()
default:
return value
}
})
dateRangeEnd: Dayjs dateRangeEnd: Dayjs
@Column('json', { nullable: false }) @Column('json', { nullable: false })
opponents: Opponent[] opponents: Opponent[]
@Column('varchar', { @Column('boolean', { default: false })
length: 32, waitListed: boolean
nullable: false,
default: ReservationStatus.Pending,
})
@IsEnum(ReservationStatus)
status: ReservationStatus
@Column('int', { nullable: true }) @Column('int', { nullable: true })
waitingListId: number waitingListId: number

View file

@ -6,7 +6,7 @@ import { Repository } from 'typeorm'
import dayjs from '../common/dayjs' import dayjs from '../common/dayjs'
import { LoggerService } from '../logger/service.logger' import { LoggerService } from '../logger/service.logger'
import { BaanReserverenService } from '../runner/baanreserveren/service' import { BaanReserverenService } from '../runner/baanreserveren/service'
import { Reservation, ReservationStatus } from './entity' import { Reservation } from './entity'
@Injectable() @Injectable()
export class ReservationsService { export class ReservationsService {
@ -21,24 +21,19 @@ export class ReservationsService {
private readonly loggerService: LoggerService, private readonly loggerService: LoggerService,
) {} ) {}
async getAll(status?: ReservationStatus) { async getAll() {
return await this.reservationsRepository.find({ where: { status } }) return await this.reservationsRepository.find()
} }
async getById(id: string) { async getById(id: string) {
return await this.reservationsRepository.findOneBy({ id }) return await this.reservationsRepository.findOneBy({ id })
} }
async getByDate(date = dayjs(), status?: ReservationStatus) { async getByDate(date = dayjs()) {
let qb = this.reservationsRepository return await this.reservationsRepository
.createQueryBuilder() .createQueryBuilder()
.where(`DATE(dateRangeStart) = DATE(:date)`, { date: date.toISOString() }) .where(`DATE(dateRangeStart) = DATE(:date)`, { date: date.toISOString() })
.getMany()
if (status != null) {
qb = qb.andWhere(`status = :status`, { status })
}
return await qb.orderBy('dateRangeStart', 'ASC').getMany()
} }
/** /**
@ -49,15 +44,13 @@ export class ReservationsService {
const query = this.reservationsRepository const query = this.reservationsRepository
.createQueryBuilder() .createQueryBuilder()
.where( .where(
`(DATE(dateRangeStart) >= DATE(:startDate) OR DATE(dateRangeStart) <= DATE(:endDate))`, `DATE(dateRangeStart) BETWEEN DATE(:startDate) AND DATE(:endDate)`,
{ {
startDate: dayjs().add(1, 'days').toISOString(), startDate: dayjs().add(1, 'days').toISOString(),
endDate: dayjs().add(7, 'days').toISOString(), endDate: dayjs().add(7, 'days').toISOString(),
}, },
) )
.andWhere('status = :status', { .andWhere(`waitListed = false`)
statuses: ReservationStatus.Pending,
})
.orderBy('dateRangeStart', 'ASC') .orderBy('dateRangeStart', 'ASC')
return await query.getMany() return await query.getMany()
@ -66,10 +59,13 @@ export class ReservationsService {
async getByDateOnWaitingList(date = dayjs()) { async getByDateOnWaitingList(date = dayjs()) {
return await this.reservationsRepository return await this.reservationsRepository
.createQueryBuilder() .createQueryBuilder()
.where(`DATE(dateRangeStart) = DATE(:date)`, { .where(`DATE(dateRangeStart) <= DATE(:date)`, {
date: date.toISOString(), date: date.toISOString(),
}) })
.andWhere('status = :status', { status: ReservationStatus.OnWaitingList }) .andWhere(`DATE(dateRangeEnd) >= DATE(:date)`, {
date: date.toISOString(),
})
.andWhere('waitListed = true')
.getMany() .getMany()
} }

View file

@ -1,5 +1,6 @@
import { Process, Processor } from '@nestjs/bull' import { Process, Processor } from '@nestjs/bull'
import { Inject } from '@nestjs/common' import { Inject } from '@nestjs/common'
import { Job } from 'bull'
import { instanceToPlain, plainToInstance } from 'class-transformer' import { instanceToPlain, plainToInstance } from 'class-transformer'
import { LoggerService } from '../logger/service.logger' import { LoggerService } from '../logger/service.logger'
@ -10,7 +11,7 @@ import {
} from '../runner/baanreserveren/service' } from '../runner/baanreserveren/service'
import { RESERVATIONS_QUEUE_NAME, ReservationsJob } from './config' import { RESERVATIONS_QUEUE_NAME, ReservationsJob } from './config'
import { DAILY_RESERVATIONS_ATTEMPTS } from './cron' import { DAILY_RESERVATIONS_ATTEMPTS } from './cron'
import { Reservation, ReservationStatus } from './entity' import { Reservation } from './entity'
import { ReservationsService } from './service' import { ReservationsService } from './service'
@Processor(RESERVATIONS_QUEUE_NAME) @Processor(RESERVATIONS_QUEUE_NAME)
@ -62,10 +63,10 @@ export class ReservationsWorker {
} }
if ( if (
(shouldWaitlist || attemptsMade === DAILY_RESERVATIONS_ATTEMPTS) && (shouldWaitlist || attemptsMade === DAILY_RESERVATIONS_ATTEMPTS) &&
reservation.status !== ReservationStatus.OnWaitingList !reservation.waitListed
) { ) {
this.loggerService.log('Adding reservation to waiting list') this.loggerService.log('Adding reservation to waiting list')
await this.ntfyProvider.sendReservationOnWaitingListNotification( await this.ntfyProvider.sendReservationWaitlistedNotification(
reservation.id, reservation.id,
reservation.dateRangeStart, reservation.dateRangeStart,
reservation.dateRangeEnd, reservation.dateRangeEnd,
@ -88,9 +89,7 @@ export class ReservationsWorker {
} else { } else {
await this.brService.performReservation(reservation, timeSensitive) await this.brService.performReservation(reservation, timeSensitive)
} }
await this.reservationsService.update(reservation.id, { await this.reservationsService.deleteById(reservation.id)
status: ReservationStatus.Booked,
})
} catch (error: unknown) { } catch (error: unknown) {
await this.handleReservationErrors( await this.handleReservationErrors(
error as Error, error as Error,
@ -106,12 +105,12 @@ export class ReservationsWorker {
timeSensitive = true, timeSensitive = true,
) { ) {
try { try {
const waitingListId = await this.brService.addReservationToWaitingList( const waitingListId = await this.brService.addReservationToWaitList(
reservation, reservation,
timeSensitive, timeSensitive,
) )
await this.reservationsService.update(reservation.id, { await this.reservationsService.update(reservation.id, {
status: ReservationStatus.OnWaitingList, waitListed: true,
waitingListId, waitingListId,
}) })
} catch (error: unknown) { } catch (error: unknown) {

View file

@ -9,11 +9,7 @@ import dayjs from '../../common/dayjs'
import { LoggerService } from '../../logger/service.logger' import { LoggerService } from '../../logger/service.logger'
import { MONITORING_QUEUE_NAME, MonitoringQueue } from '../../monitoring/config' import { MONITORING_QUEUE_NAME, MonitoringQueue } from '../../monitoring/config'
import { MonitorType } from '../../monitoring/entity' import { MonitorType } from '../../monitoring/entity'
import { import { Opponent, Reservation } from '../../reservations/entity'
Opponent,
Reservation,
ReservationStatus,
} from '../../reservations/entity'
import { EmptyPage } from '../pages/empty' import { EmptyPage } from '../pages/empty'
export const BAAN_RESERVEREN_ROOT_URL = 'https://squashcity.baanreserveren.nl' export const BAAN_RESERVEREN_ROOT_URL = 'https://squashcity.baanreserveren.nl'
@ -71,12 +67,12 @@ const CourtSlotToNumber: Record<CourtSlot, number> = {
// Lower is better // Lower is better
const CourtRank: Record<CourtSlot, number> = { const CourtRank: Record<CourtSlot, number> = {
[CourtSlot.One]: 0, [CourtSlot.One]: 2,
[CourtSlot.Two]: 2, // team at squash city has this pre-booked at 19.15 on Tuesday :sad: [CourtSlot.Two]: 1, // team at squash city has this pre-booked at 19.15 on Tuesday :sad:
[CourtSlot.Three]: 2, // team at squash city has this pre-booked at 19.15 on Tuesday :sad: [CourtSlot.Three]: 1, // team at squash city has this pre-booked at 19.15 on Tuesday :sad:
[CourtSlot.Four]: 0, [CourtSlot.Four]: 0,
[CourtSlot.Five]: 0, [CourtSlot.Five]: 99, // shitty
[CourtSlot.Six]: 0, [CourtSlot.Six]: 99, // shitty
[CourtSlot.Seven]: 0, [CourtSlot.Seven]: 0,
[CourtSlot.Eight]: 0, [CourtSlot.Eight]: 0,
[CourtSlot.Nine]: 0, [CourtSlot.Nine]: 0,
@ -86,16 +82,13 @@ const CourtRank: Record<CourtSlot, number> = {
[CourtSlot.Thirteen]: 9, // no one likes upstairs [CourtSlot.Thirteen]: 9, // no one likes upstairs
} as const } as const
export enum StartTimeClass { enum StartTimeClass {
First = 'first', First = 'first',
Second = 'second', Second = 'second',
Third = 'third', Third = 'third',
} }
export const StartTimeClassCourtSlots: Record< const StartTimeClassCourtSlots: Record<StartTimeClass, readonly CourtSlot[]> = {
StartTimeClass,
readonly CourtSlot[]
> = {
[StartTimeClass.First]: [ [StartTimeClass.First]: [
CourtSlot.One, CourtSlot.One,
CourtSlot.Two, CourtSlot.Two,
@ -683,7 +676,7 @@ export class BaanReserverenService {
return courtStatuses return courtStatuses
} }
public getCourtSlotsForDate(date: Dayjs) { private getCourtSlotsForDate(date: Dayjs) {
const time = date.format('HH:mm') const time = date.format('HH:mm')
for (const [timeClass, times] of Object.entries(StartTimeClassStartTimes)) { for (const [timeClass, times] of Object.entries(StartTimeClassStartTimes)) {
if (times.includes(time)) { if (times.includes(time)) {
@ -748,7 +741,7 @@ export class BaanReserverenService {
throw new NoCourtAvailableError('Could not reserve court') throw new NoCourtAvailableError('Could not reserve court')
} }
public async addReservationToWaitingList( public async addReservationToWaitList(
reservation: Reservation, reservation: Reservation,
timeSensitive = true, timeSensitive = true,
) { ) {
@ -781,11 +774,7 @@ export class BaanReserverenService {
public async removeReservationFromWaitList(reservation: Reservation) { public async removeReservationFromWaitList(reservation: Reservation) {
try { try {
if ( if (!reservation.waitListed || !reservation.waitingListId) return
reservation.status !== ReservationStatus.OnWaitingList ||
!reservation.waitingListId
)
return
await this.init() await this.init()
await this.navigateToWaitingList() await this.navigateToWaitingList()
await this.deleteWaitingListEntryRowById(reservation.waitingListId) await this.deleteWaitingListEntryRowById(reservation.waitingListId)

View file

@ -10,8 +10,6 @@ import {
BAAN_RESERVEREN_ROOT_URL, BAAN_RESERVEREN_ROOT_URL,
BaanReserverenService, BaanReserverenService,
CourtSlot, CourtSlot,
StartTimeClass,
StartTimeClassCourtSlots,
} from '../../../src/runner/baanreserveren/service' } from '../../../src/runner/baanreserveren/service'
import { EmptyPage } from '../../../src/runner/pages/empty' import { EmptyPage } from '../../../src/runner/pages/empty'
@ -23,7 +21,6 @@ describe('baanreserveren.service', () => {
beforeAll(async () => { beforeAll(async () => {
pageGotoSpy = jest pageGotoSpy = jest
.fn() .fn()
.mockClear()
.mockImplementation(() => Promise.resolve({ status: () => 200 })) .mockImplementation(() => Promise.resolve({ status: () => 200 }))
module = await Test.createTestingModule({ module = await Test.createTestingModule({
providers: [ providers: [
@ -57,12 +54,10 @@ describe('baanreserveren.service', () => {
brService = module.get<BaanReserverenService>(BaanReserverenService) brService = module.get<BaanReserverenService>(BaanReserverenService)
}) })
beforeEach(() => pageGotoSpy.mockClear())
describe('performSpeedyReservation', () => { describe('performSpeedyReservation', () => {
it.each([ it.each([
[18, 15, CourtSlot.Six, CourtSlot.Seven], [18, 15, CourtSlot.Seven, CourtSlot.Six],
[18, 30, CourtSlot.One, CourtSlot.Four], [18, 30, CourtSlot.Four, CourtSlot.Two],
[18, 45, CourtSlot.Twelve, CourtSlot.Thirteen], [18, 45, CourtSlot.Twelve, CourtSlot.Thirteen],
])( ])(
'should try highest ranked court first', 'should try highest ranked court first',
@ -94,8 +89,8 @@ describe('baanreserveren.service', () => {
) )
it.each([ it.each([
[18, 15, CourtSlot.Six, CourtSlot.Seven], [18, 15, CourtSlot.Seven, CourtSlot.Eight],
[18, 30, CourtSlot.One, CourtSlot.Four], [18, 30, CourtSlot.Four, CourtSlot.Two],
[18, 45, CourtSlot.Twelve, CourtSlot.Thirteen], [18, 45, CourtSlot.Twelve, CourtSlot.Thirteen],
])( ])(
'should try backup if first rank is taken', 'should try backup if first rank is taken',
@ -137,40 +132,4 @@ describe('baanreserveren.service', () => {
}, },
) )
}) })
describe.only('getCourtSlotsForDate', () => {
it.each([
{
date: '2025-04-10T16:30:00.000Z',
expectedCourtSlots: StartTimeClassCourtSlots[StartTimeClass.First],
},
{
date: '2025-04-10T16:45:00.000Z',
expectedCourtSlots: StartTimeClassCourtSlots[StartTimeClass.Third],
},
{
date: '2025-04-10T17:00:00.000Z',
expectedCourtSlots: StartTimeClassCourtSlots[StartTimeClass.Second],
},
{
date: '2025-01-10T17:30:00.000Z',
expectedCourtSlots: StartTimeClassCourtSlots[StartTimeClass.First],
},
{
date: '2025-01-10T17:45:00.000Z',
expectedCourtSlots: StartTimeClassCourtSlots[StartTimeClass.Third],
},
{
date: '2025-01-10T18:00:00.000Z',
expectedCourtSlots: StartTimeClassCourtSlots[StartTimeClass.Second],
},
])(
'should get correct court slots for $date',
({ date, expectedCourtSlots }) => {
expect(brService.getCourtSlotsForDate(dayjs(date))).toEqual(
expect.arrayContaining(expectedCourtSlots as CourtSlot[]),
)
},
)
})
}) })