From e1ce052d3caa051b88c0e9525977b0610605b078 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Sat, 9 Jan 2021 14:23:47 +0100 Subject: [PATCH 01/14] Fixed runner total distance not getting resolved ref #78 --- src/controllers/ScanController.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/controllers/ScanController.ts b/src/controllers/ScanController.ts index ed7df91..b7aa72f 100644 --- a/src/controllers/ScanController.ts +++ b/src/controllers/ScanController.ts @@ -59,7 +59,7 @@ export class ScanController { async post(@Body({ validate: true }) createScan: CreateScan) { let scan = await createScan.toScan(); scan = await this.scanRepository.save(scan); - return (await this.scanRepository.findOne({ id: scan.id }, { relations: ['runner'] })).toResponse(); + return (await this.scanRepository.findOne({ id: scan.id }, { relations: ['runner', 'runner.scans', 'runner.scans.track'] })).toResponse(); } @Post("/trackscans") @@ -68,7 +68,9 @@ export class ScanController { @ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) @OpenAPI({ description: 'Create a new track scan.
This is just a alias for posting /scans', security: [{ "ScanApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) async postTrackScans(@Body({ validate: true }) createScan: CreateTrackScan) { - return this.post(createScan); + let scan = await createScan.toScan(); + scan = await this.scanRepository.save(scan); + return (await this.scanRepository.findOne({ id: scan.id }, { relations: ['runner', 'track', 'runner.scans', 'runner.scans.track'] })).toResponse(); } @Put('/:id') From 3ceb5a0c0fc49e28ac26e1bccaa1b5ee044e616c Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Sat, 9 Jan 2021 14:24:16 +0100 Subject: [PATCH 02/14] Removed total distance from tests ref #78 --- src/tests/scans/scans_add.spec.ts | 8 ++++++++ src/tests/scans/scans_delete.spec.ts | 2 ++ 2 files changed, 10 insertions(+) diff --git a/src/tests/scans/scans_add.spec.ts b/src/tests/scans/scans_add.spec.ts index c08ee01..580ecd8 100644 --- a/src/tests/scans/scans_add.spec.ts +++ b/src/tests/scans/scans_add.spec.ts @@ -84,6 +84,7 @@ describe('POST /api/scans successfully', () => { "group": added_org.id }, axios_config); delete res2.data.group; + delete res2.data.distance; added_runner = res2.data; expect(res2.status).toEqual(200); expect(res2.headers['content-type']).toContain("application/json") @@ -96,6 +97,7 @@ describe('POST /api/scans successfully', () => { expect(res.status).toEqual(200); expect(res.headers['content-type']).toContain("application/json"); delete res.data.id; + delete res.data.runner.distance; expect(res.data).toEqual({ "runner": added_runner, "distance": 200, @@ -111,6 +113,7 @@ describe('POST /api/scans successfully', () => { expect(res.status).toEqual(200); expect(res.headers['content-type']).toContain("application/json"); delete res.data.id; + delete res.data.runner.distance; expect(res.data).toEqual({ "runner": added_runner, "distance": 200, @@ -126,6 +129,7 @@ describe('POST /api/scans successfully', () => { expect(res.status).toEqual(200); expect(res.headers['content-type']).toContain("application/json"); delete res.data.id; + delete res.data.runner.distance; expect(res.data).toEqual({ "runner": added_runner, "distance": 200, @@ -154,6 +158,7 @@ describe('POST /api/scans successfully via scan station', () => { "group": added_org.id }, axios_config); delete res2.data.group; + delete res2.data.distance; added_runner = res2.data; expect(res2.status).toEqual(200); expect(res2.headers['content-type']).toContain("application/json") @@ -186,6 +191,7 @@ describe('POST /api/scans successfully via scan station', () => { expect(res.status).toEqual(200); expect(res.headers['content-type']).toContain("application/json"); delete res.data.id; + delete res.data.runner.distance; expect(res.data).toEqual({ "runner": added_runner, "distance": 200, @@ -204,6 +210,7 @@ describe('POST /api/scans successfully via scan station', () => { expect(res.status).toEqual(200); expect(res.headers['content-type']).toContain("application/json"); delete res.data.id; + delete res.data.runner.distance; expect(res.data).toEqual({ "runner": added_runner, "distance": 200, @@ -222,6 +229,7 @@ describe('POST /api/scans successfully via scan station', () => { expect(res.status).toEqual(200); expect(res.headers['content-type']).toContain("application/json"); delete res.data.id; + delete res.data.runner.distance; expect(res.data).toEqual({ "runner": added_runner, "distance": 200, diff --git a/src/tests/scans/scans_delete.spec.ts b/src/tests/scans/scans_delete.spec.ts index e88e812..0ff0b72 100644 --- a/src/tests/scans/scans_delete.spec.ts +++ b/src/tests/scans/scans_delete.spec.ts @@ -44,6 +44,7 @@ describe('DELETE scan', () => { "distance": 1000 }, axios_config); added_scan = res.data; + delete res.data.runner.distance; expect(res.status).toEqual(200); expect(res.headers['content-type']).toContain("application/json") }); @@ -51,6 +52,7 @@ describe('DELETE scan', () => { const res2 = await axios.delete(base + '/api/scans/' + added_scan.id, axios_config); expect(res2.status).toEqual(200); expect(res2.headers['content-type']).toContain("application/json") + delete res2.data.runner.distance; expect(res2.data).toEqual(added_scan); }); it('check if scan really was deleted', async () => { From 188f26ad650a0099fba2b2b683e5dc5c9ea4613e Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Sat, 9 Jan 2021 14:52:08 +0100 Subject: [PATCH 03/14] Fixed manual trackscan creation ref #78 --- src/controllers/ScanController.ts | 17 +++++++++-------- src/models/actions/CreateTrackScan.ts | 19 ++++++------------- src/models/responses/ResponseTrackScan.ts | 13 +++++++------ 3 files changed, 22 insertions(+), 27 deletions(-) diff --git a/src/controllers/ScanController.ts b/src/controllers/ScanController.ts index b7aa72f..09833dc 100644 --- a/src/controllers/ScanController.ts +++ b/src/controllers/ScanController.ts @@ -1,6 +1,6 @@ import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam, UseBefore } from 'routing-controllers'; import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; -import { getConnectionManager, Repository } from 'typeorm'; +import { getConnection, getConnectionManager, Repository } from 'typeorm'; import { RunnerNotFoundError } from '../errors/RunnerErrors'; import { ScanIdsNotMatchingError, ScanNotFoundError } from '../errors/ScanErrors'; import ScanAuth from '../middlewares/ScanAuth'; @@ -8,6 +8,7 @@ import { CreateScan } from '../models/actions/CreateScan'; import { CreateTrackScan } from '../models/actions/CreateTrackScan'; import { UpdateScan } from '../models/actions/UpdateScan'; import { Scan } from '../models/entities/Scan'; +import { TrackScan } from '../models/entities/TrackScan'; import { ResponseEmpty } from '../models/responses/ResponseEmpty'; import { ResponseScan } from '../models/responses/ResponseScan'; import { ResponseTrackScan } from '../models/responses/ResponseTrackScan'; @@ -31,7 +32,7 @@ export class ScanController { @OpenAPI({ description: 'Lists all scans (normal or track) from all runners.
This includes the scan\'s runner\'s distance ran.' }) async getAll() { let responseScans: ResponseScan[] = new Array(); - const scans = await this.scanRepository.find({ relations: ['runner', 'runner.scans', 'runner.scans.track'] }); + const scans = await this.scanRepository.find({ relations: ['runner', 'track', 'runner.scans', 'runner.scans.track', 'card', 'station'] }); scans.forEach(scan => { responseScans.push(scan.toResponse()); }); @@ -46,7 +47,7 @@ export class ScanController { @OnUndefined(ScanNotFoundError) @OpenAPI({ description: 'Lists all information about the scan whose id got provided. This includes the scan\'s runner\'s distance ran.' }) async getOne(@Param('id') id: number) { - let scan = await this.scanRepository.findOne({ id: id }, { relations: ['runner', 'runner.scans', 'runner.scans.track'] }) + let scan = await this.scanRepository.findOne({ id: id }, { relations: ['runner', 'track', 'runner.scans', 'runner.scans.track', 'card', 'station'] }) if (!scan) { throw new ScanNotFoundError(); } return scan.toResponse(); } @@ -59,7 +60,7 @@ export class ScanController { async post(@Body({ validate: true }) createScan: CreateScan) { let scan = await createScan.toScan(); scan = await this.scanRepository.save(scan); - return (await this.scanRepository.findOne({ id: scan.id }, { relations: ['runner', 'runner.scans', 'runner.scans.track'] })).toResponse(); + return (await this.scanRepository.findOne({ id: scan.id }, { relations: ['runner', 'track', 'runner.scans', 'runner.scans.track', 'card', 'station'] })).toResponse(); } @Post("/trackscans") @@ -69,8 +70,8 @@ export class ScanController { @OpenAPI({ description: 'Create a new track scan.
This is just a alias for posting /scans', security: [{ "ScanApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) async postTrackScans(@Body({ validate: true }) createScan: CreateTrackScan) { let scan = await createScan.toScan(); - scan = await this.scanRepository.save(scan); - return (await this.scanRepository.findOne({ id: scan.id }, { relations: ['runner', 'track', 'runner.scans', 'runner.scans.track'] })).toResponse(); + scan = await getConnection().getRepository(TrackScan).save(scan); + return (await this.scanRepository.findOne({ id: scan.id }, { relations: ['runner', 'track', 'runner.scans', 'runner.scans.track', 'card', 'station'] })).toResponse(); } @Put('/:id') @@ -92,7 +93,7 @@ export class ScanController { } await this.scanRepository.save(await scan.updateScan(oldScan)); - return (await this.scanRepository.findOne({ id: id }, { relations: ['runner'] })).toResponse(); + return (await this.scanRepository.findOne({ id: id }, { relations: ['runner', 'track', 'runner.scans', 'runner.scans.track', 'card', 'station'] })).toResponse(); } @Delete('/:id') @@ -104,7 +105,7 @@ export class ScanController { async remove(@Param("id") id: number, @QueryParam("force") force: boolean) { let scan = await this.scanRepository.findOne({ id: id }); if (!scan) { return null; } - const responseScan = await this.scanRepository.findOne({ id: scan.id }, { relations: ["runner"] }); + const responseScan = await this.scanRepository.findOne({ id: scan.id }, { relations: ['runner', 'track', 'runner.scans', 'runner.scans.track', 'card', 'station'] }); await this.scanRepository.delete(scan); return responseScan.toResponse(); diff --git a/src/models/actions/CreateTrackScan.ts b/src/models/actions/CreateTrackScan.ts index 2303352..44877f0 100644 --- a/src/models/actions/CreateTrackScan.ts +++ b/src/models/actions/CreateTrackScan.ts @@ -1,35 +1,28 @@ -import { IsNotEmpty } from 'class-validator'; +import { IsInt, IsPositive } from 'class-validator'; import { getConnection } from 'typeorm'; import { RunnerNotFoundError } from '../../errors/RunnerErrors'; import { RunnerCard } from '../entities/RunnerCard'; import { ScanStation } from '../entities/ScanStation'; import { TrackScan } from '../entities/TrackScan'; -import { CreateScan } from './CreateScan'; /** * This classed is used to create a new Scan entity from a json body (post request). */ -export class CreateTrackScan extends CreateScan { - - /** - * The scan's associated track. - * This is used to determine the scan's distance. - */ - @IsNotEmpty() - track: number; - +export class CreateTrackScan { /** * The runnerCard associated with the scan. * This get's saved for documentation and management purposes. */ - @IsNotEmpty() + @IsInt() + @IsPositive() card: number; /** * The scanning station that created the scan. * Mainly used for logging and traceing back scans (or errors) */ - @IsNotEmpty() + @IsInt() + @IsPositive() station: number; /** diff --git a/src/models/responses/ResponseTrackScan.ts b/src/models/responses/ResponseTrackScan.ts index 724a8fd..dba450c 100644 --- a/src/models/responses/ResponseTrackScan.ts +++ b/src/models/responses/ResponseTrackScan.ts @@ -1,8 +1,8 @@ import { IsDateString, IsNotEmpty } from "class-validator"; -import { RunnerCard } from '../entities/RunnerCard'; -import { ScanStation } from '../entities/ScanStation'; import { TrackScan } from '../entities/TrackScan'; +import { ResponseRunnerCard } from './ResponseRunnerCard'; import { ResponseScan } from './ResponseScan'; +import { ResponseScanStation } from './ResponseScanStation'; import { ResponseTrack } from './ResponseTrack'; /** @@ -19,13 +19,13 @@ export class ResponseTrackScan extends ResponseScan { * The runnerCard associated with the scan. */ @IsNotEmpty() - card: RunnerCard; + card: ResponseRunnerCard; /** * The scanning station that created the scan. */ @IsNotEmpty() - station: ScanStation; + station: ResponseScanStation; /** * The scan's creation timestamp. @@ -41,8 +41,9 @@ export class ResponseTrackScan extends ResponseScan { public constructor(scan: TrackScan) { super(scan); this.track = new ResponseTrack(scan.track); - this.card = scan.card; - this.station = scan.station; + this.card = scan.card.toResponse(); + this.station = scan.station.toResponse(); this.timestamp = scan.timestamp; + this.distance = scan.distance; } } From 9013b9492c89c8128664cf86f4cf0ab9d4122e55 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Sat, 9 Jan 2021 15:25:11 +0100 Subject: [PATCH 04/14] Fixed runner distance resolution ref #78 --- src/controllers/RunnerController.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/controllers/RunnerController.ts b/src/controllers/RunnerController.ts index 9eeb916..5d1e5e8 100644 --- a/src/controllers/RunnerController.ts +++ b/src/controllers/RunnerController.ts @@ -27,7 +27,7 @@ export class RunnerController { @OpenAPI({ description: 'Lists all runners from all teams/orgs.
This includes the runner\'s group and distance ran.' }) async getAll() { let responseRunners: ResponseRunner[] = new Array(); - const runners = await this.runnerRepository.find({ relations: ['scans', 'group'] }); + const runners = await this.runnerRepository.find({ relations: ['scans', 'group', 'scans.track', 'cards'] }); runners.forEach(runner => { responseRunners.push(new ResponseRunner(runner)); }); @@ -41,7 +41,7 @@ export class RunnerController { @OnUndefined(RunnerNotFoundError) @OpenAPI({ description: 'Lists all information about the runner whose id got provided.' }) async getOne(@Param('id') id: number) { - let runner = await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'group'] }) + let runner = await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'group', 'scans.track', 'cards'] }) if (!runner) { throw new RunnerNotFoundError(); } return new ResponseRunner(runner); } @@ -61,7 +61,7 @@ export class RunnerController { } runner = await this.runnerRepository.save(runner) - return new ResponseRunner(await this.runnerRepository.findOne(runner, { relations: ['scans', 'group'] })); + return new ResponseRunner(await this.runnerRepository.findOne(runner, { relations: ['scans', 'group', 'scans.track', 'cards'] })); } @Put('/:id') @@ -82,7 +82,7 @@ export class RunnerController { } await this.runnerRepository.save(await runner.updateRunner(oldRunner)); - return new ResponseRunner(await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'group'] })); + return new ResponseRunner(await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'group', 'scans.track', 'cards'] })); } @Delete('/:id') @@ -94,7 +94,7 @@ export class RunnerController { async remove(@Param("id") id: number, @QueryParam("force") force: boolean) { let runner = await this.runnerRepository.findOne({ id: id }); if (!runner) { return null; } - const responseRunner = await this.runnerRepository.findOne(runner, { relations: ['scans', 'group'] }); + const responseRunner = await this.runnerRepository.findOne(runner, { relations: ['scans', 'group', 'scans.track', 'cards'] }); if (!runner) { throw new RunnerNotFoundError(); From 1a5493facf5635d8a0fb3773df70a2c6f2f4c767 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Sat, 9 Jan 2021 15:43:52 +0100 Subject: [PATCH 05/14] Implemented cascading scan, track and card deletion ref #78 --- src/controllers/RunnerController.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/controllers/RunnerController.ts b/src/controllers/RunnerController.ts index 5d1e5e8..0a92ad2 100644 --- a/src/controllers/RunnerController.ts +++ b/src/controllers/RunnerController.ts @@ -8,6 +8,7 @@ import { UpdateRunner } from '../models/actions/UpdateRunner'; import { Runner } from '../models/entities/Runner'; import { ResponseEmpty } from '../models/responses/ResponseEmpty'; import { ResponseRunner } from '../models/responses/ResponseRunner'; +import { ScanController } from './ScanController'; @JsonController('/runners') @OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) @@ -100,6 +101,12 @@ export class RunnerController { throw new RunnerNotFoundError(); } + const runnerScans = (await this.runnerRepository.findOne({ id: runner.id }, { relations: ["scans"] })).scans; + const scanController = new ScanController; + for (let scan of runnerScans) { + scanController.remove(scan.id, force); + } + await this.runnerRepository.delete(runner); return new ResponseRunner(responseRunner); } From 3d07aac9441b529ec38a47146ee2d8dcb9b5ea73 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Sat, 9 Jan 2021 15:43:52 +0100 Subject: [PATCH 06/14] Implemented cascading scan, track and card deletion ref #78 --- src/controllers/RunnerCardController.ts | 2 +- src/controllers/RunnerController.ts | 14 ++++++++++++++ src/controllers/ScanStationController.ts | 2 +- src/controllers/TrackController.ts | 4 ++-- src/controllers/UserGroupController.ts | 4 ++-- 5 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/controllers/RunnerCardController.ts b/src/controllers/RunnerCardController.ts index 2263f07..1366bed 100644 --- a/src/controllers/RunnerCardController.ts +++ b/src/controllers/RunnerCardController.ts @@ -97,7 +97,7 @@ export class RunnerCardController { } const scanController = new ScanController; for (let scan of cardScans) { - scanController.remove(scan.id, force); + await scanController.remove(scan.id, force); } await this.cardRepository.delete(card); diff --git a/src/controllers/RunnerController.ts b/src/controllers/RunnerController.ts index 5d1e5e8..a11d2c6 100644 --- a/src/controllers/RunnerController.ts +++ b/src/controllers/RunnerController.ts @@ -8,6 +8,8 @@ import { UpdateRunner } from '../models/actions/UpdateRunner'; import { Runner } from '../models/entities/Runner'; import { ResponseEmpty } from '../models/responses/ResponseEmpty'; import { ResponseRunner } from '../models/responses/ResponseRunner'; +import { RunnerCardController } from './RunnerCardController'; +import { ScanController } from './ScanController'; @JsonController('/runners') @OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) @@ -100,6 +102,18 @@ export class RunnerController { throw new RunnerNotFoundError(); } + const runnerCards = (await this.runnerRepository.findOne({ id: runner.id }, { relations: ["cards"] })).cards; + const cardController = new RunnerCardController; + for (let scan of runnerCards) { + await cardController.remove(scan.id, force); + } + + const runnerScans = (await this.runnerRepository.findOne({ id: runner.id }, { relations: ["scans"] })).scans; + const scanController = new ScanController; + for (let scan of runnerScans) { + await scanController.remove(scan.id, force); + } + await this.runnerRepository.delete(runner); return new ResponseRunner(responseRunner); } diff --git a/src/controllers/ScanStationController.ts b/src/controllers/ScanStationController.ts index 4e32971..df85d0e 100644 --- a/src/controllers/ScanStationController.ts +++ b/src/controllers/ScanStationController.ts @@ -98,7 +98,7 @@ export class ScanStationController { } const scanController = new ScanController; for (let scan of stationScans) { - scanController.remove(scan.id, force); + await scanController.remove(scan.id, force); } const responseStation = await this.stationRepository.findOne({ id: station.id }, { relations: ["track"] }); diff --git a/src/controllers/TrackController.ts b/src/controllers/TrackController.ts index 094756c..42feb7c 100644 --- a/src/controllers/TrackController.ts +++ b/src/controllers/TrackController.ts @@ -94,9 +94,9 @@ export class TrackController { if (trackStations.length != 0 && !force) { throw new TrackHasScanStationsError(); } - const scanController = new ScanStationController; + const stationController = new ScanStationController; for (let station of trackStations) { - scanController.remove(station.id, force); + await stationController.remove(station.id, force); } await this.trackRepository.delete(track); diff --git a/src/controllers/UserGroupController.ts b/src/controllers/UserGroupController.ts index 4b484fc..160a99e 100644 --- a/src/controllers/UserGroupController.ts +++ b/src/controllers/UserGroupController.ts @@ -88,9 +88,9 @@ export class UserGroupController { if (!group) { return null; } const responseGroup = await this.userGroupsRepository.findOne({ id: id }, { relations: ['permissions'] }); - const permissionControler = new PermissionController(); + const permissionController = new PermissionController(); for (let permission of responseGroup.permissions) { - await permissionControler.remove(permission.id, true); + await permissionController.remove(permission.id, true); } await this.userGroupsRepository.delete(group); From e7cd68e1c8bd419772de33e250292e1d7d65a16b Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Sat, 9 Jan 2021 15:59:36 +0100 Subject: [PATCH 07/14] removed distance checks from tests ref #78 --- src/tests/scans/scans_update.spec.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/tests/scans/scans_update.spec.ts b/src/tests/scans/scans_update.spec.ts index b4ff86c..60b7fb1 100644 --- a/src/tests/scans/scans_update.spec.ts +++ b/src/tests/scans/scans_update.spec.ts @@ -100,6 +100,7 @@ describe('adding + updating successfilly', () => { "group": added_org.id }, axios_config); delete res2.data.group; + delete res2.data.distance; added_runner = res2.data; expect(res2.status).toEqual(200); expect(res2.headers['content-type']).toContain("application/json") @@ -121,6 +122,7 @@ describe('adding + updating successfilly', () => { }, axios_config); expect(res2.status).toEqual(200); expect(res2.headers['content-type']).toContain("application/json") + delete res2.data.runner.distance; expect(res2.data).toEqual({ "id": added_scan.id, "runner": added_runner, @@ -137,7 +139,8 @@ describe('adding + updating successfilly', () => { "valid": false }, axios_config); expect(res2.status).toEqual(200); - expect(res2.headers['content-type']).toContain("application/json") + expect(res2.headers['content-type']).toContain("application/json"); + delete res2.data.runner.distance; expect(res2.data).toEqual({ "id": added_scan.id, "runner": added_runner, @@ -152,6 +155,7 @@ describe('adding + updating successfilly', () => { "group": added_org.id }, axios_config); delete res2.data.group; + delete res2.data.distance; added_runner2 = res2.data; expect(res2.status).toEqual(200); expect(res2.headers['content-type']).toContain("application/json") @@ -164,6 +168,7 @@ describe('adding + updating successfilly', () => { }, axios_config); expect(res2.status).toEqual(200); expect(res2.headers['content-type']).toContain("application/json"); + delete res2.data.runner.distance; expect(res2.data).toEqual({ "id": added_scan.id, "runner": added_runner2, From 638898fa28bbb686c920ff5603e9ef6b68af3d83 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Sat, 9 Jan 2021 16:17:50 +0100 Subject: [PATCH 08/14] Implemented trackscan updateing ref #78 --- src/controllers/RunnerController.ts | 2 +- src/controllers/ScanController.ts | 39 +++++++++++-- src/models/actions/UpdateTrackScan.ts | 79 +++++++++++++++++++++++++++ 3 files changed, 113 insertions(+), 7 deletions(-) create mode 100644 src/models/actions/UpdateTrackScan.ts diff --git a/src/controllers/RunnerController.ts b/src/controllers/RunnerController.ts index a11d2c6..4e74330 100644 --- a/src/controllers/RunnerController.ts +++ b/src/controllers/RunnerController.ts @@ -92,7 +92,7 @@ export class RunnerController { @ResponseSchema(ResponseRunner) @ResponseSchema(ResponseEmpty, { statusCode: 204 }) @OnUndefined(204) - @OpenAPI({ description: 'Delete the runner whose id you provided.
If no runner with this id exists it will just return 204(no content).' }) + @OpenAPI({ description: 'Delete the runner whose id you provided.
This will also delete all scans and cards associated with the runner.
If no runner with this id exists it will just return 204(no content).' }) async remove(@Param("id") id: number, @QueryParam("force") force: boolean) { let runner = await this.runnerRepository.findOne({ id: id }); if (!runner) { return null; } diff --git a/src/controllers/ScanController.ts b/src/controllers/ScanController.ts index 09833dc..46c7ba1 100644 --- a/src/controllers/ScanController.ts +++ b/src/controllers/ScanController.ts @@ -1,12 +1,14 @@ import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam, UseBefore } from 'routing-controllers'; import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; -import { getConnection, getConnectionManager, Repository } from 'typeorm'; +import { getConnectionManager, Repository } from 'typeorm'; import { RunnerNotFoundError } from '../errors/RunnerErrors'; import { ScanIdsNotMatchingError, ScanNotFoundError } from '../errors/ScanErrors'; +import { ScanStationNotFoundError } from '../errors/ScanStationErrors'; import ScanAuth from '../middlewares/ScanAuth'; import { CreateScan } from '../models/actions/CreateScan'; import { CreateTrackScan } from '../models/actions/CreateTrackScan'; import { UpdateScan } from '../models/actions/UpdateScan'; +import { UpdateTrackScan } from '../models/actions/UpdateTrackScan'; import { Scan } from '../models/entities/Scan'; import { TrackScan } from '../models/entities/TrackScan'; import { ResponseEmpty } from '../models/responses/ResponseEmpty'; @@ -17,12 +19,14 @@ import { ResponseTrackScan } from '../models/responses/ResponseTrackScan'; @OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) export class ScanController { private scanRepository: Repository; + private trackScanRepository: Repository; /** * Gets the repository of this controller's model/entity. */ constructor() { this.scanRepository = getConnectionManager().get().getRepository(Scan); + this.trackScanRepository = getConnectionManager().get().getRepository(TrackScan); } @Get() @@ -56,7 +60,7 @@ export class ScanController { @UseBefore(ScanAuth) @ResponseSchema(ResponseScan) @ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) - @OpenAPI({ description: 'Create a new scan.
Please remeber to provide the scan\'s runner\'s id and distance for normal scans.', security: [{ "ScanApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) + @OpenAPI({ description: 'Create a new scan (not track scan - use /scans/trackscans instead).
Please rmemember to provide the scan\'s runner\'s id and distance.', security: [{ "ScanApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) async post(@Body({ validate: true }) createScan: CreateScan) { let scan = await createScan.toScan(); scan = await this.scanRepository.save(scan); @@ -65,12 +69,12 @@ export class ScanController { @Post("/trackscans") @UseBefore(ScanAuth) - @ResponseSchema(ResponseScan) + @ResponseSchema(ResponseTrackScan) @ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) - @OpenAPI({ description: 'Create a new track scan.
This is just a alias for posting /scans', security: [{ "ScanApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) + @OpenAPI({ description: 'Create a new track scan (for "normal" scans use /scans instead).
Please remember that to provide the scan\'s card\'s station\'s id.', security: [{ "ScanApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) async postTrackScans(@Body({ validate: true }) createScan: CreateTrackScan) { let scan = await createScan.toScan(); - scan = await getConnection().getRepository(TrackScan).save(scan); + scan = await this.trackScanRepository.save(scan); return (await this.scanRepository.findOne({ id: scan.id }, { relations: ['runner', 'track', 'runner.scans', 'runner.scans.track', 'card', 'station'] })).toResponse(); } @@ -80,7 +84,7 @@ export class ScanController { @ResponseSchema(ScanNotFoundError, { statusCode: 404 }) @ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) @ResponseSchema(ScanIdsNotMatchingError, { statusCode: 406 }) - @OpenAPI({ description: "Update the scan whose id you provided.
Please remember that ids can't be changed and distances must be positive." }) + @OpenAPI({ description: "Update the scan (not track scan use /scans/trackscans/:id instead) whose id you provided.
Please remember that ids can't be changed and distances must be positive." }) async put(@Param('id') id: number, @Body({ validate: true }) scan: UpdateScan) { let oldScan = await this.scanRepository.findOne({ id: id }); @@ -96,6 +100,29 @@ export class ScanController { return (await this.scanRepository.findOne({ id: id }, { relations: ['runner', 'track', 'runner.scans', 'runner.scans.track', 'card', 'station'] })).toResponse(); } + @Put('/trackscans/:id') + @Authorized("SCAN:UPDATE") + @ResponseSchema(ResponseTrackScan) + @ResponseSchema(ScanNotFoundError, { statusCode: 404 }) + @ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) + @ResponseSchema(ScanStationNotFoundError, { statusCode: 404 }) + @ResponseSchema(ScanIdsNotMatchingError, { statusCode: 406 }) + @OpenAPI({ description: 'Update the track scan (not "normal" scan use /scans/trackscans/:id instead) whose id you provided.
Please remember that only the validity, runner and track can be changed.' }) + async putTrackScan(@Param('id') id: number, @Body({ validate: true }) scan: UpdateTrackScan) { + let oldScan = await this.trackScanRepository.findOne({ id: id }); + + if (!oldScan) { + throw new ScanNotFoundError(); + } + + if (oldScan.id != scan.id) { + throw new ScanIdsNotMatchingError(); + } + + await this.trackScanRepository.save(await scan.updateScan(oldScan)); + return (await this.scanRepository.findOne({ id: id }, { relations: ['runner', 'track', 'runner.scans', 'runner.scans.track', 'card', 'station'] })).toResponse(); + } + @Delete('/:id') @Authorized("SCAN:DELETE") @ResponseSchema(ResponseScan) diff --git a/src/models/actions/UpdateTrackScan.ts b/src/models/actions/UpdateTrackScan.ts new file mode 100644 index 0000000..9d0e754 --- /dev/null +++ b/src/models/actions/UpdateTrackScan.ts @@ -0,0 +1,79 @@ +import { IsBoolean, IsInt, IsOptional, IsPositive } from 'class-validator'; +import { getConnection } from 'typeorm'; +import { RunnerNotFoundError } from '../../errors/RunnerErrors'; +import { ScanStationNotFoundError } from '../../errors/ScanStationErrors'; +import { Runner } from '../entities/Runner'; +import { ScanStation } from '../entities/ScanStation'; +import { TrackScan } from '../entities/TrackScan'; + +/** + * This class is used to update a TrackScan entity (via put request) + */ +export abstract class UpdateTrackScan { + /** + * The updated scan's id. + * This shouldn't have changed but it is here in case anyone ever wants to enable id changes (whyever they would want to). + */ + @IsInt() + id: number; + + /** + * The updated scan's associated runner. + * This is important to link ran distances to runners. + */ + @IsInt() + @IsPositive() + runner: number; + + /** + * Is the updated scan valid (for fraud reasons). + */ + @IsBoolean() + @IsOptional() + valid?: boolean = true; + + /** + * The updated scan's associated station. + * This is important to link ran distances to runners. + */ + @IsInt() + @IsOptional() + public station?: number; + + /** + * Update a TrackScan entity based on this. + * @param scan The scan that shall be updated. + */ + public async updateScan(scan: TrackScan): Promise { + scan.valid = this.valid; + scan.runner = await this.getRunner(); + if (this.station) { + scan.station = await this.getStation(); + } + scan.track = scan.station.track; + + return scan; + } + + /** + * Gets a runner based on the runner id provided via this.runner. + */ + public async getRunner(): Promise { + const runner = await getConnection().getRepository(Runner).findOne({ id: this.runner }); + if (!runner) { + throw new RunnerNotFoundError(); + } + return runner; + } + + /** + * Gets a runner based on the runner id provided via this.runner. + */ + public async getStation(): Promise { + const station = await getConnection().getRepository(ScanStation).findOne({ id: this.station }); + if (!station) { + throw new ScanStationNotFoundError(); + } + return station; + } +} \ No newline at end of file From 0c86e5dae173c04422cf7254c72da961eca96e35 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Sat, 9 Jan 2021 16:44:52 +0100 Subject: [PATCH 09/14] added trackscan add tests ref #78 --- src/tests/trackscans/trackscans_add.spec.ts | 239 ++++++++++++++++++++ 1 file changed, 239 insertions(+) create mode 100644 src/tests/trackscans/trackscans_add.spec.ts diff --git a/src/tests/trackscans/trackscans_add.spec.ts b/src/tests/trackscans/trackscans_add.spec.ts new file mode 100644 index 0000000..4b5de54 --- /dev/null +++ b/src/tests/trackscans/trackscans_add.spec.ts @@ -0,0 +1,239 @@ +import axios from 'axios'; +import { config } from '../../config'; +const base = "http://localhost:" + config.internal_port + +let access_token; +let axios_config; + +beforeAll(async () => { + const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" }); + access_token = res.data["access_token"]; + axios_config = { + headers: { "authorization": "Bearer " + access_token }, + validateStatus: undefined + }; +}); + + +describe('POST /api/scans illegally', () => { + let added_org; + let added_runner; + let added_card; + let added_track; + let added_station; + it('creating a new org with just a name should return 200', async () => { + const res1 = await axios.post(base + '/api/organisations', { + "name": "test123" + }, axios_config); + added_org = res1.data + expect(res1.status).toEqual(200); + expect(res1.headers['content-type']).toContain("application/json") + }); + it('creating a new runner with only needed params should return 200', async () => { + const res2 = await axios.post(base + '/api/runners', { + "firstname": "first", + "lastname": "last", + "group": added_org.id + }, axios_config); + added_runner = res2.data; + expect(res2.status).toEqual(200); + expect(res2.headers['content-type']).toContain("application/json") + }); + it('creating a card with the minimum amount of parameters should return 200', async () => { + const res = await axios.post(base + '/api/cards', { + "runner": added_runner.id + }, axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json"); + added_card = res.data; + }); + it('creating a track should return 200', async () => { + const res1 = await axios.post(base + '/api/tracks', { + "name": "test123", + "distance": 123 + }, axios_config); + added_track = res1.data + expect(res1.status).toEqual(200); + expect(res1.headers['content-type']).toContain("application/json") + }); + it('correct description and track input for station creation return 200', async () => { + const res = await axios.post(base + '/api/stations', { + "track": added_track.id, + "description": "I am but a simple test." + }, axios_config); + added_station = res.data; + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('no input should return 400', async () => { + const res = await axios.post(base + '/api/scans/trackscans', null, axios_config); + expect(res.status).toEqual(400); + expect(res.headers['content-type']).toContain("application/json") + }); + it('no station should return 400', async () => { + const res = await axios.post(base + '/api/scans/trackscans', { + "card": added_card.id + }, axios_config); + expect(res.status).toEqual(400); + expect(res.headers['content-type']).toContain("application/json") + }); + it('no card should return 400', async () => { + const res = await axios.post(base + '/api/scans/trackscans', { + "station": added_station.id, + }, axios_config); + expect(res.status).toEqual(400); + expect(res.headers['content-type']).toContain("application/json") + }); + it('invalid card input should return 404', async () => { + const res = await axios.post(base + '/api/scans/trackscans', { + "card": 999999999999999999999999, + "station": added_station.id + }, axios_config); + expect(res.status).toEqual(404); + expect(res.headers['content-type']).toContain("application/json") + }); + it('invalid station input should return 404', async () => { + const res = await axios.post(base + '/api/scans/trackscans', { + "card": added_card.id, + "station": 999999999999999999999999 + }, axios_config); + expect(res.status).toEqual(404); + expect(res.headers['content-type']).toContain("application/json") + }); +}); +// --------------- +describe('POST /api/scans successfully', () => { + let added_org; + let added_runner; + let added_card; + let added_track; + let added_station; + it('creating a new org with just a name should return 200', async () => { + const res1 = await axios.post(base + '/api/organisations', { + "name": "test123" + }, axios_config); + added_org = res1.data + expect(res1.status).toEqual(200); + expect(res1.headers['content-type']).toContain("application/json") + }); + it('creating a new runner with only needed params should return 200', async () => { + const res2 = await axios.post(base + '/api/runners', { + "firstname": "first", + "lastname": "last", + "group": added_org.id + }, axios_config); + added_runner = res2.data; + expect(res2.status).toEqual(200); + expect(res2.headers['content-type']).toContain("application/json") + }); + it('creating a card with the minimum amount of parameters should return 200', async () => { + const res = await axios.post(base + '/api/cards', { + "runner": added_runner.id + }, axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json"); + added_card = res.data; + }); + it('creating a track should return 200', async () => { + const res1 = await axios.post(base + '/api/tracks', { + "name": "test123", + "distance": 123 + }, axios_config); + added_track = res1.data + expect(res1.status).toEqual(200); + expect(res1.headers['content-type']).toContain("application/json") + }); + it('correct description and track input for station creation return 200', async () => { + const res = await axios.post(base + '/api/stations', { + "track": added_track.id, + "description": "I am but a simple test." + }, axios_config); + added_station = res.data; + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a scan should return 200', async () => { + const res = await axios.post(base + '/api/scans/trackscans', { + "card": added_card.id, + "station": added_station.id + }, axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json"); + }); +}); +// --------------- +describe('POST /api/scans successfully via scan station', () => { + let added_org; + let added_runner; + let added_card; + let added_track; + let added_station; + it('creating a new org with just a name should return 200', async () => { + const res1 = await axios.post(base + '/api/organisations', { + "name": "test123" + }, axios_config); + added_org = res1.data + expect(res1.status).toEqual(200); + expect(res1.headers['content-type']).toContain("application/json") + }); + it('creating a new runner with only needed params should return 200', async () => { + const res2 = await axios.post(base + '/api/runners', { + "firstname": "first", + "lastname": "last", + "group": added_org.id + }, axios_config); + added_runner = res2.data; + expect(res2.status).toEqual(200); + expect(res2.headers['content-type']).toContain("application/json") + }); + it('creating a card with the minimum amount of parameters should return 200', async () => { + const res = await axios.post(base + '/api/cards', { + "runner": added_runner.id + }, axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json"); + added_card = res.data; + }); + it('creating a track should return 200', async () => { + const res1 = await axios.post(base + '/api/tracks', { + "name": "test123", + "distance": 123, + "minimumLapTime": 30 + }, axios_config); + added_track = res1.data + expect(res1.status).toEqual(200); + expect(res1.headers['content-type']).toContain("application/json") + }); + it('correct description and track input for station creation return 200', async () => { + const res = await axios.post(base + '/api/stations', { + "track": added_track.id, + "description": "I am but a simple test." + }, axios_config); + added_station = res.data; + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a scan should return 200', async () => { + const res = await axios.post(base + '/api/scans/trackscans', { + "card": added_card.id, + "station": added_station.id + }, { + headers: { "authorization": "Bearer " + added_station.key }, + validateStatus: undefined + }); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json"); + }); + it('creating a invalid scan should return 200', async () => { + const res = await axios.post(base + '/api/scans/trackscans', { + "card": added_card.id, + "station": added_station.id + }, { + headers: { "authorization": "Bearer " + added_station.key }, + validateStatus: undefined + }); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json"); + expect(res.data.vaild).toEqual(false); + }); +}); \ No newline at end of file From 61cf0fc08d3af733b30640880f4b3981cd9f827a Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Sat, 9 Jan 2021 16:47:54 +0100 Subject: [PATCH 10/14] Implemented proper scan invalidation ref #78 --- src/models/actions/CreateTrackScan.ts | 20 +++++++++++--------- src/models/entities/TrackScan.ts | 6 ++---- src/models/responses/ResponseTrackScan.ts | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/models/actions/CreateTrackScan.ts b/src/models/actions/CreateTrackScan.ts index 44877f0..51078c4 100644 --- a/src/models/actions/CreateTrackScan.ts +++ b/src/models/actions/CreateTrackScan.ts @@ -1,6 +1,8 @@ import { IsInt, IsPositive } from 'class-validator'; import { getConnection } from 'typeorm'; +import { RunnerCardNotFoundError } from '../../errors/RunnerCardErrors'; import { RunnerNotFoundError } from '../../errors/RunnerErrors'; +import { ScanStationNotFoundError } from '../../errors/ScanStationErrors'; import { RunnerCard } from '../entities/RunnerCard'; import { ScanStation } from '../entities/ScanStation'; import { TrackScan } from '../entities/TrackScan'; @@ -41,7 +43,7 @@ export class CreateTrackScan { throw new RunnerNotFoundError(); } - newScan.timestamp = new Date(Date.now()).toString(); + newScan.timestamp = Math.round(new Date().getTime() / 1000); newScan.valid = await this.validateScan(newScan); return newScan; @@ -50,25 +52,25 @@ export class CreateTrackScan { public async getCard(): Promise { const track = await getConnection().getRepository(RunnerCard).findOne({ id: this.card }, { relations: ["runner"] }); if (!track) { - throw new Error(); + throw new RunnerCardNotFoundError(); } return track; } public async getStation(): Promise { - const track = await getConnection().getRepository(ScanStation).findOne({ id: this.card }, { relations: ["track"] }); - if (!track) { - throw new Error(); + const station = await getConnection().getRepository(ScanStation).findOne({ id: this.station }, { relations: ["track"] }); + if (!station) { + throw new ScanStationNotFoundError(); } - return track; + return station; } public async validateScan(scan: TrackScan): Promise { - const scans = await getConnection().getRepository(TrackScan).find({ where: { runner: scan.runner }, relations: ["track"] }); + const scans = await getConnection().getRepository(TrackScan).find({ where: { runner: scan.runner, valid: true }, relations: ["track"] }); if (scans.length == 0) { return true; } - const newestScan = scans[0]; - if ((new Date(scan.timestamp).getTime() - new Date(newestScan.timestamp).getTime()) > scan.track.minimumLapTime) { + const newestScan = scans[scans.length - 1]; + if ((scan.timestamp - newestScan.timestamp) > scan.track.minimumLapTime) { return true; } diff --git a/src/models/entities/TrackScan.ts b/src/models/entities/TrackScan.ts index 8c4143b..cd8049a 100644 --- a/src/models/entities/TrackScan.ts +++ b/src/models/entities/TrackScan.ts @@ -1,5 +1,4 @@ import { - IsDateString, IsInt, IsNotEmpty, @@ -57,9 +56,8 @@ export class TrackScan extends Scan { * Will be used to implement fraud detection. */ @Column() - @IsDateString() - @IsNotEmpty() - timestamp: string; + @IsInt() + timestamp: number; /** * Turns this entity into it's response class. diff --git a/src/models/responses/ResponseTrackScan.ts b/src/models/responses/ResponseTrackScan.ts index dba450c..d52b4f7 100644 --- a/src/models/responses/ResponseTrackScan.ts +++ b/src/models/responses/ResponseTrackScan.ts @@ -32,7 +32,7 @@ export class ResponseTrackScan extends ResponseScan { */ @IsDateString() @IsNotEmpty() - timestamp: string; + timestamp: number; /** * Creates a ResponseTrackScan object from a scan. From f1dee1061dc8dc9b76d3100b7c66d7c12434fd8f Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Sat, 9 Jan 2021 16:49:17 +0100 Subject: [PATCH 11/14] added trackscan get tests ref #78 --- src/tests/trackscans/trackscans_get.spec.ts | 102 ++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 src/tests/trackscans/trackscans_get.spec.ts diff --git a/src/tests/trackscans/trackscans_get.spec.ts b/src/tests/trackscans/trackscans_get.spec.ts new file mode 100644 index 0000000..1f0eaed --- /dev/null +++ b/src/tests/trackscans/trackscans_get.spec.ts @@ -0,0 +1,102 @@ +import axios from 'axios'; +import { config } from '../../config'; +const base = "http://localhost:" + config.internal_port + +let access_token; +let axios_config; + +beforeAll(async () => { + const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" }); + access_token = res.data["access_token"]; + axios_config = { + headers: { "authorization": "Bearer " + access_token }, + validateStatus: undefined + }; +}); + +describe('GET /api/scans sucessfully', () => { + it('basic get should return 200', async () => { + const res = await axios.get(base + '/api/scans', axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); +}); +// --------------- +describe('GET /api/scans illegally', () => { + it('get for non-existant track should return 404', async () => { + const res = await axios.get(base + '/api/scans/-1', axios_config); + expect(res.status).toEqual(404); + expect(res.headers['content-type']).toContain("application/json") + }); +}); +// --------------- +describe('adding + getting scans', () => { + let added_org; + let added_runner; + let added_card; + let added_track; + let added_station; + let added_scan; + it('creating a new org with just a name should return 200', async () => { + const res1 = await axios.post(base + '/api/organisations', { + "name": "test123" + }, axios_config); + added_org = res1.data + expect(res1.status).toEqual(200); + expect(res1.headers['content-type']).toContain("application/json") + }); + it('creating a new runner with only needed params should return 200', async () => { + const res2 = await axios.post(base + '/api/runners', { + "firstname": "first", + "lastname": "last", + "group": added_org.id + }, axios_config); + added_runner = res2.data; + expect(res2.status).toEqual(200); + expect(res2.headers['content-type']).toContain("application/json") + }); + it('creating a card with the minimum amount of parameters should return 200', async () => { + const res = await axios.post(base + '/api/cards', { + "runner": added_runner.id + }, axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json"); + added_card = res.data; + }); + it('creating a track should return 200', async () => { + const res1 = await axios.post(base + '/api/tracks', { + "name": "test123", + "distance": 123, + "minimumLapTime": 30 + }, axios_config); + added_track = res1.data + expect(res1.status).toEqual(200); + expect(res1.headers['content-type']).toContain("application/json") + }); + it('correct description and track input for station creation return 200', async () => { + const res = await axios.post(base + '/api/stations', { + "track": added_track.id, + "description": "I am but a simple test." + }, axios_config); + added_station = res.data; + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a scan should return 200', async () => { + const res = await axios.post(base + '/api/scans/trackscans', { + "card": added_card.id, + "station": added_station.id + }, { + headers: { "authorization": "Bearer " + added_station.key }, + validateStatus: undefined + }); + added_scan = res.data; + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json"); + }); + it('check if scans was added (no parameter validation)', async () => { + const res = await axios.get(base + '/api/scans/' + added_scan.id, axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json"); + }); +}); \ No newline at end of file From 4fea6906706872ca6157679d47b22b70d7ff2255 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Sat, 9 Jan 2021 16:54:19 +0100 Subject: [PATCH 12/14] Added missing parameter fro negative-test ref #78 --- src/tests/trackscans/trackscans_add.spec.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/tests/trackscans/trackscans_add.spec.ts b/src/tests/trackscans/trackscans_add.spec.ts index 4b5de54..a4b475d 100644 --- a/src/tests/trackscans/trackscans_add.spec.ts +++ b/src/tests/trackscans/trackscans_add.spec.ts @@ -137,7 +137,8 @@ describe('POST /api/scans successfully', () => { it('creating a track should return 200', async () => { const res1 = await axios.post(base + '/api/tracks', { "name": "test123", - "distance": 123 + "distance": 123, + "minimumLapTime": 30 }, axios_config); added_track = res1.data expect(res1.status).toEqual(200); @@ -160,6 +161,15 @@ describe('POST /api/scans successfully', () => { expect(res.status).toEqual(200); expect(res.headers['content-type']).toContain("application/json"); }); + it('creating a invalid scan should return 200', async () => { + const res = await axios.post(base + '/api/scans/trackscans', { + "card": added_card.id, + "station": added_station.id + }, axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json"); + expect(res.data.valid).toEqual(false); + }); }); // --------------- describe('POST /api/scans successfully via scan station', () => { @@ -234,6 +244,6 @@ describe('POST /api/scans successfully via scan station', () => { }); expect(res.status).toEqual(200); expect(res.headers['content-type']).toContain("application/json"); - expect(res.data.vaild).toEqual(false); + expect(res.data.valid).toEqual(false); }); }); \ No newline at end of file From efe1a1f543aa2eebd2b2a3c2be89ce1a08c6bcd4 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Sat, 9 Jan 2021 16:56:57 +0100 Subject: [PATCH 13/14] added trackscan delete tests ref #78 --- .../trackscans/trackscans_delete.spec.ts | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 src/tests/trackscans/trackscans_delete.spec.ts diff --git a/src/tests/trackscans/trackscans_delete.spec.ts b/src/tests/trackscans/trackscans_delete.spec.ts new file mode 100644 index 0000000..646c189 --- /dev/null +++ b/src/tests/trackscans/trackscans_delete.spec.ts @@ -0,0 +1,101 @@ +import axios from 'axios'; +import { config } from '../../config'; +const base = "http://localhost:" + config.internal_port + +let access_token; +let axios_config; + +beforeAll(async () => { + const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" }); + access_token = res.data["access_token"]; + axios_config = { + headers: { "authorization": "Bearer " + access_token }, + validateStatus: undefined + }; +}); + +// --------------- + +describe('DELETE scan', () => { + let added_org; + let added_runner; + let added_card; + let added_track; + let added_station; + let added_scan; + it('creating a new org with just a name should return 200', async () => { + const res1 = await axios.post(base + '/api/organisations', { + "name": "test123" + }, axios_config); + added_org = res1.data + expect(res1.status).toEqual(200); + expect(res1.headers['content-type']).toContain("application/json") + }); + it('creating a new runner with only needed params should return 200', async () => { + const res2 = await axios.post(base + '/api/runners', { + "firstname": "first", + "lastname": "last", + "group": added_org.id + }, axios_config); + added_runner = res2.data; + expect(res2.status).toEqual(200); + expect(res2.headers['content-type']).toContain("application/json") + }); + it('creating a card with the minimum amount of parameters should return 200', async () => { + const res = await axios.post(base + '/api/cards', { + "runner": added_runner.id + }, axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json"); + added_card = res.data; + }); + it('creating a track should return 200', async () => { + const res1 = await axios.post(base + '/api/tracks', { + "name": "test123", + "distance": 123, + "minimumLapTime": 30 + }, axios_config); + added_track = res1.data + expect(res1.status).toEqual(200); + expect(res1.headers['content-type']).toContain("application/json") + }); + it('correct description and track input for station creation return 200', async () => { + const res = await axios.post(base + '/api/stations', { + "track": added_track.id, + "description": "I am but a simple test." + }, axios_config); + added_station = res.data; + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a scan should return 200', async () => { + const res = await axios.post(base + '/api/scans/trackscans', { + "card": added_card.id, + "station": added_station.id + }, { + headers: { "authorization": "Bearer " + added_station.key }, + validateStatus: undefined + }); + added_scan = res.data; + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json"); + }); + it('delete scan', async () => { + const res2 = await axios.delete(base + '/api/scans/' + added_scan.id, axios_config); + expect(res2.status).toEqual(200); + expect(res2.headers['content-type']).toContain("application/json") + expect(res2.data).toEqual(added_scan); + }); + it('check if scan really was deleted', async () => { + const res3 = await axios.get(base + '/api/scans/' + added_scan.id, axios_config); + expect(res3.status).toEqual(404); + expect(res3.headers['content-type']).toContain("application/json") + }); +}); +// --------------- +describe('DELETE scan (non-existant)', () => { + it('delete', async () => { + const res2 = await axios.delete(base + '/api/scans/0', axios_config); + expect(res2.status).toEqual(204); + }); +}); From 7e95103a2d68e9f55a6550290d36b6e62add973f Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Sat, 9 Jan 2021 17:18:33 +0100 Subject: [PATCH 14/14] added trackscan update tests ref #78 --- src/models/actions/UpdateTrackScan.ts | 10 +- .../trackscans/trackscans_update.spec.ts | 239 ++++++++++++++++++ 2 files changed, 245 insertions(+), 4 deletions(-) create mode 100644 src/tests/trackscans/trackscans_update.spec.ts diff --git a/src/models/actions/UpdateTrackScan.ts b/src/models/actions/UpdateTrackScan.ts index 9d0e754..43c37cd 100644 --- a/src/models/actions/UpdateTrackScan.ts +++ b/src/models/actions/UpdateTrackScan.ts @@ -1,4 +1,4 @@ -import { IsBoolean, IsInt, IsOptional, IsPositive } from 'class-validator'; +import { IsBoolean, IsInt, IsOptional } from 'class-validator'; import { getConnection } from 'typeorm'; import { RunnerNotFoundError } from '../../errors/RunnerErrors'; import { ScanStationNotFoundError } from '../../errors/ScanStationErrors'; @@ -22,8 +22,8 @@ export abstract class UpdateTrackScan { * This is important to link ran distances to runners. */ @IsInt() - @IsPositive() - runner: number; + @IsOptional() + runner?: number; /** * Is the updated scan valid (for fraud reasons). @@ -46,7 +46,9 @@ export abstract class UpdateTrackScan { */ public async updateScan(scan: TrackScan): Promise { scan.valid = this.valid; - scan.runner = await this.getRunner(); + if (this.runner) { + scan.runner = await this.getRunner(); + } if (this.station) { scan.station = await this.getStation(); } diff --git a/src/tests/trackscans/trackscans_update.spec.ts b/src/tests/trackscans/trackscans_update.spec.ts new file mode 100644 index 0000000..131f3b4 --- /dev/null +++ b/src/tests/trackscans/trackscans_update.spec.ts @@ -0,0 +1,239 @@ +import axios from 'axios'; +import { config } from '../../config'; +const base = "http://localhost:" + config.internal_port + +let access_token; +let axios_config; + +beforeAll(async () => { + const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" }); + access_token = res.data["access_token"]; + axios_config = { + headers: { "authorization": "Bearer " + access_token }, + validateStatus: undefined + }; +}); + +describe('adding + updating illegally', () => { + let added_org; + let added_runner; + let added_card; + let added_track; + let added_station; + let added_scan; + it('creating a new org with just a name should return 200', async () => { + const res1 = await axios.post(base + '/api/organisations', { + "name": "test123" + }, axios_config); + added_org = res1.data + expect(res1.status).toEqual(200); + expect(res1.headers['content-type']).toContain("application/json") + }); + it('creating a new runner with only needed params should return 200', async () => { + const res2 = await axios.post(base + '/api/runners', { + "firstname": "first", + "lastname": "last", + "group": added_org.id + }, axios_config); + added_runner = res2.data; + expect(res2.status).toEqual(200); + expect(res2.headers['content-type']).toContain("application/json") + }); + it('creating a card with the minimum amount of parameters should return 200', async () => { + const res = await axios.post(base + '/api/cards', { + "runner": added_runner.id + }, axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json"); + added_card = res.data; + }); + it('creating a track should return 200', async () => { + const res1 = await axios.post(base + '/api/tracks', { + "name": "test123", + "distance": 123, + "minimumLapTime": 30 + }, axios_config); + added_track = res1.data + expect(res1.status).toEqual(200); + expect(res1.headers['content-type']).toContain("application/json") + }); + it('correct description and track input for station creation return 200', async () => { + const res = await axios.post(base + '/api/stations', { + "track": added_track.id, + "description": "I am but a simple test." + }, axios_config); + added_station = res.data; + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a scan should return 200', async () => { + const res = await axios.post(base + '/api/scans/trackscans', { + "card": added_card.id, + "station": added_station.id + }, { + headers: { "authorization": "Bearer " + added_station.key }, + validateStatus: undefined + }); + added_scan = res.data; + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json"); + }); + it('updating empty should return 400', async () => { + const res2 = await axios.put(base + '/api/scans/trackscans/' + added_scan.id, null, axios_config); + expect(res2.status).toEqual(400); + expect(res2.headers['content-type']).toContain("application/json") + }); + it('updating with wrong id should return 406', async () => { + const res2 = await axios.put(base + '/api/scans/trackscans/' + added_scan.id, { + "id": added_scan.id + 1, + "station": added_station.id, + "runner": added_runner.id + }, axios_config); + expect(res2.status).toEqual(406); + expect(res2.headers['content-type']).toContain("application/json") + }); + it('updating with invalid station should return 404', async () => { + const res2 = await axios.put(base + '/api/scans/trackscans/' + added_scan.id, { + "id": added_scan.id, + "station": 9999999999999999, + "runner": added_runner.id + }, axios_config); + expect(res2.status).toEqual(404); + expect(res2.headers['content-type']).toContain("application/json") + }); + it('updating with invalid runner should return 404', async () => { + const res2 = await axios.put(base + '/api/scans/trackscans/' + added_scan.id, { + "id": added_scan.id, + "station": added_station.id, + "runner": 9999999999999999999 + }, axios_config); + expect(res2.status).toEqual(404); + expect(res2.headers['content-type']).toContain("application/json") + }); +}); +//--------------- +describe('adding + updating successfilly', () => { + let added_org; + let added_runner; + let added_runner2; + let added_card; + let added_track; + let added_track2; + let added_station; + let added_station2; + let added_scan; + it('creating a new org with just a name should return 200', async () => { + const res1 = await axios.post(base + '/api/organisations', { + "name": "test123" + }, axios_config); + added_org = res1.data + expect(res1.status).toEqual(200); + expect(res1.headers['content-type']).toContain("application/json") + }); + it('creating a new runner with only needed params should return 200', async () => { + const res2 = await axios.post(base + '/api/runners', { + "firstname": "first", + "lastname": "last", + "group": added_org.id + }, axios_config); + added_runner = res2.data; + expect(res2.status).toEqual(200); + expect(res2.headers['content-type']).toContain("application/json") + }); + it('creating a new runner with only needed params should return 200', async () => { + const res2 = await axios.post(base + '/api/runners', { + "firstname": "first", + "lastname": "last", + "group": added_org.id + }, axios_config); + added_runner2 = res2.data; + expect(res2.status).toEqual(200); + expect(res2.headers['content-type']).toContain("application/json") + }); + it('creating a card with the minimum amount of parameters should return 200', async () => { + const res = await axios.post(base + '/api/cards', { + "runner": added_runner.id + }, axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json"); + added_card = res.data; + }); + it('creating a track should return 200', async () => { + const res1 = await axios.post(base + '/api/tracks', { + "name": "test123", + "distance": 123, + "minimumLapTime": 30 + }, axios_config); + added_track = res1.data + expect(res1.status).toEqual(200); + expect(res1.headers['content-type']).toContain("application/json") + }); + it('creating a track should return 200', async () => { + const res1 = await axios.post(base + '/api/tracks', { + "name": "test123", + "distance": 123, + "minimumLapTime": 30 + }, axios_config); + added_track2 = res1.data + expect(res1.status).toEqual(200); + expect(res1.headers['content-type']).toContain("application/json") + }); + it('correct description and track input for station creation return 200', async () => { + const res = await axios.post(base + '/api/stations', { + "track": added_track.id, + "description": "I am but a simple test." + }, axios_config); + added_station = res.data; + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('correct description and track input for station creation return 200', async () => { + const res = await axios.post(base + '/api/stations', { + "track": added_track.id, + "description": "I am but a simple test." + }, axios_config); + added_station2 = res.data; + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a scan should return 200', async () => { + const res = await axios.post(base + '/api/scans/trackscans', { + "card": added_card.id, + "station": added_station.id + }, { + headers: { "authorization": "Bearer " + added_station.key }, + validateStatus: undefined + }); + added_scan = res.data; + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json"); + }); + it('updating with new runner should return 200', async () => { + const res2 = await axios.put(base + '/api/scans/trackscans/' + added_scan.id, { + "id": added_scan.id, + "station": added_station.id, + "runner": added_runner2.id + }, axios_config); + expect(res2.status).toEqual(200); + expect(res2.headers['content-type']).toContain("application/json") + }); + it('updating with new station should return 200', async () => { + const res2 = await axios.put(base + '/api/scans/trackscans/' + added_scan.id, { + "id": added_scan.id, + "station": added_station2.id, + "runner": added_runner.id + }, axios_config); + expect(res2.status).toEqual(200); + expect(res2.headers['content-type']).toContain("application/json") + }); + it('updating with valid=false should return 200', async () => { + const res2 = await axios.put(base + '/api/scans/trackscans/' + added_scan.id, { + "id": added_scan.id, + "station": added_station2.id, + "runner": added_runner.id, + "valid": false + }, axios_config); + expect(res2.status).toEqual(200); + expect(res2.headers['content-type']).toContain("application/json") + }); +});