Merge pull request 'Implemented group contacts feature/104-contacts' (#108) from feature/104-contacts into dev
Some checks failed
continuous-integration/drone/push Build is failing

Reviewed-on: #108
This commit is contained in:
Nicolai Ort 2021-01-19 19:08:53 +00:00
commit a0c2b5ade8
15 changed files with 1050 additions and 44 deletions

View File

@ -6,4 +6,4 @@ DB_USER=bla
DB_PASSWORD=bla
DB_NAME=bla
NODE_ENV=production
POSTALCODE_COUNTRYCODE=null
POSTALCODE_COUNTRYCODE=DE

View File

@ -0,0 +1,107 @@
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnection, getConnectionManager, Repository } from 'typeorm';
import { GroupContactIdsNotMatchingError, GroupContactNotFoundError } from '../errors/GroupContactErrors';
import { RunnerGroupNotFoundError } from '../errors/RunnerGroupErrors';
import { CreateGroupContact } from '../models/actions/create/CreateGroupContact';
import { UpdateGroupContact } from '../models/actions/update/UpdateGroupContact';
import { GroupContact } from '../models/entities/GroupContact';
import { RunnerGroup } from '../models/entities/RunnerGroup';
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
import { ResponseGroupContact } from '../models/responses/ResponseGroupContact';
@JsonController('/contacts')
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
export class GroupContactController {
private contactRepository: Repository<GroupContact>;
/**
* Gets the repository of this controller's model/entity.
*/
constructor() {
this.contactRepository = getConnectionManager().get().getRepository(GroupContact);
}
@Get()
@Authorized("CONTACT:GET")
@ResponseSchema(ResponseGroupContact, { isArray: true })
@OpenAPI({ description: 'Lists all contacts. <br> This includes the contact\'s associated groups.' })
async getAll() {
let responseContacts: ResponseGroupContact[] = new Array<ResponseGroupContact>();
const contacts = await this.contactRepository.find({ relations: ['groups'] });
contacts.forEach(contact => {
responseContacts.push(contact.toResponse());
});
return responseContacts;
}
@Get('/:id')
@Authorized("CONTACT:GET")
@ResponseSchema(ResponseGroupContact)
@ResponseSchema(GroupContactNotFoundError, { statusCode: 404 })
@OnUndefined(GroupContactNotFoundError)
@OpenAPI({ description: 'Lists all information about the contact whose id got provided. <br> This includes the contact\'s associated groups.' })
async getOne(@Param('id') id: number) {
let contact = await this.contactRepository.findOne({ id: id }, { relations: ['groups'] })
if (!contact) { throw new GroupContactNotFoundError(); }
return contact.toResponse();
}
@Post()
@Authorized("CONTACT:CREATE")
@ResponseSchema(ResponseGroupContact)
@ResponseSchema(RunnerGroupNotFoundError, { statusCode: 404 })
@OpenAPI({ description: 'Create a new contact.' })
async post(@Body({ validate: true }) createContact: CreateGroupContact) {
let contact;
try {
contact = await createContact.toEntity();
} catch (error) {
throw error;
}
contact = await this.contactRepository.save(contact)
return (await this.contactRepository.findOne({ id: contact.id }, { relations: ['groups'] })).toResponse();
}
@Put('/:id')
@Authorized("CONTACT:UPDATE")
@ResponseSchema(ResponseGroupContact)
@ResponseSchema(GroupContactNotFoundError, { statusCode: 404 })
@ResponseSchema(GroupContactIdsNotMatchingError, { statusCode: 406 })
@ResponseSchema(RunnerGroupNotFoundError, { statusCode: 404 })
@OpenAPI({ description: "Update the contact whose id you provided. <br> Please remember that ids can't be changed." })
async put(@Param('id') id: number, @Body({ validate: true }) contact: UpdateGroupContact) {
let oldContact = await this.contactRepository.findOne({ id: id });
if (!oldContact) {
throw new GroupContactNotFoundError();
}
if (oldContact.id != contact.id) {
throw new GroupContactIdsNotMatchingError();
}
await this.contactRepository.save(await contact.update(oldContact));
return (await this.contactRepository.findOne({ id: contact.id }, { relations: ['groups'] })).toResponse();
}
@Delete('/:id')
@Authorized("CONTACT:DELETE")
@ResponseSchema(ResponseGroupContact)
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
@OnUndefined(204)
@OpenAPI({ description: 'Delete the contact whose id you provided. <br> If no contact with this id exists it will just return 204(no content). <br> This won\'t delete any groups associated with the contact.' })
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
let contact = await this.contactRepository.findOne({ id: id });
if (!contact) { return null; }
const responseContact = await this.contactRepository.findOne(contact, { relations: ['groups'] });
for (let group of responseContact.groups) {
group.contact = null;
await getConnection().getRepository(RunnerGroup).save(group);
}
await this.contactRepository.delete(contact);
return responseContact.toResponse();
}
}

