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

Commit

Permalink
feat(create-repo): enabled creation within an org
Browse files Browse the repository at this point in the history
as long as the authenticated user is a member

for #1
  • Loading branch information
travi committed Apr 10, 2019
1 parent 26c064e commit 279306b
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 17 deletions.
9 changes: 9 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
40 changes: 33 additions & 7 deletions src/create.js
Original file line number Diff line number Diff line change
@@ -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`);
}
4 changes: 2 additions & 2 deletions src/scaffolder.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions test/mocha-setup.js
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
73 changes: 67 additions & 6 deletions test/unit/create-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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`
);
});
});
});
13 changes: 11 additions & 2 deletions test/unit/scaffolder-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
);

Expand Down

0 comments on commit 279306b

Please sign in to comment.