From 5581c03f770782d69fe17861e8e23bba942956bf Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 12 Jan 2021 18:01:03 +0100 Subject: [PATCH 01/29] Added barebones donation controller ref #66 --- src/controllers/DonationController.ts | 129 ++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 src/controllers/DonationController.ts diff --git a/src/controllers/DonationController.ts b/src/controllers/DonationController.ts new file mode 100644 index 0000000..8f3b7f3 --- /dev/null +++ b/src/controllers/DonationController.ts @@ -0,0 +1,129 @@ +import { JsonController } from 'routing-controllers'; +import { OpenAPI } from 'routing-controllers-openapi'; +import { getConnectionManager, Repository } from 'typeorm'; +import { DistanceDonation } from '../models/entities/DistanceDonation'; +import { Donation } from '../models/entities/Donation'; + +@JsonController('/scans') +@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) +export class ScanController { + private donationRepository: Repository; + private distanceDonationRepository: Repository; + + /** + * Gets the repository of this controller's model/entity. + */ + constructor() { + this.donationRepository = getConnectionManager().get().getRepository(Donation); + this.distanceDonationRepository = getConnectionManager().get().getRepository(DistanceDonation); + } + + // @Get() + // @Authorized("SCAN:GET") + // @ResponseSchema(ResponseScan, { isArray: true }) + // @ResponseSchema(ResponseTrackScan, { isArray: true }) + // @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', 'track', 'runner.scans', 'runner.scans.track', 'card', 'station'] }); + // scans.forEach(scan => { + // responseScans.push(scan.toResponse()); + // }); + // return responseScans; + // } + + // @Get('/:id') + // @Authorized("SCAN:GET") + // @ResponseSchema(ResponseScan) + // @ResponseSchema(ResponseTrackScan) + // @ResponseSchema(ScanNotFoundError, { statusCode: 404 }) + // @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', 'track', 'runner.scans', 'runner.scans.track', 'card', 'station'] }) + // if (!scan) { throw new ScanNotFoundError(); } + // return scan.toResponse(); + // } + + // @Post() + // @UseBefore(ScanAuth) + // @ResponseSchema(ResponseScan) + // @ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) + // @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.toEntity(); + // scan = await this.scanRepository.save(scan); + // return (await this.scanRepository.findOne({ id: scan.id }, { relations: ['runner', 'track', 'runner.scans', 'runner.scans.track', 'card', 'station'] })).toResponse(); + // } + + // @Post("/trackscans") + // @UseBefore(ScanAuth) + // @ResponseSchema(ResponseTrackScan) + // @ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) + // @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.toEntity(); + // 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(); + // } + + // @Put('/:id') + // @Authorized("SCAN:UPDATE") + // @ResponseSchema(ResponseScan) + // @ResponseSchema(ScanNotFoundError, { statusCode: 404 }) + // @ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) + // @ResponseSchema(ScanIdsNotMatchingError, { statusCode: 406 }) + // @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 }); + + // if (!oldScan) { + // throw new ScanNotFoundError(); + // } + + // if (oldScan.id != scan.id) { + // throw new ScanIdsNotMatchingError(); + // } + + // await this.scanRepository.save(await scan.update(oldScan)); + // 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.update(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) + // @ResponseSchema(ResponseEmpty, { statusCode: 204 }) + // @OnUndefined(204) + // @OpenAPI({ description: 'Delete the scan whose id you provided.
If no scan with this id exists it will just return 204(no content).' }) + // 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', 'track', 'runner.scans', 'runner.scans.track', 'card', 'station'] }); + + // await this.scanRepository.delete(scan); + // return responseScan.toResponse(); + // } +} From 02bb6342575de23074c4117fbc93c3fc26ecd717 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 12 Jan 2021 18:07:41 +0100 Subject: [PATCH 02/29] Implemented a response donation interface ref #66 --- src/models/responses/IResponseDonation.ts | 37 +++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 src/models/responses/IResponseDonation.ts diff --git a/src/models/responses/IResponseDonation.ts b/src/models/responses/IResponseDonation.ts new file mode 100644 index 0000000..f07b7d0 --- /dev/null +++ b/src/models/responses/IResponseDonation.ts @@ -0,0 +1,37 @@ +import { IsInt, IsNotEmpty, IsPositive } from "class-validator"; +import { Donation } from '../entities/Donation'; +import { ResponseDonor } from './ResponseDonor'; + +/** + * Defines the donation response interface. +*/ +export abstract class IResponseDonation { + /** + * The donation's id. + */ + @IsInt() + @IsPositive() + id: number; + + /** + * The donation's donor. + */ + @IsNotEmpty() + donor: ResponseDonor; + + /** + * The donation's amount in the smalles unit of your currency (default: euro cent). + */ + @IsInt() + amount: number; + + /** + * Creates a IResponseDonation object from a scan. + * @param donation The donation the response shall be build for. + */ + public constructor(donation: Donation) { + this.id = donation.id; + this.donor = donation.donor.toResponse(); + this.amount = donation.amount; + } +} From 6c53701a59a0f9559d3668ead3970b7eacefe2ec Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 12 Jan 2021 18:16:09 +0100 Subject: [PATCH 03/29] Implemented the donation response ref #66 --- src/models/entities/DistanceDonation.ts | 4 ++-- src/models/entities/Donation.ts | 8 ++++--- src/models/entities/FixedDonation.ts | 24 ++++++++++++++++--- ...esponseDonation.ts => ResponseDonation.ts} | 6 ++--- 4 files changed, 31 insertions(+), 11 deletions(-) rename src/models/responses/{IResponseDonation.ts => ResponseDonation.ts} (84%) diff --git a/src/models/entities/DistanceDonation.ts b/src/models/entities/DistanceDonation.ts index 6b9ba5d..436dc3f 100644 --- a/src/models/entities/DistanceDonation.ts +++ b/src/models/entities/DistanceDonation.ts @@ -43,7 +43,7 @@ export class DistanceDonation extends Donation { /** * Turns this entity into it's response class. */ - public toResponse() { - return new Error("NotImplemented"); + public toResponse(): DistanceDonation { + return null; } } diff --git a/src/models/entities/Donation.ts b/src/models/entities/Donation.ts index 46d7d45..1dd023d 100644 --- a/src/models/entities/Donation.ts +++ b/src/models/entities/Donation.ts @@ -3,6 +3,7 @@ import { IsNotEmpty } from "class-validator"; import { Entity, ManyToOne, PrimaryGeneratedColumn, TableInheritance } from "typeorm"; +import { ResponseDonation } from '../responses/ResponseDonation'; import { Donor } from './Donor'; /** @@ -31,12 +32,13 @@ export abstract class Donation { * The donation's amount in cents (or whatever your currency's smallest unit is.). * The exact implementation may differ for each type of donation. */ - abstract amount: number; + public abstract get amount(): number; + /** * Turns this entity into it's response class. */ - public toResponse() { - return new Error("NotImplemented"); + public toResponse(): ResponseDonation { + return new ResponseDonation(this); } } \ No newline at end of file diff --git a/src/models/entities/FixedDonation.ts b/src/models/entities/FixedDonation.ts index 6a32066..c6454d4 100644 --- a/src/models/entities/FixedDonation.ts +++ b/src/models/entities/FixedDonation.ts @@ -1,5 +1,6 @@ import { IsInt, IsPositive } from "class-validator"; import { ChildEntity, Column } from "typeorm"; +import { ResponseDonation } from '../responses/ResponseDonation'; import { Donation } from "./Donation"; /** @@ -11,16 +12,33 @@ export class FixedDonation extends Donation { /** * The donation's amount in cents (or whatever your currency's smallest unit is.). + * This is the "real" value used by fixed donations. */ @Column() @IsInt() @IsPositive() - amount: number; + private _amount: number; + + /** + * The donation's amount in cents (or whatever your currency's smallest unit is.). + */ + @IsInt() + @IsPositive() + public get amount(): number { + return this._amount; + } + + /** + * The donation's amount in cents (or whatever your currency's smallest unit is.). + */ + public set amount(value: number) { + this._amount = value; + } /** * Turns this entity into it's response class. */ - public toResponse() { - return new Error("NotImplemented"); + public toResponse(): ResponseDonation { + return new ResponseDonation(this); } } \ No newline at end of file diff --git a/src/models/responses/IResponseDonation.ts b/src/models/responses/ResponseDonation.ts similarity index 84% rename from src/models/responses/IResponseDonation.ts rename to src/models/responses/ResponseDonation.ts index f07b7d0..c2789e3 100644 --- a/src/models/responses/IResponseDonation.ts +++ b/src/models/responses/ResponseDonation.ts @@ -3,9 +3,9 @@ import { Donation } from '../entities/Donation'; import { ResponseDonor } from './ResponseDonor'; /** - * Defines the donation response interface. + * Defines the donation response. */ -export abstract class IResponseDonation { +export class ResponseDonation { /** * The donation's id. */ @@ -26,7 +26,7 @@ export abstract class IResponseDonation { amount: number; /** - * Creates a IResponseDonation object from a scan. + * Creates a ResponseDonation object from a scan. * @param donation The donation the response shall be build for. */ public constructor(donation: Donation) { From 55f72c35a62ec827866b29e2d2cde371734b2e0e Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 12 Jan 2021 18:20:36 +0100 Subject: [PATCH 04/29] Implemented the distance donation response ref #66 --- src/models/entities/DistanceDonation.ts | 7 ++-- .../responses/ResponseDistanceDonation.ts | 35 +++++++++++++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 src/models/responses/ResponseDistanceDonation.ts diff --git a/src/models/entities/DistanceDonation.ts b/src/models/entities/DistanceDonation.ts index 436dc3f..a9b323e 100644 --- a/src/models/entities/DistanceDonation.ts +++ b/src/models/entities/DistanceDonation.ts @@ -1,5 +1,6 @@ import { IsInt, IsNotEmpty, IsPositive } from "class-validator"; import { ChildEntity, Column, ManyToOne } from "typeorm"; +import { ResponseDistanceDonation } from '../responses/ResponseDistanceDonation'; import { Donation } from "./Donation"; import { Runner } from "./Runner"; @@ -31,7 +32,7 @@ export class DistanceDonation extends Donation { * Get's calculated from the runner's distance ran and the amount donated per kilometer. */ public get amount(): number { - let calculatedAmount = -1; + let calculatedAmount = 0; try { calculatedAmount = this.amountPerDistance * (this.runner.distance / 1000); } catch (error) { @@ -43,7 +44,7 @@ export class DistanceDonation extends Donation { /** * Turns this entity into it's response class. */ - public toResponse(): DistanceDonation { - return null; + public toResponse(): ResponseDistanceDonation { + return new ResponseDistanceDonation(this); } } diff --git a/src/models/responses/ResponseDistanceDonation.ts b/src/models/responses/ResponseDistanceDonation.ts new file mode 100644 index 0000000..388ece0 --- /dev/null +++ b/src/models/responses/ResponseDistanceDonation.ts @@ -0,0 +1,35 @@ +import { IsInt, IsObject, IsPositive } from 'class-validator'; +import { DistanceDonation } from '../entities/DistanceDonation'; +import { ResponseDonation } from './ResponseDonation'; +import { ResponseRunner } from './ResponseRunner'; + +/** + * Defines the distance donation response. +*/ +export class ResponseDistanceDonation extends ResponseDonation { + + /** + * The donation's associated runner. + * Used as the source of the donation's distance. + */ + @IsObject() + runner: ResponseRunner; + + /** + * The donation's amount donated per distance. + * The amount the donor set to be donated per kilometer that the runner ran. + */ + @IsInt() + @IsPositive() + amountPerDistance: number; + + /** + * Creates a ResponseDistanceDonation object from a scan. + * @param donation The distance donation the response shall be build for. + */ + public constructor(donation: DistanceDonation) { + super(donation); + this.runner = donation.runner.toResponse(); + this.amountPerDistance = donation.amountPerDistance; + } +} From e1ff8c03e15c6833e71d1b82ea25b9566ebef48c Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 12 Jan 2021 18:21:52 +0100 Subject: [PATCH 05/29] Added donation permission target ref #66 --- src/models/enums/PermissionTargets.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/models/enums/PermissionTargets.ts b/src/models/enums/PermissionTargets.ts index 551ea5c..86c547d 100644 --- a/src/models/enums/PermissionTargets.ts +++ b/src/models/enums/PermissionTargets.ts @@ -13,5 +13,6 @@ export enum PermissionTarget { DONOR = 'DONOR', SCAN = 'SCAN', STATION = 'STATION', - CARD = 'CARD' + CARD = 'CARD', + DONATION = 'DONATION' } \ No newline at end of file From 5f1ab4a2f32ca2297fcf952171e6c106c2ae3a39 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 12 Jan 2021 18:26:55 +0100 Subject: [PATCH 06/29] Added donation errors ref #66 --- src/errors/DonationErrors.ts | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/errors/DonationErrors.ts diff --git a/src/errors/DonationErrors.ts b/src/errors/DonationErrors.ts new file mode 100644 index 0000000..2c69800 --- /dev/null +++ b/src/errors/DonationErrors.ts @@ -0,0 +1,25 @@ +import { IsString } from 'class-validator'; +import { NotAcceptableError, NotFoundError } from 'routing-controllers'; + +/** + * Error to throw when a Donation couldn't be found. + */ +export class DonationNotFoundError extends NotFoundError { + @IsString() + name = "DonationNotFoundError" + + @IsString() + message = "Donation not found!" +} + +/** + * Error to throw when two Donations' ids don't match. + * Usually occurs when a user tries to change a Donation's id. + */ +export class DonationIdsNotMatchingError extends NotAcceptableError { + @IsString() + name = "DonationIdsNotMatchingError" + + @IsString() + message = "The ids don't match! \n And if you wanted to change a Donation's id: This isn't allowed!" +} \ No newline at end of file From 0df26cbd54bf914166bcb9ac1b03dee0c5dba07d Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 12 Jan 2021 18:29:55 +0100 Subject: [PATCH 07/29] Implemented donation getting ref #66 --- src/controllers/DonationController.ts | 61 ++++++++++++++------------- 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/src/controllers/DonationController.ts b/src/controllers/DonationController.ts index 8f3b7f3..ab5e9f0 100644 --- a/src/controllers/DonationController.ts +++ b/src/controllers/DonationController.ts @@ -1,12 +1,15 @@ -import { JsonController } from 'routing-controllers'; -import { OpenAPI } from 'routing-controllers-openapi'; +import { Authorized, Get, JsonController, OnUndefined, Param } from 'routing-controllers'; +import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; import { getConnectionManager, Repository } from 'typeorm'; +import { DonationNotFoundError } from '../errors/DonationErrors'; import { DistanceDonation } from '../models/entities/DistanceDonation'; import { Donation } from '../models/entities/Donation'; +import { ResponseDistanceDonation } from '../models/responses/ResponseDistanceDonation'; +import { ResponseDonation } from '../models/responses/ResponseDonation'; -@JsonController('/scans') +@JsonController('/donations') @OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) -export class ScanController { +export class DonationController { private donationRepository: Repository; private distanceDonationRepository: Repository; @@ -18,32 +21,32 @@ export class ScanController { this.distanceDonationRepository = getConnectionManager().get().getRepository(DistanceDonation); } - // @Get() - // @Authorized("SCAN:GET") - // @ResponseSchema(ResponseScan, { isArray: true }) - // @ResponseSchema(ResponseTrackScan, { isArray: true }) - // @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', 'track', 'runner.scans', 'runner.scans.track', 'card', 'station'] }); - // scans.forEach(scan => { - // responseScans.push(scan.toResponse()); - // }); - // return responseScans; - // } + @Get() + @Authorized("DONATION:GET") + @ResponseSchema(ResponseDonation, { isArray: true }) + @ResponseSchema(ResponseDistanceDonation, { isArray: true }) + @OpenAPI({ description: 'Lists all donations (fixed or distance based) from all donors.
This includes the donations\'s runner\'s distance ran(if distance donation).' }) + async getAll() { + let responseDonations: ResponseDonation[] = new Array(); + const donations = await this.donationRepository.find({ relations: ['runner', 'donor', 'runner.scans', 'runner.scans.track'] }); + donations.forEach(donation => { + responseDonations.push(donation.toResponse()); + }); + return responseDonations; + } - // @Get('/:id') - // @Authorized("SCAN:GET") - // @ResponseSchema(ResponseScan) - // @ResponseSchema(ResponseTrackScan) - // @ResponseSchema(ScanNotFoundError, { statusCode: 404 }) - // @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', 'track', 'runner.scans', 'runner.scans.track', 'card', 'station'] }) - // if (!scan) { throw new ScanNotFoundError(); } - // return scan.toResponse(); - // } + @Get('/:id') + @Authorized("DONATION:GET") + @ResponseSchema(ResponseDonation) + @ResponseSchema(ResponseDistanceDonation) + @ResponseSchema(DonationNotFoundError, { statusCode: 404 }) + @OnUndefined(DonationNotFoundError) + @OpenAPI({ description: 'Lists all information about the donation whose id got provided. This includes the donation\'s runner\'s distance ran (if distance donation).' }) + async getOne(@Param('id') id: number) { + let donation = await this.donationRepository.findOne({ id: id }, { relations: ['runner', 'donor', 'runner.scans', 'runner.scans.track'] }) + if (!donation) { throw new DonationNotFoundError(); } + return donation.toResponse(); + } // @Post() // @UseBefore(ScanAuth) From 2e760ff46149a25183172a8793275c6c76d39c75 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 12 Jan 2021 18:39:14 +0100 Subject: [PATCH 08/29] Implemented the donation creation action models ref #66 --- .../actions/create/CreateDistanceDonation.ts | 52 +++++++++++++++++++ src/models/actions/create/CreateDonation.ts | 34 ++++++++++++ .../actions/create/CreateFixedDonation.ts | 28 ++++++++++ 3 files changed, 114 insertions(+) create mode 100644 src/models/actions/create/CreateDistanceDonation.ts create mode 100644 src/models/actions/create/CreateDonation.ts create mode 100644 src/models/actions/create/CreateFixedDonation.ts diff --git a/src/models/actions/create/CreateDistanceDonation.ts b/src/models/actions/create/CreateDistanceDonation.ts new file mode 100644 index 0000000..e212068 --- /dev/null +++ b/src/models/actions/create/CreateDistanceDonation.ts @@ -0,0 +1,52 @@ +import { IsInt, IsPositive } from 'class-validator'; +import { getConnection } from 'typeorm'; +import { RunnerNotFoundError } from '../../../errors/RunnerErrors'; +import { DistanceDonation } from '../../entities/DistanceDonation'; +import { Runner } from '../../entities/Runner'; +import { CreateDonation } from './CreateDonation'; + +/** + * This class is used to create a new FixedDonation entity from a json body (post request). + */ +export class CreateDistanceDonation extends CreateDonation { + + /** + * The donation's associated runner. + * This is important to link the runner's distance ran to the donation. + */ + @IsInt() + @IsPositive() + runner: number; + + /** + * The donation's amount per distance (full kilometer aka 1000 meters). + * The unit is your currency's smallest unit (default: euro cent). + */ + @IsInt() + @IsPositive() + amountPerDistance: number; + + /** + * Creates a new FixedDonation entity from this. + */ + public async toEntity(): Promise { + let newDonation = new DistanceDonation; + + newDonation.amountPerDistance = this.amountPerDistance; + newDonation.donor = await this.getDonor(); + newDonation.runner = await this.getRunner(); + + return newDonation; + } + + /** + * 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; + } +} \ No newline at end of file diff --git a/src/models/actions/create/CreateDonation.ts b/src/models/actions/create/CreateDonation.ts new file mode 100644 index 0000000..dbed745 --- /dev/null +++ b/src/models/actions/create/CreateDonation.ts @@ -0,0 +1,34 @@ +import { IsInt, IsPositive } from 'class-validator'; +import { getConnection } from 'typeorm'; +import { DonorNotFoundError } from '../../../errors/DonorErrors'; +import { Donation } from '../../entities/Donation'; +import { Donor } from '../../entities/Donor'; + +/** + * This class is used to create a new Donation entity from a json body (post request). + */ +export abstract class CreateDonation { + /** + * The donation's associated donor. + * This is important to link donations to donors. + */ + @IsInt() + @IsPositive() + donor: number; + + /** + * Creates a new Donation entity from this. + */ + public abstract toEntity(): Promise; + + /** + * Gets a runner based on the runner id provided via this.runner. + */ + public async getDonor(): Promise { + const donor = await getConnection().getRepository(Donor).findOne({ id: this.donor }); + if (!donor) { + throw new DonorNotFoundError(); + } + return donor; + } +} \ No newline at end of file diff --git a/src/models/actions/create/CreateFixedDonation.ts b/src/models/actions/create/CreateFixedDonation.ts new file mode 100644 index 0000000..4d73f50 --- /dev/null +++ b/src/models/actions/create/CreateFixedDonation.ts @@ -0,0 +1,28 @@ +import { IsInt, IsPositive } from 'class-validator'; +import { FixedDonation } from '../../entities/FixedDonation'; +import { CreateDonation } from './CreateDonation'; + +/** + * This class is used to create a new FixedDonation entity from a json body (post request). + */ +export class CreateFixedDonation extends CreateDonation { + /** + * The donation's amount. + * The unit is your currency's smallest unit (default: euro cent). + */ + @IsInt() + @IsPositive() + amount: number; + + /** + * Creates a new FixedDonation entity from this. + */ + public async toEntity(): Promise { + let newDonation = new FixedDonation; + + newDonation.amount = this.amount; + newDonation.donor = await this.getDonor(); + + return newDonation; + } +} \ No newline at end of file From 57f62a608764914c5a213f0c2aebde0d67df70e0 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 12 Jan 2021 18:46:02 +0100 Subject: [PATCH 09/29] Implemented donation deletion ref #66 --- src/controllers/DonationController.ts | 30 ++++++++++++++------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/controllers/DonationController.ts b/src/controllers/DonationController.ts index ab5e9f0..c31c84e 100644 --- a/src/controllers/DonationController.ts +++ b/src/controllers/DonationController.ts @@ -1,4 +1,4 @@ -import { Authorized, Get, JsonController, OnUndefined, Param } from 'routing-controllers'; +import { Authorized, Delete, Get, JsonController, OnUndefined, Param, QueryParam } from 'routing-controllers'; import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; import { getConnectionManager, Repository } from 'typeorm'; import { DonationNotFoundError } from '../errors/DonationErrors'; @@ -6,6 +6,7 @@ import { DistanceDonation } from '../models/entities/DistanceDonation'; import { Donation } from '../models/entities/Donation'; import { ResponseDistanceDonation } from '../models/responses/ResponseDistanceDonation'; import { ResponseDonation } from '../models/responses/ResponseDonation'; +import { ResponseEmpty } from '../models/responses/ResponseEmpty'; @JsonController('/donations') @OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) @@ -115,18 +116,19 @@ export class DonationController { // 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) - // @ResponseSchema(ResponseEmpty, { statusCode: 204 }) - // @OnUndefined(204) - // @OpenAPI({ description: 'Delete the scan whose id you provided.
If no scan with this id exists it will just return 204(no content).' }) - // 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', 'track', 'runner.scans', 'runner.scans.track', 'card', 'station'] }); + @Delete('/:id') + @Authorized("DONATION:DELETE") + @ResponseSchema(ResponseDonation) + @ResponseSchema(ResponseDistanceDonation) + @ResponseSchema(ResponseEmpty, { statusCode: 204 }) + @OnUndefined(204) + @OpenAPI({ description: 'Delete the donation whose id you provided.
If no donation with this id exists it will just return 204(no content).' }) + async remove(@Param("id") id: number, @QueryParam("force") force: boolean) { + let donation = await this.donationRepository.findOne({ id: id }); + if (!donation) { return null; } + const responseScan = await this.donationRepository.findOne({ id: donation.id }, { relations: ['runner', 'donor', 'runner.scans', 'runner.scans.track'] }); - // await this.scanRepository.delete(scan); - // return responseScan.toResponse(); - // } + await this.donationRepository.delete(donation); + return responseScan.toResponse(); + } } From 97ecc83fe47f35638313d4cb424ccb554d8d47d4 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 12 Jan 2021 18:50:47 +0100 Subject: [PATCH 10/29] Implemented fixed donation creation ref #66 --- src/controllers/DonationController.ts | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/controllers/DonationController.ts b/src/controllers/DonationController.ts index c31c84e..a5cc7d9 100644 --- a/src/controllers/DonationController.ts +++ b/src/controllers/DonationController.ts @@ -1,9 +1,12 @@ -import { Authorized, Delete, Get, JsonController, OnUndefined, Param, QueryParam } from 'routing-controllers'; +import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, QueryParam } from 'routing-controllers'; import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; import { getConnectionManager, Repository } from 'typeorm'; import { DonationNotFoundError } from '../errors/DonationErrors'; +import { DonorNotFoundError } from '../errors/DonorErrors'; +import { CreateFixedDonation } from '../models/actions/create/CreateFixedDonation'; import { DistanceDonation } from '../models/entities/DistanceDonation'; import { Donation } from '../models/entities/Donation'; +import { FixedDonation } from '../models/entities/FixedDonation'; import { ResponseDistanceDonation } from '../models/responses/ResponseDistanceDonation'; import { ResponseDonation } from '../models/responses/ResponseDonation'; import { ResponseEmpty } from '../models/responses/ResponseEmpty'; @@ -13,6 +16,7 @@ import { ResponseEmpty } from '../models/responses/ResponseEmpty'; export class DonationController { private donationRepository: Repository; private distanceDonationRepository: Repository; + private fixedDonationRepository: Repository; /** * Gets the repository of this controller's model/entity. @@ -20,6 +24,7 @@ export class DonationController { constructor() { this.donationRepository = getConnectionManager().get().getRepository(Donation); this.distanceDonationRepository = getConnectionManager().get().getRepository(DistanceDonation); + this.fixedDonationRepository = getConnectionManager().get().getRepository(FixedDonation); } @Get() @@ -49,16 +54,16 @@ export class DonationController { return donation.toResponse(); } - // @Post() - // @UseBefore(ScanAuth) - // @ResponseSchema(ResponseScan) - // @ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) - // @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.toEntity(); - // scan = await this.scanRepository.save(scan); - // return (await this.scanRepository.findOne({ id: scan.id }, { relations: ['runner', 'track', 'runner.scans', 'runner.scans.track', 'card', 'station'] })).toResponse(); - // } + @Post('/fixed') + @Authorized("DONATION:CREATE") + @ResponseSchema(ResponseDonation) + @ResponseSchema(DonorNotFoundError, { statusCode: 404 }) + @OpenAPI({ description: 'Create a fixed donation (not distance donation - use /donations/distance instead).
Please rmemember to provide the donation\'s donors\'s id and amount.' }) + async postFixed(@Body({ validate: true }) createDonation: CreateFixedDonation) { + let donation = await createDonation.toEntity(); + donation = await this.fixedDonationRepository.save(donation); + return (await this.donationRepository.findOne({ id: donation.id }, { relations: ['donor'] })).toResponse(); + } // @Post("/trackscans") // @UseBefore(ScanAuth) From 8ee2bdf488f3aa77bc3c68956ff5ba906b743323 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 12 Jan 2021 18:50:55 +0100 Subject: [PATCH 11/29] Implemented distance donation creation ref #66 --- src/controllers/DonationController.ts | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/controllers/DonationController.ts b/src/controllers/DonationController.ts index a5cc7d9..44f94ab 100644 --- a/src/controllers/DonationController.ts +++ b/src/controllers/DonationController.ts @@ -3,6 +3,8 @@ import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; import { getConnectionManager, Repository } from 'typeorm'; import { DonationNotFoundError } from '../errors/DonationErrors'; import { DonorNotFoundError } from '../errors/DonorErrors'; +import { RunnerNotFoundError } from '../errors/RunnerErrors'; +import { CreateDistanceDonation } from '../models/actions/create/CreateDistanceDonation'; import { CreateFixedDonation } from '../models/actions/create/CreateFixedDonation'; import { DistanceDonation } from '../models/entities/DistanceDonation'; import { Donation } from '../models/entities/Donation'; @@ -65,16 +67,17 @@ export class DonationController { return (await this.donationRepository.findOne({ id: donation.id }, { relations: ['donor'] })).toResponse(); } - // @Post("/trackscans") - // @UseBefore(ScanAuth) - // @ResponseSchema(ResponseTrackScan) - // @ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) - // @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.toEntity(); - // 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(); - // } + @Post('/distance') + @Authorized("DONATION:CREATE") + @ResponseSchema(ResponseDistanceDonation) + @ResponseSchema(DonorNotFoundError, { statusCode: 404 }) + @ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) + @OpenAPI({ description: 'Create a distance donation (not fixed donation - use /donations/fixed instead).
Please rmemember to provide the donation\'s donors\'s and runner\s ids and amount per distance (kilometer).' }) + async postDistance(@Body({ validate: true }) createDonation: CreateDistanceDonation) { + let donation = await createDonation.toEntity(); + donation = await this.distanceDonationRepository.save(donation); + return (await this.donationRepository.findOne({ id: donation.id }, { relations: ['runner', 'donor', 'runner.scans', 'runner.scans.track'] })).toResponse(); + } // @Put('/:id') // @Authorized("SCAN:UPDATE") From bbaee7cd4d0cb68545a405899e7c2ff93c39bac4 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 12 Jan 2021 18:53:59 +0100 Subject: [PATCH 12/29] Added the basics for fixed donation updateing ref #66 --- src/controllers/DonationController.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/controllers/DonationController.ts b/src/controllers/DonationController.ts index 44f94ab..e19c0b9 100644 --- a/src/controllers/DonationController.ts +++ b/src/controllers/DonationController.ts @@ -82,23 +82,24 @@ export class DonationController { // @Put('/:id') // @Authorized("SCAN:UPDATE") // @ResponseSchema(ResponseScan) - // @ResponseSchema(ScanNotFoundError, { statusCode: 404 }) + // @ResponseSchema(DonationNotFoundError, { statusCode: 404 }) + // @ResponseSchema(DonorNotFoundError, { statusCode: 404 }) // @ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) - // @ResponseSchema(ScanIdsNotMatchingError, { statusCode: 406 }) - // @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 }); + // @ResponseSchema(DonationIdsNotMatchingError, { statusCode: 406 }) + // @OpenAPI({ description: "Update the fixed donation (not distance donation - use /donations/fixed instead) whose id you provided.
Please remember that ids can't be changed and amounts must be positive." }) + // async putFixed(@Param('id') id: number, @Body({ validate: true }) donation: UpdateDistanceDonation) { + // let oldDonation = await this.fixedDonationRepository.findOne({ id: id }); - // if (!oldScan) { + // if (!oldDonation) { // throw new ScanNotFoundError(); // } - // if (oldScan.id != scan.id) { + // if (oldDonation.id != donation.id) { // throw new ScanIdsNotMatchingError(); // } - // await this.scanRepository.save(await scan.update(oldScan)); - // return (await this.scanRepository.findOne({ id: id }, { relations: ['runner', 'track', 'runner.scans', 'runner.scans.track', 'card', 'station'] })).toResponse(); + // await this.fixedDonationRepository.save(await donation.update(oldDonation)); + // return (await this.donationRepository.findOne({ id: donation.id }, { relations: ['donor'] })).toResponse(); // } // @Put('/trackscans/:id') From 56cedf0144e933b34038089e840860dfb6375129 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 12 Jan 2021 18:55:20 +0100 Subject: [PATCH 13/29] Fixed typo ref #66 --- src/models/actions/create/CreateDonation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/models/actions/create/CreateDonation.ts b/src/models/actions/create/CreateDonation.ts index dbed745..3b9218f 100644 --- a/src/models/actions/create/CreateDonation.ts +++ b/src/models/actions/create/CreateDonation.ts @@ -22,7 +22,7 @@ export abstract class CreateDonation { public abstract toEntity(): Promise; /** - * Gets a runner based on the runner id provided via this.runner. + * Gets a donor based on the donor id provided via this.donor. */ public async getDonor(): Promise { const donor = await getConnection().getRepository(Donor).findOne({ id: this.donor }); From 9517df50826aff1a1cb03e611b990de5829e2132 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 12 Jan 2021 19:00:35 +0100 Subject: [PATCH 14/29] Implemented fixed donation updateing ref #66 --- src/controllers/DonationController.ts | 43 ++++++++++--------- src/models/actions/update/UpdateDonation.ts | 41 ++++++++++++++++++ .../actions/update/UpdateFixedDonation.ts | 27 ++++++++++++ 3 files changed, 90 insertions(+), 21 deletions(-) create mode 100644 src/models/actions/update/UpdateDonation.ts create mode 100644 src/models/actions/update/UpdateFixedDonation.ts diff --git a/src/controllers/DonationController.ts b/src/controllers/DonationController.ts index e19c0b9..b7dc153 100644 --- a/src/controllers/DonationController.ts +++ b/src/controllers/DonationController.ts @@ -1,11 +1,12 @@ -import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, QueryParam } from 'routing-controllers'; +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 { DonationNotFoundError } from '../errors/DonationErrors'; +import { DonationIdsNotMatchingError, DonationNotFoundError } from '../errors/DonationErrors'; import { DonorNotFoundError } from '../errors/DonorErrors'; import { RunnerNotFoundError } from '../errors/RunnerErrors'; import { CreateDistanceDonation } from '../models/actions/create/CreateDistanceDonation'; import { CreateFixedDonation } from '../models/actions/create/CreateFixedDonation'; +import { UpdateFixedDonation } from '../models/actions/update/UpdateFixedDonation'; import { DistanceDonation } from '../models/entities/DistanceDonation'; import { Donation } from '../models/entities/Donation'; import { FixedDonation } from '../models/entities/FixedDonation'; @@ -79,28 +80,28 @@ export class DonationController { return (await this.donationRepository.findOne({ id: donation.id }, { relations: ['runner', 'donor', 'runner.scans', 'runner.scans.track'] })).toResponse(); } - // @Put('/:id') - // @Authorized("SCAN:UPDATE") - // @ResponseSchema(ResponseScan) - // @ResponseSchema(DonationNotFoundError, { statusCode: 404 }) - // @ResponseSchema(DonorNotFoundError, { statusCode: 404 }) - // @ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) - // @ResponseSchema(DonationIdsNotMatchingError, { statusCode: 406 }) - // @OpenAPI({ description: "Update the fixed donation (not distance donation - use /donations/fixed instead) whose id you provided.
Please remember that ids can't be changed and amounts must be positive." }) - // async putFixed(@Param('id') id: number, @Body({ validate: true }) donation: UpdateDistanceDonation) { - // let oldDonation = await this.fixedDonationRepository.findOne({ id: id }); + @Put('/:id') + @Authorized("DONATION:UPDATE") + @ResponseSchema(ResponseDonation) + @ResponseSchema(DonationNotFoundError, { statusCode: 404 }) + @ResponseSchema(DonorNotFoundError, { statusCode: 404 }) + @ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) + @ResponseSchema(DonationIdsNotMatchingError, { statusCode: 406 }) + @OpenAPI({ description: "Update the fixed donation (not distance donation - use /donations/fixed instead) whose id you provided.
Please remember that ids can't be changed and amounts must be positive." }) + async putFixed(@Param('id') id: number, @Body({ validate: true }) donation: UpdateFixedDonation) { + let oldDonation = await this.fixedDonationRepository.findOne({ id: id }); - // if (!oldDonation) { - // throw new ScanNotFoundError(); - // } + if (!oldDonation) { + throw new DonationNotFoundError(); + } - // if (oldDonation.id != donation.id) { - // throw new ScanIdsNotMatchingError(); - // } + if (oldDonation.id != donation.id) { + throw new DonationIdsNotMatchingError(); + } - // await this.fixedDonationRepository.save(await donation.update(oldDonation)); - // return (await this.donationRepository.findOne({ id: donation.id }, { relations: ['donor'] })).toResponse(); - // } + await this.fixedDonationRepository.save(await donation.update(oldDonation)); + return (await this.donationRepository.findOne({ id: donation.id }, { relations: ['donor'] })).toResponse(); + } // @Put('/trackscans/:id') // @Authorized("SCAN:UPDATE") diff --git a/src/models/actions/update/UpdateDonation.ts b/src/models/actions/update/UpdateDonation.ts new file mode 100644 index 0000000..7f10f97 --- /dev/null +++ b/src/models/actions/update/UpdateDonation.ts @@ -0,0 +1,41 @@ +import { IsInt, IsPositive } from 'class-validator'; +import { getConnection } from 'typeorm'; +import { DonorNotFoundError } from '../../../errors/DonorErrors'; +import { Donation } from '../../entities/Donation'; +import { Donor } from '../../entities/Donor'; + +/** + * This class is used to update a Donation entity (via put request). + */ +export abstract class UpdateDonation { + /** + * The updated donation'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 donation's associated donor. + * This is important to link donations to donors. + */ + @IsInt() + @IsPositive() + donor: number; + + /** + * Creates a new Donation entity from this. + */ + public abstract update(donation: Donation): Promise; + + /** + * Gets a donor based on the donor id provided via this.donor. + */ + public async getDonor(): Promise { + const donor = await getConnection().getRepository(Donor).findOne({ id: this.donor }); + if (!donor) { + throw new DonorNotFoundError(); + } + return donor; + } +} \ No newline at end of file diff --git a/src/models/actions/update/UpdateFixedDonation.ts b/src/models/actions/update/UpdateFixedDonation.ts new file mode 100644 index 0000000..5e31068 --- /dev/null +++ b/src/models/actions/update/UpdateFixedDonation.ts @@ -0,0 +1,27 @@ +import { IsInt, IsPositive } from 'class-validator'; +import { FixedDonation } from '../../entities/FixedDonation'; +import { UpdateDonation } from './UpdateDonation'; + +/** + * This class is used to update a FixedDonation entity (via put request). + */ +export class UpdateFixedDonation extends UpdateDonation { + /** + * The updated donation's amount. + * The unit is your currency's smallest unit (default: euro cent). + */ + @IsInt() + @IsPositive() + amount: number; + + /** + * Update a FixedDonation entity based on this. + * @param donation The donation that shall be updated. + */ + public async update(donation: FixedDonation): Promise { + donation.amount = this.amount; + donation.donor = await this.getDonor(); + + return donation; + } +} \ No newline at end of file From 2820f151e873f342a03b656385ca08d7ea8bc0a5 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 12 Jan 2021 19:00:35 +0100 Subject: [PATCH 15/29] Implemented fixed donation updateing ref #66 --- src/controllers/DonationController.ts | 43 ++++++++++--------- src/models/actions/update/UpdateDonation.ts | 41 ++++++++++++++++++ .../actions/update/UpdateFixedDonation.ts | 27 ++++++++++++ 3 files changed, 90 insertions(+), 21 deletions(-) create mode 100644 src/models/actions/update/UpdateDonation.ts create mode 100644 src/models/actions/update/UpdateFixedDonation.ts diff --git a/src/controllers/DonationController.ts b/src/controllers/DonationController.ts index e19c0b9..2d474f6 100644 --- a/src/controllers/DonationController.ts +++ b/src/controllers/DonationController.ts @@ -1,11 +1,12 @@ -import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, QueryParam } from 'routing-controllers'; +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 { DonationNotFoundError } from '../errors/DonationErrors'; +import { DonationIdsNotMatchingError, DonationNotFoundError } from '../errors/DonationErrors'; import { DonorNotFoundError } from '../errors/DonorErrors'; import { RunnerNotFoundError } from '../errors/RunnerErrors'; import { CreateDistanceDonation } from '../models/actions/create/CreateDistanceDonation'; import { CreateFixedDonation } from '../models/actions/create/CreateFixedDonation'; +import { UpdateFixedDonation } from '../models/actions/update/UpdateFixedDonation'; import { DistanceDonation } from '../models/entities/DistanceDonation'; import { Donation } from '../models/entities/Donation'; import { FixedDonation } from '../models/entities/FixedDonation'; @@ -79,28 +80,28 @@ export class DonationController { return (await this.donationRepository.findOne({ id: donation.id }, { relations: ['runner', 'donor', 'runner.scans', 'runner.scans.track'] })).toResponse(); } - // @Put('/:id') - // @Authorized("SCAN:UPDATE") - // @ResponseSchema(ResponseScan) - // @ResponseSchema(DonationNotFoundError, { statusCode: 404 }) - // @ResponseSchema(DonorNotFoundError, { statusCode: 404 }) - // @ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) - // @ResponseSchema(DonationIdsNotMatchingError, { statusCode: 406 }) - // @OpenAPI({ description: "Update the fixed donation (not distance donation - use /donations/fixed instead) whose id you provided.
Please remember that ids can't be changed and amounts must be positive." }) - // async putFixed(@Param('id') id: number, @Body({ validate: true }) donation: UpdateDistanceDonation) { - // let oldDonation = await this.fixedDonationRepository.findOne({ id: id }); + @Put('/fixed/:id') + @Authorized("DONATION:UPDATE") + @ResponseSchema(ResponseDonation) + @ResponseSchema(DonationNotFoundError, { statusCode: 404 }) + @ResponseSchema(DonorNotFoundError, { statusCode: 404 }) + @ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) + @ResponseSchema(DonationIdsNotMatchingError, { statusCode: 406 }) + @OpenAPI({ description: "Update the fixed donation (not distance donation - use /donations/fixed instead) whose id you provided.
Please remember that ids can't be changed and amounts must be positive." }) + async putFixed(@Param('id') id: number, @Body({ validate: true }) donation: UpdateFixedDonation) { + let oldDonation = await this.fixedDonationRepository.findOne({ id: id }); - // if (!oldDonation) { - // throw new ScanNotFoundError(); - // } + if (!oldDonation) { + throw new DonationNotFoundError(); + } - // if (oldDonation.id != donation.id) { - // throw new ScanIdsNotMatchingError(); - // } + if (oldDonation.id != donation.id) { + throw new DonationIdsNotMatchingError(); + } - // await this.fixedDonationRepository.save(await donation.update(oldDonation)); - // return (await this.donationRepository.findOne({ id: donation.id }, { relations: ['donor'] })).toResponse(); - // } + await this.fixedDonationRepository.save(await donation.update(oldDonation)); + return (await this.donationRepository.findOne({ id: donation.id }, { relations: ['donor'] })).toResponse(); + } // @Put('/trackscans/:id') // @Authorized("SCAN:UPDATE") diff --git a/src/models/actions/update/UpdateDonation.ts b/src/models/actions/update/UpdateDonation.ts new file mode 100644 index 0000000..7f10f97 --- /dev/null +++ b/src/models/actions/update/UpdateDonation.ts @@ -0,0 +1,41 @@ +import { IsInt, IsPositive } from 'class-validator'; +import { getConnection } from 'typeorm'; +import { DonorNotFoundError } from '../../../errors/DonorErrors'; +import { Donation } from '../../entities/Donation'; +import { Donor } from '../../entities/Donor'; + +/** + * This class is used to update a Donation entity (via put request). + */ +export abstract class UpdateDonation { + /** + * The updated donation'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 donation's associated donor. + * This is important to link donations to donors. + */ + @IsInt() + @IsPositive() + donor: number; + + /** + * Creates a new Donation entity from this. + */ + public abstract update(donation: Donation): Promise; + + /** + * Gets a donor based on the donor id provided via this.donor. + */ + public async getDonor(): Promise { + const donor = await getConnection().getRepository(Donor).findOne({ id: this.donor }); + if (!donor) { + throw new DonorNotFoundError(); + } + return donor; + } +} \ No newline at end of file diff --git a/src/models/actions/update/UpdateFixedDonation.ts b/src/models/actions/update/UpdateFixedDonation.ts new file mode 100644 index 0000000..5e31068 --- /dev/null +++ b/src/models/actions/update/UpdateFixedDonation.ts @@ -0,0 +1,27 @@ +import { IsInt, IsPositive } from 'class-validator'; +import { FixedDonation } from '../../entities/FixedDonation'; +import { UpdateDonation } from './UpdateDonation'; + +/** + * This class is used to update a FixedDonation entity (via put request). + */ +export class UpdateFixedDonation extends UpdateDonation { + /** + * The updated donation's amount. + * The unit is your currency's smallest unit (default: euro cent). + */ + @IsInt() + @IsPositive() + amount: number; + + /** + * Update a FixedDonation entity based on this. + * @param donation The donation that shall be updated. + */ + public async update(donation: FixedDonation): Promise { + donation.amount = this.amount; + donation.donor = await this.getDonor(); + + return donation; + } +} \ No newline at end of file From 72c3fc78b3f7a960496fd7737c5aac8e9880db45 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 12 Jan 2021 19:03:33 +0100 Subject: [PATCH 16/29] Added the basics for distance donation updateing ref #66 --- src/controllers/DonationController.ts | 40 +++++++++++++-------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/controllers/DonationController.ts b/src/controllers/DonationController.ts index 2d474f6..9bd4b96 100644 --- a/src/controllers/DonationController.ts +++ b/src/controllers/DonationController.ts @@ -87,7 +87,7 @@ export class DonationController { @ResponseSchema(DonorNotFoundError, { statusCode: 404 }) @ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) @ResponseSchema(DonationIdsNotMatchingError, { statusCode: 406 }) - @OpenAPI({ description: "Update the fixed donation (not distance donation - use /donations/fixed instead) whose id you provided.
Please remember that ids can't be changed and amounts must be positive." }) + @OpenAPI({ description: "Update the fixed donation (not distance donation - use /donations/distance instead) whose id you provided.
Please remember that ids can't be changed and amounts must be positive." }) async putFixed(@Param('id') id: number, @Body({ validate: true }) donation: UpdateFixedDonation) { let oldDonation = await this.fixedDonationRepository.findOne({ id: id }); @@ -103,28 +103,28 @@ export class DonationController { return (await this.donationRepository.findOne({ id: donation.id }, { relations: ['donor'] })).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 }); + @Put('/distance/:id') + @Authorized("DONATION:UPDATE") + @ResponseSchema(ResponseDonation) + @ResponseSchema(DonationNotFoundError, { statusCode: 404 }) + @ResponseSchema(DonorNotFoundError, { statusCode: 404 }) + @ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) + @ResponseSchema(DonationIdsNotMatchingError, { statusCode: 406 }) + @OpenAPI({ description: "Update the distance donation (not fixed donation - use /donations/fixed instead) whose id you provided.
Please remember that ids can't be changed and amountPerDistance must be positive." }) + async putDistance(@Param('id') id: number, @Body({ validate: true }) donation: UpdateDistanceDonation) { + let oldDonation = await this.distanceDonationRepository.findOne({ id: id }); - // if (!oldScan) { - // throw new ScanNotFoundError(); - // } + if (!oldDonation) { + throw new DonationNotFoundError(); + } - // if (oldScan.id != scan.id) { - // throw new ScanIdsNotMatchingError(); - // } + if (oldDonation.id != donation.id) { + throw new DonationIdsNotMatchingError(); + } - // await this.trackScanRepository.save(await scan.update(oldScan)); - // return (await this.scanRepository.findOne({ id: id }, { relations: ['runner', 'track', 'runner.scans', 'runner.scans.track', 'card', 'station'] })).toResponse(); - // } + await this.distanceDonationRepository.save(await donation.update(oldDonation)); + return (await this.donationRepository.findOne({ id: donation.id }, { relations: ['runner', 'donor', 'runner.scans', 'runner.scans.track'] })).toResponse(); + } @Delete('/:id') @Authorized("DONATION:DELETE") From f7370bc8025f9a29b4c046ec1dd28b128398dab9 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 12 Jan 2021 19:06:26 +0100 Subject: [PATCH 17/29] Implemented distance donation updateing ref #66 --- src/controllers/DonationController.ts | 1 + .../actions/update/UpdateDistanceDonation.ts | 51 +++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 src/models/actions/update/UpdateDistanceDonation.ts diff --git a/src/controllers/DonationController.ts b/src/controllers/DonationController.ts index 9bd4b96..9c2696d 100644 --- a/src/controllers/DonationController.ts +++ b/src/controllers/DonationController.ts @@ -6,6 +6,7 @@ import { DonorNotFoundError } from '../errors/DonorErrors'; import { RunnerNotFoundError } from '../errors/RunnerErrors'; import { CreateDistanceDonation } from '../models/actions/create/CreateDistanceDonation'; import { CreateFixedDonation } from '../models/actions/create/CreateFixedDonation'; +import { UpdateDistanceDonation } from '../models/actions/update/UpdateDistanceDonation'; import { UpdateFixedDonation } from '../models/actions/update/UpdateFixedDonation'; import { DistanceDonation } from '../models/entities/DistanceDonation'; import { Donation } from '../models/entities/Donation'; diff --git a/src/models/actions/update/UpdateDistanceDonation.ts b/src/models/actions/update/UpdateDistanceDonation.ts new file mode 100644 index 0000000..85a5473 --- /dev/null +++ b/src/models/actions/update/UpdateDistanceDonation.ts @@ -0,0 +1,51 @@ +import { IsInt, IsPositive } from 'class-validator'; +import { getConnection } from 'typeorm'; +import { RunnerNotFoundError } from '../../../errors/RunnerErrors'; +import { DistanceDonation } from '../../entities/DistanceDonation'; +import { Runner } from '../../entities/Runner'; +import { UpdateDonation } from './UpdateDonation'; + +/** + * This class is used to update a DistanceDonation entity (via put request). + */ +export class UpdateDistanceDonation extends UpdateDonation { + + /** + * The donation's associated runner. + * This is important to link the runner's distance ran to the donation. + */ + @IsInt() + @IsPositive() + runner: number; + + /** + * The donation's amount per distance (full kilometer aka 1000 meters). + * The unit is your currency's smallest unit (default: euro cent). + */ + @IsInt() + @IsPositive() + amountPerDistance: number; + + /** + * Update a DistanceDonation entity based on this. + * @param donation The donation that shall be updated. + */ + public async update(donation: DistanceDonation): Promise { + donation.amountPerDistance = this.amountPerDistance; + donation.donor = await this.getDonor(); + donation.runner = await this.getRunner(); + + return donation; + } + + /** + * 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; + } +} \ No newline at end of file From e716fae1c5eec625e6d050ac8893dfbe2ff1d820 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 12 Jan 2021 19:33:54 +0100 Subject: [PATCH 18/29] Implmented cascading donation deletion for runners and donors ref #66 --- src/controllers/DonorController.ts | 12 ++++++++++-- src/controllers/RunnerController.ts | 17 ++++++++++++++--- src/errors/DonorErrors.ts | 11 +++++++++++ src/errors/RunnerErrors.ts | 11 +++++++++++ 4 files changed, 46 insertions(+), 5 deletions(-) diff --git a/src/controllers/DonorController.ts b/src/controllers/DonorController.ts index f6b8527..59e6b04 100644 --- a/src/controllers/DonorController.ts +++ b/src/controllers/DonorController.ts @@ -1,12 +1,13 @@ 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 { DonorHasDonationsError, DonorIdsNotMatchingError, DonorNotFoundError } from '../errors/DonorErrors'; import { CreateDonor } from '../models/actions/create/CreateDonor'; import { UpdateDonor } from '../models/actions/update/UpdateDonor'; import { Donor } from '../models/entities/Donor'; import { ResponseDonor } from '../models/responses/ResponseDonor'; import { ResponseEmpty } from '../models/responses/ResponseEmpty'; +import { DonationController } from './DonationController'; @JsonController('/donors') @OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) @@ -97,7 +98,14 @@ export class DonorController { throw new DonorNotFoundError(); } - //TODO: DELETE DONATIONS AND WARN FOR FORCE (https://git.odit.services/lfk/backend/issues/66) + const donorDonations = (await this.donorRepository.findOne({ id: donor.id }, { relations: ["donations"] })).donations; + if (donorDonations.length > 0 && !force) { + throw new DonorHasDonationsError(); + } + const donationController = new DonationController(); + for (let donation of donorDonations) { + await donationController.remove(donation.id, force); + } await this.donorRepository.delete(donor); return new ResponseDonor(responseDonor); diff --git a/src/controllers/RunnerController.ts b/src/controllers/RunnerController.ts index 2d83a02..389697b 100644 --- a/src/controllers/RunnerController.ts +++ b/src/controllers/RunnerController.ts @@ -1,13 +1,14 @@ 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 { RunnerGroupNeededError, RunnerIdsNotMatchingError, RunnerNotFoundError } from '../errors/RunnerErrors'; +import { RunnerGroupNeededError, RunnerHasDistanceDonationsError, RunnerIdsNotMatchingError, RunnerNotFoundError } from '../errors/RunnerErrors'; import { RunnerGroupNotFoundError } from '../errors/RunnerGroupErrors'; import { CreateRunner } from '../models/actions/create/CreateRunner'; import { UpdateRunner } from '../models/actions/update/UpdateRunner'; import { Runner } from '../models/entities/Runner'; import { ResponseEmpty } from '../models/responses/ResponseEmpty'; import { ResponseRunner } from '../models/responses/ResponseRunner'; +import { DonationController } from './DonationController'; import { RunnerCardController } from './RunnerCardController'; import { ScanController } from './ScanController'; @@ -91,6 +92,7 @@ export class RunnerController { @Authorized("RUNNER:DELETE") @ResponseSchema(ResponseRunner) @ResponseSchema(ResponseEmpty, { statusCode: 204 }) + @ResponseSchema(RunnerHasDistanceDonationsError, { statusCode: 406 }) @OnUndefined(204) @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) { @@ -102,10 +104,19 @@ export class RunnerController { throw new RunnerNotFoundError(); } + const runnerDonations = (await this.runnerRepository.findOne({ id: runner.id }, { relations: ["distanceDonations"] })).distanceDonations; + if (runnerDonations.length > 0 && !force) { + throw new RunnerHasDistanceDonationsError(); + } + const donationController = new DonationController(); + for (let donation of runnerDonations) { + await donationController.remove(donation.id, force); + } + 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); + for (let card of runnerCards) { + await cardController.remove(card.id, force); } const runnerScans = (await this.runnerRepository.findOne({ id: runner.id }, { relations: ["scans"] })).scans; diff --git a/src/errors/DonorErrors.ts b/src/errors/DonorErrors.ts index 0cd534e..bdf505a 100644 --- a/src/errors/DonorErrors.ts +++ b/src/errors/DonorErrors.ts @@ -33,4 +33,15 @@ export class DonorReceiptAddressNeededError extends NotAcceptableError { @IsString() message = "An address is needed to create a receipt for a donor. \n You didn't provide one." +} + +/** +* Error to throw when a donor still has donations associated. +*/ +export class DonorHasDonationsError extends NotAcceptableError { + @IsString() + name = "DonorHasDonationsError" + + @IsString() + message = "This donor still has donations associated with it. \n If you want to delete this donor with all it's donations and teams add `?force` to your query." } \ No newline at end of file diff --git a/src/errors/RunnerErrors.ts b/src/errors/RunnerErrors.ts index b60a70d..4dad85f 100644 --- a/src/errors/RunnerErrors.ts +++ b/src/errors/RunnerErrors.ts @@ -33,4 +33,15 @@ export class RunnerGroupNeededError extends NotAcceptableError { @IsString() message = "Runner's need to be part of one group (team or organisation)! \n You provided neither." +} + +/** +* Error to throw when a runner still has distance donations associated. +*/ +export class RunnerHasDistanceDonationsError extends NotAcceptableError { + @IsString() + name = "RunnerHasDistanceDonationsError" + + @IsString() + message = "This runner still has distance donations associated with it. \n If you want to delete this runner with all it's donations and teams add `?force` to your query." } \ No newline at end of file From 63506dac1c86e4bf4cfae9d4b94d98ac3856bbaa Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 12 Jan 2021 19:44:15 +0100 Subject: [PATCH 19/29] Added donation get tests ref #66 --- src/tests/donations/donations_get.spec.ts | 108 ++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 src/tests/donations/donations_get.spec.ts diff --git a/src/tests/donations/donations_get.spec.ts b/src/tests/donations/donations_get.spec.ts new file mode 100644 index 0000000..fe179a0 --- /dev/null +++ b/src/tests/donations/donations_get.spec.ts @@ -0,0 +1,108 @@ +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/donations sucessfully', () => { + it('basic get should return 200', async () => { + const res = await axios.get(base + '/api/donations', axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); +}); +// --------------- +describe('GET /api/donations illegally', () => { + it('get for non-existant track should return 404', async () => { + const res = await axios.get(base + '/api/donations/-1', axios_config); + expect(res.status).toEqual(404); + expect(res.headers['content-type']).toContain("application/json") + }); +}); +// --------------- +describe('adding + getting fixed donation', () => { + let added_donor; + let added_donation; + it('creating a new donor with only needed params should return 200', async () => { + const res = await axios.post(base + '/api/donors', { + "firstname": "first", + "lastname": "last" + }, axios_config); + added_donor = res.data + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new fixed donation should return 200', async () => { + const res = await axios.post(base + '/api/donations/fixed', { + "donor": added_donor.id, + "amount": 1000 + }, axios_config); + added_donation = 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/donations/' + added_donation.id, axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json"); + }); +}); +// --------------- +describe('adding + getting distance donation', () => { + let added_donor; + let added_org; + let added_runner; + let added_donation; + it('creating a new donor with only needed params should return 200', async () => { + const res = await axios.post(base + '/api/donors', { + "firstname": "first", + "lastname": "last" + }, axios_config); + added_donor = res.data + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new org with just a name should return 200', async () => { + const res = await axios.post(base + '/api/organisations', { + "name": "test123" + }, axios_config); + added_org = res.data + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new runner with only needed params should return 200', async () => { + const res = await axios.post(base + '/api/runners', { + "firstname": "first", + "lastname": "last", + "group": added_org.id + }, axios_config); + added_runner = res.data; + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new fixed donation should return 200', async () => { + const res = await axios.post(base + '/api/donations/distance', { + "runner": added_runner.id, + "amountPerDistance": 100, + "donor": added_donor.id + }, axios_config); + added_donation = 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/donations/' + added_donation.id, axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json"); + }); +}); \ No newline at end of file From 71537b283fed68a311c355718038dad702574db7 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 12 Jan 2021 19:53:03 +0100 Subject: [PATCH 20/29] Added donation delete tests ref #66 --- src/tests/donations/donations_delete.spec.ts | 113 +++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 src/tests/donations/donations_delete.spec.ts diff --git a/src/tests/donations/donations_delete.spec.ts b/src/tests/donations/donations_delete.spec.ts new file mode 100644 index 0000000..66be60e --- /dev/null +++ b/src/tests/donations/donations_delete.spec.ts @@ -0,0 +1,113 @@ +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 (non-existant)', () => { + it('delete', async () => { + const res = await axios.delete(base + '/api/scans/0', axios_config); + expect(res.status).toEqual(204); + }); +}); +// --------------- +describe('DELETE fixed donation', () => { + let added_donor; + let added_donation; + it('creating a new donor with only needed params should return 200', async () => { + const res = await axios.post(base + '/api/donors', { + "firstname": "first", + "lastname": "last" + }, axios_config); + added_donor = res.data + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new fixed donation should return 200', async () => { + const res = await axios.post(base + '/api/donations/fixed', { + "donor": added_donor.id, + "amount": 1000 + }, axios_config); + added_donation = res.data; + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('delete donation', async () => { + const res = await axios.delete(base + '/api/donations/' + added_donation.id, axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + expect(res.data).toEqual(added_donation); + }); + it('check if donation really was deleted', async () => { + const res = await axios.get(base + '/api/donations/' + added_donation.id, axios_config); + expect(res.status).toEqual(404); + expect(res.headers['content-type']).toContain("application/json") + }); +}); +// --------------- +describe('DELETE distance donation', () => { + let added_donor; + let added_org; + let added_runner; + let added_donation; + it('creating a new donor with only needed params should return 200', async () => { + const res = await axios.post(base + '/api/donors', { + "firstname": "first", + "lastname": "last" + }, axios_config); + added_donor = res.data + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new org with just a name should return 200', async () => { + const res = await axios.post(base + '/api/organisations', { + "name": "test123" + }, axios_config); + added_org = res.data + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new runner with only needed params should return 200', async () => { + const res = await axios.post(base + '/api/runners', { + "firstname": "first", + "lastname": "last", + "group": added_org.id + }, axios_config); + added_runner = res.data; + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new fixed donation should return 200', async () => { + const res = await axios.post(base + '/api/donations/distance', { + "runner": added_runner.id, + "amountPerDistance": 100, + "donor": added_donor.id + }, axios_config); + delete res.data.runner.distance; + added_donation = res.data; + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('delete donation', async () => { + const res = await axios.delete(base + '/api/donations/' + added_donation.id, axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + delete res.data.runner.distance; + expect(res.data).toEqual(added_donation); + }); + it('check if donation really was deleted', async () => { + const res = await axios.get(base + '/api/donations/' + added_donation.id, axios_config); + expect(res.status).toEqual(404); + expect(res.headers['content-type']).toContain("application/json") + }); +}); \ No newline at end of file From 4375ca92d3a2aec3b7243049cd66ac1f3248b55e Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 12 Jan 2021 20:00:02 +0100 Subject: [PATCH 21/29] Added cascading donor deletion tests ref #66 --- src/tests/donations/donations_delete.spec.ts | 2 +- src/tests/donors/donor_delete.spec.ts | 110 +++++++++++++++++++ 2 files changed, 111 insertions(+), 1 deletion(-) diff --git a/src/tests/donations/donations_delete.spec.ts b/src/tests/donations/donations_delete.spec.ts index 66be60e..f5ef564 100644 --- a/src/tests/donations/donations_delete.spec.ts +++ b/src/tests/donations/donations_delete.spec.ts @@ -87,7 +87,7 @@ describe('DELETE distance donation', () => { expect(res.status).toEqual(200); expect(res.headers['content-type']).toContain("application/json") }); - it('creating a new fixed donation should return 200', async () => { + it('creating a new distance donation should return 200', async () => { const res = await axios.post(base + '/api/donations/distance', { "runner": added_runner.id, "amountPerDistance": 100, diff --git a/src/tests/donors/donor_delete.spec.ts b/src/tests/donors/donor_delete.spec.ts index 63e10fe..344dc20 100644 --- a/src/tests/donors/donor_delete.spec.ts +++ b/src/tests/donors/donor_delete.spec.ts @@ -44,4 +44,114 @@ describe('add+delete', () => { expect(res4.status).toEqual(404); expect(res4.headers['content-type']).toContain("application/json") }); +}); +// --------------- +describe('DELETE donor with donations invalid', () => { + let added_donor; + let added_org; + let added_runner; + it('creating a new donor with only needed params should return 200', async () => { + const res = await axios.post(base + '/api/donors', { + "firstname": "first", + "lastname": "last" + }, axios_config); + added_donor = res.data + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new org with just a name should return 200', async () => { + const res = await axios.post(base + '/api/organisations', { + "name": "test123" + }, axios_config); + added_org = res.data + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new runner with only needed params should return 200', async () => { + const res = await axios.post(base + '/api/runners', { + "firstname": "first", + "lastname": "last", + "group": added_org.id + }, axios_config); + added_runner = res.data; + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new fixed donation should return 200', async () => { + const res = await axios.post(base + '/api/donations/fixed', { + "donor": added_donor.id, + "amount": 1000 + }, axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new distance donation should return 200', async () => { + const res = await axios.post(base + '/api/donations/distance', { + "runner": added_runner.id, + "amountPerDistance": 100, + "donor": added_donor.id + }, axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('delete donor w/o force', async () => { + const res = await axios.delete(base + '/api/donors/' + added_donor.id, axios_config); + expect(res.status).toEqual(406); + expect(res.headers['content-type']).toContain("application/json") + }); +}); +// --------------- +describe('DELETE donor with donations valid', () => { + let added_donor; + let added_org; + let added_runner; + it('creating a new donor with only needed params should return 200', async () => { + const res = await axios.post(base + '/api/donors', { + "firstname": "first", + "lastname": "last" + }, axios_config); + added_donor = res.data + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new org with just a name should return 200', async () => { + const res = await axios.post(base + '/api/organisations', { + "name": "test123" + }, axios_config); + added_org = res.data + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new runner with only needed params should return 200', async () => { + const res = await axios.post(base + '/api/runners', { + "firstname": "first", + "lastname": "last", + "group": added_org.id + }, axios_config); + added_runner = res.data; + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new fixed donation should return 200', async () => { + const res = await axios.post(base + '/api/donations/fixed', { + "donor": added_donor.id, + "amount": 1000 + }, axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new distance donation should return 200', async () => { + const res = await axios.post(base + '/api/donations/distance', { + "runner": added_runner.id, + "amountPerDistance": 100, + "donor": added_donor.id + }, axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('delete donor w/ force ', async () => { + const res = await axios.delete(base + '/api/donors/' + added_donor.id + "?force=true", axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); }); \ No newline at end of file From b729a7ceadf25787066ddc4d9cb5f08d140c4cd8 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 12 Jan 2021 20:01:56 +0100 Subject: [PATCH 22/29] Added cascading runner deletion tests ref #66 --- src/tests/runners/runner_delete.spec.ts | 94 +++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/src/tests/runners/runner_delete.spec.ts b/src/tests/runners/runner_delete.spec.ts index 36e72e7..de96333 100644 --- a/src/tests/runners/runner_delete.spec.ts +++ b/src/tests/runners/runner_delete.spec.ts @@ -55,4 +55,98 @@ describe('add+delete', () => { expect(res4.status).toEqual(404); expect(res4.headers['content-type']).toContain("application/json") }); +}); +// --------------- +describe('DELETE donor with donations invalid', () => { + let added_donor; + let added_org; + let added_runner; + it('creating a new donor with only needed params should return 200', async () => { + const res = await axios.post(base + '/api/donors', { + "firstname": "first", + "lastname": "last" + }, axios_config); + added_donor = res.data + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new org with just a name should return 200', async () => { + const res = await axios.post(base + '/api/organisations', { + "name": "test123" + }, axios_config); + added_org = res.data + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new runner with only needed params should return 200', async () => { + const res = await axios.post(base + '/api/runners', { + "firstname": "first", + "lastname": "last", + "group": added_org.id + }, axios_config); + added_runner = res.data; + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new distance donation should return 200', async () => { + const res = await axios.post(base + '/api/donations/distance', { + "runner": added_runner.id, + "amountPerDistance": 100, + "donor": added_donor.id + }, axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('delete runner w/o force', async () => { + const res = await axios.delete(base + '/api/runners/' + added_runner.id, axios_config); + expect(res.status).toEqual(406); + expect(res.headers['content-type']).toContain("application/json") + }); +}); +// --------------- +describe('DELETE donor with donations valid', () => { + let added_donor; + let added_org; + let added_runner; + it('creating a new donor with only needed params should return 200', async () => { + const res = await axios.post(base + '/api/donors', { + "firstname": "first", + "lastname": "last" + }, axios_config); + added_donor = res.data + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new org with just a name should return 200', async () => { + const res = await axios.post(base + '/api/organisations', { + "name": "test123" + }, axios_config); + added_org = res.data + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new runner with only needed params should return 200', async () => { + const res = await axios.post(base + '/api/runners', { + "firstname": "first", + "lastname": "last", + "group": added_org.id + }, axios_config); + added_runner = res.data; + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new distance donation should return 200', async () => { + const res = await axios.post(base + '/api/donations/distance', { + "runner": added_runner.id, + "amountPerDistance": 100, + "donor": added_donor.id + }, axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('delete donor w/ force ', async () => { + const res = await axios.delete(base + '/api/runners/' + added_runner.id + "?force=true", axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); }); \ No newline at end of file From 4a0f75044f728fadd274b4ffaee7ebb01652176a Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 12 Jan 2021 20:09:00 +0100 Subject: [PATCH 23/29] Added donation add invalid tests ref #66 --- src/tests/donations/donations_add.spec.ts | 331 ++++++++++++++++++++++ 1 file changed, 331 insertions(+) create mode 100644 src/tests/donations/donations_add.spec.ts diff --git a/src/tests/donations/donations_add.spec.ts b/src/tests/donations/donations_add.spec.ts new file mode 100644 index 0000000..5f23a0b --- /dev/null +++ b/src/tests/donations/donations_add.spec.ts @@ -0,0 +1,331 @@ +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/donations illegally', () => { + it('posting to a non-existant endpoint should return 4040', async () => { + const res1 = await axios.post(base + '/api/donations', null, axios_config); + expect(res1.status).toEqual(404); + }); +}); +// --------------- +describe('POST /api/donations/fixed illegally', () => { + let added_donor; + it('creating a new donor with only needed params should return 200', async () => { + const res = await axios.post(base + '/api/donors', { + "firstname": "first", + "lastname": "last" + }, axios_config); + added_donor = 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/donations/fixed', null, axios_config); + expect(res.status).toEqual(400); + expect(res.headers['content-type']).toContain("application/json") + }); + it('no donor should return 400', async () => { + const res = await axios.post(base + '/api/donations/fixed', { + "amount": 100 + }, axios_config); + expect(res.status).toEqual(400); + expect(res.headers['content-type']).toContain("application/json") + }); + it('no amount should return 400', async () => { + const res = await axios.post(base + '/api/donations/fixed', { + "donor": added_donor.id + }, axios_config); + expect(res.status).toEqual(400); + expect(res.headers['content-type']).toContain("application/json") + }); + it('illegal amount input should return 400', async () => { + const res = await axios.post(base + '/api/donations/fixed', { + "donor": added_donor.id, + "amount": -1 + }, axios_config); + expect(res.status).toEqual(400); + expect(res.headers['content-type']).toContain("application/json") + }); + it('invalid donor input should return 404', async () => { + const res = await axios.post(base + '/api/donations/fixed', { + "donor": 999999999999999999999999, + "amount": 100 + }, axios_config); + expect(res.status).toEqual(404); + expect(res.headers['content-type']).toContain("application/json") + }); +}); +// --------------- +describe('POST /api/donations/distance illegally', () => { + let added_donor; + let added_org; + let added_runner; + it('creating a new donor with only needed params should return 200', async () => { + const res = await axios.post(base + '/api/donors', { + "firstname": "first", + "lastname": "last" + }, axios_config); + added_donor = res.data + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new org with just a name should return 200', async () => { + const res = await axios.post(base + '/api/organisations', { + "name": "test123" + }, axios_config); + added_org = res.data + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new runner with only needed params should return 200', async () => { + const res = await axios.post(base + '/api/runners', { + "firstname": "first", + "lastname": "last", + "group": added_org.id + }, axios_config); + added_runner = 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/donations/distance', null, axios_config); + expect(res.status).toEqual(400); + expect(res.headers['content-type']).toContain("application/json") + }); + it('no donor should return 400', async () => { + const res = await axios.post(base + '/api/donations/distance', { + "runner": added_runner.id, + "amountPerDistance": 100, + }, axios_config); + expect(res.status).toEqual(400); + expect(res.headers['content-type']).toContain("application/json") + }); + it('no amountPerDistance should return 400', async () => { + const res = await axios.post(base + '/api/donations/distance', { + "runner": added_runner.id, + "donor": added_donor.id + }, axios_config); + expect(res.status).toEqual(400); + expect(res.headers['content-type']).toContain("application/json") + }); + it('no runner should return 400', async () => { + const res = await axios.post(base + '/api/donations/distance', { + "amountPerDistance": 100, + "donor": added_donor.id + }, axios_config); + expect(res.status).toEqual(400); + expect(res.headers['content-type']).toContain("application/json") + }); + it('illegal amountPerDistance input should return 400', async () => { + const res = await axios.post(base + '/api/donations/distance', { + "runner": added_runner.id, + "amountPerDistance": -1, + "donor": added_donor.id + }, axios_config); + expect(res.status).toEqual(400); + expect(res.headers['content-type']).toContain("application/json") + }); + it('invalid donor input should return 404', async () => { + const res = await axios.post(base + '/api/donations/distance', { + "donor": 999999999999999999999999, + "runner": added_runner.id, + "amountPerDistance": 100, + }, axios_config); + expect(res.status).toEqual(404); + expect(res.headers['content-type']).toContain("application/json") + }); + it('invalid runner input should return 404', async () => { + const res = await axios.post(base + '/api/donations/distance', { + "donor": added_donor.id, + "runner": 999999999999999999999999, + "amountPerDistance": 100, + }, 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; +// 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); +// 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") +// }); +// it('creating a scan with the minimum amount of parameters should return 200', async () => { +// const res = await axios.post(base + '/api/scans', { +// "runner": added_runner.id, +// "distance": 200 +// }, axios_config); +// 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, +// "valid": true +// }); +// }); +// it('creating a valid scan should return 200', async () => { +// const res = await axios.post(base + '/api/scans', { +// "runner": added_runner.id, +// "distance": 200, +// "valid": true +// }, axios_config); +// 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, +// "valid": true +// }); +// }); +// it('creating a invalid scan should return 200', async () => { +// const res = await axios.post(base + '/api/scans', { +// "runner": added_runner.id, +// "distance": 200, +// "valid": false +// }, axios_config); +// 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, +// "valid": false +// }); +// }); +// }); +// // --------------- +// describe('POST /api/scans successfully via scan station', () => { +// let added_org; +// let added_runner; +// 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); +// 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") +// }); +// 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); +// added_track = res.data; +// expect(res.status).toEqual(200); +// expect(res.headers['content-type']).toContain("application/json"); +// }); +// it('creating a station with minimum parameters should return 200', async () => { +// const res = await axios.post(base + '/api/stations', { +// "track": added_track.id +// }, axios_config); +// added_station = res.data; +// expect(res.status).toEqual(200); +// expect(res.headers['content-type']).toContain("application/json"); +// }); +// it('creating a scan with the minimum amount of parameters should return 200', async () => { +// const res = await axios.post(base + '/api/scans', { +// "runner": added_runner.id, +// "distance": 200 +// }, { +// headers: { "authorization": "Bearer " + added_station.key }, +// validateStatus: undefined +// }); +// 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, +// "valid": true +// }); +// }); +// it('creating a valid scan should return 200', async () => { +// const res = await axios.post(base + '/api/scans', { +// "runner": added_runner.id, +// "distance": 200, +// "valid": true +// }, { +// headers: { "authorization": "Bearer " + added_station.key }, +// validateStatus: undefined +// }); +// 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, +// "valid": true +// }); +// }); +// it('creating a invalid scan should return 200', async () => { +// const res = await axios.post(base + '/api/scans', { +// "runner": added_runner.id, +// "distance": 200, +// "valid": false +// }, { +// headers: { "authorization": "Bearer " + added_station.key }, +// validateStatus: undefined +// }); +// 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, +// "valid": false +// }); +// }); +// }); From badff85e287449f8b19d336b245427f83793a191 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 12 Jan 2021 20:14:23 +0100 Subject: [PATCH 24/29] Fixed typos ref #66 --- src/tests/donations/donations_delete.spec.ts | 4 ++-- src/tests/donations/donations_get.spec.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tests/donations/donations_delete.spec.ts b/src/tests/donations/donations_delete.spec.ts index f5ef564..c238836 100644 --- a/src/tests/donations/donations_delete.spec.ts +++ b/src/tests/donations/donations_delete.spec.ts @@ -14,9 +14,9 @@ beforeAll(async () => { }; }); -describe('DELETE scan (non-existant)', () => { +describe('DELETE donation (non-existant)', () => { it('delete', async () => { - const res = await axios.delete(base + '/api/scans/0', axios_config); + const res = await axios.delete(base + '/api/donations/0', axios_config); expect(res.status).toEqual(204); }); }); diff --git a/src/tests/donations/donations_get.spec.ts b/src/tests/donations/donations_get.spec.ts index fe179a0..8dd1a0f 100644 --- a/src/tests/donations/donations_get.spec.ts +++ b/src/tests/donations/donations_get.spec.ts @@ -51,7 +51,7 @@ describe('adding + getting fixed donation', () => { expect(res.status).toEqual(200); expect(res.headers['content-type']).toContain("application/json") }); - it('check if scans was added (no parameter validation)', async () => { + it('check if donation was added (no parameter validation)', async () => { const res = await axios.get(base + '/api/donations/' + added_donation.id, axios_config); expect(res.status).toEqual(200); expect(res.headers['content-type']).toContain("application/json"); @@ -100,7 +100,7 @@ describe('adding + getting distance donation', () => { expect(res.status).toEqual(200); expect(res.headers['content-type']).toContain("application/json") }); - it('check if scans was added (no parameter validation)', async () => { + it('check if donation was added (no parameter validation)', async () => { const res = await axios.get(base + '/api/donations/' + added_donation.id, axios_config); expect(res.status).toEqual(200); expect(res.headers['content-type']).toContain("application/json"); From e3e570e664c5a36d944fca3fb1214a18e474a254 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 12 Jan 2021 20:15:51 +0100 Subject: [PATCH 25/29] Added donation add validtests ref #66 --- src/tests/donations/donations_add.spec.ts | 249 +++++++--------------- 1 file changed, 77 insertions(+), 172 deletions(-) diff --git a/src/tests/donations/donations_add.spec.ts b/src/tests/donations/donations_add.spec.ts index 5f23a0b..523b767 100644 --- a/src/tests/donations/donations_add.spec.ts +++ b/src/tests/donations/donations_add.spec.ts @@ -157,175 +157,80 @@ describe('POST /api/donations/distance illegally', () => { expect(res.headers['content-type']).toContain("application/json") }); }); -// // --------------- -// describe('POST /api/scans successfully', () => { -// let added_org; -// let added_runner; -// 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); -// 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") -// }); -// it('creating a scan with the minimum amount of parameters should return 200', async () => { -// const res = await axios.post(base + '/api/scans', { -// "runner": added_runner.id, -// "distance": 200 -// }, axios_config); -// 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, -// "valid": true -// }); -// }); -// it('creating a valid scan should return 200', async () => { -// const res = await axios.post(base + '/api/scans', { -// "runner": added_runner.id, -// "distance": 200, -// "valid": true -// }, axios_config); -// 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, -// "valid": true -// }); -// }); -// it('creating a invalid scan should return 200', async () => { -// const res = await axios.post(base + '/api/scans', { -// "runner": added_runner.id, -// "distance": 200, -// "valid": false -// }, axios_config); -// 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, -// "valid": false -// }); -// }); -// }); -// // --------------- -// describe('POST /api/scans successfully via scan station', () => { -// let added_org; -// let added_runner; -// 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); -// 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") -// }); -// 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); -// added_track = res.data; -// expect(res.status).toEqual(200); -// expect(res.headers['content-type']).toContain("application/json"); -// }); -// it('creating a station with minimum parameters should return 200', async () => { -// const res = await axios.post(base + '/api/stations', { -// "track": added_track.id -// }, axios_config); -// added_station = res.data; -// expect(res.status).toEqual(200); -// expect(res.headers['content-type']).toContain("application/json"); -// }); -// it('creating a scan with the minimum amount of parameters should return 200', async () => { -// const res = await axios.post(base + '/api/scans', { -// "runner": added_runner.id, -// "distance": 200 -// }, { -// headers: { "authorization": "Bearer " + added_station.key }, -// validateStatus: undefined -// }); -// 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, -// "valid": true -// }); -// }); -// it('creating a valid scan should return 200', async () => { -// const res = await axios.post(base + '/api/scans', { -// "runner": added_runner.id, -// "distance": 200, -// "valid": true -// }, { -// headers: { "authorization": "Bearer " + added_station.key }, -// validateStatus: undefined -// }); -// 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, -// "valid": true -// }); -// }); -// it('creating a invalid scan should return 200', async () => { -// const res = await axios.post(base + '/api/scans', { -// "runner": added_runner.id, -// "distance": 200, -// "valid": false -// }, { -// headers: { "authorization": "Bearer " + added_station.key }, -// validateStatus: undefined -// }); -// 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, -// "valid": false -// }); -// }); -// }); +// --------------- +describe('POST /api/donations/fixed successfully', () => { + let added_donor; + it('creating a new donor with only needed params should return 200', async () => { + const res = await axios.post(base + '/api/donors', { + "firstname": "first", + "lastname": "last" + }, axios_config); + added_donor = res.data + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new fixed donation should return 200', async () => { + const res = await axios.post(base + '/api/donations/fixed', { + "donor": added_donor.id, + "amount": 1000 + }, axios_config); + delete res.data.id; + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json"); + expect(res.data).toEqual({ + "donor": added_donor, + "amount": 1000 + }); + }); +}); +// --------------- +describe('POST /api/donations/distance successfully', () => { + let added_donor; + let added_org; + let added_runner; + let added_donation; + it('creating a new donor with only needed params should return 200', async () => { + const res = await axios.post(base + '/api/donors', { + "firstname": "first", + "lastname": "last" + }, axios_config); + added_donor = res.data + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new org with just a name should return 200', async () => { + const res = await axios.post(base + '/api/organisations', { + "name": "test123" + }, axios_config); + added_org = res.data + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new runner with only needed params should return 200', async () => { + const res = await axios.post(base + '/api/runners', { + "firstname": "first", + "lastname": "last", + "group": added_org.id + }, axios_config); + delete res.data.group; + added_runner = res.data; + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new fixed donation should return 200', async () => { + const res = await axios.post(base + '/api/donations/distance', { + "runner": added_runner.id, + "amountPerDistance": 100, + "donor": added_donor.id + }, axios_config); + delete res.data.id; + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json"); + expect(res.data).toEqual({ + "donor": added_donor, + "amountPerDistance": 100, + "runner": added_runner, + "amount": 0 + }) + }); +}); \ No newline at end of file From a513bf13ca8d04952ba0f72905fd5306c9fd9c87 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Tue, 12 Jan 2021 20:43:07 +0100 Subject: [PATCH 26/29] Added donation update invalid tests ref #66 --- src/tests/donations/donations_update.spec.ts | 159 +++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 src/tests/donations/donations_update.spec.ts diff --git a/src/tests/donations/donations_update.spec.ts b/src/tests/donations/donations_update.spec.ts new file mode 100644 index 0000000..4af9962 --- /dev/null +++ b/src/tests/donations/donations_update.spec.ts @@ -0,0 +1,159 @@ +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 fixed donation illegally', () => { + let added_donor; + let added_donation; + it('creating a new donor with only needed params should return 200', async () => { + const res = await axios.post(base + '/api/donors', { + "firstname": "first", + "lastname": "last" + }, axios_config); + added_donor = res.data + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new fixed donation should return 200', async () => { + const res = await axios.post(base + '/api/donations/fixed', { + "donor": added_donor.id, + "amount": 1000 + }, axios_config); + added_donation = 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/donations/fixed/' + added_donation.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/donations/fixed/' + added_donation.id, { + "id": added_donation.id + 1, + "donor": added_donor.id, + "amount": 100 + }, axios_config); + expect(res2.status).toEqual(406); + expect(res2.headers['content-type']).toContain("application/json") + }); + it('updating with negative amount should return 400', async () => { + const res2 = await axios.put(base + '/api/donations/fixed/' + added_donation.id, { + "id": added_donation.id, + "donor": added_donor.id, + "amount": -1 + }, axios_config); + expect(res2.status).toEqual(400); + expect(res2.headers['content-type']).toContain("application/json") + }); + it('updating with invalid donor should return 404', async () => { + const res2 = await axios.put(base + '/api/donations/fixed/' + added_donation.id, { + "id": added_donation.id, + "donor": 9999999999999999999, + "amount": 100 + }, axios_config); + expect(res2.status).toEqual(404); + expect(res2.headers['content-type']).toContain("application/json") + }); +}); +// --------------- +describe('adding + updating distance donation illegally', () => { + let added_donor; + let added_org; + let added_runner; + let added_donation; + it('creating a new donor with only needed params should return 200', async () => { + const res = await axios.post(base + '/api/donors', { + "firstname": "first", + "lastname": "last" + }, axios_config); + added_donor = res.data + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new org with just a name should return 200', async () => { + const res = await axios.post(base + '/api/organisations', { + "name": "test123" + }, axios_config); + added_org = res.data + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new runner with only needed params should return 200', async () => { + const res = await axios.post(base + '/api/runners', { + "firstname": "first", + "lastname": "last", + "group": added_org.id + }, axios_config); + added_runner = res.data; + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new distance donation should return 200', async () => { + const res = await axios.post(base + '/api/donations/distance', { + "runner": added_runner.id, + "amountPerDistance": 100, + "donor": added_donor.id + }, axios_config); + added_donation = 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/donations/distance/' + added_donation.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/donations/distance/' + added_donation.id, { + "id": added_donation.id + 1, + "runner": added_runner.id, + "amountPerDistance": 100, + "donor": added_donor.id + }, axios_config); + expect(res2.status).toEqual(406); + expect(res2.headers['content-type']).toContain("application/json") + }); + it('updating with negative amountPerDistance should return 400', async () => { + const res2 = await axios.put(base + '/api/donations/distance/' + added_donation.id, { + "id": added_donation.id, + "runner": added_runner.id, + "amountPerDistance": -1, + "donor": added_donor.id + }, axios_config); + expect(res2.status).toEqual(400); + expect(res2.headers['content-type']).toContain("application/json") + }); + it('updating with invalid donor should return 404', async () => { + const res2 = await axios.put(base + '/api/donations/distance/' + added_donation.id, { + "id": added_donation.id, + "runner": added_runner.id, + "amountPerDistance": 100, + "donor": 9999999999999999999 + }, 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/donations/distance/' + added_donation.id, { + "id": added_donation.id, + "runner": 9999999999999999999, + "amountPerDistance": 100, + "donor": added_donor.id + }, axios_config); + expect(res2.status).toEqual(404); + expect(res2.headers['content-type']).toContain("application/json") + }); +}); \ No newline at end of file From cd5e4bbd6060debf37e6b64b6e42a95afedde881 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Wed, 13 Jan 2021 17:19:57 +0100 Subject: [PATCH 27/29] Added donation update validtests ref #66 --- src/tests/donations/donations_update.spec.ts | 235 ++++++++++++++++--- 1 file changed, 208 insertions(+), 27 deletions(-) diff --git a/src/tests/donations/donations_update.spec.ts b/src/tests/donations/donations_update.spec.ts index 4af9962..cd45b54 100644 --- a/src/tests/donations/donations_update.spec.ts +++ b/src/tests/donations/donations_update.spec.ts @@ -36,36 +36,36 @@ describe('adding + updating fixed donation illegally', () => { expect(res.headers['content-type']).toContain("application/json") }); it('updating empty should return 400', async () => { - const res2 = await axios.put(base + '/api/donations/fixed/' + added_donation.id, null, axios_config); - expect(res2.status).toEqual(400); - expect(res2.headers['content-type']).toContain("application/json") + const res = await axios.put(base + '/api/donations/fixed/' + added_donation.id, null, axios_config); + expect(res.status).toEqual(400); + expect(res.headers['content-type']).toContain("application/json") }); it('updating with wrong id should return 406', async () => { - const res2 = await axios.put(base + '/api/donations/fixed/' + added_donation.id, { + const res = await axios.put(base + '/api/donations/fixed/' + added_donation.id, { "id": added_donation.id + 1, "donor": added_donor.id, "amount": 100 }, axios_config); - expect(res2.status).toEqual(406); - expect(res2.headers['content-type']).toContain("application/json") + expect(res.status).toEqual(406); + expect(res.headers['content-type']).toContain("application/json") }); it('updating with negative amount should return 400', async () => { - const res2 = await axios.put(base + '/api/donations/fixed/' + added_donation.id, { + const res = await axios.put(base + '/api/donations/fixed/' + added_donation.id, { "id": added_donation.id, "donor": added_donor.id, "amount": -1 }, axios_config); - expect(res2.status).toEqual(400); - expect(res2.headers['content-type']).toContain("application/json") + expect(res.status).toEqual(400); + expect(res.headers['content-type']).toContain("application/json") }); it('updating with invalid donor should return 404', async () => { - const res2 = await axios.put(base + '/api/donations/fixed/' + added_donation.id, { + const res = await axios.put(base + '/api/donations/fixed/' + added_donation.id, { "id": added_donation.id, "donor": 9999999999999999999, "amount": 100 }, axios_config); - expect(res2.status).toEqual(404); - expect(res2.headers['content-type']).toContain("application/json") + expect(res.status).toEqual(404); + expect(res.headers['content-type']).toContain("application/json") }); }); // --------------- @@ -112,48 +112,229 @@ describe('adding + updating distance donation illegally', () => { expect(res.headers['content-type']).toContain("application/json") }); it('updating empty should return 400', async () => { - const res2 = await axios.put(base + '/api/donations/distance/' + added_donation.id, null, axios_config); - expect(res2.status).toEqual(400); - expect(res2.headers['content-type']).toContain("application/json") + const res = await axios.put(base + '/api/donations/distance/' + added_donation.id, null, axios_config); + expect(res.status).toEqual(400); + expect(res.headers['content-type']).toContain("application/json") }); it('updating with wrong id should return 406', async () => { - const res2 = await axios.put(base + '/api/donations/distance/' + added_donation.id, { + const res = await axios.put(base + '/api/donations/distance/' + added_donation.id, { "id": added_donation.id + 1, "runner": added_runner.id, "amountPerDistance": 100, "donor": added_donor.id }, axios_config); - expect(res2.status).toEqual(406); - expect(res2.headers['content-type']).toContain("application/json") + expect(res.status).toEqual(406); + expect(res.headers['content-type']).toContain("application/json") }); it('updating with negative amountPerDistance should return 400', async () => { - const res2 = await axios.put(base + '/api/donations/distance/' + added_donation.id, { + const res = await axios.put(base + '/api/donations/distance/' + added_donation.id, { "id": added_donation.id, "runner": added_runner.id, "amountPerDistance": -1, "donor": added_donor.id }, axios_config); - expect(res2.status).toEqual(400); - expect(res2.headers['content-type']).toContain("application/json") + expect(res.status).toEqual(400); + expect(res.headers['content-type']).toContain("application/json") }); it('updating with invalid donor should return 404', async () => { - const res2 = await axios.put(base + '/api/donations/distance/' + added_donation.id, { + const res = await axios.put(base + '/api/donations/distance/' + added_donation.id, { "id": added_donation.id, "runner": added_runner.id, "amountPerDistance": 100, "donor": 9999999999999999999 }, axios_config); - expect(res2.status).toEqual(404); - expect(res2.headers['content-type']).toContain("application/json") + expect(res.status).toEqual(404); + expect(res.headers['content-type']).toContain("application/json") }); it('updating with invalid runner should return 404', async () => { - const res2 = await axios.put(base + '/api/donations/distance/' + added_donation.id, { + const res = await axios.put(base + '/api/donations/distance/' + added_donation.id, { "id": added_donation.id, "runner": 9999999999999999999, "amountPerDistance": 100, "donor": added_donor.id }, axios_config); - expect(res2.status).toEqual(404); - expect(res2.headers['content-type']).toContain("application/json") + expect(res.status).toEqual(404); + expect(res.headers['content-type']).toContain("application/json") + }); +}); +// --------------- +describe('adding + updating fixed donation valid', () => { + let added_donor; + let added_donor2; + let added_donation; + it('creating a new donor with only needed params should return 200', async () => { + const res = await axios.post(base + '/api/donors', { + "firstname": "first", + "lastname": "last" + }, axios_config); + added_donor = res.data + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new donor with only needed params should return 200', async () => { + const res = await axios.post(base + '/api/donors', { + "firstname": "first", + "lastname": "last" + }, axios_config); + delete res.data.donationAmount; + added_donor2 = res.data + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new fixed donation should return 200', async () => { + const res = await axios.post(base + '/api/donations/fixed', { + "donor": added_donor.id, + "amount": 1000 + }, axios_config); + added_donation = res.data; + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('updating nothing should return 200', async () => { + const res = await axios.put(base + '/api/donations/fixed/' + added_donation.id, { + "id": added_donation.id, + "donor": added_donor.id, + "amount": 1000 + }, axios_config); + delete res.data.donor.donationAmount; + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json"); + expect(res.data).toEqual(added_donation); + }); + it('updating amount should return 200', async () => { + const res = await axios.put(base + '/api/donations/fixed/' + added_donation.id, { + "id": added_donation.id, + "donor": added_donor.id, + "amount": 42 + }, axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json"); + expect(res.data.amount).toEqual(42); + }); + it('updating donor should return 200', async () => { + const res = await axios.put(base + '/api/donations/fixed/' + added_donation.id, { + "id": added_donation.id, + "donor": added_donor2.id, + "amount": 42 + }, axios_config); + delete res.data.donor.donationAmount; + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json"); + expect(res.data.donor).toEqual(added_donor2); + }); +}); +// --------------- +describe('adding + updating distance donation valid', () => { + let added_donor; + let added_donor2; + let added_org; + let added_runner; + let added_runner2; + let added_donation; + it('creating a new donor with only needed params should return 200', async () => { + const res = await axios.post(base + '/api/donors', { + "firstname": "first", + "lastname": "last" + }, axios_config); + added_donor = res.data + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new donor with only needed params should return 200', async () => { + const res = await axios.post(base + '/api/donors', { + "firstname": "first", + "lastname": "last" + }, axios_config); + added_donor2 = res.data + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new org with just a name should return 200', async () => { + const res = await axios.post(base + '/api/organisations', { + "name": "test123" + }, axios_config); + added_org = res.data + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new runner with only needed params should return 200', async () => { + const res = await axios.post(base + '/api/runners', { + "firstname": "first", + "lastname": "last", + "group": added_org.id + }, axios_config); + added_runner = res.data; + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new runner with only needed params should return 200', async () => { + const res = await axios.post(base + '/api/runners', { + "firstname": "first", + "lastname": "last", + "group": added_org.id + }, axios_config); + delete res.data.group; + added_runner2 = res.data; + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('creating a new distance donation should return 200', async () => { + const res = await axios.post(base + '/api/donations/distance', { + "runner": added_runner.id, + "amountPerDistance": 100, + "donor": added_donor.id + }, axios_config); + delete res.data.donor.donationAmount; + added_donation = res.data; + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('updating nothing should return 200', async () => { + const res = await axios.put(base + '/api/donations/distance/' + added_donation.id, { + "id": added_donation.id, + "runner": added_runner.id, + "amountPerDistance": 100, + "donor": added_donor.id + }, axios_config); + delete res.data.donor.donationAmount; + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json"); + expect(res.data).toEqual(added_donation); + }); + it('updating amount should return 200', async () => { + const res = await axios.put(base + '/api/donations/distance/' + added_donation.id, { + "id": added_donation.id, + "runner": added_runner.id, + "amountPerDistance": 69, + "donor": added_donor.id + }, axios_config); + delete res.data.donor.donationAmount; + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json"); + expect(res.data.amountPerDistance).toEqual(69); + }); + it('updating runner should return 200', async () => { + const res = await axios.put(base + '/api/donations/distance/' + added_donation.id, { + "id": added_donation.id, + "runner": added_runner2.id, + "amountPerDistance": 69, + "donor": added_donor.id + }, axios_config); + delete res.data.runner.group; + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json"); + expect(res.data.runner).toEqual(added_runner2); + }); + it('updating donor should return 200', async () => { + const res = await axios.put(base + '/api/donations/distance/' + added_donation.id, { + "id": added_donation.id, + "runner": added_runner2.id, + "amountPerDistance": 69, + "donor": added_donor2.id + }, axios_config); + delete res.data.donor.donationAmount; + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json"); + expect(res.data.donor).toEqual(added_donor2); }); }); \ No newline at end of file From bba35d189eb0a2dc082c3e5553b98e29f7e12075 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Wed, 13 Jan 2021 17:32:10 +0100 Subject: [PATCH 28/29] Added donor donation amount to the donor response ref #66 --- src/controllers/DonorController.ts | 10 +++++----- src/models/entities/Donor.ts | 11 ++++++++++- src/models/responses/ResponseDonor.ts | 9 ++++++++- src/tests/donations/donations_update.spec.ts | 3 +++ 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/controllers/DonorController.ts b/src/controllers/DonorController.ts index 59e6b04..4fdfb10 100644 --- a/src/controllers/DonorController.ts +++ b/src/controllers/DonorController.ts @@ -27,7 +27,7 @@ export class DonorController { @OpenAPI({ description: 'Lists all runners from all teams/orgs.
This includes the runner\'s group and distance ran.' }) async getAll() { let responseDonors: ResponseDonor[] = new Array(); - const donors = await this.donorRepository.find(); + const donors = await this.donorRepository.find({ relations: ['donations', 'donations.runner', 'donations.runner.scans', 'donations.runner.scans.track'] }); donors.forEach(donor => { responseDonors.push(new ResponseDonor(donor)); }); @@ -41,7 +41,7 @@ export class DonorController { @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 }) + let donor = await this.donorRepository.findOne({ id: id }, { relations: ['donations', 'donations.runner', 'donations.runner.scans', 'donations.runner.scans.track'] }) if (!donor) { throw new DonorNotFoundError(); } return new ResponseDonor(donor); } @@ -59,7 +59,7 @@ export class DonorController { } donor = await this.donorRepository.save(donor) - return new ResponseDonor(await this.donorRepository.findOne(donor)); + return new ResponseDonor(await this.donorRepository.findOne(donor, { relations: ['donations', 'donations.runner', 'donations.runner.scans', 'donations.runner.scans.track'] })); } @Put('/:id') @@ -80,7 +80,7 @@ export class DonorController { } await this.donorRepository.save(await donor.update(oldDonor)); - return new ResponseDonor(await this.donorRepository.findOne({ id: id })); + return new ResponseDonor(await this.donorRepository.findOne({ id: id }, { relations: ['donations', 'donations.runner', 'donations.runner.scans', 'donations.runner.scans.track'] })); } @Delete('/:id') @@ -92,7 +92,7 @@ export class DonorController { 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); + const responseDonor = await this.donorRepository.findOne(donor, { relations: ['donations', 'donations.runner', 'donations.runner.scans', 'donations.runner.scans.track'] }); if (!donor) { throw new DonorNotFoundError(); diff --git a/src/models/entities/Donor.ts b/src/models/entities/Donor.ts index 01b365c..acf5536 100644 --- a/src/models/entities/Donor.ts +++ b/src/models/entities/Donor.ts @@ -1,4 +1,4 @@ -import { IsBoolean } from "class-validator"; +import { IsBoolean, IsInt } from "class-validator"; import { ChildEntity, Column, OneToMany } from "typeorm"; import { ResponseDonor } from '../responses/ResponseDonor'; import { Donation } from './Donation'; @@ -24,6 +24,15 @@ export class Donor extends Participant { @OneToMany(() => Donation, donation => donation.donor, { nullable: true }) donations: Donation[]; + /** + * Returns the total donations of a donor based on his linked donations. + */ + @IsInt() + public get donationAmount(): number { + if (!this.donations) { return 0; } + return this.donations.reduce((sum, current) => sum + current.amount, 0); + } + /** * Turns this entity into it's response class. */ diff --git a/src/models/responses/ResponseDonor.ts b/src/models/responses/ResponseDonor.ts index 89fea60..5f659f9 100644 --- a/src/models/responses/ResponseDonor.ts +++ b/src/models/responses/ResponseDonor.ts @@ -1,5 +1,5 @@ import { - IsBoolean + IsBoolean, IsInt } from "class-validator"; import { Donor } from '../entities/Donor'; import { ResponseParticipant } from './ResponseParticipant'; @@ -15,6 +15,12 @@ export class ResponseDonor extends ResponseParticipant { @IsBoolean() receiptNeeded: boolean; + /** + * Returns the total donations of a donor based on his linked donations. + */ + @IsInt() + donationAmount: number; + /** * Creates a ResponseRunner object from a runner. * @param runner The user the response shall be build for. @@ -22,5 +28,6 @@ export class ResponseDonor extends ResponseParticipant { public constructor(donor: Donor) { super(donor); this.receiptNeeded = donor.receiptNeeded; + this.donationAmount = donor.donationAmount; } } diff --git a/src/tests/donations/donations_update.spec.ts b/src/tests/donations/donations_update.spec.ts index cd45b54..2df6914 100644 --- a/src/tests/donations/donations_update.spec.ts +++ b/src/tests/donations/donations_update.spec.ts @@ -186,6 +186,7 @@ describe('adding + updating fixed donation valid', () => { "donor": added_donor.id, "amount": 1000 }, axios_config); + delete res.data.donor.donationAmount; added_donation = res.data; expect(res.status).toEqual(200); expect(res.headers['content-type']).toContain("application/json") @@ -236,6 +237,7 @@ describe('adding + updating distance donation valid', () => { "firstname": "first", "lastname": "last" }, axios_config); + delete res.data.donationAmount; added_donor = res.data expect(res.status).toEqual(200); expect(res.headers['content-type']).toContain("application/json") @@ -245,6 +247,7 @@ describe('adding + updating distance donation valid', () => { "firstname": "first", "lastname": "last" }, axios_config); + delete res.data.donationAmount; added_donor2 = res.data expect(res.status).toEqual(200); expect(res.headers['content-type']).toContain("application/json") From 0724932152278a1dce94f17835e00fd1bbd808f9 Mon Sep 17 00:00:00 2001 From: Nicolai Ort Date: Wed, 13 Jan 2021 18:01:53 +0100 Subject: [PATCH 29/29] Updated some openapi descriptions ref #94 --- .drone.yml | 21 --------------------- src/controllers/DonorController.ts | 10 +++++----- 2 files changed, 5 insertions(+), 26 deletions(-) diff --git a/.drone.yml b/.drone.yml index 2d5c9b2..4e7d618 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,24 +1,3 @@ ---- -kind: pipeline -name: tests:14.15.1-alpine3.12 -clone: - disable: true -steps: - - name: checkout pr - image: alpine/git - commands: - - git clone $DRONE_REMOTE_URL . - - git checkout $DRONE_SOURCE_BRANCH - - mv .env.ci .env - - name: run tests - image: node:14.15.1-alpine3.12 - commands: - - yarn - - yarn test:ci -trigger: - event: - - pull_request - --- kind: pipeline name: tests:node_latest diff --git a/src/controllers/DonorController.ts b/src/controllers/DonorController.ts index 4fdfb10..4b0d508 100644 --- a/src/controllers/DonorController.ts +++ b/src/controllers/DonorController.ts @@ -24,7 +24,7 @@ export class DonorController { @Get() @Authorized("DONOR:GET") @ResponseSchema(ResponseDonor, { isArray: true }) - @OpenAPI({ description: 'Lists all runners from all teams/orgs.
This includes the runner\'s group and distance ran.' }) + @OpenAPI({ description: 'Lists all donor.
This includes the donor\'s current donation amount.' }) async getAll() { let responseDonors: ResponseDonor[] = new Array(); const donors = await this.donorRepository.find({ relations: ['donations', 'donations.runner', 'donations.runner.scans', 'donations.runner.scans.track'] }); @@ -39,7 +39,7 @@ export class DonorController { @ResponseSchema(ResponseDonor) @ResponseSchema(DonorNotFoundError, { statusCode: 404 }) @OnUndefined(DonorNotFoundError) - @OpenAPI({ description: 'Lists all information about the runner whose id got provided.' }) + @OpenAPI({ description: 'Lists all information about the donor whose id got provided.
This includes the donor\'s current donation amount.' }) async getOne(@Param('id') id: number) { let donor = await this.donorRepository.findOne({ id: id }, { relations: ['donations', 'donations.runner', 'donations.runner.scans', 'donations.runner.scans.track'] }) if (!donor) { throw new DonorNotFoundError(); } @@ -49,7 +49,7 @@ export class DonorController { @Post() @Authorized("DONOR:CREATE") @ResponseSchema(ResponseDonor) - @OpenAPI({ description: 'Create a new runner.
Please remeber to provide the runner\'s group\'s id.' }) + @OpenAPI({ description: 'Create a new donor.' }) async post(@Body({ validate: true }) createRunner: CreateDonor) { let donor; try { @@ -67,7 +67,7 @@ export class DonorController { @ResponseSchema(ResponseDonor) @ResponseSchema(DonorNotFoundError, { statusCode: 404 }) @ResponseSchema(DonorIdsNotMatchingError, { statusCode: 406 }) - @OpenAPI({ description: "Update the runner whose id you provided.
Please remember that ids can't be changed." }) + @OpenAPI({ description: "Update the donor whose id you provided.
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 }); @@ -88,7 +88,7 @@ export class DonorController { @ResponseSchema(ResponseDonor) @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 donor whose id you provided.
If no donor with this id exists it will just return 204(no content).
If the donor still has donations associated this will fail, please provide the query param ?force=true to delete the donor with all associated donations.' }) async remove(@Param("id") id: number, @QueryParam("force") force: boolean) { let donor = await this.donorRepository.findOne({ id: id }); if (!donor) { return null; }