Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@
"sourceMapPathOverrides": {
"/app/*": "${workspaceRoot}/*"
}
},
{
"name": "Debug Jest Tests",
"type": "node",
"request": "launch",
"runtimeArgs": ["--inspect-brk", "${workspaceRoot}/node_modules/jest/bin/jest.js", "--runInBand"],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"port": 9229
}
]
}
2 changes: 1 addition & 1 deletion src/api/routes/elections/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ router.post('/', async (request, response, next) => {

electionDTO.electionOrganizer = request.electionOrganizer

const election: Election | undefined = await electionService.createElection(electionDTO)
const election: Election | undefined = await electionService.create(electionDTO)
response.status(StatusCodes.CREATED).json(election)
} catch (error) {
next(error)
Expand Down
23 changes: 14 additions & 9 deletions src/models/Election/ElectionEntity.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import { ElectionStatus } from '@/models/Election/ElectionStatus'
import { IElection } from '@/models/Election/IElection'
import { ElectionOrganizer } from '@/models/ElectionOrganizer/ElectionOrganizerEntity'
import { EligibleVoter } from '@/models/EligibleVoter/EligibleVoterEntity'
import { IsOptional, IsPositive, MinDate } from 'class-validator'
import {
Column,
CreateDateColumn,
Expand All @@ -8,12 +13,7 @@ import {
PrimaryGeneratedColumn,
UpdateDateColumn
} from 'typeorm'
import { ElectionOrganizer } from '@/models/ElectionOrganizer/ElectionOrganizerEntity'

import { EligibleVoter } from '@/models/EligibleVoter/EligibleVoterEntity'
import { IElection } from '@/models/Election/IElection'
import { ElectionStatus } from '@/models/Election/ElectionStatus'
import { IsPositive } from 'class-validator'
import { IsEarlierThan } from '../constraints/isEarlierThan'

/**
* An entity for storing an election.
Expand All @@ -22,7 +22,6 @@ import { IsPositive } from 'class-validator'
* an election can have many eligible voters.
* The purpose of an election entity is to hold ballots, which an election can have many of.
*/

@Entity()
export class Election implements IElection {
@PrimaryGeneratedColumn()
Expand All @@ -41,11 +40,17 @@ export class Election implements IElection {
@Column({ type: String, nullable: true })
image!: string

@IsEarlierThan('closeDate', { message: 'Opening date must be before closing date' })
@IsOptional({ always: true })
@MinDate(new Date(), {
groups: ['creation'],
always: false
})
@Column({ type: Date, nullable: true })
openDate!: Date
openDate?: Date

@Column({ type: Date, nullable: true })
closeDate!: Date
closeDate?: Date

@Column({ type: String, nullable: true })
password!: string
Expand Down
21 changes: 21 additions & 0 deletions src/models/constraints/isEarlierThan.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { registerDecorator, ValidationOptions, ValidationArguments } from 'class-validator'

export function IsEarlierThan(property: string, validationOptions?: ValidationOptions) {
return function (object: Object, propertyName: string) {
registerDecorator({
name: 'isEarlierThan',
target: object.constructor,
propertyName: propertyName,
constraints: [property],
options: validationOptions,
validator: {
validate(value: any, args: ValidationArguments) {
const [relatedPropertyName] = args.constraints
const relatedValue = (args.object as any)[relatedPropertyName]
if (!value || !relatedValue) return true
return value <= relatedValue // you can return a Promise<boolean> here as well, if you want to make async validation
}
}
})
}
}
6 changes: 3 additions & 3 deletions src/services/ElectionService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export class ElectionService extends BaseEntityService<Election> implements IHas
})
}

async createElection(electionDTO: IElection): Promise<Election | undefined> {
private async createElection(electionDTO: IElection): Promise<Election | undefined> {
if (electionDTO.password) {
await this.hashEntityPassword(electionDTO)
}
Expand All @@ -98,7 +98,7 @@ export class ElectionService extends BaseEntityService<Election> implements IHas
electionDTO.password = hashedPassword
}

async updateElectionById(id: number, electionDTO: IElection): Promise<Election | undefined> {
private async updateElectionById(id: number, electionDTO: IElection): Promise<Election | undefined> {
const existingElection = await this.manager.findOne(id, {
where: {
electionOrganizer: this.owner
Expand All @@ -110,7 +110,7 @@ export class ElectionService extends BaseEntityService<Election> implements IHas
if (strippedElection?.password) await this.hashEntityPassword(strippedElection)
const updatedElection = this.manager.create(strippedElection!)
updatedElection.id = existingElection.id
await validateEntity(updatedElection)
await validateEntity(updatedElection, { strictGroups: true })
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably be set by default


return await this.manager.save(updatedElection)
}
Expand Down
102 changes: 97 additions & 5 deletions tests/models/ElectionEntity.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { Connection, getConnection, Repository } from 'typeorm'
import setupConnection from '../helpers/setupTestDB'
import { clearDatabaseEntityTable } from '@/../tests/Tests.utils'
import config from '@/config'
import { validateEntity } from '@/helpers/validateEntity'
import { Election } from '@/models/Election/ElectionEntity'
import { ElectionOrganizer } from '@/models/ElectionOrganizer/ElectionOrganizerEntity'
import { ElectionStatus } from '@/models/Election/ElectionStatus'
import { validateEntity } from '@/helpers/validateEntity'
import { ElectionOrganizer } from '@/models/ElectionOrganizer/ElectionOrganizerEntity'
import { validate, ValidatorOptions } from 'class-validator'
import { Connection, getConnection, Repository } from 'typeorm'
import setupConnection from '../helpers/setupTestDB'
import { clearDatabaseEntityTable } from '../Tests.utils'

let electionRepository: Repository<Election>
let connection: Connection
Expand Down Expand Up @@ -71,6 +72,97 @@ test('Election with status set to Started should return with status started', as
expect(firstElection.status).not.toBe<ElectionStatus>(ElectionStatus.Finished)
})

it('should pass if openDate is not given', async () => {
const entity = new Election()
entity.id = 1
entity.description = 'no open date'
entity.title = 'No open date'
entity.status = ElectionStatus.NotStarted

await expect(validate(entity)).resolves.toStrictEqual([])
await expect(validateEntity(entity)).resolves.toBe(undefined)
})

it('should fail if openDate is earlier than today', async () => {
const entity = new Election()
entity.openDate = new Date(1999, 12, 30)

expect((await validate(entity)).length).toBeGreaterThan(0)
await expect(validateEntity(entity)).rejects.toThrowError()
})

it('should fail if other openDate is before closeDate', async () => {
const entity = new Election()
entity.openDate = new Date()
entity.closeDate = new Date(1999, 1, 1)

expect((await validate(entity)).length).toBeGreaterThan(0)
await expect(validateEntity(entity)).rejects.toThrowError()
})

it('should fail if openDate is before closeDate but also before now', async () => {
const entity = new Election()
entity.closeDate = new Date(2022, 1, 1)
entity.openDate = new Date(1999, 1, 1)

expect((await validate(entity)).length).toBeGreaterThan(0)
await expect(validateEntity(entity)).rejects.toThrowError()
})

it('should pass if groups is set to creation and date is later than today', async () => {
const entity = new Election()
entity.openDate = new Date(2022, 1, 1)

await expect(validateEntity(entity, { groups: ['creation'] })).resolves.toBe(undefined)
})

it('should fail if groups is set to creation and date is earlier than today', async () => {
const entity = new Election()
entity.openDate = new Date(1999, 1, 1)

await expect(validateEntity(entity, { groups: ['creation'] })).rejects.toThrowError()
})

it('should pass if openDate is set to undefined and group is creation', async () => {
const entity = new Election()
entity.openDate = undefined
const options: ValidatorOptions = { groups: ['creation'] }
// console.log(await validate(entity, options))

expect((await validate(entity, options)).length).toBe(0)
await expect(validateEntity(entity, options)).resolves.toBe(undefined)
})

it('should pass if openDate is set to undefined and group is not set', async () => {
const entity = new Election()
entity.id = 1
entity.openDate = undefined
const options: ValidatorOptions = { groups: [] }

expect((await validate(entity, options)).length).toBe(0)
await expect(validateEntity(entity, options)).resolves.toBe(undefined)
})

it('should fail if openDate is before today and group is creation', async () => {
const entity = new Election()
entity.openDate = new Date(1999, 1, 1)
const options: ValidatorOptions = { groups: ['creation'] }
// console.log(await validate(entity, options))

expect((await validate(entity, options)).length).toBeGreaterThan(0)
await expect(validateEntity(entity, options)).rejects.toThrowError()
})

it('should pass if openDate is later than today and group is creation', async () => {
const entity = new Election()
entity.openDate = new Date(2050, 1, 1)
const options: ValidatorOptions = { groups: ['creation'] }
// console.log(await validate(entity, options))

expect((await validate(entity, options)).length).toBe(0)
await expect(validateEntity(entity, options)).resolves.toBe(undefined)
})

it('should validate if id is a negative number with validation group `creation`', async () => {
const election = electionRepository.create()
election.title = 'I am being crated'
Expand Down
Loading