View File

@ -94,7 +94,7 @@ export class RunnerOrganisationController {
@ResponseSchema(RunnerOrganisationHasTeamsError, { statusCode: 406 })
@ResponseSchema(RunnerOrganisationHasRunnersError, { statusCode: 406 })
@OnUndefined(204)
@OpenAPI({ description: 'Delete the organsisation whose id you provided. <br> If the organisation still has runners and/or teams associated this will fail. <br> To delete the organisation with all associated runners and teams set the force QueryParam to true (cascading deletion might take a while). <br> If no organisation with this id exists it will just return 204(no content).' })
@OpenAPI({ description: 'Delete the organsisation whose id you provided. <br> If the organisation still has runners and/or teams associated this will fail. <br> To delete the organisation with all associated runners and teams set the force QueryParam to true (cascading deletion might take a while). <br> This won\'t delete the associated contact. <br> If no organisation with this id exists it will just return 204(no content).' })
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
let organisation = await this.runnerOrganisationRepository.findOne({ id: id });
if (!organisation) { return null; }

View File

@ -93,7 +93,7 @@ export class RunnerTeamController {
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
@ResponseSchema(RunnerTeamHasRunnersError, { statusCode: 406 })
@OnUndefined(204)
@OpenAPI({ description: 'Delete the team whose id you provided. <br> If the team still has runners associated this will fail. <br> To delete the team with all associated runners set the force QueryParam to true (cascading deletion might take a while). <br> If no team with this id exists it will just return 204(no content).' })
@OpenAPI({ description: 'Delete the team whose id you provided. <br> If the team still has runners associated this will fail. <br> To delete the team with all associated runners set the force QueryParam to true (cascading deletion might take a while). <br> This won\'t delete the associated contact.<br> If no team with this id exists it will just return 204(no content).' })
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
let team = await this.runnerTeamRepository.findOne({ id: id });
if (!team) { return null; }

View File

@ -2,18 +2,7 @@ import { IsString } from 'class-validator';
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
/**
* Error to throw, when a provided groupContact doesn't belong to the accepted types.
*/
export class GroupContactWrongTypeError extends NotAcceptableError {
@IsString()
name = "GroupContactWrongTypeError"
@IsString()
message = "The groupContact must be an existing groupContact's id. \n You provided a object of another type."
}
/**
* Error to throw, when a non-existent groupContact get's loaded.
* Error to throw, when a non-existent contact get's requested.
*/
export class GroupContactNotFoundError extends NotFoundError {
@IsString()
@ -21,4 +10,16 @@ export class GroupContactNotFoundError extends NotFoundError {
@IsString()
message = "The groupContact you provided couldn't be located in the system. \n Please check your request."
}
}
/**
* Error to throw when two contacts' ids don't match.
* Usually occurs when a user tries to change a contact's id.
*/
export class GroupContactIdsNotMatchingError extends NotAcceptableError {
@IsString()
name = "GroupContactIdsNotMatchingError"
@IsString()
message = "The ids don't match! \n And if you wanted to change a contact's id: This isn't allowed!"
}

View File

@ -1,10 +1,13 @@
import { IsEmail, IsNotEmpty, IsObject, IsOptional, IsPhoneNumber, IsString } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { config } from '../../../config';
import { RunnerGroupNotFoundError } from '../../../errors/RunnerGroupErrors';
import { Address } from '../../entities/Address';
import { GroupContact } from '../../entities/GroupContact';
import { RunnerGroup } from '../../entities/RunnerGroup';
/**
* This classed is used to create a new Group entity from a json body (post request).
* This classed is used to create a new GroupContact entity from a json body (post request).
*/
export class CreateGroupContact {
/**
@ -44,25 +47,51 @@ export class CreateGroupContact {
phone?: string;
/**
* The contact's email address.
* The new contact's email address.
*/
@IsOptional()
@IsEmail()
email?: string;
/**
* The new contacts's groups' ids.
* You can provide either one groupId or an array of groupIDs.
*/
@IsOptional()
groups?: number[] | number
/**
* Creates a new Address entity from this.
* Get's all groups for this contact by their id's;
*/
public async getGroups(): Promise<RunnerGroup[]> {
if (!this.groups) { return null; }
let groups = new Array<RunnerGroup>();
if (!Array.isArray(this.groups)) {
this.groups = [this.groups]
}
for (let group of this.groups) {
let found = await getConnectionManager().get().getRepository(RunnerGroup).findOne({ id: group });
if (!found) { throw new RunnerGroupNotFoundError(); }
groups.push(found);
}
return groups;
}
/**
* Creates a new GroupContact entity from this.
*/
public async toEntity(): Promise<GroupContact> {
let contact: GroupContact = new GroupContact();
contact.firstname = this.firstname;
contact.middlename = this.middlename;
contact.lastname = this.lastname;
contact.email = this.email;
contact.phone = this.phone;
contact.address = this.address;
Address.validate(contact.address);
return contact;
let newContact: GroupContact = new GroupContact();
newContact.firstname = this.firstname;
newContact.middlename = this.middlename;
newContact.lastname = this.lastname;
newContact.email = this.email;
newContact.phone = this.phone;
newContact.address = this.address;
Address.validate(newContact.address);
newContact.groups = await this.getGroups();
return newContact;
}
}

View File

@ -0,0 +1,106 @@
import { IsEmail, IsInt, IsNotEmpty, IsObject, IsOptional, IsPhoneNumber, IsString } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { config } from '../../../config';
import { RunnerGroupNotFoundError } from '../../../errors/RunnerGroupErrors';
import { Address } from '../../entities/Address';
import { GroupContact } from '../../entities/GroupContact';
import { RunnerGroup } from '../../entities/RunnerGroup';
/**
* This class is used to update a GroupContact entity (via put request).
*/
export class UpdateGroupContact {
/**
* The updated contact'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 contact's first name.
*/
@IsNotEmpty()
@IsString()
firstname: string;
/**
* The updated contact's middle name.
*/
@IsOptional()
@IsString()
middlename?: string;
/**
* The updated contact's last name.
*/
@IsNotEmpty()
@IsString()
lastname: string;
/**
* The updated contact's address.
*/
@IsOptional()
@IsObject()
address?: Address;
/**
* The updated contact's phone number.
* This will be validated against the configured country phone numer syntax (default: international).
*/
@IsOptional()
@IsPhoneNumber(config.phone_validation_countrycode)
phone?: string;
/**
* The updated contact's email address.
*/
@IsOptional()
@IsEmail()
email?: string;
/**
* The updated contacts's groups' ids.
* You can provide either one groupId or an array of groupIDs.
*/
@IsOptional()
groups?: number[] | number
/**
* Get's all groups for this contact by their id's;
*/
public async getGroups(): Promise<RunnerGroup[]> {
if (!this.groups) { return null; }
let groups = new Array<RunnerGroup>();
if (!Array.isArray(this.groups)) {
this.groups = [this.groups]
}
for (let group of this.groups) {
let found = await getConnectionManager().get().getRepository(RunnerGroup).findOne({ id: group });
if (!found) { throw new RunnerGroupNotFoundError(); }
groups.push(found);
}
return groups;
}
/**
* Updates a provided Donor entity based on this.
* @param contact the contact you want to update.
*/
public async update(contact: GroupContact): Promise<GroupContact> {
contact.firstname = this.firstname; GroupContact
contact.middlename = this.middlename;
contact.lastname = this.lastname;
contact.phone = this.phone;
contact.email = this.email;
if (!this.address) { contact.address.reset(); }
else { contact.address = this.address; }
Address.validate(contact.address);
contact.groups = await this.getGroups();
return contact;
}
}

View File

@ -1,6 +1,4 @@
import {
IsNotEmpty,
IsOptional,
IsPostalCode,
IsString
} from "class-validator";
@ -18,10 +16,9 @@ export class Address {
* The address's first line.
* Containing the street and house number.
*/
@Column()
@Column({ nullable: true })
@IsString()
@IsNotEmpty()
address1: string;
address1?: string;
/**
* The address's second line.
@ -29,33 +26,29 @@ export class Address {
*/
@Column({ nullable: true })
@IsString()
@IsOptional()
address2?: string;
/**
* The address's postal code.
* This will get checked against the postal code syntax for the configured country.
*/
@Column()
@Column({ nullable: true })
@IsString()
@IsNotEmpty()
@IsPostalCode(config.postalcode_validation_countrycode)
postalcode: string;
/**
* The address's city.
*/
@Column()
@Column({ nullable: true })
@IsString()
@IsNotEmpty()
city: string;
/**
* The address's country.
*/
@Column()
@Column({ nullable: true })
@IsString()
@IsNotEmpty()
country: string;
public reset() {

View File

@ -9,6 +9,7 @@ import {
} from "class-validator";
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm";
import { config } from '../../config';
import { ResponseGroupContact } from '../responses/ResponseGroupContact';
import { Address } from "./Address";
import { RunnerGroup } from "./RunnerGroup";
@ -53,7 +54,6 @@ export class GroupContact {
* The contact's address.
* This is a address object to prevent any formatting differences.
*/
@IsOptional()
@Column(type => Address)
address?: Address;
@ -84,7 +84,7 @@ export class GroupContact {
/**
* Turns this entity into it's response class.
*/
public toResponse() {
return new Error("NotImplemented");
public toResponse(): ResponseGroupContact {
return new ResponseGroupContact(this);
}
}

View File

@ -14,5 +14,6 @@ export enum PermissionTarget {
SCAN = 'SCAN',
STATION = 'STATION',
CARD = 'CARD',
DONATION = 'DONATION'
DONATION = 'DONATION',
CONTACT = 'CONTACT'
}

View File

@ -0,0 +1,76 @@
import { IsInt, IsObject, IsString } from "class-validator";
import { Address } from '../entities/Address';
import { GroupContact } from '../entities/GroupContact';
import { ResponseRunnerGroup } from './ResponseRunnerGroup';
/**
* Defines the group contact response.
*/
export class ResponseGroupContact {
/**
* The contact's id.
*/
@IsInt()
id: number;
/**
* The contact's first name.
*/
@IsString()
firstname: string;
/**
* The contact's middle name.
*/
@IsString()
middlename?: string;
/**
* The contact's last name.
*/
@IsString()
lastname: string;
/**
* The contact's phone number.
*/
@IsString()
phone?: string;
/**
* The contact's e-mail address.
*/
@IsString()
email?: string;
/**
* The contact's associated runner groups.
*/
@IsObject()
groups: ResponseRunnerGroup[];
/**
* The contact's address.
* This is a address object to prevent any formatting differences.
*/
@IsObject()
address?: Address;
/**
* Creates a ResponseGroupContact object from a contact.
* @param contact The contact the response shall be build for.
*/
public constructor(contact: GroupContact) {
this.id = contact.id;
this.firstname = contact.firstname;
this.middlename = contact.middlename;
this.lastname = contact.lastname;
this.phone = contact.phone;
this.email = contact.email;
this.address = contact.address;
this.groups = new Array<ResponseRunnerGroup>();
for (let group of contact.groups) {
this.groups.push(group.toResponse());
}
}
}

View File

@ -0,0 +1,216 @@
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/contacts with errors', () => {
it('creating a new contact without any parameters should return 400', async () => {
const res = await axios.post(base + '/api/contacts', null, axios_config);
expect(res.status).toEqual(400);
expect(res.headers['content-type']).toContain("application/json")
});
it('creating a new contact without a last name should return 400', async () => {
const res = await axios.post(base + '/api/contacts', {
"firstname": "first",
"middlename": "middle"
}, axios_config);
expect(res.status).toEqual(400);
expect(res.headers['content-type']).toContain("application/json")
});
it('creating a new contact with a invalid phone number should return 400', async () => {
const res = await axios.post(base + '/api/contacts', {
"firstname": "first",
"middlename": "middle",
"lastname": "last",
"phone": "123"
}, axios_config);
expect(res.status).toEqual(400);
expect(res.headers['content-type']).toContain("application/json")
});
it('creating a new contact with a invalid mail address should return 400', async () => {
const res = await axios.post(base + '/api/contacts', {
"firstname": "string",
"middlename": "string",
"lastname": "string",
"phone": null,
"email": "123",
}, axios_config);
expect(res.status).toEqual(400);
expect(res.headers['content-type']).toContain("application/json")
});
it('creating a new contact with an invalid address 400', async () => {
const res = await axios.post(base + '/api/contacts', {
"firstname": "string",
"middlename": "string",
"lastname": "string",
"address": {
"city": "Testcity"
}
}, axios_config);
expect(res.status).toEqual(400);
expect(res.headers['content-type']).toContain("application/json")
});
it('creating a new contact with a invalid group should return 404', async () => {
const res = await axios.post(base + '/api/contacts', {
"firstname": "string",
"middlename": "string",
"lastname": "string",
"groups": 9999999999999
}, axios_config);
expect(res.status).toEqual(404);
expect(res.headers['content-type']).toContain("application/json")
});
});
// ---------------
describe('POST /api/contacts working (simple)', () => {
it('creating a new contact with only needed params should return 200', async () => {
const res = await axios.post(base + '/api/contacts', {
"firstname": "first",
"lastname": "last"
}, axios_config);
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json")
});
it('creating a new contact with all non-relationship optional params should return 200', async () => {
const res = await axios.post(base + '/api/contacts', {
"firstname": "first",
"middlename": "middle",
"lastname": "last",
"email": "testContact@lauf-fuer-kaya.de",
"phone": "+49017612345678",
"address": {
"address1": "test",
"address2": null,
"city": "herzogenaurach",
"country": "germany",
"postalcode": "91074",
}
}, axios_config);
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json")
});
});
// ---------------
describe('POST /api/contacts working (with group)', () => {
let added_org;
let added_team;
let added_contact;
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);
delete res.data.contact;
delete res.data.teams;
added_org = res.data
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json")
});
it('creating a new team with a parent org should return 200', async () => {
const res = await axios.post(base + '/api/teams', {
"name": "test_team",
"parentGroup": added_org.id
}, axios_config);
delete res.data.contact;
delete res.data.parentGroup;
added_team = res.data;
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json")
});
it('creating a new contact with a valid org should return 200', async () => {
const res = await axios.post(base + '/api/contacts', {
"firstname": "first",
"lastname": "last",
"groups": added_org.id
}, axios_config);
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json");
delete res.data.id;
expect(res.data).toEqual({
"firstname": "first",
"middlename": null,
"lastname": "last",
"phone": null,
"email": null,
"address": {
"address1": null,
"address2": null,
"postalcode": null,
"city": null,
"country": null
},
"groups": [added_org]
});
});
it('creating a new contact with a valid team should return 200', async () => {
const res = await axios.post(base + '/api/contacts', {
"firstname": "first",
"lastname": "last",
"groups": added_team.id
}, axios_config);
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json");
delete res.data.id;
expect(res.data).toEqual({
"firstname": "first",
"middlename": null,
"lastname": "last",
"phone": null,
"email": null,
"address": {
"address1": null,
"address2": null,
"postalcode": null,
"city": null,
"country": null
},
"groups": [added_team]
});
});
it('creating a new contact with a valid org and team should return 200', async () => {
const res = await axios.post(base + '/api/contacts', {
"firstname": "first",
"lastname": "last",
"groups": [added_org.id, added_team.id]
}, axios_config);
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json");
added_contact = res.data
delete res.data.id;
expect(res.data).toEqual({
"firstname": "first",
"middlename": null,
"lastname": "last",
"phone": null,
"email": null,
"address": {
"address1": null,
"address2": null,
"postalcode": null,
"city": null,
"country": null
},
"groups": [added_org, added_team]
});
});
it('checking if the added team\'s contact is the new contact should return 200', async () => {
const res = await axios.get(base + '/api/teams/' + added_team.id, axios_config);
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json");
delete res.data.contact.groups;
delete res.data.contact.id;
delete added_contact.groups;
expect(res.data.contact).toEqual(added_contact);
});
});

View File

@ -0,0 +1,183 @@
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 res = await axios.delete(base + '/api/contacts/0', axios_config);
expect(res.status).toEqual(204);
});
});
// ---------------
describe('add+delete (simple)', () => {
let added_contact;
it('creating a new contact with only needed params should return 200', async () => {
const res = await axios.post(base + '/api/contacts', {
"firstname": "first",
"lastname": "last"
}, axios_config);
added_contact = res.data;
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json")
});
it('delete contact', async () => {
const res = await axios.delete(base + '/api/contacts/' + added_contact.id, axios_config);
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json")
let deleted_contact = res.data
expect(deleted_contact).toEqual(added_contact);
});
it('check if contact really was deleted', async () => {
const res = await axios.get(base + '/api/contacts/' + added_contact.id, axios_config);
expect(res.status).toEqual(404);
expect(res.headers['content-type']).toContain("application/json")
});
});
// ---------------
describe('add+delete (with org)', () => {
let added_org;
let added_contact;
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);
delete res.data.contact;
delete res.data.teams;
added_org = res.data
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json")
});
it('creating a new contact with a valid org should return 200', async () => {
const res = await axios.post(base + '/api/contacts', {
"firstname": "first",
"lastname": "last",
"groups": added_org.id
}, axios_config);
added_contact = res.data;
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json");
});
it('delete contact', async () => {
const res = await axios.delete(base + '/api/contacts/' + added_contact.id, axios_config);
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json")
delete res.data.groups[0].contact;
expect(res.data).toEqual(added_contact);
});
it('check if contact really was deleted', async () => {
const res = await axios.get(base + '/api/contacts/' + added_contact.id, axios_config);
expect(res.status).toEqual(404);
expect(res.headers['content-type']).toContain("application/json")
});
});
// ---------------
describe('add+delete (with team)', () => {
let added_org;
let added_team;
let added_contact;
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);
delete res.data.contact;
delete res.data.teams;
added_org = res.data
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json")
});
it('creating a new team with a parent org should return 200', async () => {
const res = await axios.post(base + '/api/teams', {
"name": "test_team",
"parentGroup": added_org.id
}, axios_config);
delete res.data.contact;
delete res.data.parentGroup;
added_team = res.data;
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json")
});
it('creating a new contact with a valid team should return 200', async () => {
const res = await axios.post(base + '/api/contacts', {
"firstname": "first",
"lastname": "last",
"groups": added_team.id
}, axios_config);
added_contact = res.data;
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json");
});
it('delete contact', async () => {
const res = await axios.delete(base + '/api/contacts/' + added_contact.id, axios_config);
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json")
delete res.data.groups[0].contact;
expect(res.data).toEqual(added_contact);
});
it('check if contact really was deleted', async () => {
const res = await axios.get(base + '/api/contacts/' + added_contact.id, axios_config);
expect(res.status).toEqual(404);
expect(res.headers['content-type']).toContain("application/json")
});
});
// ---------------
describe('add+delete (with org&team)', () => {
let added_org;
let added_team;
let added_contact;
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);
delete res.data.contact;
delete res.data.teams;
added_org = res.data
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json")
});
it('creating a new team with a parent org should return 200', async () => {
const res = await axios.post(base + '/api/teams', {
"name": "test_team",
"parentGroup": added_org.id
}, axios_config);
delete res.data.contact;
delete res.data.parentGroup;
added_team = res.data;
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json")
});
it('creating a new contact with a valid org and team should return 200', async () => {
const res = await axios.post(base + '/api/contacts', {
"firstname": "first",
"lastname": "last",
"groups": [added_org.id, added_team.id]
}, axios_config);
added_contact = res.data;
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json");
});
it('delete contact', async () => {
const res = await axios.delete(base + '/api/contacts/' + added_contact.id, axios_config);
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json")
delete res.data.groups[0].contact;
delete res.data.groups[1].contact;
expect(res.data).toEqual(added_contact);
});
it('check if contact really was deleted', async () => {
const res = await axios.get(base + '/api/contacts/' + added_contact.id, axios_config);
expect(res.status).toEqual(404);
expect(res.headers['content-type']).toContain("application/json")
});
});

