Compare commits

...

75 Commits

Author SHA1 Message Date
Nicolai Ort bc426831db Merge pull request 'Alpha Release 0.0.7' (#73) from dev into main
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details
Reviewed-on: #73
Reviewed-by: Philipp Dormann <philipp@philippdormann.de>
2021-01-03 17:22:25 +00:00
Nicolai Ort 276e553e13 Version bump
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
ref #73
2021-01-03 18:18:51 +01:00
Nicolai Ort e7ab302c61 Merge pull request 'Minimum lap times for tracks feature/71-track_times' (#72) from feature/71-track_times into dev
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is failing Details
Reviewed-on: #72
closes #71
2021-01-03 17:17:59 +00:00
Nicolai Ort a5d70ce4b5 Removed useless console.log
continuous-integration/drone/pr Build is passing Details
2021-01-03 18:13:53 +01:00
Nicolai Ort d67be313e6 Added track update tests
continuous-integration/drone/pr Build is passing Details
ref #71
2021-01-03 18:08:04 +01:00
Nicolai Ort 15d2d029dc Added track delete tests
ref #71
2021-01-03 18:07:33 +01:00
Nicolai Ort b6ea5e6549 Fixed copy-paste mistake
ref #71
2021-01-03 18:01:21 +01:00
Nicolai Ort f378b0651a Added helpful comment about the tracktime's unit
ref #71
2021-01-03 17:52:36 +01:00
Nicolai Ort 1a0573e0d0 Added track add tests
ref #71
2021-01-03 17:52:16 +01:00
Nicolai Ort 9f103d8df1 Added track get tests
ref #71
2021-01-03 17:49:55 +01:00
Nicolai Ort daa899a1ef Removed the old basic test class
ref #71
2021-01-03 17:49:44 +01:00
Nicolai Ort 59cb72a11d Implemented track upodates using the "new" method
ref #71
2021-01-03 17:30:17 +01:00
Nicolai Ort 28c1b6d31d Improved error handling for negative lap times
ref #71
2021-01-03 17:21:53 +01:00
Nicolai Ort dcb791c9a2 Added the laptime to the track response
ref #71
2021-01-03 17:06:57 +01:00
Nicolai Ort 907259bf73 Added the laptime to createtrack
ref #71
2021-01-03 17:05:43 +01:00
Nicolai Ort 02f7ddbb37 Marked property as optional
ref #71
2021-01-03 17:04:09 +01:00
Nicolai Ort 63b1ca9b56 Added the minimum lap time to the track entity
ref #71
2021-01-03 16:51:36 +01:00
Nicolai Ort 39857cf6e6 Merge pull request 'New Feature: Donor endpoints feature/65-donor_controllers' (#69) from feature/65-donor_controllers into dev
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/pr Build is passing Details
Reviewed-on: #69
closes #65

Donors go 💲💲💲
2021-01-02 21:06:08 +00:00
Nicolai Ort 3090ae69f3 Merge branch 'dev' into feature/65-donor_controllers
continuous-integration/drone/pr Build is passing Details
2021-01-02 20:55:31 +00:00
Nicolai Ort 92186a86cc Merge pull request 'bugfix/68-address_circular_dependencies' (#70) from bugfix/68-address_circular_dependencies into feature/65-donor_controllers
continuous-integration/drone/pr Build is passing Details
Reviewed-on: #70
closes #68
2021-01-02 20:55:02 +00:00
Nicolai Ort 97e8470b0d Change requested by @philipp
continuous-integration/drone/pr Build is passing Details
ref #70
2021-01-02 21:53:21 +01:00
Nicolai Ort 6b0e3503a7 Dependency: Bumped license-exporter version
continuous-integration/drone/push Build is passing Details
ref odit/license-exporter#1 odit/license-exporter#3
2021-01-02 20:49:20 +01:00
Nicolai Ort 1e2de7656e Reenabled addresses in org responses
continuous-integration/drone/pr Build is passing Details
ref #68
2021-01-02 20:03:02 +01:00
Nicolai Ort 56c6a7efb0 Revert "Removed addresses from tests until the circular dependencies are solved"
This reverts commit 599296c4e3.
2021-01-02 19:57:55 +01:00
Nicolai Ort 9c4e54fc6e Added comments to the bugfix 2021-01-02 19:57:33 +01:00
Nicolai Ort 2c47436259 Implemented a possible bugfix
ref #68
2021-01-02 19:56:04 +01:00
Nicolai Ort 9b5d16ae92 Added todo relateing to the bugfix issue
continuous-integration/drone/pr Build is passing Details
ref #65 #68
2021-01-02 19:39:02 +01:00
Nicolai Ort deb13674b2 Added donor put (update) tests
ref #65
2021-01-02 19:25:58 +01:00
Nicolai Ort 17c82ff409 Added donor delete tests
ref #65
2021-01-02 19:13:59 +01:00
Nicolai Ort f9e314bf9f Added donor add test for address needed error
ref #65
2021-01-02 19:12:02 +01:00
Nicolai Ort e4c1930dd1 Added donor post (add) tests
ref #65
2021-01-02 19:10:23 +01:00
Nicolai Ort b337ab424d Added donor get tests
ref #65
2021-01-02 19:02:31 +01:00
Nicolai Ort 82a0e194cb Updated track tests for paralellism
ref #65
2021-01-02 19:02:16 +01:00
Nicolai Ort 599296c4e3 Removed addresses from tests until the circular dependencies are solved
ref #65
2021-01-02 19:01:55 +01:00
Nicolai Ort 2594a607dc Added address check for donors that want a receipt on update
ref #65
2021-01-02 18:30:03 +01:00
Nicolai Ort 335d4e24da Added address check for donors that want a receipt
ref #65
2021-01-02 18:28:22 +01:00
Nicolai Ort becc277123 Merge branch 'feature/65-donor_controllers' of git.odit.services:lfk/backend into feature/65-donor_controllers 2021-01-02 18:19:51 +01:00
Nicolai Ort 52cdd41ec8 Fixed not null constraint
ref #65
2021-01-02 18:19:45 +01:00
Nicolai Ort 53548ba7a6 Fixed not null constraint
ref #56
2021-01-02 18:19:40 +01:00
Nicolai Ort 1dc438beb2 Mitigated circular dependency (to be fixed)
ref #65
2021-01-02 18:12:18 +01:00
Nicolai Ort c9ba69792f Extended todo w/ issue link
ref #65
2021-01-02 17:07:17 +01:00
Nicolai Ort ab67e5f4aa Added basic runner updateing
ref #65
2021-01-02 16:55:27 +01:00
Nicolai Ort 557608e318 Added everything for basic donor creation
ref #65
2021-01-02 16:51:33 +01:00
Nicolai Ort a83fedc9b8 Added first donor-specific errors
ref #65
2021-01-02 16:47:06 +01:00
Nicolai Ort 61a17b198f Implemented basic donor deletion
ref #65
2021-01-02 16:45:01 +01:00
Nicolai Ort 3df1db4ad8 Added the base logic for donor getters
ref #65
2021-01-02 16:42:55 +01:00
Nicolai Ort e46cfa0d77 Added donor response class
ref #65
2021-01-02 16:40:38 +01:00
Nicolai Ort 4126d31a5e Added copy of runnerController with some stuff reanames for donors
ref #65
2021-01-02 16:38:07 +01:00
Nicolai Ort 9d9549cdd4 Added new donor permission target
ref #65
2021-01-02 16:37:17 +01:00
Nicolai Ort eb40de6eb4 Removed legacy license txt file
continuous-integration/drone/push Build is passing Details
2020-12-31 18:05:21 +01:00
Nicolai Ort 6efd09db73 new license file version [CI SKIP] 2020-12-31 17:02:56 +00:00
Nicolai Ort 3f09e3d387 Merge pull request 'Automatic and manual license collection 📖' (#62) from feature/59-license_collection into dev
continuous-integration/drone/push Build is passing Details
Reviewed-on: #62
closes #59
2020-12-31 17:02:08 +00:00
Nicolai Ort 05868e0e00 Bumped license lib version
continuous-integration/drone/pr Build is passing Details
ref #59
2020-12-31 18:00:46 +01:00
Nicolai Ort 580a73f9a5 Switched to automatic license attribution generation via oss-attribution-generator
continuous-integration/drone/pr Build is passing Details
#59
2020-12-31 15:14:51 +01:00
Nicolai Ort ab7110d49f Merge branch 'dev' into feature/59-license_collection
continuous-integration/drone/pr Build is passing Details
# Conflicts:
#	.drone.yml
2020-12-30 21:26:07 +01:00
Nicolai Ort 875781335c Removed the testing pipeline and updated the dev license pipeline
ref #59
2020-12-30 21:24:51 +01:00
Nicolai Ort 625340cf8a Merge branch 'feature/59-license_collection' of git.odit.services:lfk/backend into feature/59-license_collection
continuous-integration/drone/push Build is passing Details
2020-12-30 21:23:37 +01:00
Nicolai Ort 8d9dbc3957 Merge branch 'feature/59-license_collection' of git.odit.services:lfk/backend into feature/59-license_collection 2020-12-30 21:23:33 +01:00
Nicolai Ort 07d813082b Merge branch 'feature/59-license_collection' of git.odit.services:lfk/backend into feature/59-license_collection 2020-12-30 21:23:04 +01:00
Nicolai Ort a684f60252 Added secondary dependency for piupeline
ref #59
2020-12-30 21:22:59 +01:00
Nicolai Ort 931cae3c98 new license file version [CI SKIP] 2020-12-30 20:22:35 +00:00
Nicolai Ort dfd82a6293 Merge branch 'feature/59-license_collection' of git.odit.services:lfk/backend into feature/59-license_collection
continuous-integration/drone/push Build is passing Details
# Conflicts:
#	.drone.yml
2020-12-30 21:21:50 +01:00
Nicolai Ort 82d4b11de3 Adjusted ci dependencies
ref #59
2020-12-30 21:21:40 +01:00
Nicolai Ort 75473937cf Adjusted ci dependencies
ref #59
2020-12-30 21:21:08 +01:00
Nicolai Ort a68bbab8ab Canged drone branch
continuous-integration/drone/push Build is passing Details
ref #59
2020-12-30 21:18:40 +01:00
Nicolai Ort 5cfd2c9a52 Revert "Added license exporter (to json)"
continuous-integration/drone/push Build is passing Details
This reverts commit 84a0bd2cd9.
2020-12-30 21:17:27 +01:00
Nicolai Ort 6c7b31d76c Revert "Moved package script related files to their own folder"
This reverts commit 395b0101a8.
2020-12-30 21:17:23 +01:00
Nicolai Ort 2924ac2900 Revert "Added automatic license export on dev push/merge"
This reverts commit 18e3ef9a79.
2020-12-30 21:17:18 +01:00
Nicolai Ort a501625dd6 Revert "Added --full option for the license exporter to export the license path and text as well"
This reverts commit 62c7f26540.
2020-12-30 21:17:13 +01:00
Nicolai Ort cc64ce4498 Revert "Added test pipeline for automatic license export"
This reverts commit c9378e6cae.
2020-12-30 21:17:09 +01:00
Nicolai Ort c9378e6cae Added test pipeline for automatic license export
continuous-integration/drone/push Build is passing Details
ref #59
2020-12-30 21:13:32 +01:00
Nicolai Ort 62c7f26540 Added --full option for the license exporter to export the license path and text as well
ref #59
2020-12-30 21:05:16 +01:00
Nicolai Ort 18e3ef9a79 Added automatic license export on dev push/merge
ref #59
2020-12-30 20:40:28 +01:00
Nicolai Ort 395b0101a8 Moved package script related files to their own folder
ref #59
2020-12-30 20:22:18 +01:00
Nicolai Ort 84a0bd2cd9 Added license exporter (to json)
ref #59
2020-12-30 20:18:28 +01:00
39 changed files with 7674 additions and 174 deletions

View File

@ -37,11 +37,23 @@ steps:
tags:
- dev
registry: registry.odit.services
when:
branch:
- dev
event:
- push
- name: run full license export
depends_on: ["clone"]
image: node:alpine
commands:
- yarn
- yarn licenses:export
- name: push new licenses file to repo
depends_on: ["run full license export"]
image: appleboy/drone-git-push
settings:
branch: dev
commit: true
commit_message: new license file version [CI SKIP]
author_email: bot@odit.services
remote: git@git.odit.services:lfk/backend.git
ssh_key:
from_secret: GITLAB_SSHKEY
trigger:
branch:

3
.gitignore vendored
View File

@ -133,4 +133,5 @@ build
*.sqlite
*.sqlite-jurnal
/docs
lib
lib
/oss-attribution

1296
licenses.md Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "@odit/lfk-backend",
"version": "0.0.6",
"version": "0.0.7",
"main": "src/app.ts",
"repository": "https://git.odit.services/lfk/backend",
"author": {
@ -48,6 +48,7 @@
"validator": "^13.5.2"
},
"devDependencies": {
"@odit/license-exporter": "^0.0.8",
"@types/cors": "^2.8.8",
"@types/csvtojson": "^1.1.5",
"@types/express": "^4.17.9",
@ -74,7 +75,8 @@
"test:watch": "jest --watchAll",
"test:ci": "start-server-and-test dev http://localhost:4010/api/docs/openapi.json test",
"seed": "ts-node ./node_modules/typeorm/cli.js schema:sync && ts-node ./node_modules/typeorm-seeding/dist/cli.js seed",
"openapi:export": "ts-node src/openapi_export.ts"
"openapi:export": "node scripts/openapi_export.js",
"licenses:export": "license-exporter --md"
},
"nodemonConfig": {
"ignore": [

View File

@ -4,9 +4,9 @@ import fs from "fs";
import "reflect-metadata";
import { createExpressServer, getMetadataArgsStorage } from "routing-controllers";
import { routingControllersToSpec } from 'routing-controllers-openapi';
import { config } from './config';
import authchecker from "./middlewares/authchecker";
import { ErrorHandler } from './middlewares/ErrorHandler';
import { config } from '../src/config';
import authchecker from "../src/middlewares/authchecker";
import { ErrorHandler } from '../src/middlewares/ErrorHandler';
const CONTROLLERS_FILE_EXTENSION = process.env.NODE_ENV === 'production' ? 'js' : 'ts';
createExpressServer({

View File

@ -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();

View File

@ -0,0 +1,105 @@
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnectionManager, Repository } from 'typeorm';
import { DonorIdsNotMatchingError, DonorNotFoundError } from '../errors/DonorErrors';
import { CreateDonor } from '../models/actions/CreateDonor';
import { UpdateDonor } from '../models/actions/UpdateDonor';
import { Donor } from '../models/entities/Donor';
import { ResponseDonor } from '../models/responses/ResponseDonor';
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
@JsonController('/donors')
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
export class DonorController {
private donorRepository: Repository<Donor>;
/**
* Gets the repository of this controller's model/entity.
*/
constructor() {
this.donorRepository = getConnectionManager().get().getRepository(Donor);
}
@Get()
@Authorized("DONOR:GET")
@ResponseSchema(ResponseDonor, { isArray: true })
@OpenAPI({ description: 'Lists all runners from all teams/orgs. <br> This includes the runner\'s group and distance ran.' })
async getAll() {
let responseDonors: ResponseDonor[] = new Array<ResponseDonor>();
const donors = await this.donorRepository.find();
donors.forEach(donor => {
responseDonors.push(new ResponseDonor(donor));
});
return responseDonors;
}
@Get('/:id')
@Authorized("DONOR:GET")
@ResponseSchema(ResponseDonor)
@ResponseSchema(DonorNotFoundError, { statusCode: 404 })
@OnUndefined(DonorNotFoundError)
@OpenAPI({ description: 'Lists all information about the runner whose id got provided.' })
async getOne(@Param('id') id: number) {
let donor = await this.donorRepository.findOne({ id: id })
if (!donor) { throw new DonorNotFoundError(); }
return new ResponseDonor(donor);
}
@Post()
@Authorized("DONOR:CREATE")
@ResponseSchema(ResponseDonor)
@OpenAPI({ description: 'Create a new runner. <br> Please remeber to provide the runner\'s group\'s id.' })
async post(@Body({ validate: true }) createRunner: CreateDonor) {
let donor;
try {
donor = await createRunner.toDonor();
} catch (error) {
throw error;
}
donor = await this.donorRepository.save(donor)
return new ResponseDonor(await this.donorRepository.findOne(donor));
}
@Put('/:id')
@Authorized("DONOR:UPDATE")
@ResponseSchema(ResponseDonor)
@ResponseSchema(DonorNotFoundError, { statusCode: 404 })
@ResponseSchema(DonorIdsNotMatchingError, { statusCode: 406 })
@OpenAPI({ description: "Update the runner whose id you provided. <br> Please remember that ids can't be changed." })
async put(@Param('id') id: number, @Body({ validate: true }) donor: UpdateDonor) {
let oldDonor = await this.donorRepository.findOne({ id: id });
if (!oldDonor) {
throw new DonorNotFoundError();
}
if (oldDonor.id != donor.id) {
throw new DonorIdsNotMatchingError();
}
await this.donorRepository.save(await donor.updateDonor(oldDonor));
return new ResponseDonor(await this.donorRepository.findOne({ id: id }));
}
@Delete('/:id')
@Authorized("DONOR:DELETE")
@ResponseSchema(ResponseDonor)
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
@OnUndefined(204)
@OpenAPI({ description: 'Delete the runner whose id you provided. <br> 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 donor = await this.donorRepository.findOne({ id: id });
if (!donor) { return null; }
const responseDonor = await this.donorRepository.findOne(donor);
if (!donor) {
throw new DonorNotFoundError();
}
//TODO: DELETE DONATIONS AND WARN FOR FORCE (https://git.odit.services/lfk/backend/issues/66)
await this.donorRepository.delete(donor);
return new ResponseDonor(responseDonor);
}
}

View File

@ -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. <br> 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. <br> 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')

36
src/errors/DonorErrors.ts Normal file
View File

@ -0,0 +1,36 @@
import { IsString } from 'class-validator';
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
/**
* Error to throw when a donor couldn't be found.
*/
export class DonorNotFoundError extends NotFoundError {
@IsString()
name = "DonorNotFoundError"
@IsString()
message = "Donor not found!"
}
/**
* Error to throw when two donors' ids don't match.
* Usually occurs when a user tries to change a donor's id.
*/
export class DonorIdsNotMatchingError extends NotAcceptableError {
@IsString()
name = "DonorIdsNotMatchingError"
@IsString()
message = "The ids don't match! \n And if you wanted to change a donor's id: This isn't allowed!"
}
/**
* Error to throw when a donor needs a receipt, but no address is associated with them.
*/
export class DonorReceiptAddressNeededError extends NotAcceptableError {
@IsString()
name = "DonorReceiptAddressNeededError"
@IsString()
message = "An address is needed to create a receipt for a donor. \n You didn't provide one."
}

View File

@ -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."
}

View File

@ -0,0 +1,38 @@
import { IsBoolean, IsOptional } from 'class-validator';
import { DonorReceiptAddressNeededError } from '../../errors/DonorErrors';
import { Donor } from '../entities/Donor';
import { CreateParticipant } from './CreateParticipant';
/**
* This classed is used to create a new Donor entity from a json body (post request).
*/
export class CreateDonor extends CreateParticipant {
/**
* Does this donor need a receipt?
*/
@IsBoolean()
@IsOptional()
receiptNeeded?: boolean = false;
/**
* Creates a new Donor entity from this.
*/
public async toDonor(): Promise<Donor> {
let newDonor: Donor = new Donor();
newDonor.firstname = this.firstname;
newDonor.middlename = this.middlename;
newDonor.lastname = this.lastname;
newDonor.phone = this.phone;
newDonor.email = this.email;
newDonor.address = await this.getAddress();
newDonor.receiptNeeded = this.receiptNeeded;
if (this.receiptNeeded == true && this.address == null) {
throw new DonorReceiptAddressNeededError()
}
return newDonor;
}
}

View File

@ -41,7 +41,7 @@ export class CreateRunnerOrganisation extends CreateRunnerGroup {
newRunnerOrganisation.name = this.name;
newRunnerOrganisation.contact = await this.getContact();
newRunnerOrganisation.address = await this.getAddress();
// newRunnerOrganisation.address = await this.getAddress();
return newRunnerOrganisation;
}

View File

@ -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;
}

View File

@ -0,0 +1,44 @@
import { IsBoolean, IsInt, IsOptional } from 'class-validator';
import { DonorReceiptAddressNeededError } from '../../errors/DonorErrors';
import { Donor } from '../entities/Donor';
import { CreateParticipant } from './CreateParticipant';
/**
* This class is used to update a Donor entity (via put request).
*/
export class UpdateDonor extends CreateParticipant {
/**
* The updated donor'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;
/**
* Does the updated donor need a receipt?
*/
@IsBoolean()
@IsOptional()
receiptNeeded?: boolean;
/**
* Updates a provided Donor entity based on this.
*/
public async updateDonor(donor: Donor): Promise<Donor> {
donor.firstname = this.firstname;
donor.middlename = this.middlename;
donor.lastname = this.lastname;
donor.phone = this.phone;
donor.email = this.email;
donor.receiptNeeded = this.receiptNeeded;
donor.address = await this.getAddress();
if (this.receiptNeeded == true && this.address == null) {
throw new DonorReceiptAddressNeededError()
}
return donor;
}
}

View File

@ -45,7 +45,7 @@ export class UpdateRunnerOrganisation extends CreateRunnerGroup {
organisation.name = this.name;
organisation.contact = await this.getContact();
organisation.address = await this.getAddress();
// organisation.address = await this.getAddress();
return organisation;
}

View File

@ -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;
}
}

View File

@ -7,8 +7,7 @@ import {
} from "class-validator";
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm";
import { config } from '../../config';
import { Participant } from "./Participant";
import { RunnerOrganisation } from "./RunnerOrganisation";
import { IAddressUser } from './IAddressUser';
/**
* Defines the Address entity.
@ -79,12 +78,6 @@ export class Address {
/**
* Used to link the address to participants.
*/
@OneToMany(() => Participant, participant => participant.address, { nullable: true })
participants: Participant[];
/**
* Used to link the address to runner groups.
*/
@OneToMany(() => RunnerOrganisation, group => group.address, { nullable: true })
groups: RunnerOrganisation[];
@OneToMany(() => IAddressUser, addressUser => addressUser.address, { nullable: true })
addressUsers: IAddressUser[];
}

View File

@ -3,7 +3,7 @@ import {
IsNotEmpty
} from "class-validator";
import { Entity, ManyToOne, PrimaryGeneratedColumn, TableInheritance } from "typeorm";
import { Participant } from "./Participant";
import { Donor } from './Donor';
/**
* Defines the Donation entity.
@ -24,8 +24,8 @@ export abstract class Donation {
* The donations's donor.
*/
@IsNotEmpty()
@ManyToOne(() => Participant, donor => donor.donations)
donor: Participant;
@ManyToOne(() => Donor, donor => donor.donations)
donor: Donor;
/**
* The donation's amount in cents (or whatever your currency's smallest unit is.).

View File

@ -1,5 +1,6 @@
import { IsBoolean } from "class-validator";
import { ChildEntity, Column } from "typeorm";
import { ChildEntity, Column, OneToMany } from "typeorm";
import { Donation } from './Donation';
import { Participant } from "./Participant";
/**
@ -14,4 +15,11 @@ export class Donor extends Participant {
@Column()
@IsBoolean()
receiptNeeded: boolean = false;
/**
* Used to link the participant as the donor of a donation.
* Attention: Only runner's can be associated as a distanceDonations distance source.
*/
@OneToMany(() => Donation, donation => donation.donor, { nullable: true })
donations: Donation[];
}

View File

@ -10,6 +10,7 @@ import {
import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm";
import { config } from '../../config';
import { Address } from "./Address";
import { IAddressUser } from './IAddressUser';
import { RunnerGroup } from "./RunnerGroup";
/**
@ -17,7 +18,7 @@ import { RunnerGroup } from "./RunnerGroup";
* Mainly it's own class to reduce duplicate code and enable contact's to be associated with multiple groups.
*/
@Entity()
export class GroupContact {
export class GroupContact implements IAddressUser {
/**
* Autogenerated unique id (primary key).
*/
@ -54,7 +55,7 @@ export class GroupContact {
* This is a address object to prevent any formatting differences.
*/
@IsOptional()
@ManyToOne(() => Address, address => address.participants, { nullable: true })
@ManyToOne(() => Address, address => address.addressUsers, { nullable: true })
address?: Address;
/**

View File

@ -0,0 +1,15 @@
import { Entity, ManyToOne, PrimaryColumn } from 'typeorm';
import { Address } from './Address';
/**
* The interface(tm) all entities using addresses have to implement.
* This is a abstract class, because apparently typeorm can't really work with interfaces :/
*/
@Entity()
export abstract class IAddressUser {
@PrimaryColumn()
id: number;
@ManyToOne(() => Address, address => address.addressUsers, { nullable: true })
address?: Address
}

View File

@ -7,10 +7,10 @@ import {
IsString
} from "class-validator";
import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn, TableInheritance } from "typeorm";
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn, TableInheritance } from "typeorm";
import { config } from '../../config';
import { Address } from "./Address";
import { Donation } from "./Donation";
import { IAddressUser } from './IAddressUser';
/**
* Defines the Participant entity.
@ -18,7 +18,7 @@ import { Donation } from "./Donation";
*/
@Entity()
@TableInheritance({ column: { name: "type", type: "varchar" } })
export abstract class Participant {
export abstract class Participant implements IAddressUser {
/**
* Autogenerated unique id (primary key).
*/
@ -54,7 +54,7 @@ export abstract class Participant {
* The participant's address.
* This is a address object to prevent any formatting differences.
*/
@ManyToOne(() => Address, address => address.participants, { nullable: true })
@ManyToOne(() => Address, address => address.addressUsers, { nullable: true })
address?: Address;
/**
@ -74,11 +74,4 @@ export abstract class Participant {
@IsOptional()
@IsEmail()
email?: string;
/**
* Used to link the participant as the donor of a donation.
* Attention: Only runner's can be associated as a distanceDonations distance source.
*/
@OneToMany(() => Donation, donation => donation.donor, { nullable: true })
donations: Donation[];
}

View File

@ -18,7 +18,7 @@ export class Runner extends Participant {
* Can be a runner team or organisation.
*/
@IsNotEmpty()
@ManyToOne(() => RunnerGroup, group => group.runners, { nullable: false })
@ManyToOne(() => RunnerGroup, group => group.runners)
group: RunnerGroup;
/**

View File

@ -1,6 +1,7 @@
import { IsInt, IsOptional } from "class-validator";
import { ChildEntity, ManyToOne, OneToMany } from "typeorm";
import { Address } from "./Address";
import { Address } from './Address';
import { IAddressUser } from './IAddressUser';
import { Runner } from './Runner';
import { RunnerGroup } from "./RunnerGroup";
import { RunnerTeam } from "./RunnerTeam";
@ -10,13 +11,13 @@ import { RunnerTeam } from "./RunnerTeam";
* This usually is a school, club or company.
*/
@ChildEntity()
export class RunnerOrganisation extends RunnerGroup {
export class RunnerOrganisation extends RunnerGroup implements IAddressUser {
/**
* The organisations's address.
*/
@IsOptional()
@ManyToOne(() => Address, address => address.groups, { nullable: true })
@ManyToOne(() => Address, address => address.addressUsers, { nullable: true })
address?: Address;
/**

View File

@ -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.

View File

@ -9,5 +9,6 @@ export enum PermissionTarget {
USER = 'USER',
USERGROUP = 'USERGROUP',
PERMISSION = 'PERMISSION',
STATSCLIENT = 'STATSCLIENT'
STATSCLIENT = 'STATSCLIENT',
DONOR = 'DONOR'
}

View File

@ -0,0 +1,26 @@
import {
IsBoolean
} from "class-validator";
import { Donor } from '../entities/Donor';
import { ResponseParticipant } from './ResponseParticipant';
/**
* Defines the donor response.
*/
export class ResponseDonor extends ResponseParticipant {
/**
* Does this donor need a receipt?
*/
@IsBoolean()
receiptNeeded: boolean;
/**
* Creates a ResponseRunner object from a runner.
* @param runner The user the response shall be build for.
*/
public constructor(donor: Donor) {
super(donor);
this.receiptNeeded = donor.receiptNeeded;
}
}

View File

@ -1,7 +1,8 @@
import {
IsArray,
IsNotEmpty,
IsObject
IsObject,
IsOptional
} from "class-validator";
import { Address } from '../entities/Address';
import { RunnerOrganisation } from '../entities/RunnerOrganisation';
@ -17,7 +18,7 @@ export class ResponseRunnerOrganisation extends ResponseRunnerGroup {
* The runnerOrganisation's address.
*/
@IsObject()
@IsNotEmpty()
@IsOptional()
address?: Address;
/**

View File

@ -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();
}
}
}

View File

@ -0,0 +1,94 @@
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/donors with errors', () => {
it('creating a new donor without any parameters should return 400', async () => {
const res1 = await axios.post(base + '/api/donors', null, axios_config);
expect(res1.status).toEqual(400);
expect(res1.headers['content-type']).toContain("application/json")
});
it('creating a new donor without a last name should return 400', async () => {
const res2 = await axios.post(base + '/api/donors', {
"firstname": "first",
"middlename": "middle"
}, axios_config);
expect(res2.status).toEqual(400);
expect(res2.headers['content-type']).toContain("application/json")
});
it('creating a new donor with a invalid address should return 404', async () => {
const res2 = await axios.post(base + '/api/donors', {
"firstname": "first",
"middlename": "middle",
"lastname": "last",
"address": 0
}, axios_config);
expect(res2.status).toEqual(404);
expect(res2.headers['content-type']).toContain("application/json")
});
it('creating a new donor with a invalid phone number should return 400', async () => {
const res2 = await axios.post(base + '/api/donors', {
"firstname": "first",
"middlename": "middle",
"lastname": "last",
"phone": "123"
}, axios_config);
expect(res2.status).toEqual(400);
expect(res2.headers['content-type']).toContain("application/json")
});
it('creating a new donor with a invalid mail address should return 400', async () => {
const res2 = await axios.post(base + '/api/donors', {
"firstname": "string",
"middlename": "string",
"lastname": "string",
"phone": null,
"email": "123",
}, axios_config);
expect(res2.status).toEqual(400);
expect(res2.headers['content-type']).toContain("application/json")
});
it('creating a new donor without an address but with receiptNeeded=true 406', async () => {
const res2 = await axios.post(base + '/api/donors', {
"firstname": "string",
"middlename": "string",
"lastname": "string",
"receiptNeeded": true
}, axios_config);
expect(res2.status).toEqual(406);
expect(res2.headers['content-type']).toContain("application/json")
});
});
// ---------------
describe('POST /api/donors working', () => {
it('creating a new donor with only needed params should return 200', async () => {
const res2 = await axios.post(base + '/api/donors', {
"firstname": "first",
"lastname": "last"
}, axios_config);
expect(res2.status).toEqual(200);
expect(res2.headers['content-type']).toContain("application/json")
});
it('creating a new donor with all non-relationship optional params should return 200', async () => {
const res3 = await axios.post(base + '/api/donors', {
"firstname": "first",
"middlename": "middle",
"lastname": "last",
"receiptNeeded": false
}, axios_config);
expect(res3.status).toEqual(200);
expect(res3.headers['content-type']).toContain("application/json")
});
});

View File

@ -0,0 +1,47 @@
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 + deletion (non-existant)', () => {
it('delete', async () => {
const res2 = await axios.delete(base + '/api/donors/0', axios_config);
expect(res2.status).toEqual(204);
});
});
// ---------------
describe('add+delete', () => {
let added_donor;
it('creating a new donor with only needed params should return 200', async () => {
const res2 = await axios.post(base + '/api/donors', {
"firstname": "first",
"lastname": "last"
}, axios_config);
added_donor = res2.data;
expect(res2.status).toEqual(200);
expect(res2.headers['content-type']).toContain("application/json")
});
it('delete donor', async () => {
const res3 = await axios.delete(base + '/api/donors/' + added_donor.id, axios_config);
expect(res3.status).toEqual(200);
expect(res3.headers['content-type']).toContain("application/json")
let deleted_runner = res3.data
expect(deleted_runner).toEqual(added_donor);
});
it('check if donor really was deleted', async () => {
const res4 = await axios.get(base + '/api/donors/' + added_donor.id, axios_config);
expect(res4.status).toEqual(404);
expect(res4.headers['content-type']).toContain("application/json")
});
});

View File

@ -0,0 +1,57 @@
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/donors', () => {
it('basic get should return 200', async () => {
const res = await axios.get(base + '/api/donors', axios_config);
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json")
});
});
// ---------------
describe('GET /api/donors/0', () => {
it('basic get should return 404', async () => {
const res = await axios.get(base + '/api/donors/0', axios_config);
expect(res.status).toEqual(404);
expect(res.headers['content-type']).toContain("application/json")
});
});
// ---------------
describe('GET /api/donors after adding', () => {
let added_donor;
it('creating a new donor with only needed params should return 200', async () => {
const res2 = await axios.post(base + '/api/donors', {
"firstname": "first",
"lastname": "last"
}, axios_config);
added_donor = res2.data;
expect(res2.status).toEqual(200);
expect(res2.headers['content-type']).toContain("application/json")
});
it('explicit get should return 200', async () => {
const res3 = await axios.get(base + '/api/donors/' + added_donor.id, axios_config);
expect(res3.status).toEqual(200);
expect(res3.headers['content-type']).toContain("application/json")
let gotten_donor = res3.data
expect(gotten_donor).toEqual(added_donor);
});
it('get from all runners should return 200', async () => {
const res4 = await axios.get(base + '/api/donors/', axios_config);
expect(res4.status).toEqual(200);
expect(res4.headers['content-type']).toContain("application/json")
let gotten_donors = res4.data
expect(gotten_donors).toContainEqual(added_donor);
});
});

View File

@ -0,0 +1,75 @@
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('Update donor name after adding', () => {
let added_donor;
it('creating a new runner with only needed params should return 200', async () => {
const res2 = await axios.post(base + '/api/donors', {
"firstname": "first",
"lastname": "last"
}, axios_config);
added_donor = res2.data;
expect(res2.status).toEqual(200);
expect(res2.headers['content-type']).toContain("application/json")
});
it('valid update should return 200', async () => {
let donor_copy = added_donor
donor_copy.firstname = "second"
const res3 = await axios.put(base + '/api/donors/' + added_donor.id, donor_copy, axios_config);
expect(res3.status).toEqual(200);
expect(res3.headers['content-type']).toContain("application/json")
let updated_donor = res3.data
expect(updated_donor).toEqual(donor_copy);
});
});
// ---------------
describe('Update donor id after adding(should fail)', () => {
let added_donor;
it('creating a new donor with only needed params should return 200', async () => {
const res2 = await axios.post(base + '/api/donors', {
"firstname": "first",
"lastname": "last"
}, axios_config);
added_donor = res2.data;
expect(res2.status).toEqual(200);
expect(res2.headers['content-type']).toContain("application/json")
});
it('invalid update should return 406', async () => {
added_donor.id++;
const res3 = await axios.put(base + '/api/donors/' + (added_donor.id - 1), added_donor, axios_config);
expect(res3.status).toEqual(406);
expect(res3.headers['content-type']).toContain("application/json")
});
});
// ---------------
describe('Update donor without address but receiptNeeded=true should fail', () => {
let added_donor;
it('creating a new donor with only needed params should return 200', async () => {
const res2 = await axios.post(base + '/api/donors', {
"firstname": "first",
"lastname": "last",
}, axios_config);
added_donor = res2.data;
expect(res2.status).toEqual(200);
expect(res2.headers['content-type']).toContain("application/json")
});
it('invalid update should return 406', async () => {
added_donor.receiptNeeded = true;
const res3 = await axios.put(base + '/api/donors/' + added_donor.id, added_donor, axios_config);
expect(res3.status).toEqual(406);
expect(res3.headers['content-type']).toContain("application/json")
});
});

View File

@ -1,116 +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_id
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")
});
it('get should return 200', async () => {
const res1 = await axios.get(base + '/api/tracks', axios_config);
expect(res1.status).toEqual(200);
expect(res1.headers['content-type']).toContain("application/json")
let added_track = res1.data[res1.data.length - 1]
added_track_id = added_track.id
delete added_track.id
expect(added_track).toEqual({
"name": "string",
"distance": 1500
})
})
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', axios_config);
expect(res3.status).toEqual(200);
expect(res3.headers['content-type']).toContain("application/json")
let added_track2 = res3.data[res3.data.length - 1]
delete added_track2.id
expect(added_track2).toEqual({
"name": "apitrack",
"distance": 5100
})
});
});

View File

@ -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
})
});
});

View File

@ -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);
});
});

View File

@ -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")
});
});

View File

@ -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");
});
});

5382
tmp.json Normal file

File diff suppressed because it is too large Load Diff