Commit 08709e12 authored by Eric Eastwood's avatar Eric Eastwood
parent 7be6dbf9
# 19.13.0 - *upcoming*
- Add GitLab issue decorations, https://gitlab.com/gitlab-org/gitter/webapp/merge_requests/1077
Developer facing:
- Update to Mocha@5.x for better debugging, `--inspect` (node inspector devtools), https://gitlab.com/gitlab-org/gitter/webapp/merge_requests/1212
......
......@@ -42,17 +42,19 @@ function wrapFunction(cache, moduleName, func, funcName, getInstanceIdFunc) {
var args = Array.prototype.slice.apply(arguments);
var self = this;
var instanceId = getInstanceIdFunc ? getInstanceIdFunc(this) : '';
var key = generateKey(moduleName, instanceId, funcName, args);
return new Promise(function(resolve, reject) {
cache.lookup(key, function(cb) {
func.apply(self, args).nodeify(cb);
}, function(err, result) {
if (err) return reject(err);
resolve(result);
});
var instanceIdPromise = Promise.resolve(getInstanceIdFunc ? getInstanceIdFunc(this) : '');
return instanceIdPromise.then(function(instanceId) {
var key = generateKey(moduleName, instanceId, funcName, args);
return new Promise(function(resolve, reject) {
cache.lookup(key, function(cb) {
func.apply(self, args).nodeify(cb);
}, function(err, result) {
if (err) return reject(err);
resolve(result);
});
});
});
};
......
......@@ -111,6 +111,22 @@ describe('cache-wrapper', function() {
wrapped.addPrefix('world');
});
it('looks up correct key when getInstanceId returns promise', function(done) {
var wrapper = getWrapper(function(key) {
assert.equal(key, 'my-module:hello:addPrefix:world');
done();
});
var Wrapped = wrapper('my-module', Klass, {
getInstanceId: function(obj) {
return Promise.resolve(obj.prefix);
}
});
var wrapped = new Wrapped('hello');
wrapped.addPrefix('world');
});
it('calls method correctly on cache miss', function(done) {
var wrapper = getWrapper(function(key, cachedFunc, cb) {
cachedFunc(cb);
......
......@@ -23,7 +23,7 @@
"gitter-web-text-processor": "file:../text-processor",
"gitter-web-persistence-utils": "file:../persistence-utils",
"gitter-web-unread-items": "file:../unread-items",
"gitter-markdown-processor": "^11.4.0",
"gitter-markdown-processor": "^12.1.0",
"gitter-web-shared": "file:../../shared",
"gitter-web-rooms": "file:../rooms",
"gitter-web-permissions": "file:../permissions",
......
"use strict";
var StatusError = require('statuserror');
var wrap = require('./github-cache-wrapper');
var tentacles = require('./tentacles-client');
var userTokenSelector = require('./user-token-selector').full;
function standardizeResponse(response) {
return {
id: response.id,
iid: response.number,
title: response.title,
body: response.body,
state: response.state,
labels: (response.labels || []).map(function(label) {
return label.name;
}),
author: response.user && {
id: response.user.id,
username: response.user.login,
//displayName: n/a,
avatarUrl: response.user.avatar_url
},
assignee: response.assignee && {
id: response.assignee.id,
username: response.assignee.login,
//displayName: n/a,
avatarUrl: response.assignee.avatar_url
},
createdAt: response.created_at,
updatedAt: response.updated_at
};
}
function GitHubIssueService(user) {
this.user = user;
this.accessToken = userTokenSelector(user);
}
GitHubIssueService.prototype.getIssue = function(repo, issueNumber) {
return tentacles.issue.get(repo, issueNumber, { accessToken: this.accessToken });
return tentacles.issue.get(repo, issueNumber, { accessToken: this.accessToken })
.then((response) => {
if(response) {
return standardizeResponse(response);
}
throw new StatusError(404, `Unable to fetch GitHub issue ${repo}#${issueNumber}`);
});
};
......
"use strict";
var wrap = require('./github-cache-wrapper');
var tentacles = require('./tentacles-client');
var userTokenSelector = require('./user-token-selector').full;
var IssueService = require('./github-issue-service');
function GitHubIssueStateService(user) {
this.user = user;
this.accessToken = userTokenSelector(user);
this.issueService = new IssueService(user);
}
GitHubIssueStateService.prototype.getIssueState = function(repo, issueNumber) {
return tentacles.issue.get(repo, issueNumber, { accessToken: this.accessToken })
return this.issueService.getIssue(repo, issueNumber)
.then(function(issue) {
if (!issue) return '';
return issue.state;
});
};
......
......@@ -26,7 +26,7 @@ describe('github-issue-service #slow #github', function() {
.then(() => underTest.getIssue(fixtureLoader.GITTER_INTEGRATION_REPO_FULL, 1))
.then(function(f) {
assert(f);
assert.strictEqual(f.number, 1);
assert.strictEqual(f.iid, 1);
})
.nodeify(done);
});
......@@ -48,19 +48,25 @@ describe('github-issue-service #slow #github', function() {
.nodeify(done);
}*/);
it('return empty for missing issue', function(done) {
it('return error for missing issue', function() {
var repoService = new GitHubRepoService(FAKE_USER);
var underTest = new GitHubIssueService(FAKE_USER);
repoService.getRepo(fixtureLoader.GITTER_INTEGRATION_REPO_FULL)
return repoService.getRepo(fixtureLoader.GITTER_INTEGRATION_REPO_FULL)
.then((repo) => {
assert.strictEqual(repo.private, false);
})
.then(() => underTest.getIssue(fixtureLoader.GITTER_INTEGRATION_REPO_FULL, 999999))
.then(function(f) {
assert.strictEqual(f, null);
.then(() => {
assert.fail('Shouldn\'t be able to fetch issue in unauthorized private project');
})
.nodeify(done);
.catch((err) => {
if(err instanceof assert.AssertionError) {
throw err;
}
assert.strictEqual(err.status, 404);
});
});
it('return the state for an anonymous user', function(done) {
......@@ -74,7 +80,7 @@ describe('github-issue-service #slow #github', function() {
.then(() => underTest.getIssue(fixtureLoader.GITTER_INTEGRATION_REPO_FULL, 1))
.then(function(f) {
assert(f);
assert.strictEqual(f.number, 1);
assert.strictEqual(f.iid, 1);
})
.nodeify(done);
});
......
......@@ -13,24 +13,29 @@ describe('github-issue-state-search #slow #github', function() {
githubToken: fixtureLoader.GITTER_INTEGRATION_USER_SCOPE_TOKEN
};
it('return the state', function(done) {
it('return the state', function() {
var underTest = new GitHubIssueStateService(FAKE_USER);
underTest.getIssueState(fixtureLoader.GITTER_INTEGRATION_REPO_FULL, 1)
return underTest.getIssueState(fixtureLoader.GITTER_INTEGRATION_REPO_FULL, 1)
.then(function(f) {
assert.strictEqual(f, 'open');
})
.nodeify(done);
});
});
it('return empty for missing issue', function(done) {
it('throw error for missing issue', function() {
var underTest = new GitHubIssueStateService(FAKE_USER);
underTest.getIssueState(fixtureLoader.GITTER_INTEGRATION_REPO_FULL, 999999)
.then(function(f) {
assert.strictEqual(f, '');
return underTest.getIssueState(fixtureLoader.GITTER_INTEGRATION_REPO_FULL, 999999)
.then(() => {
assert.fail('Shouldn\'t be able to fetch missing issue');
})
.nodeify(done);
.catch((err) => {
if(err instanceof assert.AssertionError) {
throw err;
}
assert.strictEqual(err.status, 404);
});
});
});
'use strict';
var Promise = require('bluebird');
var identityService = require('gitter-web-identity');
module.exports = function(user) {
if(!user) return Promise.resolve();
return identityService.getIdentityForUser(user, identityService.GITLAB_IDENTITY_PROVIDER)
.then(function(glIdentity) {
if(!glIdentity) return null;
return glIdentity.accessToken;
});
};
'use strict';
var env = require('gitter-web-env');
var nconf = env.config;
// A comma-separated list of GitLab private-tokens
var accessTokenPoolString = nconf.get('gitlab:public_token_pool') || '';
var accessTokenPool = accessTokenPoolString.split(',');
var usageIterator = 0;
module.exports = function() {
var accessToken = null;
if(accessTokenPool.length > 0) {
accessToken = accessTokenPool[usageIterator++ % accessTokenPool.length];
}
return accessToken;
};
'use strict';
module.exports = {
GitLabIssuableService: require('./issuable-service'),
GitLabIssuableStateService: require('./issuable-state-service')
};
'use strict';
var { Issues, MergeRequests } = require('gitlab/dist/es5');
var cacheWrapper = require('gitter-web-cache-wrapper');
var avatars = require('gitter-web-avatars');
var getGitlabAccessTokenFromUser = require('./get-gitlab-access-token-from-user');
var getPublicTokenFromPool = require('./get-public-token-from-pool');
function standardizeResponse(response) {
var state = '';
if(response.state === 'opened' || response.state === 'reopened') {
state = 'open';
}
else if(response.state === 'closed') {
state = 'closed';
}
return {
id: response.id,
iid: response.iid,
title: response.title,
body: response.description,
state: state,
labels: response.labels || [],
author: {
id: response.author && response.author.id,
username: response.author && response.author.username,
displayName: response.author && response.author.name,
avatarUrl: response.author && response.author.avatar_url || avatars.getDefault()
},
assignee: (response.assignees && response.assignees.length > 0) && {
id: response.assignees[0].id,
username: response.assignees[0].username,
displayName: response.assignees[0].name,
avatarUrl: response.assignees[0].avatar_url
},
createdAt: response.created_at,
updatedAt: response.updated_at
};
}
function GitLabIssuableService(user, type) {
this.type = type || 'issues';
this.user = user;
this.getAccessTokenPromise = getGitlabAccessTokenFromUser(user);
}
GitLabIssuableService.prototype.getIssue = function(project, iid) {
var type = this.type;
return this.getAccessTokenPromise
.then(function(accessToken) {
const gitlabLibOpts = {
url: 'https://gitlab.com',
oauthToken: accessToken,
token: getPublicTokenFromPool()
};
var resource = new Issues(gitlabLibOpts);
if(type === 'mr') {
resource = new MergeRequests(gitlabLibOpts);
}
return resource.show(project, iid);
})
.then(standardizeResponse);
}
module.exports = cacheWrapper('GitLabIssuableService', GitLabIssuableService, {
getInstanceId: function(gitLabIssuableService) {
return gitLabIssuableService.getAccessTokenPromise;
}
});
"use strict";
var cacheWrapper = require('gitter-web-cache-wrapper');
var IssuableService = require('./issuable-service');
var getGitlabAccessTokenFromUser = require('./get-gitlab-access-token-from-user');
function GitLabIssuableStateService(user, type) {
this.type = type || 'issues';
this.issuableService = new IssuableService(user, this.type);
this.getAccessTokenPromise = getGitlabAccessTokenFromUser(user);
}
GitLabIssuableStateService.prototype.getIssueState = function(project, iid) {
return this.issuableService.getIssue(project, iid)
.then(function(issuable) {
return issuable.state;
});
};
module.exports = cacheWrapper('GitLabIssuableStateService', GitLabIssuableStateService, {
getInstanceId: function(gitLabIssuableStateService) {
return gitLabIssuableStateService.getAccessTokenPromise;
}
});
{
"name": "gitter-web-gitlab",
"version": "1.0.0",
"main": "lib/index.js",
"directories": {
"lib": "lib",
"test": "test"
},
"scripts": {
"test": "mocha test/"
},
"private": true,
"dependencies": {
"bluebird": "^3.2.1",
"debug": "^2.2.0",
"gitlab": "^3.6.0",
"gitter-web-cache-wrapper": "file:../cache-wrapper",
"gitter-web-github": "file:../github",
"gitter-web-identity": "file:../identity"
},
"devDependencies": {
"mocha": "^5.2.0"
}
}
{
"env": {
"commonjs": true,
"node": true,
"mocha": true
},
"plugins": [
"mocha"
],
"rules": {
"mocha/no-exclusive-tests": "error",
"node/no-unpublished-require": "off"
}
}
'use strict';
const Promise = require('bluebird');
const assert = require('assert');
const proxyquireNoCallThru = require('proxyquire').noCallThru();
//const GitLabIssuableService = require('..').GitLabIssuableService;
const fixtureLoader = require('gitter-web-test-utils/lib/test-fixtures');
describe('gitlab-issue-service #slow #gitlab', function() {
fixtureLoader.ensureIntegrationEnvironment(
'GITLAB_USER_USERNAME',
'GITLAB_USER_TOKEN',
'GITLAB_PUBLIC_PROJECT1_URI',
'GITLAB_PRIVATE_PROJECT1_URI',
'GITLAB_UNAUTHORIZED_PRIVATE_PROJECT1_URI'
);
const FAKE_USER = {
username: 'FAKE_USER',
};
let oauthToken = null;
let GitLabIssuableService;
beforeEach(() => {
GitLabIssuableService = proxyquireNoCallThru('../lib/issuable-service', {
'./get-gitlab-access-token-from-user': function() {
return Promise.resolve(oauthToken);
}
});
});
afterEach(() => {
oauthToken = null;
});
describe('as a GitLab user', () => {
beforeEach(() => {
oauthToken = fixtureLoader.GITLAB_USER_TOKEN;
});
it('should fetch public issue', () => {
const glService = new GitLabIssuableService(FAKE_USER, 'issues');
return glService.getIssue(fixtureLoader.GITLAB_PUBLIC_PROJECT1_URI, 1)
.then((issue) => {
assert.strictEqual(issue.iid, 1);
assert.strictEqual(issue.state, 'open');
assert.strictEqual(issue.author.username, fixtureLoader.GITLAB_USER_USERNAME);
});
});
it('shouldn\'t fetch missing issue', () => {
const glService = new GitLabIssuableService(FAKE_USER, 'issues');
return glService.getIssue(fixtureLoader.GITLAB_PUBLIC_PROJECT1_URI, 999999)
.then(() => {
assert.fail('Shouldn\'t be able to fetch missing issue');
})
.catch((err) => {
if(err instanceof assert.AssertionError) {
throw err;
}
assert.strictEqual(err.statusCode, 404);
});
});
it('should fetch confidential issue', () => {
const glService = new GitLabIssuableService(FAKE_USER, 'issues');
return glService.getIssue(fixtureLoader.GITLAB_PUBLIC_PROJECT1_URI, 2)
.then((issue) => {
assert.strictEqual(issue.iid, 2);
assert.strictEqual(issue.state, 'open');
assert.strictEqual(issue.author.username, fixtureLoader.GITLAB_USER_USERNAME);
});
});
it('should fetch private issue', () => {
const glService = new GitLabIssuableService(FAKE_USER, 'issues');
return glService.getIssue(fixtureLoader.GITLAB_PRIVATE_PROJECT1_URI, 1)
.then((issue) => {
assert.strictEqual(issue.iid, 1);
assert.strictEqual(issue.state, 'open');
assert.strictEqual(issue.author.username, fixtureLoader.GITLAB_USER_USERNAME);
});
});
it('shouldn\'t fetch issue in unauthroized private project', () => {
const glService = new GitLabIssuableService(FAKE_USER, 'issues');
return glService.getIssue(fixtureLoader.GITLAB_UNAUTHORIZED_PRIVATE_PROJECT1_URI, 1)
.then(() => {
assert.fail('Shouldn\'t be able to fetch issue in unauthorized private project');
})
.catch((err) => {
if(err instanceof assert.AssertionError) {
throw err;
}
assert.strictEqual(err.statusCode, 404);
});
});
});
describe('using public token pool', () => {
beforeEach(() => {
oauthToken = null;
});
it('should fetch public issue', () => {
const glService = new GitLabIssuableService(FAKE_USER, 'issues');
return glService.getIssue(fixtureLoader.GITLAB_PUBLIC_PROJECT1_URI, 1)
.then((issue) => {
assert.strictEqual(issue.iid, 1);
assert.strictEqual(issue.state, 'open');
assert.strictEqual(issue.author.username, fixtureLoader.GITLAB_USER_USERNAME);
});
});
it('shouldn\'t fetch confidential issue', () => {
const glService = new GitLabIssuableService(FAKE_USER, 'issues');
return glService.getIssue(fixtureLoader.GITLAB_PUBLIC_PROJECT1_URI, 2)
.then(() => {
assert.fail('Shouldn\'t be able to fetch confidential issue');
})
.catch((err) => {
if(err instanceof assert.AssertionError) {
throw err;
}
assert.strictEqual(err.statusCode, 404);
});
});
it('shouldn\'t fetch private issue', () => {
const glService = new GitLabIssuableService(FAKE_USER, 'issues');
return glService.getIssue(fixtureLoader.GITLAB_PRIVATE_PROJECT1_URI, 1)
.then(() => {
assert.fail('Shouldn\'t be able to fetch issue in unauthorized private project');
})
.catch((err) => {
if(err instanceof assert.AssertionError) {
throw err;
}
assert.strictEqual(err.statusCode, 404);
});
});
it('shouldn\'t fetch issue in unauthroized private project', () => {
const glService = new GitLabIssuableService(FAKE_USER, 'issues');
return glService.getIssue(fixtureLoader.GITLAB_UNAUTHORIZED_PRIVATE_PROJECT1_URI, 1)
.then(() => {
assert.fail('Shouldn\'t be able to fetch issue in unauthorized private project');
})
.catch((err) => {
if(err instanceof assert.AssertionError) {
throw err;
}
assert.strictEqual(err.statusCode, 404);
});
});
});
});
......@@ -177,8 +177,8 @@ module.exports = {
findPrimaryIdentityForUser: Promise.method(findPrimaryIdentityForUser),
removeForUser: Promise.method(removeForUser),
GITHUB_IDENTITY_PROVIDER: 'github',
GITLAB_IDENTITY_PROVIDER: 'gitlab',
GITHUB_IDENTITY_PROVIDER: 'github',
GOOGLE_IDENTITY_PROVIDER: 'google',
TWITTER_IDENTITY_PROVIDER: 'twitter',
LINKEDIN_IDENTITY_PROVIDER: 'linkedin',
......
......@@ -35,6 +35,12 @@ var configurationMappings = {
GITHUB_ANON_CLIENT_SECRET: "github:anonymous_app:client_secret",
TWITTER_CONSUMER_KEY: "twitteroauth:consumer_key",
TWITTER_CONSUMER_SECRET: "twitteroauth:consumer_secret",
GITLAB_USER_USERNAME: 'integrationTests:gitlabTestUser:username',
GITLAB_USER_TOKEN: 'integrationTests:gitlabTestUser:token',
GITLAB_PUBLIC_PROJECT1_URI: 'integrationTests:gitlabPublicProject1:uri',
GITLAB_PRIVATE_PROJECT1_URI: 'integrationTests:gitlabPrivateProject1:uri',
GITLAB_UNAUTHORIZED_PRIVATE_PROJECT1_URI: 'integrationTests:gitlabUnauthorizedPrivateProject1:uri'
}
var configSets = {
......
......@@ -8,6 +8,7 @@ var fixtureUtils = require('./fixture-utils');
var onMongoConnect = require('gitter-web-persistence-utils/lib/on-mongo-connect');
var _ = require('lodash');
var integrationFixtures = require('./integration-fixtures');
var shutdown = require('shutdown');
var fixtureSteps = [
require('./delete-documents'),
......@@ -131,7 +132,10 @@ fixtureLoader.ensureIntegrationEnvironment = function() {
logger.warn('Skipping this test due to missing config items', missing.join(', '))
// Just skip these tests
this._skipFixtureSetup = true;
this.skip();
// Skip hack from, https://github.com/mochajs/mocha/issues/2683#issuecomment-375629901
this.test.parent.pending = true;
}
});
......
......@@ -12,7 +12,7 @@
"private": true,
"dependencies": {
"bluebird": "^3.5.1",
"gitter-markdown-processor": "^11.2.0",
"gitter-markdown-processor": "^12.1.0",
"gitter-web-env": "file:../env",
"gitter-web-github": "file:../github",
"shutdown": "^0.2.3"
......
......@@ -13,7 +13,7 @@
"dependencies": {
"bluebird": "^3.5.1",
"debug": "^2.2.0",
"gitter-markdown-processor": "^11.2.0",
"gitter-markdown-processor": "^12.1.0",
"gitter-web-env": "file:../env",
"gitter-web-github": "file:../github",
"gitter-web-live-collection-events": "file:../live-collection-events",
......
This diff is collapsed.
......@@ -3,7 +3,7 @@
@import "../js/views/chat/chatCollectionView";
@import "../js/views/righttoolbar/rightToolbarView";
@import "../js/views/chat/decorators/issueDecorator";