From 279306bf1b38c10227d3c654d016369eb284d390 Mon Sep 17 00:00:00 2001 From: Matt Travi Date: Wed, 10 Apr 2019 01:00:13 -0500 Subject: [PATCH] feat(create-repo): enabled creation within an org as long as the authenticated user is a member for #1 --- package-lock.json | 9 +++++ package.json | 1 + src/create.js | 40 ++++++++++++++++---- src/scaffolder.js | 4 +- test/mocha-setup.js | 2 + test/unit/create-test.js | 73 +++++++++++++++++++++++++++++++++--- test/unit/scaffolder-test.js | 13 ++++++- 7 files changed, 125 insertions(+), 17 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7633946c..88281dc3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2073,6 +2073,15 @@ "type-detect": "^4.0.0" } }, + "chai-as-promised": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz", + "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", + "dev": true, + "requires": { + "check-error": "^1.0.2" + } + }, "chalk": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", diff --git a/package.json b/package.json index 26e6879b..fa7c5106 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "babel-register": "^6.26.0", "ban-sensitive-files": "^1.9.2", "chai": "^4.1.2", + "chai-as-promised": "^7.1.1", "codecov": "^3.0.4", "commitlint-config-travi": "^1.2.30", "cz-conventional-changelog": "^2.1.0", diff --git a/src/create.js b/src/create.js index 0c620949..956a515a 100644 --- a/src/create.js +++ b/src/create.js @@ -1,15 +1,41 @@ import chalk from 'chalk'; import {factory} from './github-client-factory'; -export default async function (name, visibility) { +async function authenticatedUserIsMemberOfRequestedOrganization(account, octokit) { + const {data: organizations} = await octokit.orgs.listForAuthenticatedUser(); + + return organizations.reduce((acc, organization) => acc || account === organization.login, false); +} + +export default async function (name, owner, visibility) { console.error(chalk.grey('Creating repository on GitHub')); // eslint-disable-line no-console - const {data: {ssh_url: sshUrl, html_url: htmlUrl}} = await factory().repos.createForAuthenticatedUser({ - name, - private: 'Private' === visibility - }); + const octokit = factory(); + const {data: {login: authenticatedUser}} = await octokit.users.getAuthenticated(); + + if (owner === authenticatedUser) { + const {data: {ssh_url: sshUrl, html_url: htmlUrl}} = await octokit.repos.createForAuthenticatedUser({ + name, + private: 'Private' === visibility + }); + + console.error(chalk.grey(`Repository created for user ${name} at ${htmlUrl}`)); // eslint-disable-line no-console + + return {sshUrl, htmlUrl}; + } + + if (await authenticatedUserIsMemberOfRequestedOrganization(owner, octokit)) { + const {data: {ssh_url: sshUrl, html_url: htmlUrl}} = await octokit.repos.createInOrg({ + org: owner, + name, + private: 'Private' === visibility + }); + + // eslint-disable-next-line no-console + console.error(chalk.grey(`Repository created for organization ${name} at ${htmlUrl}`)); - console.error(chalk.grey(`Repository created at ${htmlUrl}`)); // eslint-disable-line no-console + return {sshUrl, htmlUrl}; + } - return {sshUrl, htmlUrl}; + throw new Error(`User ${authenticatedUser} does not have access to create a repository in the ${owner} account`); } diff --git a/src/scaffolder.js b/src/scaffolder.js index a77bfdac..a77cf8b1 100644 --- a/src/scaffolder.js +++ b/src/scaffolder.js @@ -2,12 +2,12 @@ import chalk from 'chalk'; import scaffoldSettings from './settings-scaffolder'; import create from './create'; -export async function scaffold({name, projectRoot, projectType, description, homepage, visibility}) { +export async function scaffold({name, owner, projectRoot, projectType, description, homepage, visibility}) { console.error(chalk.blue('Generating GitHub')); // eslint-disable-line no-console const [, creationResult] = await Promise.all([ scaffoldSettings(projectRoot, name, description, homepage, visibility, projectType), - create(name, visibility) + create(name, owner, visibility) ]); return creationResult; diff --git a/test/mocha-setup.js b/test/mocha-setup.js index 99a6e506..6fb50dd9 100644 --- a/test/mocha-setup.js +++ b/test/mocha-setup.js @@ -1,7 +1,9 @@ import sinon from 'sinon'; import chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; sinon.assert.expose(chai.assert, {prefix: ''}); +chai.use(chaiAsPromised); process.on('unhandledRejection', reason => { throw reason; diff --git a/test/unit/create-test.js b/test/unit/create-test.js index 6434a0ab..68f1a00e 100644 --- a/test/unit/create-test.js +++ b/test/unit/create-test.js @@ -9,6 +9,8 @@ suite('creation', () => { const sshUrl = any.url(); const htmlUrl = any.url(); const creationResponse = {data: {ssh_url: sshUrl, html_url: htmlUrl}}; + const account = any.word(); + const name = any.word(); setup(() => { sandbox = sinon.createSandbox(); @@ -20,23 +22,82 @@ suite('creation', () => { suite('for user', () => { test('that the repository is created for the provided user account', async () => { - const name = any.word(); const createForAuthenticatedUser = sinon.stub(); - const client = {repos: {createForAuthenticatedUser}}; + const getAuthenticated = sinon.stub(); + const client = {repos: {createForAuthenticatedUser}, users: {getAuthenticated}}; clientFactory.factory.returns(client); createForAuthenticatedUser.withArgs({name, private: false}).resolves(creationResponse); + getAuthenticated.resolves({data: {login: account}}); - assert.deepEqual(await create(name, 'Public'), {sshUrl, htmlUrl}); + assert.deepEqual(await create(name, account, 'Public'), {sshUrl, htmlUrl}); }); test('that the repository is created as private when visibility is `Private`', async () => { - const name = any.word(); const createForAuthenticatedUser = sinon.stub(); - const client = {repos: {createForAuthenticatedUser}}; + const getAuthenticated = sinon.stub(); + const client = {repos: {createForAuthenticatedUser}, users: {getAuthenticated}}; clientFactory.factory.returns(client); createForAuthenticatedUser.withArgs({name, private: true}).resolves(creationResponse); + getAuthenticated.resolves({data: {login: account}}); - assert.deepEqual(await create(name, 'Private'), {sshUrl, htmlUrl}); + assert.deepEqual(await create(name, account, 'Private'), {sshUrl, htmlUrl}); + }); + }); + + suite('for organization', () => { + test('that the repository is created for the provided organization account', async () => { + const getAuthenticated = sinon.stub(); + const listForAuthenticatedUser = sinon.stub(); + const createInOrg = sinon.stub(); + const client = {repos: {createInOrg}, users: {getAuthenticated}, orgs: {listForAuthenticatedUser}}; + clientFactory.factory.returns(client); + getAuthenticated.resolves({data: {login: any.word()}}); + listForAuthenticatedUser + .resolves({ + data: [ + ...any.listOf(() => ({...any.simpleObject(), login: any.word})), + {...any.simpleObject(), login: account} + ] + }); + createInOrg.withArgs({org: account, name, private: false}).resolves(creationResponse); + + assert.deepEqual(await create(name, account, 'Public'), {sshUrl, htmlUrl}); + }); + + test('that the repository is created as private when visibility is `Private`', async () => { + const getAuthenticated = sinon.stub(); + const listForAuthenticatedUser = sinon.stub(); + const createInOrg = sinon.stub(); + const client = {repos: {createInOrg}, users: {getAuthenticated}, orgs: {listForAuthenticatedUser}}; + clientFactory.factory.returns(client); + getAuthenticated.resolves({data: {login: any.word()}}); + listForAuthenticatedUser + .resolves({ + data: [ + ...any.listOf(() => ({...any.simpleObject(), login: any.word})), + {...any.simpleObject(), login: account} + ] + }); + createInOrg.withArgs({org: account, name, private: true}).resolves(creationResponse); + + assert.deepEqual(await create(name, account, 'Private'), {sshUrl, htmlUrl}); + }); + }); + + suite('unauthorized account', () => { + test('that an error is thrown if the authenticated user does not have access to the requested account', () => { + const authenticatedUser = any.word(); + const getAuthenticated = sinon.stub(); + const listForAuthenticatedUser = sinon.stub(); + const client = {users: {getAuthenticated}, orgs: {listForAuthenticatedUser}}; + clientFactory.factory.returns(client); + getAuthenticated.resolves({data: {login: authenticatedUser}}); + listForAuthenticatedUser.resolves({data: any.listOf(() => ({...any.simpleObject(), login: any.word}))}); + + return assert.isRejected( + create(name, account, any.word()), + `User ${authenticatedUser} does not have access to create a repository in the ${account} account` + ); }); }); }); diff --git a/test/unit/scaffolder-test.js b/test/unit/scaffolder-test.js index 1ef3db69..4ff07d46 100644 --- a/test/unit/scaffolder-test.js +++ b/test/unit/scaffolder-test.js @@ -23,13 +23,22 @@ suite('github', () => { const description = any.sentence(); const homepage = any.url(); const projectType = any.word(); + const projectOwner = any.word(); const visibility = any.word(); const creationResult = any.simpleObject(); settingsSecaffolder.default.resolves(); - creator.default.withArgs(projectName, visibility).resolves(creationResult); + creator.default.withArgs(projectName, projectOwner, visibility).resolves(creationResult); assert.equal( - await scaffold({projectRoot, name: projectName, description, homepage, projectType, visibility}), + await scaffold({ + projectRoot, + name: projectName, + owner: projectOwner, + description, + homepage, + projectType, + visibility + }), creationResult );