Skip to content

Commit

Permalink
feature(Issue): add Milestone API
Browse files Browse the repository at this point in the history
Closes #68
Closes #268
  • Loading branch information
clayreimann committed May 12, 2016
1 parent 3d761f5 commit 4268802
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 27 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## master
### Features
Added Milestone API
* `Issue.listMilestones`
* `Issue.getMilestone`
* `Issue.editMilestone`
* `Issue.deleteMilestone`

### Fixes

Expand Down
56 changes: 56 additions & 0 deletions lib/Issue.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,62 @@ class Issue extends Requestable {
getIssue(issue, cb) {
return this._request('GET', `/repos/${this.__repository}/issues/${issue}`, null, cb);
}

/**
* List the milestones for the repository
* @see https://developer.github.com/v3/issues/milestones/#list-milestones-for-a-repository
* @param {Object} options - filtering options
* @param {Requestable.callback} [cb] - will receive the array of milestones
* @return {Promise} - the promise for the http request
*/
listMilestones(options, cb) {
return this._request('GET', `/repos/${this.__repository}/milestones`, options, cb);
}

/**
* Get a milestone
* @see https://developer.github.com/v3/issues/milestones/#get-a-single-milestone
* @param {string} milestone - the id of the milestone to fetch
* @param {Requestable.callback} [cb] - will receive the array of milestones
* @return {Promise} - the promise for the http request
*/
getMilestone(milestone, cb) {
return this._request('GET', `/repos/${this.__repository}/milestones/${milestone}`, null, cb);
}

/**
* Create a new milestone
* @see https://developer.github.com/v3/issues/milestones/#create-a-milestone
* @param {Object} milestoneData - the milestone definition
* @param {Requestable.callback} [cb] - will receive the array of milestones
* @return {Promise} - the promise for the http request
*/
createMilestone(milestoneData, cb) {
return this._request('POST', `/repos/${this.__repository}/milestones`, milestoneData, cb);
}

/**
* Edit a milestone
* @see https://developer.github.com/v3/issues/milestones/#update-a-milestone
* @param {string} milestone - the id of the milestone to edit
* @param {Object} milestoneData - the updates to make to the milestone
* @param {Requestable.callback} [cb] - will receive the array of milestones
* @return {Promise} - the promise for the http request
*/
editMilestone(milestone, milestoneData, cb) {
return this._request('PATCH', `/repos/${this.__repository}/milestones/${milestone}`, milestoneData, cb);
}

/**
* Delete a milestone (this is distinct from closing a milestone)
* @see https://developer.github.com/v3/issues/milestones/#delete-a-milestone
* @param {string} milestone - the id of the milestone to delete
* @param {Requestable.callback} [cb] - will receive the array of milestones
* @return {Promise} - the promise for the http request
*/
deleteMilestone(milestone, cb) {
return this._request('DELETE', `/repos/${this.__repository}/milestones/${milestone}`, null, cb);
}
}

