Skip to content

Commit

Permalink
feat(saveVotes): saveVotes allows to override previous vote
Browse files Browse the repository at this point in the history
It is possible for a voter to send a vote for the same VotingEvent mote than one time and specify to
override the previous one
  • Loading branch information
EnricoPicci committed Jul 21, 2019
1 parent aa7c224 commit 547444c
Show file tree
Hide file tree
Showing 6 changed files with 176 additions and 35 deletions.
2 changes: 1 addition & 1 deletion src/api/votes-apis.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ describe('Votes', () => {
assert.calledWith(findObs, votesCollection, { cancelled: { $exists: false } });
});
it('when called with string id gets the votes for that id', () => {
getVotes(votesCollection, 'abc123').subscribe(
getVotes(votesCollection, { eventId: 'abc123' }).subscribe(
value => {
expect(value).to.deep.equal(votes);
},
Expand Down
29 changes: 16 additions & 13 deletions src/api/votes-apis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Collection, ObjectId } from 'mongodb';
import { map as _map } from 'lodash';
// import groupBy from 'lodash/groupBy';

import { findObs, dropObs, insertManyObs, aggregateObs, updateOneObs } from 'observable-mongo';
import { findObs, dropObs, insertManyObs, aggregateObs, updateOneObs, deleteObs } from 'observable-mongo';

import { Vote } from '../model/vote';
import { VoteCredentialized } from '../model/vote-credentialized';
Expand All @@ -26,20 +26,15 @@ import { Credentials } from '../model/credentials';

export function getVotes(
votesColl: Collection,
params?: string | { eventId: string; voterId?: any },
params?: { eventId: string; voterId?: { nickname?: string; userId?: string } },
): Observable<Vote[]> {
let _eventId: any;
if (params && typeof params === 'string') {
_eventId = params;
} else if (params) {
_eventId = params['eventId'];
}
let selector: any = _eventId
? { cancelled: { $exists: false }, eventId: _eventId }
: { cancelled: { $exists: false } };
if (params && params['voterId']) {
let selector: any =
params && params.eventId
? { cancelled: { $exists: false }, eventId: params.eventId }
: { cancelled: { $exists: false } };
if (params && params.voterId) {
const upperCaseVoterId: any = {};
const voterId = params['voterId'];
const voterId = params.voterId;
for (const key in voterId) {
upperCaseVoterId[key] = voterId[key].toUpperCase();
}
Expand Down Expand Up @@ -118,6 +113,14 @@ export function saveVotes(
switchMap(() => hasAlreadyVoted(votesColl, votingEventColl, { credentials: vote.credentials })),
switchMap(alreadyVoted => {
if (alreadyVoted) {
if (vote.override) {
const selector = {
voterId: voterIdToUpperCase(vote.credentials.voterId),
};
return deleteObs(selector, votesColl).pipe(
concatMap(() => insertManyObs(votesToInsert, votesColl)),
);
}
return throwError(ERRORS.voteAlreadyPresent);
} else {
return insertManyObs(votesToInsert, votesColl);
Expand Down
38 changes: 20 additions & 18 deletions src/api/voting-event-apis.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
createNewVotingEvent,
deleteVotingEvents,
getAllVotingEvents,
getVoters,
// getVoters,
getVotingEvent,
getVotingEvents,
laodVotingEvents,
Expand Down Expand Up @@ -328,23 +328,25 @@ describe('Voting Events API', () => {
});
});

describe('getVoters', () => {
const votesCollection: any = { collectionName: 'votes' };
const votes = [
{ voterId: { firstName: 'abc', lastName: 'def' } },
{ voterId: { firstName: 'abc', lastName: 'bcf' } },
{ voterId: { firstName: 'def', lastName: 'rgh' } },
{ voterId: { firstName: 'abc', lastName: 'def' } },
];
it('closes the event for voting', () => {
const params = { votingEvent: votingEvents[0] };
findObs.withArgs(votesCollection, { cancelled: { $exists: false } }).returns(from(votes));

getVoters(votesCollection, params).subscribe(value => {
expect(value).to.deep.equal(['abc def', 'abc bcf', 'def rgh']);
});
});
});
// I need to delete this test since now the "getVotes" api requires an eventId as property of the params and
// this test assumes that there is no such property in the params object passed
// describe('getVoters', () => {
// const votesCollection: any = { collectionName: 'votes' };
// const votes = [
// { voterId: { firstName: 'abc', lastName: 'def' } },
// { voterId: { firstName: 'abc', lastName: 'bcf' } },
// { voterId: { firstName: 'def', lastName: 'rgh' } },
// { voterId: { firstName: 'abc', lastName: 'def' } },
// ];
// it('closes the event for voting', () => {
// const params = { votingEvent: votingEvents[0] };
// findObs.withArgs(votesCollection, { cancelled: { $exists: false }, }).returns(from(votes));

// getVoters(votesCollection, params).subscribe(value => {
// expect(value).to.deep.equal(['abc def', 'abc bcf', 'def rgh']);
// });
// });
// });

describe('calculateWinner', () => {
it('closes the event for voting', () => {
Expand Down
6 changes: 3 additions & 3 deletions src/api/voting-event-apis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ function _getVotingEventWithNumberOfCommentsAndVotes(
_id: any,
) {
const eventId = _id._id ? _id._id : _id;
return forkJoin(getVotingEvent(votingEventsCollection, _id), getVotes(votesCollection, eventId)).pipe(
return forkJoin(getVotingEvent(votingEventsCollection, _id), getVotes(votesCollection, { eventId })).pipe(
map(([votingEvent, votes]) => {
if (!votingEvent) throw Error(`No Voting Event present with ID ${_id}`);
const technologies = votingEvent.technologies;
Expand Down Expand Up @@ -219,7 +219,7 @@ export function undoCancelVotingEvent(
);
}
export function getVoters(votesColl: Collection, params: { votingEvent: any }) {
return getVotes(votesColl, params.votingEvent._id).pipe(
return getVotes(votesColl, { eventId: params.votingEvent._id }).pipe(
map(votes => {
const voters = votes.map(vote => `${vote.voterId.firstName} ${vote.voterId.lastName}`);
return Array.from(new Set(voters));
Expand All @@ -233,7 +233,7 @@ export function calculateWinner(
params: { votingEvent: any },
) {
// perform the calculation on the votes collection to extract the id of the winner
const winnerObs = getVotes(votesColl, params.votingEvent._id).pipe(
const winnerObs = getVotes(votesColl, { eventId: params.votingEvent._id }).pipe(
// as first simulation I take the first one as winner
map((votes: Vote[]) => votes[0]),
map(vote => ({ winner: vote.voterId, ipAdrressWinner: vote.ipAddress })),
Expand Down
1 change: 1 addition & 0 deletions src/model/vote-credentialized.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ import { VoteCredentials } from './vote-credentials';
export interface VoteCredentialized {
credentials: VoteCredentials;
votes: Vote[];
override?: boolean;
}
135 changes: 135 additions & 0 deletions src/mongodb/votes-apis.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import { Blip } from '../model/blip';
import { initializeVotingEventsAndVotes } from './base.spec';
import { Technology } from '../model/technology';
import { Comment } from '../model/comment';
import { Credentials } from '../model/credentials';
import { VotingEvent } from '../model/voting-event';

describe('CRUD operations on Votes collection', () => {
it('1.0 loads the votes and then read them', done => {
Expand Down Expand Up @@ -629,6 +631,139 @@ describe('CRUD operations on Votes collection', () => {
);
}).timeout(20000);

it(`1.3.1 submits a vote 2 times, the second time using the override option
so that the second vote overrides the first`, done => {
const cachedDb: CachedDB = { dbName: config.dbname, client: null, db: null };

const votingEventName = 'an event where the second vote overrides the first';

let votingEvent: VotingEvent;

const voterId1: Credentials = { nickname: 'Nick the voter' };
const voterId2: Credentials = { userId: 'User the voter' };
const credentialsVoter1 = { votingEvent: null, voterId: voterId1 };
const credentialsVoter2 = { votingEvent: null, voterId: voterId2 };
let credentializedVoteVoter11: VoteCredentialized;
let credentializedVoteVoter12: VoteCredentialized;
let credentializedVote2: VoteCredentialized;
let tech1: Technology;
let tech2: Technology;
const ringFirstVoteVoter1Tech1 = 'hold';
const ringFirstVoteVoter1Tech2 = 'adopt';
const ringSecondVoteVoter1Tech1 = 'adopt';
const ringVoter2 = 'hold';

let votingEventId;
initializeVotingEventsAndVotes(cachedDb.dbName)
.pipe(
switchMap(() => mongodbService(cachedDb, ServiceNames.createVotingEvent, { name: votingEventName })),
tap(id => (votingEventId = id)),
switchMap(() => mongodbService(cachedDb, ServiceNames.openVotingEvent, { _id: votingEventId })),
switchMap(() => mongodbService(cachedDb, ServiceNames.getVotingEvent, votingEventId)),
// both voters vote for the first time
tap((vEvent: VotingEvent) => {
votingEvent = vEvent;
tech1 = vEvent.technologies[0];
tech2 = vEvent.technologies[2];
credentialsVoter1.votingEvent = vEvent;
credentialsVoter2.votingEvent = vEvent;
credentializedVoteVoter11 = {
credentials: credentialsVoter1,
votes: [
{
ring: ringFirstVoteVoter1Tech1,
technology: tech1,
eventName: credentialsVoter1.votingEvent.name,
eventId: credentialsVoter1.votingEvent._id,
eventRound: 1,
},
{
ring: ringFirstVoteVoter1Tech2,
technology: tech2,
eventName: credentialsVoter1.votingEvent.name,
eventId: credentialsVoter1.votingEvent._id,
eventRound: 1,
},
],
};
credentializedVote2 = {
credentials: credentialsVoter2,
votes: [
{
ring: ringVoter2,
technology: tech1,
eventName: credentialsVoter1.votingEvent.name,
eventId: credentialsVoter1.votingEvent._id,
eventRound: 1,
},
{
ring: ringVoter2,
technology: tech2,
eventName: credentialsVoter1.votingEvent.name,
eventId: credentialsVoter1.votingEvent._id,
eventRound: 1,
},
],
};
}),
switchMap(() => mongodbService(cachedDb, ServiceNames.saveVotes, credentializedVoteVoter11)),
switchMap(() => mongodbService(cachedDb, ServiceNames.saveVotes, credentializedVote2)),

// second time the voter1 votes just for 1 technology with the override option
tap(() => {
credentializedVoteVoter12 = {
credentials: credentialsVoter1,
votes: [
{
ring: ringSecondVoteVoter1Tech1,
technology: tech1,
eventName: credentialsVoter1.votingEvent.name,
eventId: credentialsVoter1.votingEvent._id,
eventRound: 1,
},
],
override: true,
};
}),
switchMap(() => mongodbService(cachedDb, ServiceNames.saveVotes, credentializedVoteVoter12)),

switchMap(() =>
mongodbService(cachedDb, ServiceNames.getVotes, {
votingEvent,
voterId: voterId1,
}),
),
tap((votes: Vote[]) => {
expect(votes.length).to.equal(1);
expect(votes[0].technology.name).to.equal(tech1.name);
expect(votes[0].ring).to.equal(ringSecondVoteVoter1Tech1);
}),
// the votes of the second voter remain the ones set in the first vote since the second voter
// does not vote any more
switchMap(() =>
mongodbService(cachedDb, ServiceNames.getVotes, {
votingEvent,
voterId: voterId2,
}),
),
tap((votes: Vote[]) => {
expect(votes.length).to.equal(2);
votes.forEach(v => expect(v.ring).to.equal(ringVoter2));
}),
)
.subscribe(
null,
err => {
cachedDb.client.close();
done(err);
},
() => {
cachedDb.client.close();
done();
},
);
}).timeout(20000);

it('1.4 saves some votes on different voting events and reads the ones of one voting event', done => {
const cachedDb: CachedDB = { dbName: config.dbname, client: null, db: null };

Expand Down

0 comments on commit 547444c

Please sign in to comment.