diff --git a/src/controllers/AuthController.ts b/src/controllers/AuthController.ts
index cddb677..9672de5 100644
--- a/src/controllers/AuthController.ts
+++ b/src/controllers/AuthController.ts
@@ -70,7 +70,6 @@ export class AuthController {
if (refresh_token && refresh_token.length != 0 && refreshAuth.token == undefined) {
refreshAuth.token = refresh_token;
}
- console.log(req.headers)
let auth;
try {
auth = await refreshAuth.toAuth();
diff --git a/src/controllers/TrackController.ts b/src/controllers/TrackController.ts
index 1f74193..f03718f 100644
--- a/src/controllers/TrackController.ts
+++ b/src/controllers/TrackController.ts
@@ -1,9 +1,9 @@
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnectionManager, Repository } from 'typeorm';
-import { EntityFromBody } from 'typeorm-routing-controllers-extensions';
-import { TrackIdsNotMatchingError, TrackNotFoundError } from "../errors/TrackErrors";
+import { TrackIdsNotMatchingError, TrackLapTimeCantBeNegativeError, TrackNotFoundError } from "../errors/TrackErrors";
import { CreateTrack } from '../models/actions/CreateTrack';
+import { UpdateTrack } from '../models/actions/UpdateTrack';
import { Track } from '../models/entities/Track';
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
import { ResponseTrack } from '../models/responses/ResponseTrack';
@@ -48,6 +48,7 @@ export class TrackController {
@Post()
@Authorized("TRACK:CREATE")
@ResponseSchema(ResponseTrack)
+ @ResponseSchema(TrackLapTimeCantBeNegativeError, { statusCode: 406 })
@OpenAPI({ description: "Create a new track.
Please remember that the track\'s distance must be greater than 0." })
async post(
@Body({ validate: true })
@@ -61,20 +62,21 @@ export class TrackController {
@ResponseSchema(ResponseTrack)
@ResponseSchema(TrackNotFoundError, { statusCode: 404 })
@ResponseSchema(TrackIdsNotMatchingError, { statusCode: 406 })
+ @ResponseSchema(TrackLapTimeCantBeNegativeError, { statusCode: 406 })
@OpenAPI({ description: "Update the track whose id you provided.
Please remember that ids can't be changed." })
- async put(@Param('id') id: number, @EntityFromBody() track: Track) {
+ async put(@Param('id') id: number, @Body({ validate: true }) updateTrack: UpdateTrack) {
let oldTrack = await this.trackRepository.findOne({ id: id });
if (!oldTrack) {
throw new TrackNotFoundError();
}
- if (oldTrack.id != track.id) {
+ if (oldTrack.id != updateTrack.id) {
throw new TrackIdsNotMatchingError();
}
+ await this.trackRepository.save(await updateTrack.updateTrack(oldTrack));
- await this.trackRepository.save(track);
- return new ResponseTrack(track);
+ return new ResponseTrack(await this.trackRepository.findOne({ id: id }));
}
@Delete('/:id')
diff --git a/src/errors/TrackErrors.ts b/src/errors/TrackErrors.ts
index 7d4cfa9..e3d1902 100644
--- a/src/errors/TrackErrors.ts
+++ b/src/errors/TrackErrors.ts
@@ -22,4 +22,15 @@ export class TrackIdsNotMatchingError extends NotAcceptableError {
@IsString()
message = "The ids don't match! \n And if you wanted to change a track's id: This isn't allowed"
+}
+
+/**
+ * Error to throw when a track's lap time is set to a negative value.
+ */
+export class TrackLapTimeCantBeNegativeError extends NotAcceptableError {
+ @IsString()
+ name = "TrackLapTimeCantBeNegativeError"
+
+ @IsString()
+ message = "The minimum lap time you provided is negative - That isn't possible. \n If you wanted to disable it: Just set it to 0/null."
}
\ No newline at end of file
diff --git a/src/models/actions/CreateTrack.ts b/src/models/actions/CreateTrack.ts
index f04e55b..fdeae71 100644
--- a/src/models/actions/CreateTrack.ts
+++ b/src/models/actions/CreateTrack.ts
@@ -1,4 +1,5 @@
-import { IsInt, IsNotEmpty, IsPositive, IsString } from 'class-validator';
+import { IsInt, IsNotEmpty, IsOptional, IsPositive, IsString } from 'class-validator';
+import { TrackLapTimeCantBeNegativeError } from '../../errors/TrackErrors';
import { Track } from '../entities/Track';
/**
@@ -19,6 +20,14 @@ export class CreateTrack {
@IsPositive()
distance: number;
+ /**
+ * The minimum time a runner should take to run a lap on this track (in seconds).
+ * Will be used for fraud detection.
+ */
+ @IsInt()
+ @IsOptional()
+ minimumLapTime: number;
+
/**
* Creates a new Track entity from this.
*/
@@ -27,6 +36,10 @@ export class CreateTrack {
newTrack.name = this.name;
newTrack.distance = this.distance;
+ newTrack.minimumLapTime = this.minimumLapTime;
+ if (this.minimumLapTime < 0) {
+ throw new TrackLapTimeCantBeNegativeError();
+ }
return newTrack;
}
diff --git a/src/models/actions/UpdateTrack.ts b/src/models/actions/UpdateTrack.ts
new file mode 100644
index 0000000..bc64d54
--- /dev/null
+++ b/src/models/actions/UpdateTrack.ts
@@ -0,0 +1,50 @@
+import { IsInt, IsNotEmpty, IsOptional, IsPositive, IsString } from 'class-validator';
+import { TrackLapTimeCantBeNegativeError } from '../../errors/TrackErrors';
+import { Track } from '../entities/Track';
+
+/**
+ * This class is used to update a Track entity (via put request).
+ */
+export class UpdateTrack {
+ /**
+ * The updated track'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;
+
+ @IsString()
+ @IsNotEmpty()
+ name: string;
+
+ /**
+ * The updated track's distance in meters (must be greater than 0).
+ */
+ @IsInt()
+ @IsPositive()
+ distance: number;
+
+ /**
+ * The minimum time a runner should take to run a lap on this track (in seconds).
+ * Will be used for fraud detection.
+ */
+ @IsInt()
+ @IsOptional()
+ minimumLapTime: number;
+
+
+ /**
+ * Update a Track entity based on this.
+ * @param track The track that shall be updated.
+ */
+ public updateTrack(track: Track): Track {
+ track.name = this.name;
+ track.distance = this.distance;
+ track.minimumLapTime = this.minimumLapTime;
+ if (this.minimumLapTime < 0) {
+ throw new TrackLapTimeCantBeNegativeError();
+ }
+
+ return track;
+ }
+}
\ No newline at end of file
diff --git a/src/models/entities/Track.ts b/src/models/entities/Track.ts
index 22544d0..df18762 100644
--- a/src/models/entities/Track.ts
+++ b/src/models/entities/Track.ts
@@ -1,6 +1,7 @@
import {
IsInt,
IsNotEmpty,
+ IsOptional,
IsPositive,
IsString
} from "class-validator";
@@ -18,7 +19,7 @@ export class Track {
*/
@PrimaryGeneratedColumn()
@IsInt()
- id: number;;
+ id: number;
/**
* The track's name.
@@ -38,6 +39,15 @@ export class Track {
@IsPositive()
distance: number;
+ /**
+ * The minimum time a runner should take to run a lap on this track (in seconds).
+ * Will be used for fraud detection.
+ */
+ @Column({ nullable: true })
+ @IsInt()
+ @IsOptional()
+ minimumLapTime?: number;
+
/**
* Used to link scan stations to a certain track.
* This makes the configuration of the scan stations easier.
diff --git a/src/models/responses/ResponseTrack.ts b/src/models/responses/ResponseTrack.ts
index 83da863..27ef813 100644
--- a/src/models/responses/ResponseTrack.ts
+++ b/src/models/responses/ResponseTrack.ts
@@ -1,4 +1,5 @@
-import { IsInt, IsString } from "class-validator";
+import { IsInt, IsOptional, IsString } from "class-validator";
+import { TrackLapTimeCantBeNegativeError } from '../../errors/TrackErrors';
import { Track } from '../entities/Track';
/**
@@ -23,6 +24,14 @@ export class ResponseTrack {
@IsInt()
distance: number;
+ /**
+ * The minimum time a runner should take to run a lap on this track (in seconds).
+ * Will be used for fraud detection.
+ */
+ @IsInt()
+ @IsOptional()
+ minimumLapTime?: number;
+
/**
* Creates a ResponseTrack object from a track.
* @param track The track the response shall be build for.
@@ -31,5 +40,9 @@ export class ResponseTrack {
this.id = track.id;
this.name = track.name;
this.distance = track.distance;
+ this.minimumLapTime = track.minimumLapTime;
+ if (this.minimumLapTime < 0) {
+ throw new TrackLapTimeCantBeNegativeError();
+ }
}
}
diff --git a/src/tests/tracks.spec.ts b/src/tests/tracks.spec.ts
deleted file mode 100644
index 81e4850..0000000
--- a/src/tests/tracks.spec.ts
+++ /dev/null
@@ -1,112 +0,0 @@
-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/tracks', () => {
- it('basic get should return 200', async () => {
- const res = await axios.get(base + '/api/tracks', axios_config);
- expect(res.status).toEqual(200);
- expect(res.headers['content-type']).toContain("application/json")
- });
- it('correct distance input should return 200', async () => {
- const res = await axios.post(base + '/api/tracks', {
- "name": "string",
- "distance": 400
- }, axios_config);
- expect(res.status).toEqual(200);
- expect(res.headers['content-type']).toContain("application/json")
- });
-});
-// ---------------
-describe('POST /api/tracks', () => {
- it('illegal distance input should return 400', async () => {
- const res = await axios.post(base + '/api/tracks', {
- "name": "string",
- "distance": -1
- }, axios_config);
- expect(res.status).toEqual(400);
- expect(res.headers['content-type']).toContain("application/json")
- });
- it('correct distance input should return 200', async () => {
- const res = await axios.post(base + '/api/tracks', {
- "name": "string",
- "distance": 400
- }, axios_config);
- expect(res.status).toEqual(200);
- expect(res.headers['content-type']).toContain("application/json")
- });
-});
-// ---------------
-describe('adding + getting tracks', () => {
- it('correct distance input should return 200', async () => {
- const res = await axios.post(base + '/api/tracks', {
- "name": "string",
- "distance": 1000
- }, axios_config);
- expect(res.status).toEqual(200);
- expect(res.headers['content-type']).toContain("application/json")
- });
- it('check if track was added', async () => {
- const res = await axios.get(base + '/api/tracks', axios_config);
- expect(res.status).toEqual(200);
- expect(res.headers['content-type']).toContain("application/json")
- let added_track = res.data[res.data.length - 1]
- delete added_track.id
- expect(added_track).toEqual({
- "name": "string",
- "distance": 1000
- })
- });
-});
-// ---------------
-describe('adding + getting + updating', () => {
- let added_track;
- it('correct distance input should return 200', async () => {
- const res = await axios.post(base + '/api/tracks', {
- "name": "string",
- "distance": 1500
- }, axios_config);
- expect(res.status).toEqual(200);
- expect(res.headers['content-type']).toContain("application/json");
- added_track = res.data;
- });
- it('get should return 200', async () => {
- const res1 = await axios.get(base + '/api/tracks/' + added_track.id, axios_config);
- expect(res1.status).toEqual(200);
- expect(res1.headers['content-type']).toContain("application/json")
- const compareTrack = res1.data;
- expect(compareTrack).toEqual(added_track)
- })
- it('get should return 200', async () => {
- const res2 = await axios.put(base + '/api/tracks/' + added_track.id, {
- "id": added_track.id,
- "name": "apitrack",
- "distance": 5100
- }, axios_config);
- expect(res2.status).toEqual(200);
- expect(res2.headers['content-type']).toContain("application/json")
- })
- it('get should return 200', async () => {
- const res3 = await axios.get(base + '/api/tracks/' + added_track.id, axios_config);
- expect(res3.status).toEqual(200);
- expect(res3.headers['content-type']).toContain("application/json")
- let added_track2 = res3.data;
- delete added_track2.id
- expect(added_track2).toEqual({
- "name": "apitrack",
- "distance": 5100
- })
- });
-});
diff --git a/src/tests/tracks/track_add.spec.ts b/src/tests/tracks/track_add.spec.ts
new file mode 100644
index 0000000..7f39f8d
--- /dev/null
+++ b/src/tests/tracks/track_add.spec.ts
@@ -0,0 +1,90 @@
+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/tracks illegally', () => {
+ it('no distance input should return 400', async () => {
+ const res = await axios.post(base + '/api/tracks', {
+ "name": "string",
+ }, axios_config);
+ expect(res.status).toEqual(400);
+ expect(res.headers['content-type']).toContain("application/json")
+ });
+ it('illegal distance input should return 400', async () => {
+ const res = await axios.post(base + '/api/tracks', {
+ "name": "string",
+ "distance": -1
+ }, axios_config);
+ expect(res.status).toEqual(400);
+ expect(res.headers['content-type']).toContain("application/json")
+ });
+ it('negative minimum lap time input should return 406', async () => {
+ const res = await axios.post(base + '/api/tracks', {
+ "name": "string",
+ "distance": 200,
+ "minimumLapTime": -1
+ }, axios_config);
+ expect(res.status).toEqual(406);
+ expect(res.headers['content-type']).toContain("application/json")
+ });
+});
+// ---------------
+describe('POST /api/tracks successfully', () => {
+ it('creating a track with the minimum amount of parameters should return 200', async () => {
+ const res = await axios.post(base + '/api/tracks', {
+ "name": "testtrack",
+ "distance": 200,
+ }, axios_config);
+ expect(res.status).toEqual(200);
+ expect(res.headers['content-type']).toContain("application/json");
+ delete res.data.id;
+ expect(res.data).toEqual({
+ "name": "testtrack",
+ "distance": 200,
+ "minimumLapTime": null
+ })
+ });
+ it('creating a track with all parameters (optional set to null) should return 200', async () => {
+ const res = await axios.post(base + '/api/tracks', {
+ "name": "testtrack",
+ "distance": 200,
+ "minimumLapTime": null
+ }, axios_config);
+ expect(res.status).toEqual(200);
+ expect(res.headers['content-type']).toContain("application/json");
+ delete res.data.id;
+ expect(res.data).toEqual({
+ "name": "testtrack",
+ "distance": 200,
+ "minimumLapTime": null
+ })
+ });
+ it('creating a track with all parameters should return 200', async () => {
+ const res = await axios.post(base + '/api/tracks', {
+ "name": "testtrack",
+ "distance": 200,
+ "minimumLapTime": 123
+ }, axios_config);
+ expect(res.status).toEqual(200);
+ expect(res.headers['content-type']).toContain("application/json");
+ delete res.data.id;
+ expect(res.data).toEqual({
+ "name": "testtrack",
+ "distance": 200,
+ "minimumLapTime": 123
+ })
+ });
+});
diff --git a/src/tests/tracks/track_delete.spec.ts b/src/tests/tracks/track_delete.spec.ts
new file mode 100644
index 0000000..cb7ae89
--- /dev/null
+++ b/src/tests/tracks/track_delete.spec.ts
@@ -0,0 +1,53 @@
+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('DETELE track', () => {
+ let added_track
+ it('creating a track with the minimum amount of parameters should return 200', async () => {
+ const res = await axios.post(base + '/api/tracks', {
+ "name": "testtrack",
+ "distance": 200,
+ }, axios_config);
+ expect(res.status).toEqual(200);
+ expect(res.headers['content-type']).toContain("application/json");
+ added_track = res.data
+ });
+ it('delete track', async () => {
+ const res2 = await axios.delete(base + '/api/tracks/' + added_track.id, axios_config);
+ expect(res2.status).toEqual(200);
+ expect(res2.headers['content-type']).toContain("application/json")
+ let deleted_track = res2.data
+ delete deleted_track.id;
+ expect(res2.data).toEqual({
+ "name": "testtrack",
+ "distance": 200,
+ "minimumLapTime": null
+ });
+ });
+ it('check if track really was deleted', async () => {
+ const res3 = await axios.get(base + '/api/tracks/' + added_track.id, axios_config);
+ expect(res3.status).toEqual(404);
+ expect(res3.headers['content-type']).toContain("application/json")
+ });
+});
+// ---------------
+describe('DELETE track (non-existant)', () => {
+ it('delete', async () => {
+ const res2 = await axios.delete(base + '/api/tracks/0', axios_config);
+ expect(res2.status).toEqual(204);
+ });
+});
diff --git a/src/tests/tracks/track_update.spec.ts b/src/tests/tracks/track_update.spec.ts
new file mode 100644
index 0000000..ab2b707
--- /dev/null
+++ b/src/tests/tracks/track_update.spec.ts
@@ -0,0 +1,98 @@
+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_track;
+ it('correct distance input should return 200', async () => {
+ const res = await axios.post(base + '/api/tracks', {
+ "name": "apitrack",
+ "distance": 1500
+ }, axios_config);
+ expect(res.status).toEqual(200);
+ expect(res.headers['content-type']).toContain("application/json");
+ added_track = res.data;
+ });
+ it('update with negative distance should return 400', async () => {
+ const res2 = await axios.put(base + '/api/tracks/' + added_track.id, {
+ "id": added_track.id,
+ "name": "apitrack",
+ "distance": -1
+ }, axios_config);
+ expect(res2.status).toEqual(400);
+ expect(res2.headers['content-type']).toContain("application/json")
+ });
+ it('update with negative laptime should return 406', async () => {
+ const res2 = await axios.put(base + '/api/tracks/' + added_track.id, {
+ "id": added_track.id,
+ "name": "apitrack",
+ "distance": 2,
+ "minimumLapTime": -1
+ }, axios_config);
+ expect(res2.status).toEqual(406);
+ expect(res2.headers['content-type']).toContain("application/json")
+ });
+});
+// ---------------
+describe('adding + updating successfilly', () => {
+ let added_track;
+ it('correct distance input should return 200', async () => {
+ const res = await axios.post(base + '/api/tracks', {
+ "name": "apitrack2",
+ "distance": 1500
+ }, axios_config);
+ expect(res.status).toEqual(200);
+ expect(res.headers['content-type']).toContain("application/json");
+ added_track = res.data;
+ });
+ it('valid name change should return 200', async () => {
+ const res2 = await axios.put(base + '/api/tracks/' + added_track.id, {
+ "id": added_track.id,
+ "name": "apitrackk",
+ "distance": 1500
+ }, axios_config);
+ expect(res2.status).toEqual(200);
+ expect(res2.headers['content-type']).toContain("application/json")
+ });
+ it('valid distance change should return 200', async () => {
+ const res2 = await axios.put(base + '/api/tracks/' + added_track.id, {
+ "id": added_track.id,
+ "name": "apitrack2",
+ "distance": 5100
+ }, axios_config);
+ expect(res2.status).toEqual(200);
+ expect(res2.headers['content-type']).toContain("application/json")
+ });
+ it('valid laptime change (to a set number) should return 200', async () => {
+ const res2 = await axios.put(base + '/api/tracks/' + added_track.id, {
+ "id": added_track.id,
+ "name": "apitrack2",
+ "distance": 5100,
+ "minimumLapTime": 3
+ }, axios_config);
+ expect(res2.status).toEqual(200);
+ expect(res2.headers['content-type']).toContain("application/json")
+ });
+ it('valid laptime change (to a set null) should return 200', async () => {
+ const res2 = await axios.put(base + '/api/tracks/' + added_track.id, {
+ "id": added_track.id,
+ "name": "apitrack2",
+ "distance": 5100,
+ "minimumLapTime": null
+ }, axios_config);
+ expect(res2.status).toEqual(200);
+ expect(res2.headers['content-type']).toContain("application/json")
+ });
+});
diff --git a/src/tests/tracks/tracks_get.spec.ts b/src/tests/tracks/tracks_get.spec.ts
new file mode 100644
index 0000000..1057034
--- /dev/null
+++ b/src/tests/tracks/tracks_get.spec.ts
@@ -0,0 +1,49 @@
+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/tracks sucessfully', () => {
+ it('basic get should return 200', async () => {
+ const res = await axios.get(base + '/api/tracks', axios_config);
+ expect(res.status).toEqual(200);
+ expect(res.headers['content-type']).toContain("application/json")
+ });
+});
+// ---------------
+describe('GET /api/tracks illegally', () => {
+ it('get for non-existant track should return 404', async () => {
+ const res = await axios.get(base + '/api/tracks/-1', axios_config);
+ expect(res.status).toEqual(404);
+ expect(res.headers['content-type']).toContain("application/json")
+ });
+});
+// ---------------
+describe('adding + getting tracks', () => {
+ let added_track;
+ it('correct distance input should return 200', async () => {
+ const res = await axios.post(base + '/api/tracks', {
+ "name": "string",
+ "distance": 1000
+ }, axios_config);
+ added_track = res.data;
+ expect(res.status).toEqual(200);
+ expect(res.headers['content-type']).toContain("application/json")
+ });
+ it('check if track was added (no parameter validation)', async () => {
+ const res = await axios.get(base + '/api/tracks/' + added_track.id, axios_config);
+ expect(res.status).toEqual(200);
+ expect(res.headers['content-type']).toContain("application/json");
+ });
+});
\ No newline at end of file