View File

@ -0,0 +1,57 @@
import axios from 'axios';
import { config } from '../../config';
const base = "http://localhost:" + config.internal_port
let access_token;
let axios_config;
beforeAll(async () => {
const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" });
access_token = res.data["access_token"];
axios_config = {
headers: { "authorization": "Bearer " + access_token },
validateStatus: undefined
};
});
describe('GET /api/contacts', () => {
it('basic get should return 200', async () => {
const res = await axios.get(base + '/api/contacts', axios_config);
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json")
});
});
// ---------------
describe('GET /api/contacts/0', () => {
it('basic get should return 404', async () => {
const res = await axios.get(base + '/api/contacts/0', axios_config);
expect(res.status).toEqual(404);
expect(res.headers['content-type']).toContain("application/json")
});
});
// ---------------
describe('GET /api/contacts after adding', () => {
let added_contact;
it('creating a new donor with only needed params should return 200', async () => {
const res = await axios.post(base + '/api/contacts', {
"firstname": "first",
"lastname": "last"
}, axios_config);
added_contact = res.data;
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json")
});
it('explicit get should return 200', async () => {
const res = await axios.get(base + '/api/contacts/' + added_contact.id, axios_config);
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json")
let gotten_donor = res.data
expect(gotten_donor).toEqual(added_contact);
});
it('get from all runners should return 200', async () => {
const res = await axios.get(base + '/api/contacts/', axios_config);
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json")
let gotten_donors = res.data
expect(gotten_donors).toContainEqual(added_contact);
});
});