module.exports = Issue;
19 changes: 11 additions & 8 deletions lib/Requestable.js
Original file line number Diff line number Diff line change
Expand Up @@ -228,13 +228,14 @@ module.exports = Requestable;
// ////////////////////////// //
// Private helper functions //
// ////////////////////////// //
function buildError(path, response) {
return {
path: path,
request: response.config,
response: response,
status: response.status
};
class ResponseError extends Error {
constructor(path, response) {
super(`error making request ${response.config.method} ${response.config.url}`);
this.path = path;
this.request = response.config;
this.response = response;
this.status = response.status;
}
}

const METHODS_WITH_NO_BODY = ['GET', 'HEAD', 'DELETE'];
Expand All @@ -256,10 +257,12 @@ function getNextPage(linksHeader = '') {
function callbackErrorOrThrow(cb, path) {
return function handler(response) {
log(`error making request ${response.config.method} ${response.config.url} ${JSON.stringify(response.data)}`);
let error = buildError(path, response);
let error = new ResponseError(path, response);
if (cb) {
log('going to error callback');
cb(error);
} else {
log('throwing error');
throw error;
}
};
Expand Down
100 changes: 81 additions & 19 deletions test/issue.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import {assertSuccessful} from './helpers/callbacks';
describe('Issue', function() {
let github;
let remoteIssues;
let remoteIssueId;
let remoteIssueCommentId;

before(function() {
github = new Github({
Expand All @@ -21,6 +19,9 @@ describe('Issue', function() {
});

describe('reading', function() {
let remoteIssueId;
let milestoneId;

it('should list issues', function(done) {
remoteIssues.listIssues({}, assertSuccessful(done, function(err, issues) {
expect(issues).to.be.an.array();
Expand All @@ -37,9 +38,31 @@ describe('Issue', function() {
done();
}));
});

it('should get all milestones', function(done) {
remoteIssues.listMilestones()
.then(({data: milestones}) => {
expect(milestones).to.be.an.array();
milestoneId = milestones[0].number;

done();
}).catch(done);
});

it('should get a single milestone', function(done) {
remoteIssues.getMilestone(milestoneId)
.then(({data: milestone}) => {
expect(milestone).to.have.own('title', 'Default Milestone');
done();
}).catch(done);
});
});

describe('creating/modifiying/editing/deleting', function() {
describe('creating/editing/deleting', function() {
let createdIssueId;
let issueCommentId;
let createdMilestoneId;

// 200ms between tests so that Github has a chance to settle
beforeEach(function(done) {
setTimeout(done, 200);
Expand All @@ -52,6 +75,7 @@ describe('Issue', function() {
};

remoteIssues.createIssue(newIssue, assertSuccessful(done, function(err, issue) {
createdIssueId = issue.number;
expect(issue).to.have.own('url');
expect(issue).to.have.own('title', newIssue.title);
expect(issue).to.have.own('body', newIssue.body);
Expand All @@ -60,56 +84,94 @@ describe('Issue', function() {
}));
});

it('should edit issue', function(done) {
const newProps = {
title: 'Edited title',
state: 'closed'
};

remoteIssues.editIssue(createdIssueId, newProps, assertSuccessful(done, function(err, issue) {
expect(issue).to.have.own('title', newProps.title);

done();
}));
});

it('should post issue comment', function(done) {
remoteIssues.createIssueComment(remoteIssueId, 'Comment test', assertSuccessful(done, function(err, issue) {
remoteIssues.createIssueComment(createdIssueId, 'Comment test', assertSuccessful(done, function(err, issue) {
expect(issue).to.have.own('body', 'Comment test');

done();
}));
});

it('should list issue comments', function(done) {
remoteIssues.listIssueComments(remoteIssueId, assertSuccessful(done, function(err, comments) {
remoteIssues.listIssueComments(createdIssueId, assertSuccessful(done, function(err, comments) {
expect(comments).to.be.an.array();
expect(comments[0]).to.have.own('body', 'Comment test');
remoteIssueCommentId = comments[0].id
issueCommentId = comments[0].id;
done();
}));
});

it('should get a single issue comment', function(done) {
remoteIssues.getIssueComment(remoteIssueCommentId, assertSuccessful(done, function(err, comment) {
remoteIssues.getIssueComment(issueCommentId, assertSuccessful(done, function(err, comment) {
expect(comment).to.have.own('body', 'Comment test');
done();
}));
});

it('should edit issue comment', function(done) {
remoteIssues.editIssueComment(remoteIssueCommentId, 'Comment test edited', assertSuccessful(done, function(err, comment) {
expect(comment).to.have.own('body', 'Comment test edited');
remoteIssues.editIssueComment(issueCommentId, 'Comment test edited',
assertSuccessful(done, function(err, comment) {
expect(comment).to.have.own('body', 'Comment test edited');

done();
}));
done();
}));
});

it('should delete issue comment', function(done) {
remoteIssues.deleteIssueComment(remoteIssueCommentId, assertSuccessful(done, function(err, response) {
remoteIssues.deleteIssueComment(issueCommentId, assertSuccessful(done, function(err, response) {
expect(response).to.be.true;

done();
}));
});

it('should edit issues title', function(done) {
const newProps = {
title: 'Edited title'
it('should create a milestone', function(done) {
let milestone = {
title: 'v42',
description: 'The ultimate version'
};

remoteIssues.editIssue(remoteIssueId, newProps, assertSuccessful(done, function(err, issue) {
expect(issue).to.have.own('title', newProps.title);
remoteIssues.createMilestone(milestone)
.then(({data: createdMilestone}) => {
expect(createdMilestone).to.have.own('number');
expect(createdMilestone).to.have.own('title', milestone.title);

done();
}));
createdMilestoneId = createdMilestone.number;
done();
}).catch(done);
});
it('should update a milestone', function(done) {
let milestone = {
description: 'Version 6 * 7'
};

remoteIssues.editMilestone(createdMilestoneId, milestone)
.then(({data: createdMilestone}) => {
expect(createdMilestone).to.have.own('number', createdMilestoneId);
expect(createdMilestone).to.have.own('describe', milestone.description);

done();
}).catch(done);
});
it('should delete a milestone', function(done) {
remoteIssues.deleteMilestone(createdMilestoneId)
.then(({status}) => {
expect(status).to.equal(204);
done();
}).catch(done);
});
});
});

0 comments on commit 4268802

Please sign in to comment.