Merge pull request 'New Feature: Donor endpoints feature/65-donor_controllers' (#69) from feature/65-donor_controllers into dev
Reviewed-on: #69 closes #65 Donors go 💲💲💲
This commit is contained in:
		
							
								
								
									
										105
									
								
								src/controllers/DonorController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								src/controllers/DonorController.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,105 @@ | ||||
| import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers'; | ||||
| import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; | ||||
| import { getConnectionManager, Repository } from 'typeorm'; | ||||
| import { DonorIdsNotMatchingError, DonorNotFoundError } from '../errors/DonorErrors'; | ||||
| import { CreateDonor } from '../models/actions/CreateDonor'; | ||||
| import { UpdateDonor } from '../models/actions/UpdateDonor'; | ||||
| import { Donor } from '../models/entities/Donor'; | ||||
| import { ResponseDonor } from '../models/responses/ResponseDonor'; | ||||
| import { ResponseEmpty } from '../models/responses/ResponseEmpty'; | ||||
|  | ||||
| @JsonController('/donors') | ||||
| @OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) | ||||
| export class DonorController { | ||||
| 	private donorRepository: Repository<Donor>; | ||||
|  | ||||
| 	/** | ||||
| 	 * Gets the repository of this controller's model/entity. | ||||
| 	 */ | ||||
| 	constructor() { | ||||
| 		this.donorRepository = getConnectionManager().get().getRepository(Donor); | ||||
| 	} | ||||
|  | ||||
| 	@Get() | ||||
| 	@Authorized("DONOR:GET") | ||||
| 	@ResponseSchema(ResponseDonor, { isArray: true }) | ||||
| 	@OpenAPI({ description: 'Lists all runners from all teams/orgs. <br> This includes the runner\'s group and distance ran.' }) | ||||
| 	async getAll() { | ||||
| 		let responseDonors: ResponseDonor[] = new Array<ResponseDonor>(); | ||||
| 		const donors = await this.donorRepository.find(); | ||||
| 		donors.forEach(donor => { | ||||
| 			responseDonors.push(new ResponseDonor(donor)); | ||||
| 		}); | ||||
| 		return responseDonors; | ||||
| 	} | ||||
|  | ||||
| 	@Get('/:id') | ||||
| 	@Authorized("DONOR:GET") | ||||
| 	@ResponseSchema(ResponseDonor) | ||||
| 	@ResponseSchema(DonorNotFoundError, { statusCode: 404 }) | ||||
| 	@OnUndefined(DonorNotFoundError) | ||||
| 	@OpenAPI({ description: 'Lists all information about the runner whose id got provided.' }) | ||||
| 	async getOne(@Param('id') id: number) { | ||||
| 		let donor = await this.donorRepository.findOne({ id: id }) | ||||
| 		if (!donor) { throw new DonorNotFoundError(); } | ||||
| 		return new ResponseDonor(donor); | ||||
| 	} | ||||
|  | ||||
| 	@Post() | ||||
| 	@Authorized("DONOR:CREATE") | ||||
| 	@ResponseSchema(ResponseDonor) | ||||
| 	@OpenAPI({ description: 'Create a new runner. <br> Please remeber to provide the runner\'s group\'s id.' }) | ||||
| 	async post(@Body({ validate: true }) createRunner: CreateDonor) { | ||||
| 		let donor; | ||||
| 		try { | ||||
| 			donor = await createRunner.toDonor(); | ||||
| 		} catch (error) { | ||||
| 			throw error; | ||||
| 		} | ||||
|  | ||||
| 		donor = await this.donorRepository.save(donor) | ||||
| 		return new ResponseDonor(await this.donorRepository.findOne(donor)); | ||||
| 	} | ||||
|  | ||||
| 	@Put('/:id') | ||||
| 	@Authorized("DONOR:UPDATE") | ||||
| 	@ResponseSchema(ResponseDonor) | ||||
| 	@ResponseSchema(DonorNotFoundError, { statusCode: 404 }) | ||||
| 	@ResponseSchema(DonorIdsNotMatchingError, { statusCode: 406 }) | ||||
| 	@OpenAPI({ description: "Update the runner whose id you provided. <br> Please remember that ids can't be changed." }) | ||||
| 	async put(@Param('id') id: number, @Body({ validate: true }) donor: UpdateDonor) { | ||||
| 		let oldDonor = await this.donorRepository.findOne({ id: id }); | ||||
|  | ||||
| 		if (!oldDonor) { | ||||
| 			throw new DonorNotFoundError(); | ||||
| 		} | ||||
|  | ||||
| 		if (oldDonor.id != donor.id) { | ||||
| 			throw new DonorIdsNotMatchingError(); | ||||
| 		} | ||||
|  | ||||
| 		await this.donorRepository.save(await donor.updateDonor(oldDonor)); | ||||
| 		return new ResponseDonor(await this.donorRepository.findOne({ id: id })); | ||||
| 	} | ||||
|  | ||||
| 	@Delete('/:id') | ||||
| 	@Authorized("DONOR:DELETE") | ||||
| 	@ResponseSchema(ResponseDonor) | ||||
| 	@ResponseSchema(ResponseEmpty, { statusCode: 204 }) | ||||
| 	@OnUndefined(204) | ||||
| 	@OpenAPI({ description: 'Delete the runner whose id you provided. <br> If no runner with this id exists it will just return 204(no content).' }) | ||||
| 	async remove(@Param("id") id: number, @QueryParam("force") force: boolean) { | ||||
| 		let donor = await this.donorRepository.findOne({ id: id }); | ||||
| 		if (!donor) { return null; } | ||||
| 		const responseDonor = await this.donorRepository.findOne(donor); | ||||
|  | ||||
| 		if (!donor) { | ||||
| 			throw new DonorNotFoundError(); | ||||
| 		} | ||||
|  | ||||
| 		//TODO: DELETE DONATIONS AND WARN FOR FORCE (https://git.odit.services/lfk/backend/issues/66) | ||||
|  | ||||
| 		await this.donorRepository.delete(donor); | ||||
| 		return new ResponseDonor(responseDonor); | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										36
									
								
								src/errors/DonorErrors.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/errors/DonorErrors.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | ||||
| import { IsString } from 'class-validator'; | ||||
| import { NotAcceptableError, NotFoundError } from 'routing-controllers'; | ||||
|  | ||||
| /** | ||||
|  * Error to throw when a donor couldn't be found. | ||||
|  */ | ||||
| export class DonorNotFoundError extends NotFoundError { | ||||
| 	@IsString() | ||||
| 	name = "DonorNotFoundError" | ||||
|  | ||||
| 	@IsString() | ||||
| 	message = "Donor not found!" | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Error to throw when two donors' ids don't match. | ||||
|  * Usually occurs when a user tries to change a donor's id. | ||||
|  */ | ||||
| export class DonorIdsNotMatchingError extends NotAcceptableError { | ||||
| 	@IsString() | ||||
| 	name = "DonorIdsNotMatchingError" | ||||
|  | ||||
| 	@IsString() | ||||
| 	message = "The ids don't match! \n And if you wanted to change a donor's id: This isn't allowed!" | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Error to throw when a donor needs a receipt, but no address is associated with them. | ||||
|  */ | ||||
| export class DonorReceiptAddressNeededError extends NotAcceptableError { | ||||
| 	@IsString() | ||||
| 	name = "DonorReceiptAddressNeededError" | ||||
|  | ||||
| 	@IsString() | ||||
| 	message = "An address is needed to create a receipt for a donor. \n You didn't provide one." | ||||
| } | ||||
							
								
								
									
										38
									
								
								src/models/actions/CreateDonor.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/models/actions/CreateDonor.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| import { IsBoolean, IsOptional } from 'class-validator'; | ||||
| import { DonorReceiptAddressNeededError } from '../../errors/DonorErrors'; | ||||
| import { Donor } from '../entities/Donor'; | ||||
| import { CreateParticipant } from './CreateParticipant'; | ||||
|  | ||||
| /** | ||||
|  * This classed is used to create a new Donor entity from a json body (post request). | ||||
|  */ | ||||
| export class CreateDonor extends CreateParticipant { | ||||
|  | ||||
|     /** | ||||
|      * Does this donor need a receipt? | ||||
|      */ | ||||
|     @IsBoolean() | ||||
|     @IsOptional() | ||||
|     receiptNeeded?: boolean = false; | ||||
|  | ||||
|     /** | ||||
|      * Creates a new Donor entity from this. | ||||
|      */ | ||||
|     public async toDonor(): Promise<Donor> { | ||||
|         let newDonor: Donor = new Donor(); | ||||
|  | ||||
|         newDonor.firstname = this.firstname; | ||||
|         newDonor.middlename = this.middlename; | ||||
|         newDonor.lastname = this.lastname; | ||||
|         newDonor.phone = this.phone; | ||||
|         newDonor.email = this.email; | ||||
|         newDonor.address = await this.getAddress(); | ||||
|         newDonor.receiptNeeded = this.receiptNeeded; | ||||
|  | ||||
|         if (this.receiptNeeded == true && this.address == null) { | ||||
|             throw new DonorReceiptAddressNeededError() | ||||
|         } | ||||
|  | ||||
|         return newDonor; | ||||
|     } | ||||
| } | ||||
| @@ -41,7 +41,7 @@ export class CreateRunnerOrganisation extends CreateRunnerGroup { | ||||
|  | ||||
|         newRunnerOrganisation.name = this.name; | ||||
|         newRunnerOrganisation.contact = await this.getContact(); | ||||
|         newRunnerOrganisation.address = await this.getAddress(); | ||||
|         // newRunnerOrganisation.address = await this.getAddress(); | ||||
|  | ||||
|         return newRunnerOrganisation; | ||||
|     } | ||||
|   | ||||
							
								
								
									
										44
									
								
								src/models/actions/UpdateDonor.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/models/actions/UpdateDonor.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | ||||
| import { IsBoolean, IsInt, IsOptional } from 'class-validator'; | ||||
| import { DonorReceiptAddressNeededError } from '../../errors/DonorErrors'; | ||||
| import { Donor } from '../entities/Donor'; | ||||
| import { CreateParticipant } from './CreateParticipant'; | ||||
|  | ||||
| /** | ||||
|  * This class is used to update a Donor entity (via put request). | ||||
|  */ | ||||
| export class UpdateDonor extends CreateParticipant { | ||||
|  | ||||
|     /** | ||||
|      * The updated donor's id. | ||||
|      * This shouldn't have changed but it is here in case anyone ever wants to enable id changes (whyever they would want to). | ||||
|      */ | ||||
|     @IsInt() | ||||
|     id: number; | ||||
|  | ||||
|     /** | ||||
|      * Does the updated donor need a receipt? | ||||
|      */ | ||||
|     @IsBoolean() | ||||
|     @IsOptional() | ||||
|     receiptNeeded?: boolean; | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * Updates a provided Donor entity based on this. | ||||
|      */ | ||||
|     public async updateDonor(donor: Donor): Promise<Donor> { | ||||
|         donor.firstname = this.firstname; | ||||
|         donor.middlename = this.middlename; | ||||
|         donor.lastname = this.lastname; | ||||
|         donor.phone = this.phone; | ||||
|         donor.email = this.email; | ||||
|         donor.receiptNeeded = this.receiptNeeded; | ||||
|         donor.address = await this.getAddress(); | ||||
|  | ||||
|         if (this.receiptNeeded == true && this.address == null) { | ||||
|             throw new DonorReceiptAddressNeededError() | ||||
|         } | ||||
|  | ||||
|         return donor; | ||||
|     } | ||||
| } | ||||
| @@ -45,7 +45,7 @@ export class UpdateRunnerOrganisation extends CreateRunnerGroup { | ||||
|  | ||||
|         organisation.name = this.name; | ||||
|         organisation.contact = await this.getContact(); | ||||
|         organisation.address = await this.getAddress(); | ||||
|         // organisation.address = await this.getAddress(); | ||||
|  | ||||
|         return organisation; | ||||
|     } | ||||
|   | ||||
| @@ -7,8 +7,7 @@ import { | ||||
| } from "class-validator"; | ||||
| import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm"; | ||||
| import { config } from '../../config'; | ||||
| import { Participant } from "./Participant"; | ||||
| import { RunnerOrganisation } from "./RunnerOrganisation"; | ||||
| import { IAddressUser } from './IAddressUser'; | ||||
|  | ||||
| /** | ||||
|  * Defines the Address entity. | ||||
| @@ -79,12 +78,6 @@ export class Address { | ||||
|   /** | ||||
|    * Used to link the address to participants. | ||||
|    */ | ||||
|   @OneToMany(() => Participant, participant => participant.address, { nullable: true }) | ||||
|   participants: Participant[]; | ||||
|  | ||||
|   /** | ||||
|    * Used to link the address to runner groups. | ||||
|    */ | ||||
|   @OneToMany(() => RunnerOrganisation, group => group.address, { nullable: true }) | ||||
|   groups: RunnerOrganisation[]; | ||||
|   @OneToMany(() => IAddressUser, addressUser => addressUser.address, { nullable: true }) | ||||
|   addressUsers: IAddressUser[]; | ||||
| } | ||||
|   | ||||
| @@ -3,7 +3,7 @@ import { | ||||
|   IsNotEmpty | ||||
| } from "class-validator"; | ||||
| import { Entity, ManyToOne, PrimaryGeneratedColumn, TableInheritance } from "typeorm"; | ||||
| import { Participant } from "./Participant"; | ||||
| import { Donor } from './Donor'; | ||||
|  | ||||
| /** | ||||
|  * Defines the Donation entity. | ||||
| @@ -24,8 +24,8 @@ export abstract class Donation { | ||||
|    * The donations's donor. | ||||
|    */ | ||||
|   @IsNotEmpty() | ||||
|   @ManyToOne(() => Participant, donor => donor.donations) | ||||
|   donor: Participant; | ||||
|   @ManyToOne(() => Donor, donor => donor.donations) | ||||
|   donor: Donor; | ||||
|  | ||||
|   /** | ||||
|    * The donation's amount in cents (or whatever your currency's smallest unit is.). | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| import { IsBoolean } from "class-validator"; | ||||
| import { ChildEntity, Column } from "typeorm"; | ||||
| import { ChildEntity, Column, OneToMany } from "typeorm"; | ||||
| import { Donation } from './Donation'; | ||||
| import { Participant } from "./Participant"; | ||||
|  | ||||
| /** | ||||
| @@ -14,4 +15,11 @@ export class Donor extends Participant { | ||||
|   @Column() | ||||
|   @IsBoolean() | ||||
|   receiptNeeded: boolean = false; | ||||
|  | ||||
|   /** | ||||
|  * Used to link the participant as the donor of a donation. | ||||
|  * Attention: Only runner's can be associated as a distanceDonations distance source. | ||||
|  */ | ||||
|   @OneToMany(() => Donation, donation => donation.donor, { nullable: true }) | ||||
|   donations: Donation[]; | ||||
| } | ||||
| @@ -10,6 +10,7 @@ import { | ||||
| import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm"; | ||||
| import { config } from '../../config'; | ||||
| import { Address } from "./Address"; | ||||
| import { IAddressUser } from './IAddressUser'; | ||||
| import { RunnerGroup } from "./RunnerGroup"; | ||||
|  | ||||
| /** | ||||
| @@ -17,7 +18,7 @@ import { RunnerGroup } from "./RunnerGroup"; | ||||
|  * Mainly it's own class to reduce duplicate code and enable contact's to be associated with multiple groups. | ||||
| */ | ||||
| @Entity() | ||||
| export class GroupContact { | ||||
| export class GroupContact implements IAddressUser { | ||||
|   /** | ||||
|    * Autogenerated unique id (primary key). | ||||
|    */ | ||||
| @@ -54,7 +55,7 @@ export class GroupContact { | ||||
|    * This is a address object to prevent any formatting differences. | ||||
|    */ | ||||
|   @IsOptional() | ||||
|   @ManyToOne(() => Address, address => address.participants, { nullable: true }) | ||||
|   @ManyToOne(() => Address, address => address.addressUsers, { nullable: true }) | ||||
|   address?: Address; | ||||
|  | ||||
|   /** | ||||
|   | ||||
							
								
								
									
										15
									
								
								src/models/entities/IAddressUser.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/models/entities/IAddressUser.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| import { Entity, ManyToOne, PrimaryColumn } from 'typeorm'; | ||||
| import { Address } from './Address'; | ||||
|  | ||||
| /** | ||||
|  * The interface(tm) all entities using addresses have to implement. | ||||
|  * This is a abstract class, because apparently typeorm can't really work with interfaces :/ | ||||
|  */ | ||||
| @Entity() | ||||
| export abstract class IAddressUser { | ||||
|     @PrimaryColumn() | ||||
|     id: number; | ||||
|  | ||||
|     @ManyToOne(() => Address, address => address.addressUsers, { nullable: true }) | ||||
|     address?: Address | ||||
| } | ||||
| @@ -7,10 +7,10 @@ import { | ||||
|  | ||||
|   IsString | ||||
| } from "class-validator"; | ||||
| import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn, TableInheritance } from "typeorm"; | ||||
| import { Column, Entity, ManyToOne, PrimaryGeneratedColumn, TableInheritance } from "typeorm"; | ||||
| import { config } from '../../config'; | ||||
| import { Address } from "./Address"; | ||||
| import { Donation } from "./Donation"; | ||||
| import { IAddressUser } from './IAddressUser'; | ||||
|  | ||||
| /** | ||||
|  * Defines the Participant entity. | ||||
| @@ -18,7 +18,7 @@ import { Donation } from "./Donation"; | ||||
| */ | ||||
| @Entity() | ||||
| @TableInheritance({ column: { name: "type", type: "varchar" } }) | ||||
| export abstract class Participant { | ||||
| export abstract class Participant implements IAddressUser { | ||||
|   /** | ||||
|    * Autogenerated unique id (primary key). | ||||
|    */ | ||||
| @@ -54,7 +54,7 @@ export abstract class Participant { | ||||
|    * The participant's address. | ||||
|    * This is a address object to prevent any formatting differences. | ||||
|    */ | ||||
|   @ManyToOne(() => Address, address => address.participants, { nullable: true }) | ||||
|   @ManyToOne(() => Address, address => address.addressUsers, { nullable: true }) | ||||
|   address?: Address; | ||||
|  | ||||
|   /** | ||||
| @@ -74,11 +74,4 @@ export abstract class Participant { | ||||
|   @IsOptional() | ||||
|   @IsEmail() | ||||
|   email?: string; | ||||
|  | ||||
|   /** | ||||
|    * Used to link the participant as the donor of a donation. | ||||
|    * Attention: Only runner's can be associated as a distanceDonations distance source. | ||||
|    */ | ||||
|   @OneToMany(() => Donation, donation => donation.donor, { nullable: true }) | ||||
|   donations: Donation[]; | ||||
| } | ||||
| @@ -18,7 +18,7 @@ export class Runner extends Participant { | ||||
|    * Can be a runner team or organisation. | ||||
|    */ | ||||
|   @IsNotEmpty() | ||||
|   @ManyToOne(() => RunnerGroup, group => group.runners, { nullable: false }) | ||||
|   @ManyToOne(() => RunnerGroup, group => group.runners) | ||||
|   group: RunnerGroup; | ||||
|  | ||||
|   /** | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| import { IsInt, IsOptional } from "class-validator"; | ||||
| import { ChildEntity, ManyToOne, OneToMany } from "typeorm"; | ||||
| import { Address } from "./Address"; | ||||
| import { Address } from './Address'; | ||||
| import { IAddressUser } from './IAddressUser'; | ||||
| import { Runner } from './Runner'; | ||||
| import { RunnerGroup } from "./RunnerGroup"; | ||||
| import { RunnerTeam } from "./RunnerTeam"; | ||||
| @@ -10,13 +11,13 @@ import { RunnerTeam } from "./RunnerTeam"; | ||||
|  * This usually is a school, club or company. | ||||
| */ | ||||
| @ChildEntity() | ||||
| export class RunnerOrganisation extends RunnerGroup { | ||||
| export class RunnerOrganisation extends RunnerGroup implements IAddressUser { | ||||
|  | ||||
|   /** | ||||
|    * The organisations's address. | ||||
|    */ | ||||
|   @IsOptional() | ||||
|   @ManyToOne(() => Address, address => address.groups, { nullable: true }) | ||||
|   @ManyToOne(() => Address, address => address.addressUsers, { nullable: true }) | ||||
|   address?: Address; | ||||
|  | ||||
|   /** | ||||
|   | ||||
| @@ -9,5 +9,6 @@ export enum PermissionTarget { | ||||
|     USER = 'USER', | ||||
|     USERGROUP = 'USERGROUP', | ||||
|     PERMISSION = 'PERMISSION', | ||||
|     STATSCLIENT = 'STATSCLIENT' | ||||
|     STATSCLIENT = 'STATSCLIENT', | ||||
|     DONOR = 'DONOR' | ||||
| } | ||||
							
								
								
									
										26
									
								
								src/models/responses/ResponseDonor.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/models/responses/ResponseDonor.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| import { | ||||
|     IsBoolean | ||||
| } from "class-validator"; | ||||
| import { Donor } from '../entities/Donor'; | ||||
| import { ResponseParticipant } from './ResponseParticipant'; | ||||
|  | ||||
| /** | ||||
|  * Defines the donor response. | ||||
| */ | ||||
| export class ResponseDonor extends ResponseParticipant { | ||||
|  | ||||
|     /** | ||||
|      * Does this donor need a receipt? | ||||
|      */ | ||||
|     @IsBoolean() | ||||
|     receiptNeeded: boolean; | ||||
|  | ||||
|     /** | ||||
|      * Creates a ResponseRunner object from a runner. | ||||
|      * @param runner The user the response shall be build for. | ||||
|      */ | ||||
|     public constructor(donor: Donor) { | ||||
|         super(donor); | ||||
|         this.receiptNeeded = donor.receiptNeeded; | ||||
|     } | ||||
| } | ||||
| @@ -1,7 +1,8 @@ | ||||
| import { | ||||
|     IsArray, | ||||
|     IsNotEmpty, | ||||
|     IsObject | ||||
|  | ||||
|     IsObject, | ||||
|     IsOptional | ||||
| } from "class-validator"; | ||||
| import { Address } from '../entities/Address'; | ||||
| import { RunnerOrganisation } from '../entities/RunnerOrganisation'; | ||||
| @@ -17,7 +18,7 @@ export class ResponseRunnerOrganisation extends ResponseRunnerGroup { | ||||
|      * The runnerOrganisation's address. | ||||
|      */ | ||||
|     @IsObject() | ||||
|     @IsNotEmpty() | ||||
|     @IsOptional() | ||||
|     address?: Address; | ||||
|  | ||||
|     /** | ||||
|   | ||||
							
								
								
									
										94
									
								
								src/tests/donors/donor_add.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								src/tests/donors/donor_add.spec.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,94 @@ | ||||
| import axios from 'axios'; | ||||
| import { config } from '../../config'; | ||||
| const base = "http://localhost:" + config.internal_port | ||||
|  | ||||
| let access_token; | ||||
| let axios_config; | ||||
|  | ||||
| beforeAll(async () => { | ||||
|     const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" }); | ||||
|     access_token = res.data["access_token"]; | ||||
|     axios_config = { | ||||
|         headers: { "authorization": "Bearer " + access_token }, | ||||
|         validateStatus: undefined | ||||
|     }; | ||||
| }); | ||||
|  | ||||
| // --------------- | ||||
| describe('POST /api/donors with errors', () => { | ||||
|     it('creating a new donor without any parameters should return 400', async () => { | ||||
|         const res1 = await axios.post(base + '/api/donors', null, axios_config); | ||||
|         expect(res1.status).toEqual(400); | ||||
|         expect(res1.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
|     it('creating a new donor without a last name should return 400', async () => { | ||||
|         const res2 = await axios.post(base + '/api/donors', { | ||||
|             "firstname": "first", | ||||
|             "middlename": "middle" | ||||
|         }, axios_config); | ||||
|         expect(res2.status).toEqual(400); | ||||
|         expect(res2.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
|     it('creating a new donor with a invalid address should return 404', async () => { | ||||
|         const res2 = await axios.post(base + '/api/donors', { | ||||
|             "firstname": "first", | ||||
|             "middlename": "middle", | ||||
|             "lastname": "last", | ||||
|             "address": 0 | ||||
|         }, axios_config); | ||||
|         expect(res2.status).toEqual(404); | ||||
|         expect(res2.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
|     it('creating a new donor with a invalid phone number should return 400', async () => { | ||||
|         const res2 = await axios.post(base + '/api/donors', { | ||||
|             "firstname": "first", | ||||
|             "middlename": "middle", | ||||
|             "lastname": "last", | ||||
|             "phone": "123" | ||||
|         }, axios_config); | ||||
|         expect(res2.status).toEqual(400); | ||||
|         expect(res2.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
|     it('creating a new donor with a invalid mail address should return 400', async () => { | ||||
|         const res2 = await axios.post(base + '/api/donors', { | ||||
|             "firstname": "string", | ||||
|             "middlename": "string", | ||||
|             "lastname": "string", | ||||
|             "phone": null, | ||||
|             "email": "123", | ||||
|         }, axios_config); | ||||
|         expect(res2.status).toEqual(400); | ||||
|         expect(res2.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
|     it('creating a new donor without an address but with receiptNeeded=true 406', async () => { | ||||
|         const res2 = await axios.post(base + '/api/donors', { | ||||
|             "firstname": "string", | ||||
|             "middlename": "string", | ||||
|             "lastname": "string", | ||||
|             "receiptNeeded": true | ||||
|         }, axios_config); | ||||
|         expect(res2.status).toEqual(406); | ||||
|         expect(res2.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
| }); | ||||
| // --------------- | ||||
| describe('POST /api/donors working', () => { | ||||
|     it('creating a new donor with only needed params should return 200', async () => { | ||||
|         const res2 = await axios.post(base + '/api/donors', { | ||||
|             "firstname": "first", | ||||
|             "lastname": "last" | ||||
|         }, axios_config); | ||||
|         expect(res2.status).toEqual(200); | ||||
|         expect(res2.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
|     it('creating a new donor with all non-relationship optional params should return 200', async () => { | ||||
|         const res3 = await axios.post(base + '/api/donors', { | ||||
|             "firstname": "first", | ||||
|             "middlename": "middle", | ||||
|             "lastname": "last", | ||||
|             "receiptNeeded": false | ||||
|         }, axios_config); | ||||
|         expect(res3.status).toEqual(200); | ||||
|         expect(res3.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
| }); | ||||
							
								
								
									
										47
									
								
								src/tests/donors/donor_delete.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								src/tests/donors/donor_delete.spec.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | ||||
| import axios from 'axios'; | ||||
| import { config } from '../../config'; | ||||
| const base = "http://localhost:" + config.internal_port | ||||
|  | ||||
| let access_token; | ||||
| let axios_config; | ||||
|  | ||||
| beforeAll(async () => { | ||||
|     const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" }); | ||||
|     access_token = res.data["access_token"]; | ||||
|     axios_config = { | ||||
|         headers: { "authorization": "Bearer " + access_token }, | ||||
|         validateStatus: undefined | ||||
|     }; | ||||
| }); | ||||
|  | ||||
| describe('adding + deletion (non-existant)', () => { | ||||
|     it('delete', async () => { | ||||
|         const res2 = await axios.delete(base + '/api/donors/0', axios_config); | ||||
|         expect(res2.status).toEqual(204); | ||||
|     }); | ||||
| }); | ||||
| // --------------- | ||||
| describe('add+delete', () => { | ||||
|     let added_donor; | ||||
|     it('creating a new donor with only needed params should return 200', async () => { | ||||
|         const res2 = await axios.post(base + '/api/donors', { | ||||
|             "firstname": "first", | ||||
|             "lastname": "last" | ||||
|         }, axios_config); | ||||
|         added_donor = res2.data; | ||||
|         expect(res2.status).toEqual(200); | ||||
|         expect(res2.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
|     it('delete donor', async () => { | ||||
|         const res3 = await axios.delete(base + '/api/donors/' + added_donor.id, axios_config); | ||||
|         expect(res3.status).toEqual(200); | ||||
|         expect(res3.headers['content-type']).toContain("application/json") | ||||
|         let deleted_runner = res3.data | ||||
|         expect(deleted_runner).toEqual(added_donor); | ||||
|     }); | ||||
|     it('check if donor really was deleted', async () => { | ||||
|         const res4 = await axios.get(base + '/api/donors/' + added_donor.id, axios_config); | ||||
|         expect(res4.status).toEqual(404); | ||||
|         expect(res4.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
| }); | ||||
							
								
								
									
										57
									
								
								src/tests/donors/donor_get.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								src/tests/donors/donor_get.spec.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | ||||
| import axios from 'axios'; | ||||
| import { config } from '../../config'; | ||||
| const base = "http://localhost:" + config.internal_port | ||||
| let access_token; | ||||
| let axios_config; | ||||
|  | ||||
| beforeAll(async () => { | ||||
|     const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" }); | ||||
|     access_token = res.data["access_token"]; | ||||
|     axios_config = { | ||||
|         headers: { "authorization": "Bearer " + access_token }, | ||||
|         validateStatus: undefined | ||||
|     }; | ||||
| }); | ||||
|  | ||||
| describe('GET /api/donors', () => { | ||||
|     it('basic get should return 200', async () => { | ||||
|         const res = await axios.get(base + '/api/donors', axios_config); | ||||
|         expect(res.status).toEqual(200); | ||||
|         expect(res.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
| }); | ||||
| // --------------- | ||||
| describe('GET /api/donors/0', () => { | ||||
|     it('basic get should return 404', async () => { | ||||
|         const res = await axios.get(base + '/api/donors/0', axios_config); | ||||
|         expect(res.status).toEqual(404); | ||||
|         expect(res.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
| }); | ||||
| // --------------- | ||||
| describe('GET /api/donors after adding', () => { | ||||
|     let added_donor; | ||||
|     it('creating a new donor with only needed params should return 200', async () => { | ||||
|         const res2 = await axios.post(base + '/api/donors', { | ||||
|             "firstname": "first", | ||||
|             "lastname": "last" | ||||
|         }, axios_config); | ||||
|         added_donor = res2.data; | ||||
|         expect(res2.status).toEqual(200); | ||||
|         expect(res2.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
|     it('explicit get should return 200', async () => { | ||||
|         const res3 = await axios.get(base + '/api/donors/' + added_donor.id, axios_config); | ||||
|         expect(res3.status).toEqual(200); | ||||
|         expect(res3.headers['content-type']).toContain("application/json") | ||||
|         let gotten_donor = res3.data | ||||
|         expect(gotten_donor).toEqual(added_donor); | ||||
|     }); | ||||
|     it('get from all runners should return 200', async () => { | ||||
|         const res4 = await axios.get(base + '/api/donors/', axios_config); | ||||
|         expect(res4.status).toEqual(200); | ||||
|         expect(res4.headers['content-type']).toContain("application/json") | ||||
|         let gotten_donors = res4.data | ||||
|         expect(gotten_donors).toContainEqual(added_donor); | ||||
|     }); | ||||
| }); | ||||
							
								
								
									
										75
									
								
								src/tests/donors/donor_update.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								src/tests/donors/donor_update.spec.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,75 @@ | ||||
| import axios from 'axios'; | ||||
| import { config } from '../../config'; | ||||
| const base = "http://localhost:" + config.internal_port | ||||
|  | ||||
| let access_token; | ||||
| let axios_config; | ||||
|  | ||||
| beforeAll(async () => { | ||||
|     const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" }); | ||||
|     access_token = res.data["access_token"]; | ||||
|     axios_config = { | ||||
|         headers: { "authorization": "Bearer " + access_token }, | ||||
|         validateStatus: undefined | ||||
|     }; | ||||
| }); | ||||
|  | ||||
| describe('Update donor name after adding', () => { | ||||
|     let added_donor; | ||||
|     it('creating a new runner with only needed params should return 200', async () => { | ||||
|         const res2 = await axios.post(base + '/api/donors', { | ||||
|             "firstname": "first", | ||||
|             "lastname": "last" | ||||
|         }, axios_config); | ||||
|         added_donor = res2.data; | ||||
|         expect(res2.status).toEqual(200); | ||||
|         expect(res2.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
|     it('valid update should return 200', async () => { | ||||
|         let donor_copy = added_donor | ||||
|         donor_copy.firstname = "second" | ||||
|         const res3 = await axios.put(base + '/api/donors/' + added_donor.id, donor_copy, axios_config); | ||||
|         expect(res3.status).toEqual(200); | ||||
|         expect(res3.headers['content-type']).toContain("application/json") | ||||
|         let updated_donor = res3.data | ||||
|         expect(updated_donor).toEqual(donor_copy); | ||||
|     }); | ||||
| }); | ||||
| // --------------- | ||||
| describe('Update donor id after adding(should fail)', () => { | ||||
|     let added_donor; | ||||
|     it('creating a new donor with only needed params should return 200', async () => { | ||||
|         const res2 = await axios.post(base + '/api/donors', { | ||||
|             "firstname": "first", | ||||
|             "lastname": "last" | ||||
|         }, axios_config); | ||||
|         added_donor = res2.data; | ||||
|         expect(res2.status).toEqual(200); | ||||
|         expect(res2.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
|     it('invalid update should return 406', async () => { | ||||
|         added_donor.id++; | ||||
|         const res3 = await axios.put(base + '/api/donors/' + (added_donor.id - 1), added_donor, axios_config); | ||||
|         expect(res3.status).toEqual(406); | ||||
|         expect(res3.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
| }); | ||||
| // --------------- | ||||
| describe('Update donor without address but receiptNeeded=true should fail', () => { | ||||
|     let added_donor; | ||||
|     it('creating a new donor with only needed params should return 200', async () => { | ||||
|         const res2 = await axios.post(base + '/api/donors', { | ||||
|             "firstname": "first", | ||||
|             "lastname": "last", | ||||
|         }, axios_config); | ||||
|         added_donor = res2.data; | ||||
|         expect(res2.status).toEqual(200); | ||||
|         expect(res2.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
|     it('invalid update should return 406', async () => { | ||||
|         added_donor.receiptNeeded = true; | ||||
|         const res3 = await axios.put(base + '/api/donors/' + added_donor.id, added_donor, axios_config); | ||||
|         expect(res3.status).toEqual(406); | ||||
|         expect(res3.headers['content-type']).toContain("application/json") | ||||
|     }); | ||||
| }); | ||||
| @@ -72,30 +72,26 @@ describe('adding + getting tracks', () => { | ||||
| }); | ||||
| // --------------- | ||||
| describe('adding + getting + updating', () => { | ||||
| 	let added_track_id | ||||
| 	let added_track; | ||||
| 	it('correct distance input should return 200', async () => { | ||||
| 		const res = await axios.post(base + '/api/tracks', { | ||||
| 			"name": "string", | ||||
| 			"distance": 1500 | ||||
| 		}, axios_config); | ||||
| 		expect(res.status).toEqual(200); | ||||
| 		expect(res.headers['content-type']).toContain("application/json") | ||||
| 		expect(res.headers['content-type']).toContain("application/json"); | ||||
| 		added_track = res.data; | ||||
| 	}); | ||||
| 	it('get should return 200', async () => { | ||||
| 		const res1 = await axios.get(base + '/api/tracks', axios_config); | ||||
| 		const res1 = await axios.get(base + '/api/tracks/' + added_track.id, axios_config); | ||||
| 		expect(res1.status).toEqual(200); | ||||
| 		expect(res1.headers['content-type']).toContain("application/json") | ||||
| 		let added_track = res1.data[res1.data.length - 1] | ||||
| 		added_track_id = added_track.id | ||||
| 		delete added_track.id | ||||
| 		expect(added_track).toEqual({ | ||||
| 			"name": "string", | ||||
| 			"distance": 1500 | ||||
| 		}) | ||||
| 		const compareTrack = res1.data; | ||||
| 		expect(compareTrack).toEqual(added_track) | ||||
| 	}) | ||||
| 	it('get should return 200', async () => { | ||||
| 		const res2 = await axios.put(base + '/api/tracks/' + added_track_id, { | ||||
| 			"id": added_track_id, | ||||
| 		const res2 = await axios.put(base + '/api/tracks/' + added_track.id, { | ||||
| 			"id": added_track.id, | ||||
| 			"name": "apitrack", | ||||
| 			"distance": 5100 | ||||
| 		}, axios_config); | ||||
| @@ -103,10 +99,10 @@ describe('adding + getting + updating', () => { | ||||
| 		expect(res2.headers['content-type']).toContain("application/json") | ||||
| 	}) | ||||
| 	it('get should return 200', async () => { | ||||
| 		const res3 = await axios.get(base + '/api/tracks', axios_config); | ||||
| 		const res3 = await axios.get(base + '/api/tracks/' + added_track.id, axios_config); | ||||
| 		expect(res3.status).toEqual(200); | ||||
| 		expect(res3.headers['content-type']).toContain("application/json") | ||||
| 		let added_track2 = res3.data[res3.data.length - 1] | ||||
| 		let added_track2 = res3.data; | ||||
| 		delete added_track2.id | ||||
| 		expect(added_track2).toEqual({ | ||||
| 			"name": "apitrack", | ||||
|   | ||||
		Reference in New Issue
	
	Block a user