View File

@ -0,0 +1,237 @@
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 contact name after adding', () => {
let added_contact;
it('creating a new contact with only needed params should return 200', async () => {
const res = await axios.post(base + '/api/contacts', {
"firstname": "first",
"lastname": "last"
}, axios_config);
added_contact = res.data;
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json")
});
it('valid update should return 200', async () => {
let contact_copy = added_contact
contact_copy.firstname = "second"
const res = await axios.put(base + '/api/contacts/' + added_contact.id, contact_copy, axios_config);
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json")
expect(res.data).toEqual(contact_copy);
});
});
// ---------------
describe('Update contact id after adding(should fail)', () => {
let added_contact;
it('creating a new donor with only needed params should return 200', async () => {
const res = await axios.post(base + '/api/contacts', {
"firstname": "first",
"lastname": "last"
}, axios_config);
added_contact = res.data;
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json")
});
it('invalid update should return 406', async () => {
added_contact.id++;
const res = await axios.put(base + '/api/contacts/' + (added_contact.id - 1), added_contact, axios_config);
expect(res.status).toEqual(406);
expect(res.headers['content-type']).toContain("application/json")
});
});
// ---------------
describe('Update contact group after adding (should work)', () => {
let added_org;
let added_team;
let added_contact;
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);
delete res.data.contact;
delete res.data.teams;
added_org = res.data
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json")
});
it('creating a new team with a parent org should return 200', async () => {
const res = await axios.post(base + '/api/teams', {
"name": "test_team",
"parentGroup": added_org.id
}, axios_config);
delete res.data.contact;
delete res.data.parentGroup;
added_team = res.data;
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json")
});
it('creating a new contact with a valid org should return 200', async () => {
const res = await axios.post(base + '/api/contacts', {
"firstname": "first",
"lastname": "last",
"groups": added_org.id
}, axios_config);
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json");
added_contact = res.data
expect(res.data).toEqual({
"id": res.data.id,
"firstname": "first",
"middlename": null,
"lastname": "last",
"phone": null,
"email": null,
"address": {
"address1": null,
"address2": null,
"postalcode": null,
"city": null,
"country": null
},
"groups": [added_org]
});
});
it('valid group update to single team should return 200', async () => {
const res = await axios.put(base + '/api/contacts/' + added_contact.id, {
"id": added_contact.id,
"firstname": "first",
"lastname": "last",
"groups": added_team.id
}, axios_config);
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json");
expect(res.data).toEqual({
"id": res.data.id,
"firstname": "first",
"middlename": null,
"lastname": "last",
"phone": null,
"email": null,
"address": {
"address1": null,
"address2": null,
"postalcode": null,
"city": null,
"country": null
},
"groups": [added_team]
});
});
it('valid group update to org and team should return 200', async () => {
const res = await axios.put(base + '/api/contacts/' + added_contact.id, {
"id": added_contact.id,
"firstname": "first",
"lastname": "last",
"groups": [added_org.id, added_team.id]
}, axios_config);
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json");
expect(res.data).toEqual({
"id": res.data.id,
"firstname": "first",
"middlename": null,
"lastname": "last",
"phone": null,
"email": null,
"address": {
"address1": null,
"address2": null,
"postalcode": null,
"city": null,
"country": null
},
"groups": [added_org, added_team]
});
});
it('valid group update to none should return 200', async () => {
const res = await axios.put(base + '/api/contacts/' + added_contact.id, {
"id": added_contact.id,
"firstname": "first",
"lastname": "last",
"groups": null
}, axios_config);
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json");
expect(res.data).toEqual({
"id": res.data.id,
"firstname": "first",
"middlename": null,
"lastname": "last",
"phone": null,
"email": null,
"address": {
"address1": null,
"address2": null,
"postalcode": null,
"city": null,
"country": null
},
"groups": []
});
});
});
// ---------------
describe('Update contact group invalid after adding (should fail)', () => {
let added_org;
let added_contact;
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);
delete res.data.contact;
delete res.data.teams;
added_org = res.data
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json")
});
it('creating a new contact with a valid org should return 200', async () => {
const res = await axios.post(base + '/api/contacts', {
"firstname": "first",
"lastname": "last",
"groups": added_org.id
}, axios_config);
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json");
added_contact = res.data
expect(res.data).toEqual({
"id": res.data.id,
"firstname": "first",
"middlename": null,
"lastname": "last",
"phone": null,
"email": null,
"address": {
"address1": null,
"address2": null,
"postalcode": null,
"city": null,
"country": null
},
"groups": [added_org]
});
});
it('invalid group update to single team should return 404', async () => {
const res = await axios.put(base + '/api/contacts/' + added_contact.id, {
"id": added_contact.id,
"firstname": "first",
"lastname": "last",
"groups": 999999999999999
}, axios_config);
expect(res.status).toEqual(404);
expect(res.headers['content-type']).toContain("application/json");
});
});