diff --git a/src/controllers/RunnerCardController.ts b/src/controllers/RunnerCardController.ts new file mode 100644 index 0000000..2263f07 --- /dev/null +++ b/src/controllers/RunnerCardController.ts @@ -0,0 +1,106 @@ +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 { RunnerCardHasScansError, RunnerCardIdsNotMatchingError, RunnerCardNotFoundError } from '../errors/RunnerCardErrors'; +import { RunnerNotFoundError } from '../errors/RunnerErrors'; +import { CreateRunnerCard } from '../models/actions/CreateRunnerCard'; +import { UpdateRunnerCard } from '../models/actions/UpdateRunnerCard'; +import { RunnerCard } from '../models/entities/RunnerCard'; +import { ResponseEmpty } from '../models/responses/ResponseEmpty'; +import { ResponseRunnerCard } from '../models/responses/ResponseRunnerCard'; +import { ScanController } from './ScanController'; + +@JsonController('/cards') +@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) +export class RunnerCardController { + private cardRepository: Repository; + + /** + * Gets the repository of this controller's model/entity. + */ + constructor() { + this.cardRepository = getConnectionManager().get().getRepository(RunnerCard); + } + + @Get() + @Authorized("CARD:GET") + @ResponseSchema(ResponseRunnerCard, { isArray: true }) + @OpenAPI({ description: 'Lists all card.' }) + async getAll() { + let responseCards: ResponseRunnerCard[] = new Array(); + const cards = await this.cardRepository.find({ relations: ['runner'] }); + cards.forEach(card => { + responseCards.push(new ResponseRunnerCard(card)); + }); + return responseCards; + } + + @Get('/:id') + @Authorized("CARD:GET") + @ResponseSchema(ResponseRunnerCard) + @ResponseSchema(RunnerCardNotFoundError, { statusCode: 404 }) + @OnUndefined(RunnerCardNotFoundError) + @OpenAPI({ description: "Lists all information about the card whose id got provided." }) + async getOne(@Param('id') id: number) { + let card = await this.cardRepository.findOne({ id: id }, { relations: ['runner'] }); + if (!card) { throw new RunnerCardNotFoundError(); } + return card.toResponse(); + } + + @Post() + @Authorized("CARD:CREATE") + @ResponseSchema(ResponseRunnerCard) + @ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) + @OpenAPI({ description: "Create a new card.
You can provide a associated runner by id but you don't have to." }) + async post(@Body({ validate: true }) createCard: CreateRunnerCard) { + let card = await createCard.toEntity(); + card = await this.cardRepository.save(card); + return (await this.cardRepository.findOne({ id: card.id }, { relations: ['runner'] })).toResponse(); + } + + @Put('/:id') + @Authorized("CARD:UPDATE") + @ResponseSchema(ResponseRunnerCard) + @ResponseSchema(RunnerCardNotFoundError, { statusCode: 404 }) + @ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) + @ResponseSchema(RunnerCardIdsNotMatchingError, { statusCode: 406 }) + @OpenAPI({ description: "Update the card whose id you provided.
Scans created via this card will still be associated with the old runner.
Please remember that ids can't be changed." }) + async put(@Param('id') id: number, @Body({ validate: true }) card: UpdateRunnerCard) { + let oldCard = await this.cardRepository.findOne({ id: id }); + + if (!oldCard) { + throw new RunnerCardNotFoundError(); + } + + if (oldCard.id != card.id) { + throw new RunnerCardIdsNotMatchingError(); + } + + await this.cardRepository.save(await card.update(oldCard)); + return (await this.cardRepository.findOne({ id: id }, { relations: ['runner'] })).toResponse(); + } + + @Delete('/:id') + @Authorized("CARD:DELETE") + @ResponseSchema(ResponseRunnerCard) + @ResponseSchema(ResponseEmpty, { statusCode: 204 }) + @ResponseSchema(RunnerCardHasScansError, { statusCode: 406 }) + @OnUndefined(204) + @OpenAPI({ description: "Delete the card whose id you provided.
If no card with this id exists it will just return 204(no content).
If the card still has scans associated you have to provide the force=true query param (warning: this deletes all scans associated with by this card - please disable it instead or just remove the runner association)." }) + async remove(@Param("id") id: number, @QueryParam("force") force: boolean) { + let card = await this.cardRepository.findOne({ id: id }); + if (!card) { return null; } + + const cardScans = (await this.cardRepository.findOne({ id: id }, { relations: ["scans"] })).scans; + if (cardScans.length != 0 && !force) { + throw new RunnerCardHasScansError(); + } + const scanController = new ScanController; + for (let scan of cardScans) { + scanController.remove(scan.id, force); + } + + await this.cardRepository.delete(card); + return card.toResponse(); + } +} \ No newline at end of file diff --git a/src/errors/RunnerCardErrors.ts b/src/errors/RunnerCardErrors.ts new file mode 100644 index 0000000..63c3485 --- /dev/null +++ b/src/errors/RunnerCardErrors.ts @@ -0,0 +1,48 @@ +import { IsString } from 'class-validator'; +import { NotAcceptableError, NotFoundError } from 'routing-controllers'; + +/** + * Error to throw when a card couldn't be found. + */ +export class RunnerCardNotFoundError extends NotFoundError { + @IsString() + name = "RunnerCardNotFoundError" + + @IsString() + message = "Card not found!" +} + +/** + * Error to throw when two cards' ids don't match. + * Usually occurs when a user tries to change a card's id. + */ +export class RunnerCardIdsNotMatchingError extends NotAcceptableError { + @IsString() + name = "RunnerCardIdsNotMatchingError" + + @IsString() + message = "The ids don't match! \n And if you wanted to change a cards's id: This isn't allowed" +} + +/** + * Error to throw when a card still has scans associated. + */ +export class RunnerCardHasScansError extends NotAcceptableError { + @IsString() + name = "RunnerCardHasScansError" + + @IsString() + message = "This card still has scans associated with it. \n If you want to delete this card with all it's scans add `?force` to your query. \n Otherwise please consider just diableing it." +} + +/** + * Error to throw when a card's id is too big to generate a ean-13 barcode for it. + * This error should never reach a enduser. + */ +export class RunnerCardIdOutOfRangeError extends Error { + @IsString() + name = "RunnerCardIdOutOfRangeError" + + @IsString() + message = "The card's id is too big to fit into a ean-13 barcode. \n This has a very low probability of happening but means that you might want to switch your barcode format for something that can accept numbers over 9999999999." +} \ No newline at end of file diff --git a/src/models/actions/CreateRunnerCard.ts b/src/models/actions/CreateRunnerCard.ts new file mode 100644 index 0000000..2baf1e9 --- /dev/null +++ b/src/models/actions/CreateRunnerCard.ts @@ -0,0 +1,45 @@ +import { IsBoolean, IsInt, IsOptional } from 'class-validator'; +import { getConnection } from 'typeorm'; +import { RunnerNotFoundError } from '../../errors/RunnerErrors'; +import { Runner } from '../entities/Runner'; +import { RunnerCard } from '../entities/RunnerCard'; + +/** + * This classed is used to create a new RunnerCard entity from a json body (post request). + */ +export class CreateRunnerCard { + /** + * The card's associated runner. + */ + @IsInt() + @IsOptional() + runner?: number; + + /** + * Is the new card enabled (for fraud reasons)? + * Default: true + */ + @IsBoolean() + enabled: boolean = true; + + /** + * Creates a new RunnerCard entity from this. + */ + public async toEntity(): Promise { + let newCard: RunnerCard = new RunnerCard(); + + newCard.enabled = this.enabled; + newCard.runner = await this.getRunner(); + + return newCard; + } + + public async getRunner(): Promise { + if (!this.runner) { return null; } + 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/UpdateRunnerCard.ts b/src/models/actions/UpdateRunnerCard.ts new file mode 100644 index 0000000..2ee34ab --- /dev/null +++ b/src/models/actions/UpdateRunnerCard.ts @@ -0,0 +1,51 @@ +import { IsBoolean, IsInt, IsOptional, IsPositive } from 'class-validator'; +import { getConnection } from 'typeorm'; +import { RunnerNotFoundError } from '../../errors/RunnerErrors'; +import { Runner } from '../entities/Runner'; +import { RunnerCard } from '../entities/RunnerCard'; + +/** + * This class is used to update a RunnerCard entity (via put request). + */ +export class UpdateRunnerCard { + /** + * The updated card'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() + @IsPositive() + id?: number; + + /** + * The updated card's associated runner. + */ + @IsInt() + @IsOptional() + runner?: number; + + /** + * Is the updated card enabled (for fraud reasons)? + * Default: true + */ + @IsBoolean() + enabled: boolean = true; + + /** + * Creates a new RunnerCard entity from this. + */ + public async update(card: RunnerCard): Promise { + card.enabled = this.enabled; + card.runner = await this.getRunner(); + + return card; + } + + public async getRunner(): Promise { + if (!this.runner) { return null; } + 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/entities/RunnerCard.ts b/src/models/entities/RunnerCard.ts index 4ea40e2..a7bccc4 100644 --- a/src/models/entities/RunnerCard.ts +++ b/src/models/entities/RunnerCard.ts @@ -1,12 +1,13 @@ import { IsBoolean, - IsEAN, + IsInt, - IsNotEmpty, - IsOptional, - IsString + + IsOptional } from "class-validator"; import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm"; +import { RunnerCardIdOutOfRangeError } from '../../errors/RunnerCardErrors'; +import { ResponseRunnerCard } from '../responses/ResponseRunnerCard'; import { Runner } from "./Runner"; import { TrackScan } from "./TrackScan"; @@ -32,17 +33,6 @@ export class RunnerCard { @ManyToOne(() => Runner, runner => runner.cards, { nullable: true }) runner: Runner; - /** - * The card's code. - * This has to be able to being converted to something barcode compatible. - * Will get automaticlly generated (not implemented yet). - */ - @Column() - @IsEAN() - @IsString() - @IsNotEmpty() - code: string; - /** * Is the card enabled (for fraud reasons)? * Default: true @@ -58,10 +48,37 @@ export class RunnerCard { @OneToMany(() => TrackScan, scan => scan.track, { nullable: true }) scans: TrackScan[]; + /** + * Generates a ean-13 compliant string for barcode generation. + */ + public get code(): string { + const multiply = [1, 3]; + let total = 0; + this.paddedId.split('').forEach((letter, index) => { + total += parseInt(letter, 10) * multiply[index % 2]; + }); + const checkSum = (Math.ceil(total / 10) * 10) - total; + return this.paddedId + checkSum.toString(); + } + + /** + * Returns this card's id as a string padded to the length of 12 characters with leading zeros. + */ + private get paddedId(): string { + let id: string = this.id.toString(); + + if (id.length > 12) { + throw new RunnerCardIdOutOfRangeError(); + } + while (id.length < 12) { id = '0' + id; } + + return id; + } + /** * Turns this entity into it's response class. */ public toResponse() { - return new Error("NotImplemented"); + return new ResponseRunnerCard(this); } } diff --git a/src/models/enums/PermissionTargets.ts b/src/models/enums/PermissionTargets.ts index dae9192..551ea5c 100644 --- a/src/models/enums/PermissionTargets.ts +++ b/src/models/enums/PermissionTargets.ts @@ -12,5 +12,6 @@ export enum PermissionTarget { STATSCLIENT = 'STATSCLIENT', DONOR = 'DONOR', SCAN = 'SCAN', - STATION = 'STATION' + STATION = 'STATION', + CARD = 'CARD' } \ No newline at end of file diff --git a/src/models/responses/ResponseRunnerCard.ts b/src/models/responses/ResponseRunnerCard.ts new file mode 100644 index 0000000..f895a9c --- /dev/null +++ b/src/models/responses/ResponseRunnerCard.ts @@ -0,0 +1,53 @@ +import { IsBoolean, IsEAN, IsInt, IsNotEmpty, IsObject, IsString } from "class-validator"; +import { RunnerCard } from '../entities/RunnerCard'; +import { ResponseRunner } from './ResponseRunner'; + +/** + * Defines the runner card response. +*/ +export class ResponseRunnerCard { + /** + * The card's id. + */ + @IsInt() + id: number;; + + /** + * The card's associated runner. + * This is important to link scans to runners. + */ + @IsObject() + runner: ResponseRunner | null; + + /** + * The card's code. + */ + @IsEAN() + @IsString() + @IsNotEmpty() + code: string; + + /** + * Is the enabled valid (for fraud reasons). + * The determination of validity will work differently for every child class. + */ + @IsBoolean() + enabled: boolean = true; + + /** + * Creates a ResponseRunnerCard object from a runner card. + * @param card The card the response shall be build for. + */ + public constructor(card: RunnerCard) { + this.id = card.id; + if (!card.runner) { this.runner = null } + else { this.runner = card.runner.toResponse(); } + try { + this.code = card.code; + } catch (error) { + this.code = "0000000000000" + } + + this.enabled = card.enabled; + } +} diff --git a/src/tests/cards/cards_add.spec.ts b/src/tests/cards/cards_add.spec.ts new file mode 100644 index 0000000..c89e55b --- /dev/null +++ b/src/tests/cards/cards_add.spec.ts @@ -0,0 +1,144 @@ +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/cards illegally', () => { + it('non-existant runner input should return 404', async () => { + const res = await axios.post(base + '/api/cards', { + "runner": 999999999999999999999999 + }, axios_config); + expect(res.status).toEqual(404); + expect(res.headers['content-type']).toContain("application/json") + }); +}); +// --------------- +describe('POST /api/cards successfully (without runner)', () => { + it('creating a card with the minimum amount of parameters should return 200', async () => { + const res = await axios.post(base + '/api/cards', null, axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json"); + delete res.data.id; + delete res.data.code; + expect(res.data).toEqual({ + "runner": null, + "enabled": true + }); + }); + it('creating a disabled card should return 200', async () => { + const res = await axios.post(base + '/api/cards', { + "enabled": false + }, axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json"); + delete res.data.id; + delete res.data.code; + expect(res.data).toEqual({ + "runner": null, + "enabled": false + }); + }); + it('creating a enabled card should return 200', async () => { + const res = await axios.post(base + '/api/cards', { + "enabled": true + }, axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json"); + delete res.data.id; + delete res.data.code; + expect(res.data).toEqual({ + "runner": null, + "enabled": true + }); + }); +}); +// --------------- +describe('POST /api/cards successfully (with runner)', () => { + 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; + added_runner = res2.data; + expect(res2.status).toEqual(200); + expect(res2.headers['content-type']).toContain("application/json") + }); + it('creating a card with the minimum amount of parameters should return 200', async () => { + const res = await axios.post(base + '/api/cards', { + "runner": added_runner.id + }, axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json"); + delete res.data.id; + delete res.data.code; + expect(res.data).toEqual({ + "runner": added_runner, + "enabled": true + }); + }); + it('creating a card with runner (no optional params) should return 200', async () => { + const res = await axios.post(base + '/api/cards', { + "runner": added_runner.id + }, axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json"); + delete res.data.id; + delete res.data.code; + expect(res.data).toEqual({ + "runner": added_runner, + "enabled": true + }); + }); + it('creating a enabled card with runner should return 200', async () => { + const res = await axios.post(base + '/api/cards', { + "runner": added_runner.id, + "enabled": true + }, axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json"); + delete res.data.id; + delete res.data.code; + expect(res.data).toEqual({ + "runner": added_runner, + "enabled": true + }); + }); + it('creating a disabled card with runner should return 200', async () => { + const res = await axios.post(base + '/api/cards', { + "runner": added_runner.id, + "enabled": false + }, axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json"); + delete res.data.id; + delete res.data.code; + expect(res.data).toEqual({ + "runner": added_runner, + "enabled": false + }); + }); +}); \ No newline at end of file diff --git a/src/tests/cards/cards_delete.spec.ts b/src/tests/cards/cards_delete.spec.ts new file mode 100644 index 0000000..1e657c8 --- /dev/null +++ b/src/tests/cards/cards_delete.spec.ts @@ -0,0 +1,45 @@ +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 card', () => { + let added_card; + it('creating card without runner should return 200', async () => { + const res = await axios.post(base + '/api/cards', null, axios_config); + added_card = res.data; + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('delete card', async () => { + const res2 = await axios.delete(base + '/api/cards/' + added_card.id, axios_config); + expect(res2.status).toEqual(200); + expect(res2.headers['content-type']).toContain("application/json") + expect(res2.data).toEqual(added_card); + }); + it('check if card really was deleted', async () => { + const res3 = await axios.get(base + '/api/cards/' + added_card.id, axios_config); + expect(res3.status).toEqual(404); + expect(res3.headers['content-type']).toContain("application/json") + }); +}); +// --------------- +describe('DELETE card (non-existant)', () => { + it('delete', async () => { + const res2 = await axios.delete(base + '/api/cards/0', axios_config); + expect(res2.status).toEqual(204); + }); +}); diff --git a/src/tests/cards/cards_get.spec.ts b/src/tests/cards/cards_get.spec.ts new file mode 100644 index 0000000..6ca41ec --- /dev/null +++ b/src/tests/cards/cards_get.spec.ts @@ -0,0 +1,46 @@ +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/cards sucessfully', () => { + it('basic get should return 200', async () => { + const res = await axios.get(base + '/api/cards', axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); +}); +// --------------- +describe('GET /api/cards illegally', () => { + it('get for non-existant track should return 404', async () => { + const res = await axios.get(base + '/api/cards/-1', axios_config); + expect(res.status).toEqual(404); + expect(res.headers['content-type']).toContain("application/json") + }); +}); +// --------------- +describe('adding + getting cards (no runner)', () => { + let added_card; + it('correct distance and runner input should return 200', async () => { + const res = await axios.post(base + '/api/cards', null, axios_config); + added_card = 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/cards/' + added_card.id, axios_config); + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json"); + }); +}); \ No newline at end of file diff --git a/src/tests/cards/cards_update.spec.ts b/src/tests/cards/cards_update.spec.ts new file mode 100644 index 0000000..4ef31ee --- /dev/null +++ b/src/tests/cards/cards_update.spec.ts @@ -0,0 +1,163 @@ +import axios from 'axios'; +import { config } from '../../config'; +const base = "http://localhost:" + config.internal_port + +let access_token; +let axios_config; + +beforeAll(async () => { + const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" }); + access_token = res.data["access_token"]; + axios_config = { + headers: { "authorization": "Bearer " + access_token }, + validateStatus: undefined + }; +}); + +describe('adding + updating illegally', () => { + let added_card; + it('creating card without runner should return 200', async () => { + const res = await axios.post(base + '/api/cards', null, axios_config); + added_card = 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/cards/' + added_card.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/cards/' + added_card.id, { + "id": added_card.id + 1 + }, axios_config); + expect(res2.status).toEqual(406); + expect(res2.headers['content-type']).toContain("application/json") + }); + it('update with invalid runner id should return 404', async () => { + const res2 = await axios.put(base + '/api/cards/' + added_card.id, { + "id": added_card.id, + "runner": 9999999999999999999999999 + }, axios_config); + expect(res2.status).toEqual(404); + expect(res2.headers['content-type']).toContain("application/json") + }); +}); +// --------------- +describe('adding + updating card.runner successfully', () => { + let added_org; + let added_runner; + let added_runner2; + let added_card; + 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; + added_runner = res2.data; + expect(res2.status).toEqual(200); + expect(res2.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; + added_runner2 = res2.data; + expect(res2.status).toEqual(200); + expect(res2.headers['content-type']).toContain("application/json") + }); + it('creating card without runner should return 200', async () => { + const res = await axios.post(base + '/api/cards', null, axios_config); + added_card = res.data; + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('valid runner update (add runner) should return 200', async () => { + const res2 = await axios.put(base + '/api/cards/' + added_card.id, { + "id": added_card.id, + "runner": added_runner.id + }, axios_config); + expect(res2.status).toEqual(200); + expect(res2.headers['content-type']).toContain("application/json"); + expect(res2.data).toEqual({ + "id": added_card.id, + "runner": added_runner, + "enabled": true, + "code": added_card.code + }); + }); + it('valid runner update (change runner) should return 200', async () => { + const res2 = await axios.put(base + '/api/cards/' + added_card.id, { + "id": added_card.id, + "runner": added_runner2.id + }, axios_config); + expect(res2.status).toEqual(200); + expect(res2.headers['content-type']).toContain("application/json"); + expect(res2.data).toEqual({ + "id": added_card.id, + "runner": added_runner2, + "enabled": true, + "code": added_card.code + }); + }); +}); +// --------------- +describe('adding + updating other values successfully', () => { + let added_card; + it('creating card without runner should return 200', async () => { + const res = await axios.post(base + '/api/cards', null, axios_config); + added_card = res.data; + expect(res.status).toEqual(200); + expect(res.headers['content-type']).toContain("application/json") + }); + it('valid update changeing nothing should return 200', async () => { + const res2 = await axios.put(base + '/api/cards/' + added_card.id, { + "id": added_card.id + }, axios_config); + expect(res2.status).toEqual(200); + expect(res2.headers['content-type']).toContain("application/json"); + expect(res2.data).toEqual(added_card); + }); + it('valid disable update should return 200', async () => { + const res2 = await axios.put(base + '/api/cards/' + added_card.id, { + "id": added_card.id, + "enabled": false + }, axios_config); + expect(res2.status).toEqual(200); + expect(res2.headers['content-type']).toContain("application/json"); + expect(res2.data).toEqual({ + "id": added_card.id, + "runner": null, + "enabled": false, + "code": added_card.code + }); + }); + it('valid enable update should return 200', async () => { + const res2 = await axios.put(base + '/api/cards/' + added_card.id, { + "id": added_card.id, + "enabled": true + }, axios_config); + expect(res2.status).toEqual(200); + expect(res2.headers['content-type']).toContain("application/json"); + expect(res2.data).toEqual({ + "id": added_card.id, + "runner": null, + "enabled": true, + "code": added_card.code + }); + }); +}); +