From 8737844d607185ebf5846c2006b3eda45d68aa45 Mon Sep 17 00:00:00 2001 From: Katy Bowman Date: Wed, 28 Feb 2024 09:44:20 -0500 Subject: [PATCH 01/10] refactor: initial access:index command migration --- packages/cli/src/commands/access/index.ts | 67 +++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 packages/cli/src/commands/access/index.ts diff --git a/packages/cli/src/commands/access/index.ts b/packages/cli/src/commands/access/index.ts new file mode 100644 index 0000000000..dcddb99c2e --- /dev/null +++ b/packages/cli/src/commands/access/index.ts @@ -0,0 +1,67 @@ +import color from '@heroku-cli/color' +import {Command, flags} from '@heroku-cli/command' +import {Args, ux} from '@oclif/core' +import * as Heroku from '@heroku-cli/schema' + +let _ = require('lodash') +let Utils = require('../../lib/utils') +function printJSON(collaborators) { + ux.log(JSON.stringify(collaborators, null, 2)) +} + +function printAccess(app, collaborators) { + let showPermissions = Utils.isteamApp(app.owner.email) + collaborators = _.chain(collaborators) + .sortBy(c => c.email || c.user.email) + .reject(c => /herokumanager\.com$/.test(c.user.email)) + .map(collab => { + let email = collab.user.email + let role = collab.role + let data = {email: email, role: role || 'collaborator'} + if (showPermissions) { + data.permissions = _.map(_.sortBy(collab.permissions, 'name'), 'name') + } + + return data + }) + .value() + let columns = [ + {key: 'email', label: 'Email', format: e => color.cyan(e)}, {key: 'role', label: 'Role', format: r => color.green(r)}, + ] + if (showPermissions) + columns.push({key: 'permissions', label: 'Permissions'}) + cli.table(collaborators, {printHeader: false, columns}) +} + +export default class Index extends Command { + public async run(): Promise { + const {flags, argv, args} = await this.parse(Index) + let appName = app + let app = await this.heroku.get(`/apps/${appName}`) + let isTeamApp = Utils.isteamApp(app.owner.email) + let collaborators = await this.heroku.get(`/apps/${appName}/collaborators`) + if (isTeamApp) { + let teamName = Utils.getOwner(app.owner.email) + try { + const members = await this.heroku.get(`/teams/${teamName}/members`) + let admins = members.filter(member => member.role === 'admin') + let adminPermissions = await this.heroku.get('/teams/permissions') + admins = _.forEach(admins, function (admin) { + admin.user = {email: admin.email} + admin.permissions = adminPermissions + return admin + }) + collaborators = _.reject(collaborators, {role: 'admin'}) + collaborators = _.union(collaborators, admins) + } catch (error) { + if (error.statusCode !== 403) + throw error + } + } + + if (flags.json) + printJSON(collaborators) + else + printAccess(app, collaborators) + } +} From aaac764e1791f15de423eae125fbc42a2003dc7a Mon Sep 17 00:00:00 2001 From: Katy Bowman Date: Wed, 28 Feb 2024 10:27:02 -0500 Subject: [PATCH 02/10] refactor: initial linting fixes and add types --- packages/cli/src/commands/access/index.ts | 62 +++++++++++++++-------- 1 file changed, 41 insertions(+), 21 deletions(-) diff --git a/packages/cli/src/commands/access/index.ts b/packages/cli/src/commands/access/index.ts index dcddb99c2e..31423d2417 100644 --- a/packages/cli/src/commands/access/index.ts +++ b/packages/cli/src/commands/access/index.ts @@ -2,22 +2,30 @@ import color from '@heroku-cli/color' import {Command, flags} from '@heroku-cli/command' import {Args, ux} from '@oclif/core' import * as Heroku from '@heroku-cli/schema' +import HTTP from 'http-call' +import * as _ from 'lodash' -let _ = require('lodash') let Utils = require('../../lib/utils') -function printJSON(collaborators) { + +type MemberData = { + email: string, + role: string, + permissions?: Heroku.TeamAppPermission[] +} + +function printJSON(collaborators: HTTP) { ux.log(JSON.stringify(collaborators, null, 2)) } -function printAccess(app, collaborators) { - let showPermissions = Utils.isteamApp(app.owner.email) +function printAccess(app: Heroku.App, collaborators) { + const showPermissions = Utils.isteamApp(app.owner?.email) collaborators = _.chain(collaborators) .sortBy(c => c.email || c.user.email) .reject(c => /herokumanager\.com$/.test(c.user.email)) .map(collab => { - let email = collab.user.email - let role = collab.role - let data = {email: email, role: role || 'collaborator'} + const email = collab.user.email + const role = collab.role + const data: MemberData = {email: email, role: role || 'collaborator'} if (showPermissions) { data.permissions = _.map(_.sortBy(collab.permissions, 'name'), 'name') } @@ -25,41 +33,53 @@ function printAccess(app, collaborators) { return data }) .value() - let columns = [ - {key: 'email', label: 'Email', format: e => color.cyan(e)}, {key: 'role', label: 'Role', format: r => color.green(r)}, + const columns = [ + {key: 'email', label: 'Email', format: (e: string) => color.cyan(e)}, { + key: 'role', + label: 'Role', + format: (r: string) => color.green(r), + }, ] if (showPermissions) columns.push({key: 'permissions', label: 'Permissions'}) cli.table(collaborators, {printHeader: false, columns}) } -export default class Index extends Command { +export default class AccessIndex extends Command { + static description = 'list who has access to an app' + static topic = 'access' + static flags = { + app: flags.app({required: true}), + remote: flags.remote({char: 'r'}), + json: flags.boolean({description: 'output in json format'}), + } + public async run(): Promise { const {flags, argv, args} = await this.parse(Index) - let appName = app - let app = await this.heroku.get(`/apps/${appName}`) - let isTeamApp = Utils.isteamApp(app.owner.email) - let collaborators = await this.heroku.get(`/apps/${appName}/collaborators`) + const {app: appName, json} = flags + const app = await this.heroku.get(`/apps/${appName}`) + const isTeamApp = Utils.isteamApp(app.owner.email) + let collaborators: HTTP = await this.heroku.get(`/apps/${appName}/collaborators`) if (isTeamApp) { - let teamName = Utils.getOwner(app.owner.email) + const teamName = Utils.getOwner(app.owner.email) try { - const members = await this.heroku.get(`/teams/${teamName}/members`) - let admins = members.filter(member => member.role === 'admin') - let adminPermissions = await this.heroku.get('/teams/permissions') - admins = _.forEach(admins, function (admin) { + const members = await this.heroku.get(`/teams/${teamName}/members`) + let admins = members.filter((member: { role: string }) => member.role === 'admin') + const adminPermissions = await this.heroku.get('/teams/permissions') + admins = _.forEach(admins, function (admin: { user: { email: any }; email: any; permissions: HTTP }) { admin.user = {email: admin.email} admin.permissions = adminPermissions return admin }) collaborators = _.reject(collaborators, {role: 'admin'}) collaborators = _.union(collaborators, admins) - } catch (error) { + } catch (error: any) { if (error.statusCode !== 403) throw error } } - if (flags.json) + if (json) printJSON(collaborators) else printAccess(app, collaborators) From dafa31b2f1f5d49bc5824797840e67054a178f58 Mon Sep 17 00:00:00 2001 From: Katy Bowman Date: Wed, 28 Feb 2024 10:49:34 -0500 Subject: [PATCH 03/10] refactor: migrate isTeamApp and getOwner util functions --- packages/cli/src/commands/access/index.ts | 18 ++++++++---------- packages/cli/src/lib/access/access-utils.ts | 11 +++++++++++ 2 files changed, 19 insertions(+), 10 deletions(-) create mode 100644 packages/cli/src/lib/access/access-utils.ts diff --git a/packages/cli/src/commands/access/index.ts b/packages/cli/src/commands/access/index.ts index 31423d2417..1f16eb5b45 100644 --- a/packages/cli/src/commands/access/index.ts +++ b/packages/cli/src/commands/access/index.ts @@ -4,8 +4,7 @@ import {Args, ux} from '@oclif/core' import * as Heroku from '@heroku-cli/schema' import HTTP from 'http-call' import * as _ from 'lodash' - -let Utils = require('../../lib/utils') +import {isTeamApp, getOwner} from '../../lib/access/access-utils' type MemberData = { email: string, @@ -18,7 +17,7 @@ function printJSON(collaborators: HTTP) { } function printAccess(app: Heroku.App, collaborators) { - const showPermissions = Utils.isteamApp(app.owner?.email) + const showPermissions = isTeamApp(app.owner?.email) collaborators = _.chain(collaborators) .sortBy(c => c.email || c.user.email) .reject(c => /herokumanager\.com$/.test(c.user.email)) @@ -55,15 +54,14 @@ export default class AccessIndex extends Command { } public async run(): Promise { - const {flags, argv, args} = await this.parse(Index) + const {flags, argv, args} = await this.parse(AccessIndex) const {app: appName, json} = flags - const app = await this.heroku.get(`/apps/${appName}`) - const isTeamApp = Utils.isteamApp(app.owner.email) - let collaborators: HTTP = await this.heroku.get(`/apps/${appName}/collaborators`) - if (isTeamApp) { - const teamName = Utils.getOwner(app.owner.email) + const {body: app} = await this.heroku.get(`/apps/${appName}`) + let {body: collaborators} = await this.heroku.get(`/apps/${appName}/collaborators`) + if (isTeamApp(app.owner?.email)) { + const teamName = getOwner(app.owner?.email) try { - const members = await this.heroku.get(`/teams/${teamName}/members`) + const {body: members} = await this.heroku.get(`/teams/${teamName}/members`) let admins = members.filter((member: { role: string }) => member.role === 'admin') const adminPermissions = await this.heroku.get('/teams/permissions') admins = _.forEach(admins, function (admin: { user: { email: any }; email: any; permissions: HTTP }) { diff --git a/packages/cli/src/lib/access/access-utils.ts b/packages/cli/src/lib/access/access-utils.ts new file mode 100644 index 0000000000..125f3a69c5 --- /dev/null +++ b/packages/cli/src/lib/access/access-utils.ts @@ -0,0 +1,11 @@ +export const isTeamApp = function (owner: string | undefined) { + return owner ? (/@herokumanager\.com$/.test(owner)) : false +} + +export const getOwner = function (owner: string | undefined) { + if (owner && isTeamApp(owner)) { + return owner.split('@herokumanager.com')[0] + } + + return owner +} From 18732b602ee2ddde4b3dd3ec9c656bcd2a10e1c2 Mon Sep 17 00:00:00 2001 From: Katy Bowman Date: Wed, 28 Feb 2024 11:14:12 -0500 Subject: [PATCH 04/10] refactor: reformat table construction --- packages/cli/src/commands/access/index.ts | 37 +++++++++++++++++------ 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/packages/cli/src/commands/access/index.ts b/packages/cli/src/commands/access/index.ts index 1f16eb5b45..c6450728af 100644 --- a/packages/cli/src/commands/access/index.ts +++ b/packages/cli/src/commands/access/index.ts @@ -5,6 +5,7 @@ import * as Heroku from '@heroku-cli/schema' import HTTP from 'http-call' import * as _ from 'lodash' import {isTeamApp, getOwner} from '../../lib/access/access-utils' +import {table} from '@oclif/core/lib/cli-ux/styled/table' type MemberData = { email: string, @@ -16,6 +17,26 @@ function printJSON(collaborators: HTTP) { ux.log(JSON.stringify(collaborators, null, 2)) } +function buildTableColumns(showPermissions: boolean) { + const baseColumns = { + Email: { + get: ({email}: any): string => color.cyan(email), + }, + Role: { + get: ({role}: any) => color.green(role), + }, + } + + if (showPermissions) { + return { + ...baseColumns, + Permissions: {}, + } + } + + return baseColumns +} + function printAccess(app: Heroku.App, collaborators) { const showPermissions = isTeamApp(app.owner?.email) collaborators = _.chain(collaborators) @@ -32,16 +53,12 @@ function printAccess(app: Heroku.App, collaborators) { return data }) .value() - const columns = [ - {key: 'email', label: 'Email', format: (e: string) => color.cyan(e)}, { - key: 'role', - label: 'Role', - format: (r: string) => color.green(r), - }, - ] - if (showPermissions) - columns.push({key: 'permissions', label: 'Permissions'}) - cli.table(collaborators, {printHeader: false, columns}) + + const tableColumns = buildTableColumns(showPermissions) + ux.table( + collaborators, + tableColumns, + ) } export default class AccessIndex extends Command { From b87dc94d32119aa654765c2020d6b2032f87709f Mon Sep 17 00:00:00 2001 From: Katy Bowman Date: Wed, 28 Feb 2024 11:37:36 -0500 Subject: [PATCH 05/10] refactor: refactor collaborators array functions --- packages/cli/src/commands/access/index.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/cli/src/commands/access/index.ts b/packages/cli/src/commands/access/index.ts index c6450728af..c9475bb534 100644 --- a/packages/cli/src/commands/access/index.ts +++ b/packages/cli/src/commands/access/index.ts @@ -1,11 +1,10 @@ import color from '@heroku-cli/color' import {Command, flags} from '@heroku-cli/command' -import {Args, ux} from '@oclif/core' +import {ux} from '@oclif/core' import * as Heroku from '@heroku-cli/schema' import HTTP from 'http-call' import * as _ from 'lodash' import {isTeamApp, getOwner} from '../../lib/access/access-utils' -import {table} from '@oclif/core/lib/cli-ux/styled/table' type MemberData = { email: string, @@ -13,7 +12,7 @@ type MemberData = { permissions?: Heroku.TeamAppPermission[] } -function printJSON(collaborators: HTTP) { +function printJSON(collaborators: Heroku.TeamAppCollaborator[]) { ux.log(JSON.stringify(collaborators, null, 2)) } @@ -37,7 +36,7 @@ function buildTableColumns(showPermissions: boolean) { return baseColumns } -function printAccess(app: Heroku.App, collaborators) { +function printAccess(app: Heroku.App, collaborators: any[]) { const showPermissions = isTeamApp(app.owner?.email) collaborators = _.chain(collaborators) .sortBy(c => c.email || c.user.email) @@ -61,6 +60,11 @@ function printAccess(app: Heroku.App, collaborators) { ) } +function buildCollaboratorsArray(collaboratorsRaw: Heroku.TeamAppCollaborator[], admins: Heroku.TeamMember[]) { + const collaboratorsNoAdmins = _.reject(collaboratorsRaw, {role: 'admin'}) + return _.union(collaboratorsNoAdmins, admins) +} + export default class AccessIndex extends Command { static description = 'list who has access to an app' static topic = 'access' @@ -86,8 +90,7 @@ export default class AccessIndex extends Command { admin.permissions = adminPermissions return admin }) - collaborators = _.reject(collaborators, {role: 'admin'}) - collaborators = _.union(collaborators, admins) + collaborators = buildCollaboratorsArray(collaborators, admins) } catch (error: any) { if (error.statusCode !== 403) throw error From f46d76675f230070d29fc2b824e28830e3959f39 Mon Sep 17 00:00:00 2001 From: Katy Bowman Date: Wed, 28 Feb 2024 12:05:14 -0500 Subject: [PATCH 06/10] refactor: more typing fixes --- packages/cli/src/commands/access/index.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/cli/src/commands/access/index.ts b/packages/cli/src/commands/access/index.ts index c9475bb534..4a99284a16 100644 --- a/packages/cli/src/commands/access/index.ts +++ b/packages/cli/src/commands/access/index.ts @@ -2,7 +2,6 @@ import color from '@heroku-cli/color' import {Command, flags} from '@heroku-cli/command' import {ux} from '@oclif/core' import * as Heroku from '@heroku-cli/schema' -import HTTP from 'http-call' import * as _ from 'lodash' import {isTeamApp, getOwner} from '../../lib/access/access-utils' @@ -12,6 +11,10 @@ type MemberData = { permissions?: Heroku.TeamAppPermission[] } +type AdminWithPermissions = Heroku.TeamMember & { + permissions?: Heroku.TeamAppPermission[], +} + function printJSON(collaborators: Heroku.TeamAppCollaborator[]) { ux.log(JSON.stringify(collaborators, null, 2)) } @@ -83,9 +86,9 @@ export default class AccessIndex extends Command { const teamName = getOwner(app.owner?.email) try { const {body: members} = await this.heroku.get(`/teams/${teamName}/members`) - let admins = members.filter((member: { role: string }) => member.role === 'admin') - const adminPermissions = await this.heroku.get('/teams/permissions') - admins = _.forEach(admins, function (admin: { user: { email: any }; email: any; permissions: HTTP }) { + let admins: AdminWithPermissions[] = members.filter(member => member.role === 'admin') + const {body: adminPermissions} = await this.heroku.get('/teams/permissions') + admins = _.forEach(admins, function (admin) { admin.user = {email: admin.email} admin.permissions = adminPermissions return admin From 4bfa3fbd2f41011b246cb53c2da58dc3df993e3f Mon Sep 17 00:00:00 2001 From: Katy Bowman Date: Wed, 28 Feb 2024 14:00:18 -0500 Subject: [PATCH 07/10] refactor: migrate access tests, stubs, and update table format --- packages/cli/src/commands/access/index.ts | 10 +- packages/cli/test/helpers/stubs/get.ts | 178 ++++++++++++++++++ .../unit/commands/access/index.unit.test.ts | 50 +++++ 3 files changed, 233 insertions(+), 5 deletions(-) create mode 100644 packages/cli/test/helpers/stubs/get.ts create mode 100644 packages/cli/test/unit/commands/access/index.unit.test.ts diff --git a/packages/cli/src/commands/access/index.ts b/packages/cli/src/commands/access/index.ts index 4a99284a16..938ad61e31 100644 --- a/packages/cli/src/commands/access/index.ts +++ b/packages/cli/src/commands/access/index.ts @@ -8,7 +8,7 @@ import {isTeamApp, getOwner} from '../../lib/access/access-utils' type MemberData = { email: string, role: string, - permissions?: Heroku.TeamAppPermission[] + permissions?: string } type AdminWithPermissions = Heroku.TeamMember & { @@ -21,10 +21,10 @@ function printJSON(collaborators: Heroku.TeamAppCollaborator[]) { function buildTableColumns(showPermissions: boolean) { const baseColumns = { - Email: { + email: { get: ({email}: any): string => color.cyan(email), }, - Role: { + role: { get: ({role}: any) => color.green(role), }, } @@ -32,7 +32,7 @@ function buildTableColumns(showPermissions: boolean) { if (showPermissions) { return { ...baseColumns, - Permissions: {}, + permissions: {}, } } @@ -49,7 +49,7 @@ function printAccess(app: Heroku.App, collaborators: any[]) { const role = collab.role const data: MemberData = {email: email, role: role || 'collaborator'} if (showPermissions) { - data.permissions = _.map(_.sortBy(collab.permissions, 'name'), 'name') + data.permissions = _.map(_.sortBy(collab.permissions, 'name'), 'name').join(', ') } return data diff --git a/packages/cli/test/helpers/stubs/get.ts b/packages/cli/test/helpers/stubs/get.ts new file mode 100644 index 0000000000..8c3d713ae9 --- /dev/null +++ b/packages/cli/test/helpers/stubs/get.ts @@ -0,0 +1,178 @@ +'use strict' +import * as nock from 'nock' + +export function apps() { + return nock('https://api.heroku.com:443') + .get('/apps') + .reply(200, [ + {name: 'my-team-app', owner: {email: 'team@herokumanager.com'}}, + {name: 'myapp', owner: {email: 'foo@foo.com'}}, + ]) +} + +export function appCollaborators(collaborators = +[{user: {email: 'raulb@heroku.com'}, role: 'owner'}, + {user: {email: 'jeff@heroku.com'}, role: 'collaborator'}]) { + return nock('https://api.heroku.com:443') + .get('/apps/myapp/collaborators') + .reply(200, collaborators) +} + +export function appPermissions() { + return nock('https://api.heroku.com:443', { + reqheaders: {Accept: 'application/vnd.heroku+json; version=3'}, + }) + .get('/teams/permissions') + .reply(200, [ + {name: 'deploy'}, + {name: 'manage'}, + {name: 'operate'}, + {name: 'view'}, + ]) +} + +export function teams(teams = [ + {name: 'enterprise a', role: 'collaborator', type: 'enterprise'}, + {name: 'team a', role: 'collaborator', type: 'team'}, + {name: 'enterprise b', role: 'admin', type: 'enterprise'}, + {name: 'team b', role: 'admin', type: 'team'}, +]) { + return nock('https://api.heroku.com:443') + .get('/teams') + .reply(200, teams) +} + +export function teamApp(locked = false) { + return nock('https://api.heroku.com:443') + .get('/apps/myapp') + .reply(200, { + name: 'myapp', + owner: {email: 'myteam@herokumanager.com'}, + locked: locked, + }) +} + +export function teamAppCollaboratorsWithPermissions() { + return nock('https://api.heroku.com:443', { + reqheaders: {Accept: 'application/vnd.heroku+json; version=3'}, + }) + .get('/apps/myapp/collaborators') + .reply(200, [ + {permissions: [], + role: 'owner', + user: {email: 'myteam@herokumanager.com'}, + }, + { + permissions: [{name: 'deploy'}, {name: 'view'}], + role: 'member', + user: {email: 'bob@heroku.com'}, + }, + ]) +} + +export function teamFeatures(features: any) { + return nock('https://api.heroku.com:443', { + reqheaders: {Accept: 'application/vnd.heroku+json; version=3'}, + }) + .get('/teams/myteam/features') + .reply(200, features) +} + +export function teamInfo(type = 'enterprise') { + return nock('https://api.heroku.com:443', { + reqheaders: {Accept: 'application/vnd.heroku+json; version=3'}, + }) + .get('/teams/myteam') + .reply(200, { + name: 'myteam', + role: 'admin', + type: type, + }) +} + +export function teamInvites(invites = [ + { + invited_by: {email: 'raulb@heroku.com'}, + role: 'admin', + user: {email: 'invited-user@mail.com'}, + }, +]) { + return nock('https://api.heroku.com:443', { + reqheaders: {Accept: 'application/vnd.heroku+json; version=3.team-invitations'}, + }) + .get('/teams/myteam/invitations') + .reply(200, invites) +} + +export function teamMembers(members = [ + { + email: 'raulb@heroku.com', + role: 'admin', + user: {email: 'raulb@heroku.com'}, + }, + { + email: 'bob@heroku.com', + role: 'viewer', + user: {email: 'bob@heroku.com'}, + }, + { + email: 'peter@heroku.com', + role: 'collaborator', + user: {email: 'peter@heroku.com'}, + }, +]) { + return nock('https://api.heroku.com:443') + .get('/teams/myteam/members') + .reply(200, members) +} + +export function personalApp() { + return nock('https://api.heroku.com:443') + .get('/apps/myapp') + .reply(200, { + name: 'myapp', + owner: {email: 'raulb@heroku.com'}, + }) +} + +export function userAccount(email = 'raulb@heroku.com') { + return nock('https://api.heroku.com:443') + .get('/account') + .reply(200, {email}) +} + +export function userFeatureFlags(features: any) { + return nock('https://api.heroku.com:443') + .get('/account/features') + .reply(200, features) +} + +export function variableSizeTeamInvites(teamSize: number) { + teamSize = (typeof (teamSize) === 'undefined') ? 1 : teamSize + const invites = [] + for (let i = 0; i < teamSize; i++) { + invites.push({ + role: 'member', user: {email: `invited-user-${i}@mail.com`}, + }) + } + + return nock('https://api.heroku.com:443', { + reqheaders: {Accept: 'application/vnd.heroku+json; version=3.team-invitations'}, + }) + .get('/teams/myteam/invitations') + .reply(200, invites) +} + +export function variableSizeTeamMembers(teamSize: number) { + teamSize = (typeof (teamSize) === 'undefined') ? 1 : teamSize + const teamMembers = [] + for (let i = 0; i < teamSize; i++) { + teamMembers.push({email: `test${i}@heroku.com`, + role: 'admin', + user: {email: `test${i}@heroku.com`}}) + } + + return nock('https://api.heroku.com:443') + .get('/teams/myteam/members') + .reply(200, teamMembers) +} diff --git a/packages/cli/test/unit/commands/access/index.unit.test.ts b/packages/cli/test/unit/commands/access/index.unit.test.ts new file mode 100644 index 0000000000..e1a11e9118 --- /dev/null +++ b/packages/cli/test/unit/commands/access/index.unit.test.ts @@ -0,0 +1,50 @@ +import {stdout, stderr} from 'stdout-stderr' +import * as nock from 'nock' +import {expect} from 'chai' +import Cmd from '../../../../src/commands/access/index' +import runCommand from '../../../helpers/runCommand' +import { + personalApp, + appCollaborators, + teamApp, + teamMembers, + appPermissions, + teamAppCollaboratorsWithPermissions, +} from '../../../helpers/stubs/get' + +describe('heroku access', () => { + context('with personal app', () => { + afterEach(() => nock.cleanAll()) + it('shows the app collaborators', () => { + const apiGetPersonalApp = personalApp() + const apiGetAppCollaborators = appCollaborators() + return runCommand(Cmd, [ + '--app', + 'myapp', + ]) + .then(() => expect(stdout.output).to.contain('jeff@heroku.com collaborator \n raulb@heroku.com owner')) + .then(() => expect('').to.eq(stderr.output)) + .then(() => apiGetPersonalApp.done()) + .then(() => apiGetAppCollaborators.done()) + }) + }) + context('with team', () => { + afterEach(() => nock.cleanAll()) + it('shows the app collaborators and hides the team collaborator record', () => { + const apiGetTeamApp = teamApp() + const apiGetOrgMembers = teamMembers() + const apiGetAppPermissions = appPermissions() + const apiGetTeamAppCollaboratorsWithPermissions = teamAppCollaboratorsWithPermissions() + return runCommand(Cmd, [ + '--app', + 'myapp', + ]) + .then(() => expect(stdout.output).to.contain('bob@heroku.com member deploy, view \n raulb@heroku.com admin deploy, manage, operate, view \n')) + .then(() => expect('').to.eq(stderr.output)) + .then(() => apiGetTeamApp.done()) + .then(() => apiGetOrgMembers.done()) + .then(() => apiGetAppPermissions.done()) + .then(() => apiGetTeamAppCollaboratorsWithPermissions.done()) + }) + }) +}) From 9fd9bc514cbae52d222a3009dd2992c880596947 Mon Sep 17 00:00:00 2001 From: Katy Bowman Date: Wed, 28 Feb 2024 14:15:47 -0500 Subject: [PATCH 08/10] refactor: rename utils file --- packages/cli/src/commands/access/index.ts | 2 +- packages/cli/src/lib/access/{access-utils.ts => utils.ts} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename packages/cli/src/lib/access/{access-utils.ts => utils.ts} (100%) diff --git a/packages/cli/src/commands/access/index.ts b/packages/cli/src/commands/access/index.ts index 938ad61e31..cb7115ee19 100644 --- a/packages/cli/src/commands/access/index.ts +++ b/packages/cli/src/commands/access/index.ts @@ -3,7 +3,7 @@ import {Command, flags} from '@heroku-cli/command' import {ux} from '@oclif/core' import * as Heroku from '@heroku-cli/schema' import * as _ from 'lodash' -import {isTeamApp, getOwner} from '../../lib/access/access-utils' +import {isTeamApp, getOwner} from '../../lib/access/utils' type MemberData = { email: string, diff --git a/packages/cli/src/lib/access/access-utils.ts b/packages/cli/src/lib/access/utils.ts similarity index 100% rename from packages/cli/src/lib/access/access-utils.ts rename to packages/cli/src/lib/access/utils.ts From 4409a25eaef4d9686acbbf67eb453ae77694fb5b Mon Sep 17 00:00:00 2001 From: Katy Bowman Date: Wed, 28 Feb 2024 16:43:46 -0500 Subject: [PATCH 09/10] refactor: remove orgs-v5 access command and tests. Add integration test --- .../integration/access.integration.test.ts | 13 +++++ packages/orgs-v5/index.js | 1 - .../commands/index.integration.test.js | 15 ------ .../unit/commands/access/index.unit.test.js | 49 ------------------- 4 files changed, 13 insertions(+), 65 deletions(-) create mode 100644 packages/cli/test/integration/access.integration.test.ts delete mode 100644 packages/orgs-v5/test/integration/commands/index.integration.test.js delete mode 100644 packages/orgs-v5/test/unit/commands/access/index.unit.test.js diff --git a/packages/cli/test/integration/access.integration.test.ts b/packages/cli/test/integration/access.integration.test.ts new file mode 100644 index 0000000000..f4a151de9a --- /dev/null +++ b/packages/cli/test/integration/access.integration.test.ts @@ -0,0 +1,13 @@ +import {expect, test} from '@oclif/test' + +describe('access', () => { + test + .stdout() + .command(['access', '--app=heroku-cli-ci-smoke-test-app']) + .it('shows a table with access status', ctx => { + // This is asserting that logs are returned by checking for the presence of the first two + // digits of the year in the timetstamp + expect(ctx.stdout.includes('admin')).to.be.true + expect(ctx.stdout.includes('deploy, manage, operate, view')).to.be.true + }) +}) diff --git a/packages/orgs-v5/index.js b/packages/orgs-v5/index.js index 3140d274e2..27617d49b3 100644 --- a/packages/orgs-v5/index.js +++ b/packages/orgs-v5/index.js @@ -15,7 +15,6 @@ exports.topics = [ ] exports.commands = flatten([ - require('./commands/access'), require('./commands/access/add'), require('./commands/access/remove'), require('./commands/access/update'), diff --git a/packages/orgs-v5/test/integration/commands/index.integration.test.js b/packages/orgs-v5/test/integration/commands/index.integration.test.js deleted file mode 100644 index a3128ce97f..0000000000 --- a/packages/orgs-v5/test/integration/commands/index.integration.test.js +++ /dev/null @@ -1,15 +0,0 @@ -'use strict' -/* globals beforeEach */ - -const cli = require('heroku-cli-util') -const cmd = require('../../../commands/access/index')[0] -const {expect} = require('chai') - -describe('access', () => { - beforeEach(() => cli.mockConsole()) - - it('runs a command', () => { - return cmd.run({app: 'heroku-cli-ci-smoke-test-app', flags: {}, auth: {password: global.apikey}}) - .then(() => expect(cli.stdout).to.contain('heroku-cli@salesforce.com')) - }) -}) diff --git a/packages/orgs-v5/test/unit/commands/access/index.unit.test.js b/packages/orgs-v5/test/unit/commands/access/index.unit.test.js deleted file mode 100644 index 7cf1165dc1..0000000000 --- a/packages/orgs-v5/test/unit/commands/access/index.unit.test.js +++ /dev/null @@ -1,49 +0,0 @@ -'use strict' -/* globals beforeEach afterEach context cli nock expect */ - -let cmd = require('../../../../commands/access')[0] -let stubGet = require('../../stub/get') - -describe('heroku access', () => { - context('with personal app', () => { - beforeEach(() => cli.mockConsole()) - afterEach(() => nock.cleanAll()) - - it('shows the app collaborators', () => { - let apiGetPersonalApp = stubGet.personalApp() - let apiGetAppCollaborators = stubGet.appCollaborators() - - return cmd.run({app: 'myapp', flags: {}}) - .then(() => expect( - `jeff@heroku.com collaborator -raulb@heroku.com owner -`).to.eq(cli.stdout)) - .then(() => expect('').to.eq(cli.stderr)) - .then(() => apiGetPersonalApp.done()) - .then(() => apiGetAppCollaborators.done()) - }) - }) - - context('with team', () => { - beforeEach(() => cli.mockConsole()) - afterEach(() => nock.cleanAll()) - - it('shows the app collaborators and hides the team collaborator record', () => { - let apiGetteamApp = stubGet.teamApp() - let apiGetOrgMembers = stubGet.teamMembers() - let apiGetAppPermissions = stubGet.appPermissions() - let apiGetteamAppCollaboratorsWithPermissions = stubGet.teamAppCollaboratorsWithPermissions() - - return cmd.run({app: 'myapp', flags: {}}) - .then(() => expect( - `bob@heroku.com member deploy,view -raulb@heroku.com admin deploy,manage,operate,view -`).to.eq(cli.stdout)) - .then(() => expect('').to.eq(cli.stderr)) - .then(() => apiGetteamApp.done()) - .then(() => apiGetOrgMembers.done()) - .then(() => apiGetAppPermissions.done()) - .then(() => apiGetteamAppCollaboratorsWithPermissions.done()) - }) - }) -}) From bc8d6c6ee4e2c42673f12a1c6043102db6bf7f8e Mon Sep 17 00:00:00 2001 From: Katy Bowman Date: Wed, 28 Feb 2024 17:06:54 -0500 Subject: [PATCH 10/10] refactor: remove integration test script from orgs-v5 --- packages/orgs-v5/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/orgs-v5/package.json b/packages/orgs-v5/package.json index 7593e10f48..6726a70f5f 100644 --- a/packages/orgs-v5/package.json +++ b/packages/orgs-v5/package.json @@ -72,7 +72,6 @@ "postpublish": "rm oclif.manifest.json", "prepack": "oclif manifest", "test": "nyc mocha ./test/**/*.unit.test.js && yarn lint", - "test:integration": "mocha './test/**/*.integration.test.js'", "version": "oclif readme && git add README.md" } }