Skip to content
This repository has been archived by the owner on Aug 3, 2018. It is now read-only.

Commit

Permalink
refactor(appointment-helper): split getNow() into two smaller functio…
Browse files Browse the repository at this point in the history
…ns and add unit tests
  • Loading branch information
Sebastian committed Dec 15, 2017
1 parent a377a22 commit ff3ac90
Show file tree
Hide file tree
Showing 3 changed files with 284 additions and 69 deletions.
142 changes: 74 additions & 68 deletions src/app/helper/appointment.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ let appointmentHelper = {
* @return {Object} Modified appointment object
*/
addIncrement: (appointment, increment) => {
if (!appointment) {
return null;
}

// add increment
appointment.hour = (appointment.hour + 100 * increment);

Expand Down Expand Up @@ -140,14 +144,80 @@ appointmentHelper.notify = () => new Promise(async (resolve) => {
resolve('Notifications were send. Exiting.');
});

/**
* Find an appointment for the given date/time.
*
* @param {'Datable'} datetime The date, moment, date-string or 'now'
* @param {number} toleranceBefore The minutes of tolerance before
* @param {number} toleranceAfter The minutes tolerance after
* @return The appointment object
*/
appointmentHelper.getAt = (datetime, toleranceBefore = 0, toleranceAfter = 0) => new Promise(async resolve => {
// load settings entity
const settings = await Settings.findOne();
let at = datetime === 'now'
? moment.tz(settings.appointmentsTimezone)
: moment(datetime);

if (!at.isValid()) {
return resolve(null);
}

// apply increment
const increment = settings && settings.appointmentsIncrement
? settings.appointmentsIncrement
: 0;
at = at.subtract(increment, 'hours');

const minDate = at.clone().subtract(toleranceAfter, 'minutes');
const maxDate = at.clone().add(toleranceBefore, 'minutes');

let doc = null;
if (minDate.weekday() !== maxDate.weekday()) {
// handle the case where the tolerance range
// includes two days
doc = await Appointment
.findOne({
$or: [
{
weekDay: minDate.weekday(),
hour: {
$gte: timeToNumber(minDate)
}
},
{
weekDay: maxDate.weekday(),
hour: {
$lte: timeToNumber(maxDate)
}
}
]
})
.populate('user', 'name username gravatarHash')
.exec();
} else {
doc = await Appointment
.findOne({
weekDay: minDate.weekday(),
hour: {
$gte: timeToNumber(minDate),
$lte: timeToNumber(maxDate)
}
})
.populate('user', 'name username gravatarHash')
.exec();
}

resolve(appointmentHelper.addIncrement(doc, increment));
});

/**
* Searches for appointments the user is allowed to
* join right now. For regular users this means at
* the time of their booked appointment (with a 5 minute
* tolerance). For admins marked as teacher, it means
* within the timespan of 5 minutes before any appointment
* starts and 25 minutes after it was scheduled.
* starts and 20 minutes after it was scheduled.
*
* @param {Object} user User that needs authorization
* @param {Boolean} reconnect If set to true, the time tolarance
Expand All @@ -156,72 +226,8 @@ appointmentHelper.notify = () => new Promise(async (resolve) => {
*
* @return the appointment object
*/
appointmentHelper.getNow = (user, reconnect = false) => new Promise(async (resolve) => {
if (!user) {
return resolve(null);
}

// load settings entity
const settings = await Settings.findOne();

// appointments are formatted in this timezone
const now = moment.tz(settings.appointmentsTimezone);

const increment = settings && settings.appointmentsIncrement
? settings.appointmentsIncrement
: 0;

// find any appointment for right now
const doc = await Appointment
.findOne({
weekDay: now.weekday(),
hour: {
$lte: timeToNumber(
// 5 minutes before appointment
now.clone().add(5 + 60 * increment , 'minutes')
),
$gte: timeToNumber(
// 25 minutes after appointment
now.clone().subtract(25 + 60 * increment, 'minutes')
)
},
user: { $exists: true, $ne: null }
})
.populate('user', 'name username gravatarHash')
.exec();

if (!doc) {
return resolve(null);
}

// check if user is admin and marked as teacher
const isCallee = user.role === 'ROLE_ADMIN' && user.appointmentsCallee === true;

// check if appointment is the one of requested user
const isOwnAppointment = doc.user._id.toString() === user._id.toString();
// check whether the time now is before 10 minutes after the appointment starts
const isAppOnTime = doc.hour >= timeToNumber(now.clone().subtract(10 + 60 * increment, 'minutes'));


if (doc && (isCallee || isOwnAppointment && (reconnect || isAppOnTime))) {

if (!isCallee && !reconnect) {
// register the user's request to initiate an appointment.
// Use 'await' in order to not confuse the count of
// the appointments before this update with the count
// afterwards.
await User
.findOneAndUpdate({
_id: user._id
}, {
$addToSet: { appointments: moment.utc().startOf('day').toDate() }
});
}

return resolve(doc);
}

resolve(null);
});
appointmentHelper.getNowAuthorized = (user, reconnect = false) => appointmentHelper.getAt('now', 5,
reconnect || (user && user.role === 'ROLE_ADMIN' && user.appointmentsCallee === true) ? 25 : 8
);

export default appointmentHelper;
209 changes: 209 additions & 0 deletions src/app/helper/appointment.spec.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
// eslint-disable-next-line no-unused-vars
import { app } from '../../server.conf.js';
import Appointment from '../models/appointment.model.js';
import Settings from '../models/settings.model.js';
import appointHelper from './appointment.js';
import { expect } from 'chai';
import moment from 'moment-timezone';

describe('Appointment Helper', () => {

it('should format hour correctly', () => {
expect(appointHelper.printHour(6)).to.equal('00:06');
expect(appointHelper.printHour(59)).to.equal('00:59');
Expand Down Expand Up @@ -36,4 +42,207 @@ describe('Appointment Helper', () => {
expect(appointHelper.addIncrement(dummy3, 0).weekDay).to.equal(0);
expect(appointHelper.addIncrement(dummy3, -1).weekDay).to.equal(6);
});

describe('Find appointment at certain time', () => {
before(done => {
Appointment.remove(() => Appointment
.insertMany([
{ weekDay: 0, hour: 0 },
{ weekDay: 1, hour: 10 },
{ weekDay: 2, hour: 700 },
{ weekDay: 3, hour: 1400 },
{ weekDay: 4, hour: 1800 },
{ weekDay: 5, hour: 1730 },
{ weekDay: 6, hour: 2300 }
])
.then((docs, err) => {
if (err) return done(err);
done();
})
);
});

describe('Without increment', () => {
before(done => {
Settings.remove(() => {
Settings
.create({
appointmentsIncrement: 0,
appointmentsTimezone: 'America/Toronto'
})
.then((docs, err) => {
if (err) return done(err);
done();
});
});
});

it('should find appointments correctly', async () => {
const dt = moment();

const app1 = await appointHelper.getAt(dt.weekday(0).hours(0).minutes(0));

expect(app1).to.not.be.null;
expect(app1.weekDay).to.equal(0);
expect(app1.hour).to.equal(0);

const app2 = await appointHelper.getAt(dt.weekday(4).hours(18).minutes(0));
expect(app2).to.not.be.null;
expect(app2.weekDay).to.equal(4);
expect(app2.hour).to.equal(1800);

const app3 = await appointHelper.getAt(dt.weekday(5).hours(17).minutes(30));
expect(app3).to.not.be.null;
expect(app3.weekDay).to.equal(5);
expect(app3.hour).to.equal(1730);
});

it('should find within range correctly', async () => {
const dt = moment();

let app1 = await appointHelper.getAt(dt.weekday(0).hours(0).minutes(10).seconds(0), 0, 10);

expect(app1).to.not.be.null;
expect(app1.weekDay).to.equal(0);
expect(app1.hour).to.equal(0);

app1 = await appointHelper.getAt(dt.weekday(0).hours(0).minutes(10), 0, 15);
expect(app1).to.not.be.null;
expect(app1.weekDay).to.equal(0);
expect(app1.hour).to.equal(0);

app1 = await appointHelper.getAt(dt.weekday(0).hours(0).minutes(10), 0, 9);
expect(app1).to.be.null;

app1 = await appointHelper.getAt(dt.weekday(6).hours(23).minutes(55), 5, 0);
expect(app1).to.not.be.null;
expect(app1.weekDay).to.equal(0);
expect(app1.hour).to.equal(0);

app1 = await appointHelper.getAt(dt.weekday(6).hours(23).minutes(52), 15, 0);
expect(app1).to.not.be.null;
expect(app1.weekDay).to.equal(0);
expect(app1.hour).to.equal(0);

app1 = await appointHelper.getAt(dt.weekday(6).hours(23).minutes(52), 3, 0);
expect(app1).to.be.null;

let app2 = await appointHelper.getAt(dt.weekday(5).hours(18).minutes(25), 0, 65);
expect(app2).to.not.be.null;
expect(app2.weekDay).to.equal(5);
expect(app2.hour).to.equal(1730);

app2 = await appointHelper.getAt(dt.weekday(5).hours(18).minutes(0), 0, 30);
expect(app2).to.not.be.null;
expect(app2.weekDay).to.equal(5);
expect(app2.hour).to.equal(1730);

let app3 = await appointHelper.getAt(dt.weekday(4).hours(0).minutes(0), 18 * 60, 0);
expect(app3).to.not.be.null;
expect(app3.weekDay).to.equal(4);
expect(app3.hour).to.equal(1800);

app3 = await appointHelper.getAt(dt.weekday(5).hours(0).minutes(5), 0, 6 * 60 + 6);
expect(app3).to.not.be.null;
expect(app3.weekDay).to.equal(4);
expect(app3.hour).to.equal(1800);
});
});

describe('With increment', () => {
before(done => {
Settings.remove(() => {
Settings
.create({
appointmentsIncrement: 2,
appointmentsTimezone: 'America/Toronto'
})
.then((docs, err) => {
if (err) return done(err);
done();
});
});
});

it('should find appointments correctly', async () => {
const dt = moment();

const app1 = await appointHelper.getAt(dt.weekday(0).hours(2).minutes(0));

expect(app1).to.not.be.null;
expect(app1.weekDay).to.equal(0);
expect(app1.hour).to.equal(200);

const app2 = await appointHelper.getAt(dt.weekday(4).hours(20).minutes(0));
expect(app2).to.not.be.null;
expect(app2.weekDay).to.equal(4);
expect(app2.hour).to.equal(2000);

const app3 = await appointHelper.getAt(dt.weekday(5).hours(19).minutes(30));
expect(app3).to.not.be.null;
expect(app3.weekDay).to.equal(5);
expect(app3.hour).to.equal(1930);
});

it('should find within range correctly', async () => {
const dt = moment();

let app1 = await appointHelper.getAt(dt.weekday(0).hours(2).minutes(10).seconds(0), 0, 10);

expect(app1).to.not.be.null;
expect(app1.weekDay).to.equal(0);
expect(app1.hour).to.equal(200);

app1 = await appointHelper.getAt(dt.weekday(0).hours(2).minutes(10), 0, 15);
expect(app1).to.not.be.null;
expect(app1.weekDay).to.equal(0);
expect(app1.hour).to.equal(200);

app1 = await appointHelper.getAt(dt.weekday(0).hours(2).minutes(10), 0, 9);
expect(app1).to.be.null;

app1 = await appointHelper.getAt(dt.weekday(0).hours(1).minutes(55), 5, 0);
expect(app1).to.not.be.null;
expect(app1.weekDay).to.equal(0);
expect(app1.hour).to.equal(200);

app1 = await appointHelper.getAt(dt.weekday(0).hours(1).minutes(52), 15, 0);
expect(app1).to.not.be.null;
expect(app1.weekDay).to.equal(0);
expect(app1.hour).to.equal(200);

app1 = await appointHelper.getAt(dt.weekday(6).hours(23).minutes(52), 3, 0);
expect(app1).to.be.null;

let app2 = await appointHelper.getAt(dt.weekday(5).hours(20).minutes(25), 0, 65);
expect(app2).to.not.be.null;
expect(app2.weekDay).to.equal(5);
expect(app2.hour).to.equal(1930);

app2 = await appointHelper.getAt(dt.weekday(5).hours(20).minutes(0), 0, 30);
expect(app2).to.not.be.null;
expect(app2.weekDay).to.equal(5);
expect(app2.hour).to.equal(1930);

let app3 = await appointHelper.getAt(dt.weekday(4).hours(2).minutes(0), 18 * 60, 0);
expect(app3).to.not.be.null;
expect(app3.weekDay).to.equal(4);
expect(app3.hour).to.equal(2000);

app3 = await appointHelper.getAt(dt.weekday(5).hours(2).minutes(5), 0, 6 * 60 + 6);
expect(app3).to.not.be.null;
expect(app3.weekDay).to.equal(4);
expect(app3.hour).to.equal(2000);

let app4 = await appointHelper.getAt(dt.weekday(1).hours(23).minutes(55), 9 * 60 + 5, 0);
expect(app4).to.not.be.null;
expect(app4.weekDay).to.equal(2);
expect(app4.hour).to.equal(900);

app4 = await appointHelper.getAt(dt.weekday(1).hours(23).minutes(55), 9 * 60 + 4, 0);
expect(app4).to.be.null;
});
});
});

});
2 changes: 1 addition & 1 deletion src/sockets/appointment.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export default (socket, io) => {

// if an authorized user is already in the room 'Appointment', then this means that a
// reconnect is given (-> second parameter).
const appointment = await appointHelper.getNow(userNow, roomLen === 1 && !inRoom());
const appointment = await appointHelper.getNowAuthorized(userNow, roomLen === 1 && !inRoom());

// send appointment and status to client
socket.emit('appointment', {
Expand Down

0 comments on commit ff3ac90

Please sign in to comment.