diff --git a/postman.json b/postman.json index 145003ff..a3336ae5 100644 --- a/postman.json +++ b/postman.json @@ -3802,7 +3802,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"startDate\": \"2018-05-04T00:00:00.000Z\",\r\n \"endDate\": \"2018-05-06T00:00:00.000Z\",\r\n \"completionDate\": \"2018-05-07T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n }\r\n}" + "raw": "{\r\n \"param\":{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-05-07T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n }\r\n}" }, "url": { "raw": "{{api-url}}/v4/timelines/1/milestones/1", @@ -3836,7 +3836,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"startDate\": \"2018-05-04T00:00:00.000Z\",\r\n \"endDate\": \"2018-05-06T00:00:00.000Z\",\r\n \"completionDate\": \"2018-05-07T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 2,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n }\r\n}" + "raw": "{\r\n \"param\":{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-05-07T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 2,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n }\r\n}" }, "url": { "raw": "{{api-url}}/v4/timelines/1/milestones/1", @@ -3870,7 +3870,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"startDate\": \"2018-05-04T00:00:00.000Z\",\r\n \"endDate\": \"2018-05-06T00:00:00.000Z\",\r\n \"completionDate\": \"2018-05-07T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n }\r\n}" + "raw": "{\r\n \"param\":{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-05-07T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n }\r\n}" }, "url": { "raw": "{{api-url}}/v4/timelines/1/milestones/1", @@ -3904,7 +3904,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"startDate\": \"2018-05-04T00:00:00.000Z\",\r\n \"endDate\": \"2018-05-06T00:00:00.000Z\",\r\n \"completionDate\": \"2018-05-07T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 3,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n }\r\n}" + "raw": "{\r\n \"param\":{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-05-07T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 3,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n }\r\n}" }, "url": { "raw": "{{api-url}}/v4/timelines/1/milestones/1", @@ -3938,7 +3938,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"param\":{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"startDate\": \"2018-05-04T00:00:00.000Z\",\r\n \"endDate\": \"2018-05-06T00:00:00.000Z\",\r\n \"completionDate\": \"2018-05-07T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n }\r\n}" + "raw": "{\r\n \"param\":{\r\n \"name\": \"milestone 1-updated\",\r\n \"description\": \"description-updated\",\r\n \"duration\": 3,\r\n \"completionDate\": \"2018-05-07T00:00:00.000Z\",\r\n \"status\": \"closed\",\r\n \"type\": \"type2\",\r\n \"details\": {\r\n \"detail1\": {\r\n \"subDetail1C\": 3\r\n },\r\n \"detail2\": [\r\n 4\r\n ]\r\n },\r\n \"order\": 1,\r\n \"plannedText\": \"plannedText 1-updated\",\r\n \"activeText\": \"activeText 1-updated\",\r\n \"completedText\": \"completedText 1-updated\",\r\n \"blockedText\": \"blockedText 1-updated\"\r\n }\r\n}" }, "url": { "raw": "{{api-url}}/v4/timelines/1/milestones/1", diff --git a/src/routes/milestones/update.js b/src/routes/milestones/update.js index 9f4e19b9..9cb2c698 100644 --- a/src/routes/milestones/update.js +++ b/src/routes/milestones/update.js @@ -3,6 +3,7 @@ */ import validate from 'express-validation'; import _ from 'lodash'; +import moment from 'moment'; import Joi from 'joi'; import Sequelize from 'sequelize'; import { middleware as tcMiddleware } from 'tc-core-library-js'; @@ -13,6 +14,52 @@ import models from '../../models'; const permissions = tcMiddleware.permissions; +/** + * Cascades endDate/completionDate changes to all milestones with a greater order than the given one. + * @param {Object} updatedMilestone the milestone that was updated + * @returns {Promise} a promise that resolves to the last found milestone. If no milestone exists with an + * order greater than the passed updatedMilestone, the promise will resolve to the passed + * updatedMilestone + */ +function updateComingMilestones(updatedMilestone) { + return models.Milestone.findAll({ + where: { + timelineId: updatedMilestone.timelineId, + order: { $gt: updatedMilestone.order }, + }, + }).then((affectedMilestones) => { + const comingMilestones = _.sortBy(affectedMilestones, 'order'); + let startDate = moment.utc(updatedMilestone.completionDate + ? updatedMilestone.completionDate + : updatedMilestone.endDate).add(1, 'days').toDate(); + const promises = _.map(comingMilestones, (_milestone) => { + const milestone = _milestone; + + // Update the milestone startDate if different than the iterated startDate + if (!_.isEqual(milestone.startDate, startDate)) { + milestone.startDate = startDate; + milestone.updatedBy = updatedMilestone.updatedBy; + } + + // Calculate the endDate, and update it if different + const endDate = moment.utc(startDate).add(milestone.duration - 1, 'days').toDate(); + if (!_.isEqual(milestone.endDate, endDate)) { + milestone.endDate = endDate; + milestone.updatedBy = updatedMilestone.updatedBy; + } + + // Set the next startDate value to the next day after completionDate if present or the endDate + startDate = moment.utc(milestone.completionDate + ? milestone.completionDate + : milestone.endDate).add(1, 'days').toDate(); + return milestone.save(); + }); + + // Resolve promise to the last updated milestone, or to the passed in updatedMilestone + return Promise.all(promises).then(updatedMilestones => updatedMilestones.pop() || updatedMilestone); + }); +} + const schema = { params: { timelineId: Joi.number().integer().positive().required(), @@ -23,9 +70,9 @@ const schema = { id: Joi.any().strip(), name: Joi.string().max(255).optional(), description: Joi.string().max(255), - duration: Joi.number().integer().optional(), - startDate: Joi.date().optional(), - endDate: Joi.date().allow(null), + duration: Joi.number().integer().min(1).optional(), + startDate: Joi.any().forbidden(), + endDate: Joi.any().forbidden(), completionDate: Joi.date().allow(null), status: Joi.string().max(45).optional(), type: Joi.string().max(45).optional(), @@ -62,24 +109,7 @@ module.exports = [ timelineId: req.params.timelineId, }); - // Validate startDate and endDate to be within the timeline startDate and endDate - let error; - if (req.body.param.startDate < req.timeline.startDate) { - error = 'Milestone startDate must not be before the timeline startDate'; - } else if (req.body.param.endDate && req.timeline.endDate && req.body.param.endDate > req.timeline.endDate) { - error = 'Milestone endDate must not be after the timeline endDate'; - } - if (entityToUpdate.endDate && entityToUpdate.endDate < entityToUpdate.startDate) { - error = 'Milestone endDate must not be before startDate'; - } - if (entityToUpdate.completionDate && entityToUpdate.completionDate < entityToUpdate.startDate) { - error = 'Milestone endDate must not be before startDate'; - } - if (error) { - const apiErr = new Error(error); - apiErr.status = 422; - return next(apiErr); - } + const timeline = req.timeline; let original; let updated; @@ -95,11 +125,21 @@ module.exports = [ return Promise.reject(apiErr); } + if (entityToUpdate.completionDate && entityToUpdate.completionDate < milestone.startDate) { + const apiErr = new Error('The milestone completionDate should be greater or equal than the startDate.'); + apiErr.status = 422; + return Promise.reject(apiErr); + } + original = _.omit(milestone.toJSON(), ['deletedAt', 'deletedBy']); // Merge JSON fields entityToUpdate.details = util.mergeJsonObjects(milestone.details, entityToUpdate.details); + if (entityToUpdate.duration && entityToUpdate.duration !== milestone.duration) { + entityToUpdate.endDate = moment.utc(milestone.startDate).add(entityToUpdate.duration - 1, 'days').toDate(); + } + // Update return milestone.update(entityToUpdate); }) @@ -146,6 +186,21 @@ module.exports = [ }, }); }); + }) + .then(() => { + // Update dates of the other milestones only if the completionDate or the duration changed + if (!_.isEqual(original.completionDate, updated.completionDate) || original.duration !== updated.duration) { + return updateComingMilestones(updated) + .then((lastTimelineMilestone) => { + if (!_.isEqual(lastTimelineMilestone.endDate, timeline.endDate)) { + timeline.endDate = lastTimelineMilestone.endDate; + timeline.updatedBy = lastTimelineMilestone.updatedBy; + return timeline.save(); + } + return Promise.resolve(); + }); + } + return Promise.resolve(); }), ) .then(() => { diff --git a/src/routes/milestones/update.spec.js b/src/routes/milestones/update.spec.js index 1d5edcc1..b3fde3fe 100644 --- a/src/routes/milestones/update.spec.js +++ b/src/routes/milestones/update.spec.js @@ -256,8 +256,6 @@ describe('UPDATE Milestone', () => { param: { name: 'Milestone 1-updated', duration: 3, - startDate: '2018-05-14T00:00:00.000Z', - endDate: '2018-05-15T00:00:00.000Z', completionDate: '2018-05-16T00:00:00.000Z', description: 'description-updated', status: 'closed', @@ -464,74 +462,23 @@ describe('UPDATE Milestone', () => { .expect(200, done); }); - it('should return 422 if startDate is after endDate', (done) => { - const invalidBody = { - param: _.assign({}, body.param, { - startDate: '2018-05-29T00:00:00.000Z', - endDate: '2018-05-28T00:00:00.000Z', - }), - }; - - request(server) - .patch('/v4/timelines/1/milestones/1') - .set({ - Authorization: `Bearer ${testUtil.jwts.admin}`, - }) - .send(invalidBody) - .expect('Content-Type', /json/) - .expect(422, done); - }); - - it('should return 422 if startDate is after completionDate', (done) => { - const invalidBody = { - param: _.assign({}, body.param, { - startDate: '2018-05-29T00:00:00.000Z', - completionDate: '2018-05-28T00:00:00.000Z', - }), - }; - - request(server) - .patch('/v4/timelines/1/milestones/1') - .set({ - Authorization: `Bearer ${testUtil.jwts.admin}`, - }) - .send(invalidBody) - .expect('Content-Type', /json/) - .expect(422, done); - }); - - it('should return 422 if startDate is before timeline startDate', (done) => { - const invalidBody = { - param: _.assign({}, body.param, { - startDate: '2018-05-01T00:00:00.000Z', - }), - }; - - request(server) - .patch('/v4/timelines/1/milestones/1') - .set({ - Authorization: `Bearer ${testUtil.jwts.admin}`, - }) - .send(invalidBody) - .expect('Content-Type', /json/) - .expect(422, done); - }); - - it('should return 422 if endDate is after timeline endDate', (done) => { - const invalidBody = { - param: _.assign({}, body.param, { - endDate: '2018-07-01T00:00:00.000Z', - }), - }; - - request(server) - .patch('/v4/timelines/1/milestones/1') - .set({ - Authorization: `Bearer ${testUtil.jwts.admin}`, - }) - .send(invalidBody) - .expect('Content-Type', /json/) - .expect(422, done); + ['startDate', 'endDate'].forEach((field) => { + it(`should return 422 if ${field} is present in the payload`, (done) => { + const invalidBody = { + param: _.assign({}, body.param, { + [field]: '2018-07-01T00:00:00.000Z', + }), + }; + + request(server) + .patch('/v4/timelines/1/milestones/1') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send(invalidBody) + .expect('Content-Type', /json/) + .expect(422, done); + }); }); it('should return 200 for admin', (done) => { @@ -548,8 +495,6 @@ describe('UPDATE Milestone', () => { resJson.name.should.be.eql(body.param.name); resJson.description.should.be.eql(body.param.description); resJson.duration.should.be.eql(body.param.duration); - resJson.startDate.should.be.eql(body.param.startDate); - resJson.endDate.should.be.eql(body.param.endDate); resJson.completionDate.should.be.eql(body.param.completionDate); resJson.status.should.be.eql(body.param.status); resJson.type.should.be.eql(body.param.type); @@ -739,14 +684,13 @@ describe('UPDATE Milestone', () => { .expect(200) .end(() => { // Milestone 6: order 0 - setTimeout(() => { - models.Milestone.findById(6) - .then((milestone) => { - milestone.order.should.be.eql(0); - - done(); - }); - }, 3000); + models.Milestone.findById(6) + .then((milestone) => { + milestone.order.should.be.eql(0); + + done(); + }) + .catch(done); }); }); @@ -804,22 +748,21 @@ describe('UPDATE Milestone', () => { // Milestone 6: order 1 => 1 // Milestone 7: order 3 => 3 // Milestone 8: order 4 => 2 - setTimeout(() => { - models.Milestone.findById(6) - .then((milestone) => { - milestone.order.should.be.eql(1); - }) - .then(() => models.Milestone.findById(7)) - .then((milestone) => { - milestone.order.should.be.eql(3); - }) - .then(() => models.Milestone.findById(8)) - .then((milestone) => { - milestone.order.should.be.eql(2); - - done(); - }); - }, 3000); + models.Milestone.findById(6) + .then((milestone) => { + milestone.order.should.be.eql(1); + }) + .then(() => models.Milestone.findById(7)) + .then((milestone) => { + milestone.order.should.be.eql(3); + }) + .then(() => models.Milestone.findById(8)) + .then((milestone) => { + milestone.order.should.be.eql(2); + + done(); + }) + .catch(done); }); }); }); @@ -878,26 +821,157 @@ describe('UPDATE Milestone', () => { // Milestone 6: order 1 => 1 // Milestone 7: order 2 => 3 // Milestone 8: order 4 => 2 - setTimeout(() => { - models.Milestone.findById(6) - .then((milestone) => { - milestone.order.should.be.eql(1); - }) - .then(() => models.Milestone.findById(7)) - .then((milestone) => { - milestone.order.should.be.eql(3); - }) - .then(() => models.Milestone.findById(8)) - .then((milestone) => { - milestone.order.should.be.eql(2); - - done(); - }); - }, 3000); + models.Milestone.findById(6) + .then((milestone) => { + milestone.order.should.be.eql(1); + }) + .then(() => models.Milestone.findById(7)) + .then((milestone) => { + milestone.order.should.be.eql(3); + }) + .then(() => models.Milestone.findById(8)) + .then((milestone) => { + milestone.order.should.be.eql(2); + + done(); + }) + .catch(done); }); }); }); + it('should return 200 for admin - changing completionDate will cascade changes to coming ' + + // eslint-disable-next-line func-names + 'milestones', function (done) { + this.timeout(10000); + + request(server) + .patch('/v4/timelines/1/milestones/2') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send({ param: _.assign({}, body.param, { + completionDate: '2018-05-18T00:00:00.000Z', order: undefined, duration: undefined, + }) }) + .expect(200) + .end(() => { + // Milestone 3: startDate: '2018-05-14T00:00:00.000Z' to '2018-05-19T00:00:00.000Z' + // endDate: null to '2018-05-21T00:00:00.000Z' + // Milestone 4: startDate: '2018-05-14T00:00:00.000Z' to '2018-05-22T00:00:00.000Z' + // endDate: null to '2018-05-24T00:00:00.000Z' + models.Milestone.findById(3) + .then((milestone) => { + milestone.startDate.should.be.eql(new Date('2018-05-19T00:00:00.000Z')); + milestone.endDate.should.be.eql(new Date('2018-05-21T00:00:00.000Z')); + return models.Milestone.findById(4); + }) + .then((milestone) => { + milestone.startDate.should.be.eql(new Date('2018-05-22T00:00:00.000Z')); + milestone.endDate.should.be.eql(new Date('2018-05-24T00:00:00.000Z')); + done(); + }) + .catch(done); + }); + }); + + it('should return 200 for admin - changing completionDate will change the timeline\'s ' + + // eslint-disable-next-line func-names + 'endDate', function (done) { + this.timeout(10000); + + request(server) + .patch('/v4/timelines/1/milestones/2') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send({ param: _.assign({}, body.param, { + completionDate: '2018-05-18T00:00:00.000Z', order: undefined, duration: undefined, + }) }) + .expect(200) + .end(() => { + // Milestone 3: startDate: '2018-05-14T00:00:00.000Z' to '2018-05-19T00:00:00.000Z' + // endDate: null to '2018-05-21T00:00:00.000Z' + // Milestone 4: startDate: '2018-05-14T00:00:00.000Z' to '2018-05-22T00:00:00.000Z' + // BELOW will be the new timeline's endDate + // endDate: null to '2018-05-24T00:00:00.000Z' + models.Timeline.findById(1) + .then((timeline) => { + // timeline start shouldn't change + timeline.startDate.should.be.eql(new Date('2018-05-02T00:00:00.000Z')); + + // timeline end should change + timeline.endDate.should.be.eql(new Date('2018-05-24T00:00:00.000Z')); + + done(); + }) + .catch(done); + }); + }); + + it('should return 200 for admin - changing duration will cascade changes to coming ' + + // eslint-disable-next-line func-names + 'milestones', function (done) { + this.timeout(10000); + + request(server) + .patch('/v4/timelines/1/milestones/2') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send({ param: _.assign({}, body.param, { duration: 5, order: undefined, completionDate: undefined }) }) + .expect(200) + .end(() => { + // Milestone 3: startDate: '2018-05-14T00:00:00.000Z' to '2018-05-19T00:00:00.000Z' + // endDate: null to '2018-05-21T00:00:00.000Z' + // Milestone 4: startDate: '2018-05-14T00:00:00.000Z' to '2018-05-22T00:00:00.000Z' + // endDate: null to '2018-05-24T00:00:00.000Z' + models.Milestone.findById(3) + .then((milestone) => { + milestone.startDate.should.be.eql(new Date('2018-05-19T00:00:00.000Z')); + milestone.endDate.should.be.eql(new Date('2018-05-21T00:00:00.000Z')); + return models.Milestone.findById(4); + }) + .then((milestone) => { + milestone.startDate.should.be.eql(new Date('2018-05-22T00:00:00.000Z')); + milestone.endDate.should.be.eql(new Date('2018-05-24T00:00:00.000Z')); + done(); + }) + .catch(done); + }); + }); + + it('should return 200 for admin - changing duration will change the timeline\'s ' + + // eslint-disable-next-line func-names + 'endDate', function (done) { + this.timeout(10000); + + request(server) + .patch('/v4/timelines/1/milestones/2') + .set({ + Authorization: `Bearer ${testUtil.jwts.admin}`, + }) + .send({ param: _.assign({}, body.param, { duration: 5, order: undefined, completionDate: undefined }) }) + .expect(200) + .end(() => { + // Milestone 3: startDate: '2018-05-14T00:00:00.000Z' to '2018-05-19T00:00:00.000Z' + // endDate: null to '2018-05-21T00:00:00.000Z' + // Milestone 4: startDate: '2018-05-14T00:00:00.000Z' to '2018-05-22T00:00:00.000Z' + // BELOW will be the new timeline's endDate + // endDate: null to '2018-05-24T00:00:00.000Z' + models.Timeline.findById(1) + .then((timeline) => { + // timeline start shouldn't change + timeline.startDate.should.be.eql(new Date('2018-05-02T00:00:00.000Z')); + + // timeline end should change + timeline.endDate.should.be.eql(new Date('2018-05-24T00:00:00.000Z')); + + done(); + }) + .catch(done); + }); + }); + it('should return 200 for connect admin', (done) => { request(server) .patch('/v4/timelines/1/milestones/1') diff --git a/swagger.yaml b/swagger.yaml index d2799dee..b95bbe94 100755 --- a/swagger.yaml +++ b/swagger.yaml @@ -1335,7 +1335,7 @@ paths: name: body required: true schema: - $ref: '#/definitions/MilestoneBodyParam' + $ref: '#/definitions/MilestonePostBodyParam' responses: '403': description: No permission or wrong token @@ -1413,7 +1413,7 @@ paths: in: body required: true schema: - $ref: "#/definitions/MilestoneBodyParam" + $ref: "#/definitions/MilestonePatchBodyParam" delete: tags: @@ -3201,7 +3201,7 @@ definitions: items: $ref: "#/definitions/Timeline" - MilestoneRequest: + MilestonePostRequest: title: Milestone request object type: object required: @@ -3264,14 +3264,77 @@ definitions: type: string description: the milestone blocked text - MilestoneBodyParam: + MilestonePatchRequest: + title: Milestone request object + type: object + required: + - name + - duration + - status + - type + - order + - plannedText + - activeText + - completedText + - blockedText + properties: + name: + type: string + description: the milestone name + description: + type: string + description: the milestone description + duration: + type: number + format: integer + description: the milestone duration + completionDate: + type: string + format: date + description: the milestone completion date + status: + type: string + description: the milestone status + type: + type: string + description: the milestone type + details: + type: object + description: the milestone details + order: + type: number + format: integer + description: the milestone order + plannedText: + type: string + description: the milestone planned text + activeText: + type: string + description: the milestone active text + completedText: + type: string + description: the milestone completed text + blockedText: + type: string + description: the milestone blocked text + + MilestonePostBodyParam: + title: Milestone body param + type: object + required: + - param + properties: + param: + $ref: "#/definitions/MilestonePostRequest" + + MilestonePatchBodyParam: title: Milestone body param type: object required: - param properties: param: - $ref: "#/definitions/MilestoneRequest" + $ref: "#/definitions/MilestonePatchRequest" Milestone: title: Milestone object @@ -3306,7 +3369,7 @@ definitions: format: int64 description: READ-ONLY. User that last updated this object readOnly: true - - $ref: "#/definitions/MilestoneRequest" + - $ref: "#/definitions/MilestonePostRequest" MilestoneResponse: title: Single milestone response object