diff --git a/src/commands/ci/debug.ts b/src/commands/ci/debug.ts index b22c522494..42be170cb0 100644 --- a/src/commands/ci/debug.ts +++ b/src/commands/ci/debug.ts @@ -9,7 +9,6 @@ import {getPipeline} from '../../lib/ci/pipelines.js' import {createSourceBlob} from '../../lib/ci/source.js' import {waitForStates} from '../../lib/ci/test-run.js' import Git from '../../lib/git/git.js' -import KolkrabbiAPI from '../../lib/pipelines/kolkrabbi-api.js' import Dyno from '../../lib/run/dyno.js' // Default command. Run setup, source profile.d scripts and open a bash session @@ -38,11 +37,8 @@ export default class Debug extends Command { const {flags} = await this.parse(Debug) const pipeline = await getPipeline(flags, this.heroku) - const kolkrabbi = new KolkrabbiAPI(this.config.userAgent, () => this.heroku.auth) - - const pipelineRepository = await kolkrabbi.getPipelineRepository(pipeline.id) - const organization = pipelineRepository.organization - && pipelineRepository.organization.name + const {body: fullPipeline} = await this.heroku.get(`/pipelines/${pipeline.id}`, {headers: {Accept: 'application/vnd.heroku+json; version=3.pipelines'}}) + const organization = fullPipeline.owner?.type === 'team' ? (fullPipeline.owner.name ?? fullPipeline.owner.id) : undefined const git = new Git() const commit = await git.readCommit('HEAD') diff --git a/src/commands/ci/rerun.ts b/src/commands/ci/rerun.ts index 0a81750633..f5a9bdf5c4 100644 --- a/src/commands/ci/rerun.ts +++ b/src/commands/ci/rerun.ts @@ -3,7 +3,6 @@ import * as Heroku from '@heroku-cli/schema' import * as color from '@heroku/heroku-cli-util/color' import {Args, ux} from '@oclif/core' -import * as Kolkrabbi from '../../lib/ci/interfaces/kolkrabbi.js' import {getPipeline} from '../../lib/ci/pipelines.js' import {createSourceBlob} from '../../lib/ci/source.js' import {displayAndExit} from '../../lib/ci/test-run.js' @@ -43,10 +42,10 @@ export default class CiReRun extends Command { const sourceBlobUrl = await createSourceBlob(sourceTestRun.commit_sha, this) ux.action.stop() - const {body: pipelineRepository} = await this.heroku.get(`https://kolkrabbi.heroku.com/pipelines/${pipeline.id}/repository`) + const {body: fullPipeline} = await this.heroku.get(`/pipelines/${pipeline.id}`, {headers: {Accept: 'application/vnd.heroku+json; version=3.pipelines'}}) ux.action.start('Starting test run') - const organization = pipelineRepository.organization && pipelineRepository.organization.name + const organization = fullPipeline.owner?.type === 'team' ? (fullPipeline.owner.name ?? fullPipeline.owner.id) : undefined const {body: testRun} = await this.heroku.post('/test-runs', { body: { diff --git a/src/commands/ci/run.ts b/src/commands/ci/run.ts index 8bbbbe415b..329e5a2c91 100644 --- a/src/commands/ci/run.ts +++ b/src/commands/ci/run.ts @@ -4,7 +4,6 @@ import * as color from '@heroku/heroku-cli-util/color' import {ux} from '@oclif/core/ux' import {gitService} from '../../lib/ci/git.js' -import * as Kolkrabbi from '../../lib/ci/interfaces/kolkrabbi.js' import {getPipeline} from '../../lib/ci/pipelines.js' import {createSourceBlob} from '../../lib/ci/source.js' import {displayAndExit} from '../../lib/ci/test-run.js' @@ -31,8 +30,8 @@ export default class CiRun extends Command { ux.action.stop() ux.action.start('Starting test run') - const {body: pipelineRepository} = await this.heroku.get(`https://kolkrabbi.heroku.com/pipelines/${pipeline.id}/repository`) - const organization = pipelineRepository.organization && pipelineRepository.organization.name + const {body: fullPipeline} = await this.heroku.get(`/pipelines/${pipeline.id}`, {headers: {Accept: 'application/vnd.heroku+json; version=3.pipelines'}}) + const organization = fullPipeline.owner?.type === 'team' ? (fullPipeline.owner.name ?? fullPipeline.owner.id) : undefined const {body: testRun} = await this.heroku.post('/test-runs', { body: { commit_branch: commit.branch, diff --git a/src/commands/pipelines/connect.ts b/src/commands/pipelines/connect.ts index db0f29ecd5..64e4974f44 100644 --- a/src/commands/pipelines/connect.ts +++ b/src/commands/pipelines/connect.ts @@ -3,9 +3,6 @@ import * as color from '@heroku/heroku-cli-util/color' import {Args, ux} from '@oclif/core' import {getPipeline} from '../../lib/api.js' -import GitHubAPI from '../../lib/pipelines/github-api.js' -import KolkrabbiAPI from '../../lib/pipelines/kolkrabbi-api.js' -import getGitHubToken from '../../lib/pipelines/setup/get-github-token.js' import getNameAndRepo from '../../lib/pipelines/setup/get-name-and-repo.js' import getRepo from '../../lib/pipelines/setup/get-repo.js' import {nameAndRepo} from '../../lib/pipelines/setup/validate.js' @@ -45,20 +42,20 @@ export default class Connect extends Command { return } - const kolkrabbi = new KolkrabbiAPI(this.config.userAgent, () => this.heroku.auth) - const github = new GitHubAPI(this.config.userAgent, await getGitHubToken(kolkrabbi)) - const { name: pipelineName, repo: repoName, } = await getNameAndRepo(combinedInputs) - const repo = await getRepo(github, repoName) + const repo = await getRepo(this.heroku, repoName) const pipeline = await getPipeline(this.heroku, pipelineName) ux.action.start('Linking to repo') - await kolkrabbi.createPipelineRepository(pipeline.body.id, repo.id) + await this.heroku.post(`/pipelines/${pipeline.body.id}/repo`, { + body: {repo_url: `https://github.com/${repo.full_name}`}, + headers: {Accept: 'application/vnd.heroku+json; version=3.repositories-api'}, + }) ux.action.stop() } } diff --git a/src/commands/pipelines/diff.ts b/src/commands/pipelines/diff.ts index f20580dd73..4b929c80d5 100644 --- a/src/commands/pipelines/diff.ts +++ b/src/commands/pipelines/diff.ts @@ -1,6 +1,5 @@ import {Command, flags} from '@heroku-cli/command' import {color, hux} from '@heroku/heroku-cli-util' -import {HTTP} from '@heroku/http-call' import {ux} from '@oclif/core/ux' import type {OciImage, PipelineCoupling, Slug} from '../../lib/types/fir.js' @@ -14,7 +13,8 @@ import { SDK_HEADER, } from '../../lib/api.js' import {GenerationKind, getGeneration} from '../../lib/apps/generation.js' -import KolkrabbiAPI from '../../lib/pipelines/kolkrabbi-api.js' + +const REPOSITORIES_API_HEADER = 'application/vnd.heroku+json; version=3.repositories-api' interface AppInfo { hash?: string; @@ -35,8 +35,10 @@ export default class PipelinesDiff extends Command { } getAppInfo = async (appName: string, appId: string, generation: GenerationKind): Promise => { // Find GitHub connection for the app - const githubApp = await this.kolkrabbi.getAppLink(appId) - .catch(() => ({hash: null, name: appName, repo: null})) + const githubApp = await this.heroku.get<{full_name: string}>(`/apps/${appId}/repo`, { + headers: {Accept: REPOSITORIES_API_HEADER}, + }).then(res => res.body) + .catch(() => ({full_name: undefined})) // Find the commit hash of the latest release for this app let slug: Slug @@ -62,12 +64,58 @@ export default class PipelinesDiff extends Command { commit = ociImages[0]?.commit } } catch { - return {hash: undefined, name: appName, repo: githubApp.repo} + return {hash: undefined, name: appName, repo: githubApp.full_name} + } + + return {hash: commit, name: appName, repo: githubApp.full_name} + } + private diff = async (targetApp: AppInfo, downstreamApp: AppInfo, pipelineId: string) => { + if (!downstreamApp.repo) { + return ux.stdout(`\n${color.app(targetApp.name)} was not compared to ${color.app(downstreamApp.name)} as ${color.app(downstreamApp.name)} is not connected to GitHub`) + } + + if (downstreamApp.repo !== targetApp.repo) { + return ux.stdout(`\n${color.app(targetApp.name)} was not compared to ${color.app(downstreamApp.name)} as ${color.app(downstreamApp.name)} is not connected to the same GitHub repo as ${color.app(targetApp.name)}`) + } + + if (!downstreamApp.hash) { + return ux.stdout(`\n${color.app(targetApp.name)} was not compared to ${color.app(downstreamApp.name)} as ${color.app(downstreamApp.name)} does not have any releases`) + } + + if (downstreamApp.hash === targetApp.hash) { + return ux.stdout(`\n${color.app(targetApp.name)} is up to date with ${color.app(downstreamApp.name)}`) } - return {hash: commit, name: appName, repo: githubApp.repo} + // Do the actual GitHub diff via the repositories-api server-side proxy + try { + const {body: githubDiff} = await this.heroku.get(`/pipelines/${pipelineId}/repo/compare?base=${downstreamApp.hash}&head=${targetApp.hash}`, { + headers: {Accept: REPOSITORIES_API_HEADER}, + }) + + ux.stdout('') + hux.styledHeader(`${color.app(targetApp.name)} is ahead of ${color.app(downstreamApp.name)} by ${githubDiff.ahead_by} commit${githubDiff.ahead_by === 1 ? '' : 's'}`) + /* eslint-disable perfectionist/sort-objects */ + const mapped = githubDiff.commits.map((commit: Commit) => ({ + sha: commit.sha.slice(0, 7), + date: commit.commit.author.date, + author: commit.commit.author.name, + message: commit.commit.message.split('\n')[0], + })).reverse() + hux.table(mapped, { + sha: { + header: 'SHA', + }, + date: {}, + author: {}, + message: {}, + }) + /* eslint-enable perfectionist/sort-objects */ + ux.stdout(`\n${color.info(`https://github.com/${targetApp.repo}/compare/${downstreamApp.hash}...${targetApp.hash}`)}`) + } catch { + ux.stdout(`\n${color.app(targetApp.name)} was not compared to ${color.app(downstreamApp.name)} because we were unable to perform a diff`) + ux.stdout('are you sure you have pushed your latest commits to GitHub?') + } } - kolkrabbi: KolkrabbiAPI = new KolkrabbiAPI(this.config.userAgent, () => this.heroku.auth) async run() { const {flags} = await this.parse(PipelinesDiff) @@ -85,6 +133,7 @@ export default class PipelinesDiff extends Command { const targetAppId = coupling!.app!.id! const generation = getGeneration(pipeline)! + const pipelineId = coupling!.pipeline!.id! ux.action.start('Fetching apps from pipeline') const allApps = await listPipelineApps(this.heroku, coupling!.pipeline!.id!) @@ -130,69 +179,10 @@ export default class PipelinesDiff extends Command { return ux.error(`No release was found for ${targetAppName}, unable to diff`) } - // Fetch GitHub token for the user - const githubAccount = await this.kolkrabbi.getAccount() // Diff [{target, downstream[0]}, {target, downstream[1]}, .., {target, downstream[n]}] const downstreamAppsInfo = appInfo.slice(1) for (const downstreamAppInfo of downstreamAppsInfo) { - await diff(targetAppInfo, downstreamAppInfo, githubAccount.github.token, this.config.userAgent) - } - } -} - -async function diff(targetApp: AppInfo, downstreamApp: AppInfo, githubToken: string, herokuUserAgent: string) { - if (!downstreamApp.repo) { - return ux.stdout(`\n${color.app(targetApp.name)} was not compared to ${color.app(downstreamApp.name)} as ${color.app(downstreamApp.name)} is not connected to GitHub`) - } - - if (downstreamApp.repo !== targetApp.repo) { - return ux.stdout(`\n${color.app(targetApp.name)} was not compared to ${color.app(downstreamApp.name)} as ${color.app(downstreamApp.name)} is not connected to the same GitHub repo as ${color.app(targetApp.name)}`) - } - - if (!downstreamApp.hash) { - return ux.stdout(`\n${color.app(targetApp.name)} was not compared to ${color.app(downstreamApp.name)} as ${color.app(downstreamApp.name)} does not have any releases`) - } - - if (downstreamApp.hash === targetApp.hash) { - return ux.stdout(`\n${color.app(targetApp.name)} is up to date with ${color.app(downstreamApp.name)}`) - } - - // Do the actual GitHub diff - try { - const path = `${targetApp.repo}/compare/${downstreamApp.hash}...${targetApp.hash}` - const headers = { - authorization: 'token ' + githubToken, - 'Content-Type': 'application/vnd.github+json', - 'X-GitHub-Api-Version': '2022-11-28', + await this.diff(targetAppInfo, downstreamAppInfo, pipelineId) } - - if (herokuUserAgent) { - Reflect.set(headers, 'user-agent', herokuUserAgent) - } - - const {body: githubDiff} = await HTTP.get(`https://api.github.com/repos/${path}`, {headers}) - - ux.stdout('') - hux.styledHeader(`${color.app(targetApp.name)} is ahead of ${color.app(downstreamApp.name)} by ${githubDiff.ahead_by} commit${githubDiff.ahead_by === 1 ? '' : 's'}`) - /* eslint-disable perfectionist/sort-objects */ - const mapped = githubDiff.commits.map((commit: Commit) => ({ - sha: commit.sha.slice(0, 7), - date: commit.commit.author.date, - author: commit.commit.author.name, - message: commit.commit.message.split('\n')[0], - })).reverse() - hux.table(mapped, { - sha: { - header: 'SHA', - }, - date: {}, - author: {}, - message: {}, - }) - /* eslint-enable perfectionist/sort-objects */ - ux.stdout(`\n${color.info(`https://github.com/${path}`)}`) - } catch { - ux.stdout(`\n${color.app(targetApp.name)} was not compared to ${color.app(downstreamApp.name)} because we were unable to perform a diff`) - ux.stdout('are you sure you have pushed your latest commits to GitHub?') } } diff --git a/src/commands/pipelines/setup.ts b/src/commands/pipelines/setup.ts index 3229898e28..bf614291ef 100644 --- a/src/commands/pipelines/setup.ts +++ b/src/commands/pipelines/setup.ts @@ -5,11 +5,7 @@ import debug from 'debug' import openBrowser from 'open' import {createPipeline, getAccountInfo, getTeam} from '../../lib/api.js' -import GitHubAPI from '../../lib/pipelines/github-api.js' -import KolkrabbiAPI from '../../lib/pipelines/kolkrabbi-api.js' import createApps from '../../lib/pipelines/setup/create-apps.js' -import getCISettings from '../../lib/pipelines/setup/get-ci-settings.js' -import getGitHubToken from '../../lib/pipelines/setup/get-github-token.js' import getNameAndRepo from '../../lib/pipelines/setup/get-name-and-repo.js' import getRepo from '../../lib/pipelines/setup/get-repo.js' import getSettings from '../../lib/pipelines/setup/get-settings.js' @@ -55,17 +51,13 @@ export default class Setup extends Command { return } - const kolkrabbi = new KolkrabbiAPI(this.config.userAgent, () => this.heroku.auth) - const github = new GitHubAPI(this.config.userAgent, await getGitHubToken(kolkrabbi)) - const {team, yes} = flags const {name: pipelineName, repo: repoName} = await getNameAndRepo(args) const stagingAppName = pipelineName + STAGING_APP_INDICATOR - const repo = await getRepo(github, repoName) - const settings = await getSettings(yes, repo.default_branch) + const repo = await getRepo(this.heroku, repoName) + const settings = await getSettings(yes) - const ciSettings = await getCISettings(yes, team) const ownerType = team ? 'team' : 'user' // If team or org is not specified, we assign ownership to the user creating @@ -79,10 +71,16 @@ export default class Setup extends Command { ux.action.stop() ux.action.start('Linking to repo') - await kolkrabbi.createPipelineRepository(pipeline.id, repo.id) + await this.heroku.post(`/pipelines/${pipeline.id}/repo`, { + body: {repo_url: `https://github.com/${repo.full_name}`}, + headers: {Accept: 'application/vnd.heroku+json; version=3.repositories-api'}, + }) ux.action.stop() - const archiveURL = await kolkrabbi.getArchiveURL(repoName, repo.default_branch) + const {body: archive} = await this.heroku.get<{archive_link: string}>(`/repos/${repo.full_name}/archives/${repo.default_branch}`, { + headers: {Accept: 'application/vnd.heroku+json; version=3.repositories-api'}, + }) + const archiveURL = archive.archive_link const appSetupsResult: any = await createApps(this.heroku, archiveURL, pipeline, pipelineName, stagingAppName, team) const appSetups = appSetupsResult.map((result: any) => result.body) @@ -91,9 +89,7 @@ export default class Setup extends Command { await pollAppSetups(this.heroku, appSetups) ux.action.stop() - const stagingApp = appSetups.find((appSetup: any) => appSetup.app.name === stagingAppName).app - - const setup = setupPipeline(kolkrabbi, stagingApp.id, settings, pipeline.id, ciSettings) + const setup = setupPipeline(this.heroku, settings, pipeline.id, repo.full_name) ux.action.start('Configuring pipeline') try { diff --git a/src/commands/reviewapps/disable.ts b/src/commands/reviewapps/disable.ts index a1c4f3de56..e25dd642f8 100644 --- a/src/commands/reviewapps/disable.ts +++ b/src/commands/reviewapps/disable.ts @@ -3,8 +3,6 @@ import * as Heroku from '@heroku-cli/schema' import * as color from '@heroku/heroku-cli-util/color' import {ux} from '@oclif/core/ux' -import KolkrabbiAPI from '../../lib/pipelines/kolkrabbi-api.js' - export default class ReviewappsDisable extends Command { static description = 'disable review apps and/or settings on an existing pipeline' static examples = [ @@ -81,27 +79,16 @@ export default class ReviewappsDisable extends Command { settings.wait_for_ci = false } - const kolkrabbi = new KolkrabbiAPI(this.config.userAgent, () => this.heroku.auth) - ux.action.start('Configuring pipeline') const {body: pipeline} = await this.heroku.get(`/pipelines/${flags.pipeline}`) settings.pipeline = pipeline.id - try { - const {body: feature} = await this.heroku.get('/account/features/dashboard-repositories-api') - - if (feature.enabled) { - const {body: repo} = await this.heroku.get<{full_name: string}>(`/pipelines/${pipeline.id}/repo`, { - headers: {Accept: 'application/vnd.heroku+json; version=3.repositories-api'}, - }) - settings.repo = repo.full_name - } - } catch { - const {repository} = await kolkrabbi.getPipelineRepository(pipeline.id) - settings.repo = repository.name - } + const {body: repo} = await this.heroku.get<{full_name: string}>(`/pipelines/${pipeline.id}/repo`, { + headers: {Accept: 'application/vnd.heroku+json; version=3.repositories-api'}, + }) + settings.repo = repo.full_name // eslint-disable-next-line unicorn/prefer-ternary if (flags.autodeploy || flags['no-autodeploy'] || flags.autodestroy || flags['no-autodestroy'] || flags['wait-for-ci'] || flags['no-wait-for-ci']) { diff --git a/src/commands/reviewapps/enable.ts b/src/commands/reviewapps/enable.ts index 85822e344e..d7be1da418 100644 --- a/src/commands/reviewapps/enable.ts +++ b/src/commands/reviewapps/enable.ts @@ -3,8 +3,6 @@ import * as Heroku from '@heroku-cli/schema' import * as color from '@heroku/heroku-cli-util/color' import {ux} from '@oclif/core/ux' -import KolkrabbiAPI from '../../lib/pipelines/kolkrabbi-api.js' - export default class ReviewappsEnable extends Command { static description = 'enable review apps and/or settings on an existing pipeline' static examples = [ @@ -68,27 +66,16 @@ export default class ReviewappsEnable extends Command { settings.wait_for_ci = true } - const kolkrabbi = new KolkrabbiAPI(this.config.userAgent, () => this.heroku.auth) - ux.action.start('Configuring pipeline') const {body: pipeline} = await this.heroku.get(`/pipelines/${flags.pipeline}`) settings.pipeline = pipeline.id - try { - const {body: feature} = await this.heroku.get('/account/features/dashboard-repositories-api') - - if (feature.enabled) { - const {body: repo} = await this.heroku.get<{full_name: string}>(`/pipelines/${pipeline.id}/repo`, { - headers: {Accept: 'application/vnd.heroku+json; version=3.repositories-api'}, - }) - settings.repo = repo.full_name - } - } catch { - const {repository} = await kolkrabbi.getPipelineRepository(pipeline.id) - settings.repo = repository.name - } + const {body: repo} = await this.heroku.get<{full_name: string}>(`/pipelines/${pipeline.id}/repo`, { + headers: {Accept: 'application/vnd.heroku+json; version=3.repositories-api'}, + }) + settings.repo = repo.full_name // eslint-disable-next-line unicorn/prefer-ternary if (flags.autodeploy || flags.autodestroy || flags['wait-for-ci']) { diff --git a/src/lib/ci/interfaces/kolkrabbi.ts b/src/lib/ci/interfaces/kolkrabbi.ts deleted file mode 100644 index 69ebe7c3dd..0000000000 --- a/src/lib/ci/interfaces/kolkrabbi.ts +++ /dev/null @@ -1,312 +0,0 @@ -/** - * This file was automatically generated by json-schema-to-typescript. - * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, - * and run json-schema-to-typescript to regenerate this file. - */ - -/** - * Kolkrabbi API. - */ -export interface KolkrabbiApi { - [k: string]: any; - 'account-link'?: KolkrabbiApiAccountLink; - 'app-json'?: KolkrabbiApiAppJson; - 'app-json-schema'?: AppJsonSchema; - apps?: KolkrabbiApiApps; - organization?: HerokuPlatformApiOrganization; - pipeline?: HerokuPlatformApiPipeline; - 'pipeline-repository'?: KolkrabbiApiPipelineRepositories; - 'pull-requests'?: KolkrabbiApiPullRequests; - repository?: KolkrabbiApiRepositories; -} -/** - * An account link represents the relationship between a Heroku user and a GitHub user. - */ -export interface KolkrabbiApiAccountLink { - [k: string]: any; - /** - * GitHub details for the account link - */ - github?: { - [k: string]: any; - /** - * unique identifier of the GitHub user - */ - user_id?: number; - }; - /** - * Heroku details for the account link - */ - heroku?: { - [k: string]: any; - /** - * unique identifier of the Heroku user - */ - user_id?: string; - }; - /** - * unique identifier of an account link - */ - id?: string; -} -export interface AppJsonSchema { - /** - * An array of strings specifying Heroku addons to provision on the app before deploying. Each addon should be in the format `addon:plan` or `addon`. If plan is omitted, that addon's default plan will be provisioned. - */ - addons?: any[]; - /** - * An ordered array of objects specifying the buildpacks to be applied to this app - */ - buildpacks?: any[]; - /** - * A brief summary of the app: what it does, who it's for, why it exists, etc. - */ - description?: string; - /** - * A key-value object for environment variables, or [config vars](https://devcenter.heroku.com/articles/config-vars) in Heroku parlance. Keys are the names of the environment variables. Values can be strings or objects. If the value is a string, it will be used. If the value is an object, it defines specific requirements for that variable: - * - * - `description`: a human-friendly blurb about what the value is for and how to determine what it should be - * - `value`: a default value to use. This should always be a string. - * - `required`: A boolean indicating whether the given value is required for the app to function (default: `true`). - * - `generator`: a string representing a function to call to generate the value. Currently the only supported generator is `secret`, which generates a pseudo-random string of characters. - */ - env?: { - [k: string]: any; - }; - /** - * Dynos to scale on the app before deploying. - */ - formation?: { - [k: string]: any; - }; - /** - * An array of strings describing the app. - */ - keywords?: any[]; - /** - * The URL of the application's logo image. Dimensions should be square. Format can be SVG, PNG, or JPG. - */ - logo?: string; - /** - * A clean and simple name to identify the template (30 characters max). - */ - name?: string; - /** - * The location of the application's source code, such as a Git URL, GitHub URL, Subversion URL, or Mercurial URL. - */ - repository?: string; - /** - * A key-value object specifying scripts or shell commands to execute at different stages in the build/release process. Currently, `postdeploy` is the only supported script. - */ - scripts?: { - [k: string]: any; - }; - /** - * A URL specifying where to redirect the user once their new app is deployed. If value is a fully-qualified URL, the user should be redirected to that URL. If value begins with a slash `/`, the user should be redirected to that path in their newly deployed app. - */ - success_url?: string; - /** - * The project's website. - */ - website?: string; -} -export interface KolkrabbiApiAppJson { - [k: string]: any; - app_json?: AppJsonSchema; - /** - * name of branch to commit to - */ - branch?: null | string; -} -/** - * FIXME - */ -export interface KolkrabbiApiApps { - [k: string]: any; - /** - * unique identifier of app - */ - app_id?: string; - /** - * when app was created - */ - created_at?: string; - /** - * unique identifier of app - */ - id?: string; - /** - * when app was updated - */ - updated_at?: null | string; -} -/** - * Deprecated: Organizations allow you to manage access to a shared group of applications across your development team. - */ -export interface HerokuPlatformApiOrganization { - [k: string]: any; - /** - * when the organization was created - */ - created_at?: string; - /** - * whether charges incurred by the org are paid by credit card. - */ - credit_card_collections?: boolean; - /** - * whether to use this organization when none is specified - */ - default?: boolean; - /** - * unique identifier of organization - */ - id?: string; - /** - * upper limit of members allowed in an organization. - */ - membership_limit?: null | number; - /** - * unique name of organization - */ - name?: string; - /** - * whether the org is provisioned licenses by salesforce. - */ - provisioned_licenses?: boolean; - /** - * role in the organization - */ - role?: 'admin' | 'collaborator' | 'member' | 'owner' | null; - /** - * type of organization. - */ - type?: 'enterprise' | 'team'; - /** - * when the organization was updated - */ - updated_at?: string; -} -/** - * Pipeline repositories link a pipeline to a GitHub repository. - */ -export interface KolkrabbiApiPipelineRepositories { - [k: string]: any; - /** - * whether automatic review apps is enabled - */ - automatic_review_apps?: boolean; - /** - * whether CI is enabled - */ - ci?: boolean; - /** - * when the pipeline repository was created - */ - created_at?: string; - creator?: KolkrabbiApiAccountLink; - /** - * unique identifier of a pipeline repository - */ - id?: string; - /** - * organization tied to this pipeline repository - */ - organization?: null | { - [k: string]: any; - }; - owner?: KolkrabbiApiAccountLink; - pipeline?: HerokuPlatformApiPipeline; - repository?: KolkrabbiApiRepositories; - /** - * whether review apps is enabled - */ - review_apps?: boolean; - /** - * a collection of statuses - */ - statuses?: any[]; - /** - * when pipeline repository was updated - */ - updated_at?: null | string; -} -/** - * A pipeline allows grouping of apps into different stages. - */ -export interface HerokuPlatformApiPipeline { - [k: string]: any; - /** - * unique identifier of pipeline - */ - id?: string; -} -/** - * A Repository is a reference to a remote DVCS codebase - */ -export interface KolkrabbiApiRepositories { - [k: string]: any; - /** - * when the repository reference was created - */ - created_at?: string; - /** - * The id that the remote DVCS uses for the repository - */ - id?: number; - /** - * The name for the remote DVCS uses for the repository - */ - name?: string; - /** - * The type of DVCS - */ - type?: string; - /** - * when repository was updated - */ - updated_at?: null | string; -} -/** - * FIXME - */ -export interface KolkrabbiApiPullRequests { - [k: string]: any; - app_setup?: { - [k: string]: any; - /** - * unique identifier of app setup - */ - id?: string; - /** - * the overall status of app setup - */ - status?: 'failed' | 'pending' | 'succeeded'; - }; - /** - * when app was created - */ - created_at?: string; - pull_request?: { - [k: string]: any; - /** - * unique identifier of pull request - */ - id?: number; - /** - * pull request number - */ - number?: number; - /** - * unique name of app - */ - ref?: string; - /** - * pull request title - */ - title?: string; - }; - /** - * when app was updated - */ - updated_at?: null | string; -} diff --git a/src/lib/ci/source.ts b/src/lib/ci/source.ts index 0b13ee34a2..893b576851 100644 --- a/src/lib/ci/source.ts +++ b/src/lib/ci/source.ts @@ -47,7 +47,7 @@ export async function createSourceBlob(ref: any, command: Command) { const githubRepository = await gitService.githubRepository() const {repo, user} = githubRepository - const {body: archiveLink} = await command.heroku.get(`https://kolkrabbi.heroku.com/github/repos/${user}/${repo}/tarball/${ref}`) + const {body: archiveLink} = await command.heroku.get<{archive_link: string}>(`/repos/${user}/${repo}/archives/${ref}`, {headers: {Accept: 'application/vnd.heroku+json; version=3.repositories-api'}}) if (await command.heroku.request(archiveLink.archive_link, {method: 'HEAD'})) { return archiveLink.archive_link } diff --git a/src/lib/pipelines/github-api.ts b/src/lib/pipelines/github-api.ts deleted file mode 100644 index 9127b80401..0000000000 --- a/src/lib/pipelines/github-api.ts +++ /dev/null @@ -1,26 +0,0 @@ -import {HTTP} from '@heroku/http-call' -const GITHUB_API = 'https://api.github.com' - -export default class GitHubAPI { - token: any - version: any - - constructor(version: any, token: any) { - this.version = version - this.token = token - } - - getRepo(name: any) { - return this.request(`/repos/${name}`).then((res: any) => res.body) - } - - request(url: any, options: any = {}) { - options.headers = { - Authorization: `Token ${this.token}`, - 'User-Agent': this.version, - ...options.headers, - } - - return HTTP.get(`${GITHUB_API}${url}`, options) - } -} diff --git a/src/lib/pipelines/kolkrabbi-api.ts b/src/lib/pipelines/kolkrabbi-api.ts deleted file mode 100644 index 49e7897ebc..0000000000 --- a/src/lib/pipelines/kolkrabbi-api.ts +++ /dev/null @@ -1,75 +0,0 @@ -import {HTTP} from '@heroku/http-call' - -const KOLKRABBI_BASE_URL = 'https://kolkrabbi.heroku.com' - -export default class KolkrabbiApi { - getToken: () => any - version: any - - constructor(version: any, getToken: () => any) { - this.version = version - this.getToken = getToken - } - - createPipelineRepository(pipeline: any, repository: any) { - return this.request(`/pipelines/${pipeline}/repository`, { - body: {repository}, - method: 'POST', - }) - } - - getAccount() { - return this.request('/account/github/token') - } - - getAppLink(app: any) { - return this.request(`/apps/${app}/github`, { - method: 'GET', - }) - } - - getArchiveURL(repo: any, ref: any) { - return this.request(`/github/repos/${repo}/tarball/${ref}`, { - followRedirect: false, - }).then(res => res.archive_link) - } - - getPipelineGithub(pipeline: any) { - return this.request(`/pipelines/${pipeline}/github`, { - method: 'GET', - }) - } - - getPipelineRepository(pipeline: any) { - return this.request(`/pipelines/${pipeline}/repository`, { - method: 'GET', - }) - } - - request(url: string, options: any = {}) { - options.headers = { - Authorization: `Bearer ${this.getToken()}`, - 'User-Agent': this.version, - } - - if (['DELETE', 'PATCH', 'POST'].includes(options.method)) { - options.headers['Content-type'] = 'application/json' - } - - return HTTP.request(KOLKRABBI_BASE_URL + url, options).then((res: any) => res.body) - } - - updateAppLink(app: any, body: any) { - return this.request(`/apps/${app}/github`, { - body, - method: 'PATCH', - }) - } - - updatePipelineRepository(pipeline: any, body: any) { - return this.request(`/pipelines/${pipeline}/repository`, { - body, - method: 'PATCH', - }) - } -} diff --git a/src/lib/pipelines/setup/get-ci-settings.ts b/src/lib/pipelines/setup/get-ci-settings.ts deleted file mode 100644 index dc39e48a6d..0000000000 --- a/src/lib/pipelines/setup/get-ci-settings.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {hux} from '@heroku/heroku-cli-util' - -export default async function getCISettings(yes: any, organization: any) { - const settings = { - ci: true, - organization: undefined, - } - - if (yes) { - delete settings.organization - return settings - } - - settings.ci = await hux.confirm('Enable automatic Heroku CI test runs?') - - if (settings.ci && organization) { - settings.organization = organization - } - - return settings -} diff --git a/src/lib/pipelines/setup/get-github-token.ts b/src/lib/pipelines/setup/get-github-token.ts deleted file mode 100644 index fdf6991b55..0000000000 --- a/src/lib/pipelines/setup/get-github-token.ts +++ /dev/null @@ -1,5 +0,0 @@ -export default function getGitHubToken(kolkrabbi: any) { - return kolkrabbi.getAccount().then((account: any) => account.github.token, () => { - throw new Error('Account not connected to GitHub.') - }) -} diff --git a/src/lib/pipelines/setup/get-repo.ts b/src/lib/pipelines/setup/get-repo.ts index 6067a13fe5..5ef714e69f 100644 --- a/src/lib/pipelines/setup/get-repo.ts +++ b/src/lib/pipelines/setup/get-repo.ts @@ -1,5 +1,10 @@ -export default function getRepo(github: any, name: any) { - return github.getRepo(name).catch((error: any) => { +import {APIClient} from '@heroku-cli/command' + +export default function getRepo(heroku: APIClient, name: string) { + return heroku.get(`/repos/${name}`, { + headers: {Accept: 'application/vnd.heroku+json; version=3.repositories-api'}, + retryAuth: false, + }).then((res: any) => res.body).catch((error: any) => { const err: any = new Error('Couldn\'t access that repo') err.statusCode = error.statusCode || error.http?.statusCode throw err diff --git a/src/lib/pipelines/setup/get-settings.ts b/src/lib/pipelines/setup/get-settings.ts index 9dbf4777d0..060adf60ea 100644 --- a/src/lib/pipelines/setup/get-settings.ts +++ b/src/lib/pipelines/setup/get-settings.ts @@ -1,7 +1,6 @@ import {hux} from '@heroku/heroku-cli-util' const DEFAULT_SETTINGS = { - auto_deploy: true, pull_requests: { auto_deploy: true, auto_destroy: true, @@ -10,13 +9,12 @@ const DEFAULT_SETTINGS = { wait_for_ci: true, } -export default async function getSettings(yes: any, branch: any) { +export default async function getSettings(yes: any) { if (yes) { return DEFAULT_SETTINGS } const settings = { - auto_deploy: true, pull_requests: { auto_deploy: true, auto_destroy: true, @@ -25,20 +23,12 @@ export default async function getSettings(yes: any, branch: any) { wait_for_ci: true, } - settings.auto_deploy = await hux.confirm(`Automatically deploy the ${branch} branch to staging?`) - - if (settings.auto_deploy) { - settings.wait_for_ci = await hux.confirm(`Wait for CI to pass before deploying the ${branch} branch to staging?`) - } - settings.pull_requests.enabled = await hux.confirm('Enable review apps?') if (settings.pull_requests.enabled) { settings.pull_requests.auto_deploy = await hux.confirm('Automatically create review apps for every PR?') - } - - if (settings.pull_requests.enabled) { settings.pull_requests.auto_destroy = await hux.confirm('Automatically destroy idle review apps after 5 days?') + settings.wait_for_ci = await hux.confirm('Wait for CI to pass before deploying review apps?') } return settings diff --git a/src/lib/pipelines/setup/setup-pipeline.ts b/src/lib/pipelines/setup/setup-pipeline.ts index f69556d229..4357df5a95 100644 --- a/src/lib/pipelines/setup/setup-pipeline.ts +++ b/src/lib/pipelines/setup/setup-pipeline.ts @@ -1,13 +1,23 @@ +import {APIClient} from '@heroku-cli/command' import {ux} from '@oclif/core/ux' -export default function setupPipeline(kolkrabbi: any, app: any, settings: any, pipelineID: any, ciSettings: any = {}) { - const promises = [kolkrabbi.updateAppLink(app, settings)] - - if (ciSettings.ci) { - promises.push(kolkrabbi.updatePipelineRepository(pipelineID, ciSettings)) +export default async function setupPipeline(heroku: APIClient, settings: any, pipelineID: string, repoFullName: string) { + if (!settings.pull_requests?.enabled) { + return } - return Promise.all(promises).then(([appLink]) => appLink, error => { - ux.error(error.body.message || error.message) - }) + try { + await heroku.post(`/pipelines/${pipelineID}/review-app-config`, { + body: { + automatic_review_apps: settings.pull_requests.auto_deploy, + destroy_stale_apps: settings.pull_requests.auto_destroy, + pipeline: pipelineID, + repo: repoFullName, + wait_for_ci: settings.wait_for_ci, + }, + headers: {Accept: 'application/vnd.heroku+json; version=3.review-apps'}, + }) + } catch (error: any) { + ux.error(error.body?.message || error.message) + } } diff --git a/test/unit/commands/ci/rerun.unit.test.ts b/test/unit/commands/ci/rerun.unit.test.ts index 4f2ee551c6..c872739884 100644 --- a/test/unit/commands/ci/rerun.unit.test.ts +++ b/test/unit/commands/ci/rerun.unit.test.ts @@ -94,6 +94,8 @@ describe('ci:rerun', function () { ]) .get(`/pipelines/${pipeline.id}/test-runs`) .reply(200, [oldTestRun]) + .get(`/pipelines/${pipeline.id}`) + .reply(200, {id: pipeline.id, owner: {id: '463147bf-d572-41cf-bbf4-11ebc1c0bc3b', type: 'user'}}) .post('/test-runs') .reply(200, newTestRun) .get(`/pipelines/${pipeline.id}/test-runs/${newTestRun.number}`) @@ -125,23 +127,6 @@ describe('ci:rerun', function () { .get(`/${newTestRun.id.slice(0, 3)}/test-runs/${newTestRun.id}`) .reply(200, 'New Test output') - nock('https://kolkrabbi.heroku.com') - .get(`/pipelines/${pipeline.id}/repository`) - .reply(200, { - ci: true, - organization: {id: 'e037ed63-5781-48ee-b2b7-8c55c571b63e'}, - owner: { - github: {user_id: 306_015}, - heroku: {user_id: '463147bf-d572-41cf-bbf4-11ebc1c0bc3b'}, - id: '463147bf-d572-41cf-bbf4-11ebc1c0bc3b', - }, - repository: { - id: 138_865_824, - name: 'raulb/atleti', - type: 'github', - }, - }) - const {stdout} = await runCommand(Cmd, [`--pipeline=${pipeline.name}`]) expect(stdout).to.equal('Rerunning test run #10...\nNew Test setup outputNew Test output\n✓ #11 my-test-branch:668a5ce succeeded\n') @@ -157,6 +142,8 @@ describe('ci:rerun', function () { ]) .get(`/pipelines/${pipeline.id}/test-runs/${oldTestRun.number}`) .reply(200, oldTestRun) + .get(`/pipelines/${pipeline.id}`) + .reply(200, {id: pipeline.id, owner: {id: '463147bf-d572-41cf-bbf4-11ebc1c0bc3b', type: 'user'}}) .post('/test-runs') .reply(200, newTestRun) .get(`/pipelines/${pipeline.id}/test-runs/${newTestRun.number}`) @@ -188,23 +175,6 @@ describe('ci:rerun', function () { .get(`/${newTestRun.id.slice(0, 3)}/test-runs/${newTestRun.id}`) .reply(200, 'New Test output') - nock('https://kolkrabbi.heroku.com') - .get(`/pipelines/${pipeline.id}/repository`) - .reply(200, { - ci: true, - organization: {id: 'e037ed63-5781-48ee-b2b7-8c55c571b63e'}, - owner: { - github: {user_id: 306_015}, - heroku: {user_id: '463147bf-d572-41cf-bbf4-11ebc1c0bc3b'}, - id: '463147bf-d572-41cf-bbf4-11ebc1c0bc3b', - }, - repository: { - id: 138_865_824, - name: 'raulb/atleti', - type: 'github', - }, - }) - const {stdout} = await runCommand(Cmd, [`${oldTestRun.number}`, `--pipeline=${pipeline.name}`]) expect(stdout).to.equal('Rerunning test run #10...\nNew Test setup outputNew Test output\n✓ #11 my-test-branch:668a5ce succeeded\n') diff --git a/test/unit/commands/ci/run.unit.test.ts b/test/unit/commands/ci/run.unit.test.ts index 3fd046ecc8..ac13b3f6d0 100644 --- a/test/unit/commands/ci/run.unit.test.ts +++ b/test/unit/commands/ci/run.unit.test.ts @@ -87,6 +87,8 @@ describe('ci:run', function () { .reply(200, [ {id: pipeline.id}, ]) + .get(`/pipelines/${pipeline.id}`) + .reply(200, {id: pipeline.id, owner: {id: '463147bf-d572-41cf-bbf4-11ebc1c0bc3b', type: 'user'}}) .post('/test-runs') .reply(200, newTestRun) .get(`/pipelines/${pipeline.id}/test-runs/${newTestRun.number}`) @@ -118,22 +120,49 @@ describe('ci:run', function () { .get(`/${newTestRun.id.slice(0, 3)}/test-runs/${newTestRun.id}`) .reply(200, 'New Test output') - nock('https://kolkrabbi.heroku.com') - .get(`/pipelines/${pipeline.id}/repository`) - .reply(200, { - ci: true, - organization: {id: 'e037ed63-5781-48ee-b2b7-8c55c571b63e'}, - owner: { - github: {user_id: 306_015}, - heroku: {user_id: '463147bf-d572-41cf-bbf4-11ebc1c0bc3b'}, - id: '463147bf-d572-41cf-bbf4-11ebc1c0bc3b', - }, - repository: { - id: 138_865_824, - name: 'raulb/atleti', - type: 'github', + const {stdout} = await runCommand(Cmd, [`--pipeline=${pipeline.name}`]) + + expect(stdout).to.equal('New Test setup outputNew Test output\n✓ #11 my-test-branch:668a5ce succeeded\n') + }) + + it('derives the organization from a team-owned pipeline', async function () { + api + .get(`/pipelines?eq[name]=${pipeline.name}`) + .reply(200, [ + {id: pipeline.id}, + ]) + .get(`/pipelines/${pipeline.id}`) + .reply(200, {id: pipeline.id, owner: {id: '463147bf-d572-41cf-bbf4-11ebc1c0bc3b', name: 'my-team', type: 'team'}}) + .post('/test-runs', body => body.organization === 'my-team') + .reply(200, newTestRun) + .get(`/pipelines/${pipeline.id}/test-runs/${newTestRun.number}`) + .reply(200, newTestRun) + .get(`/test-runs/${newTestRun.id}/test-nodes`) + .times(2) + .reply(200, [ + { + commit_branch: newTestRun.commit_branch, + commit_message: newTestRun.commit_message, + commit_sha: newTestRun.commit_sha, + exit_code: 0, + id: newTestRun.id, + number: newTestRun.number, + output_stream_url: `https://test-output.heroku.com/streams/${newTestRun.id.slice(0, 3)}/test-runs/${newTestRun.id}`, + pipeline: {id: pipeline.id}, + setup_stream_url: `https://test-setup-output.heroku.com/streams/${newTestRun.id.slice(0, 3)}/test-runs/${newTestRun.id}`, + status: newTestRun.status, }, - }) + ]) + .post('/sources') + .reply(200, {source_blob: {get_url: 'https://aws-geturl', put_url: 'https://aws-puturl'}}) + + nock('https://test-setup-output.heroku.com/streams') + .get(`/${newTestRun.id.slice(0, 3)}/test-runs/${newTestRun.id}`) + .reply(200, 'New Test setup output') + + nock('https://test-output.heroku.com/streams') + .get(`/${newTestRun.id.slice(0, 3)}/test-runs/${newTestRun.id}`) + .reply(200, 'New Test output') const {stdout} = await runCommand(Cmd, [`--pipeline=${pipeline.name}`]) @@ -147,6 +176,8 @@ describe('ci:run', function () { .reply(200, [ {id: pipeline.id}, ]) + .get(`/pipelines/${pipeline.id}`) + .reply(200, {id: pipeline.id, owner: {id: '463147bf-d572-41cf-bbf4-11ebc1c0bc3b', type: 'user'}}) .post('/test-runs') .reply(200, newTestRun) .get(`/pipelines/${pipeline.id}/test-runs/${newTestRun.number}`) @@ -178,23 +209,6 @@ describe('ci:run', function () { .get(`/${newTestRun.id.slice(0, 3)}/test-runs/${newTestRun.id}`) .reply(200, 'New Test output') - nock('https://kolkrabbi.heroku.com') - .get(`/pipelines/${pipeline.id}/repository`) - .reply(200, { - ci: true, - organization: {id: 'e037ed63-5781-48ee-b2b7-8c55c571b63e'}, - owner: { - github: {user_id: 306_015}, - heroku: {user_id: '463147bf-d572-41cf-bbf4-11ebc1c0bc3b'}, - id: '463147bf-d572-41cf-bbf4-11ebc1c0bc3b', - }, - repository: { - id: 138_865_824, - name: 'raulb/atleti', - type: 'github', - }, - }) - const {stdout} = await runCommand(Cmd, [`--pipeline=${pipeline.name}`]) expect(stdout).to.equal('New Test setup outputNew Test output\n✓ #11 my-test-branch:668a5ce succeeded\n') diff --git a/test/unit/commands/pipelines/connect.unit.test.ts b/test/unit/commands/pipelines/connect.unit.test.ts index 7cfa4c86ab..2a9d0792e4 100644 --- a/test/unit/commands/pipelines/connect.unit.test.ts +++ b/test/unit/commands/pipelines/connect.unit.test.ts @@ -6,68 +6,39 @@ import PipelinesConnect from '../../../../src/commands/pipelines/connect.js' describe('pipelines:connect', function () { let api: nock.Scope - let kolkrabbiApi: nock.Scope - let githubApi: nock.Scope beforeEach(function () { api = nock('https://api.heroku.com') - kolkrabbiApi = nock('https://kolkrabbi.heroku.com') - githubApi = nock('https://api.github.com') }) afterEach(function () { api.done() - kolkrabbiApi.done() - githubApi.done() nock.cleanAll() }) - describe('when the user is not linked to GitHub', function () { - it('displays an error', async function () { - kolkrabbiApi - .get('/account/github/token') - .reply(401, {}) - - const {error} = await runCommand(PipelinesConnect, ['my-pipeline', '--repo=my-org/my-repo']) - - expect(error?.message).to.equal('Account not connected to GitHub.') - }) - }) - describe('with an account connected to GitHub', function () { it('shows success', async function () { - const kolkrabbiAccount = { - github: { - token: '123-abc', - }, - } const pipeline = { id: 123, name: 'my-pipeline', } - kolkrabbiApi - .get('/account/github/token') - .reply(200, kolkrabbiAccount) - .post(`/pipelines/${pipeline.id}/repository`) - .reply(201, {}) - const repo = { default_branch: 'main', + full_name: 'my-org/my-repo', id: 1235, - name: 'my-org/my-repo', } - githubApi - .get(`/repos/${repo.name}`) - .reply(200, {repo}) - api + .get(`/repos/${repo.full_name}`) + .reply(200, repo) .get(`/pipelines/${pipeline.name}`) .reply(200, { id: pipeline.id, name: pipeline.name, }) + .post(`/pipelines/${pipeline.id}/repo`, {repo_url: `https://github.com/${repo.full_name}`}) + .reply(201, {}) const {stderr, stdout} = await runCommand(PipelinesConnect, ['my-pipeline', '--repo=my-org/my-repo']) @@ -77,31 +48,21 @@ describe('pipelines:connect', function () { }) describe('with an account connected to GitHub experiencing request failures', function () { - it('shows an error if GitHub request fails', async function () { - const kolkrabbiAccount = { - github: { - token: '123-abc', - }, - } - - kolkrabbiApi - .get('/account/github/token') - .reply(200, kolkrabbiAccount) - + it('shows an error if the repo request fails', async function () { const repo = { default_branch: 'main', + full_name: 'my-org/my-repo', id: 1235, - name: 'my-org/my-repo', } - githubApi - .get(`/repos/${repo.name}`) + api + .get(`/repos/${repo.full_name}`) .reply(401, {}) const {error} = await runCommand(PipelinesConnect, ['my-pipeline', '--repo=my-org/my-repo']) expect(error?.message).to.contain('Couldn\'t access that repo') - expect(error?.message).not.to.contain(repo.name) + expect(error?.message).not.to.contain(repo.full_name) expect((error as any)?.statusCode).to.equal(401) }) }) diff --git a/test/unit/commands/pipelines/diff.unit.test.ts b/test/unit/commands/pipelines/diff.unit.test.ts index 3c432e263b..891a59aa2c 100644 --- a/test/unit/commands/pipelines/diff.unit.test.ts +++ b/test/unit/commands/pipelines/diff.unit.test.ts @@ -48,7 +48,7 @@ describe('pipelines:diff', function () { } const targetGithubApp = { - repo: 'heroku/example-app', + full_name: 'heroku/example-app', } const downstreamApp1 = { @@ -80,7 +80,7 @@ describe('pipelines:diff', function () { } const downstreamApp1Github = { - repo: 'heroku/example-app', + full_name: 'heroku/example-app', } const downstreamApp2 = { @@ -112,23 +112,23 @@ describe('pipelines:diff', function () { } const downstreamApp2Github = { - repo: 'heroku/some-other-app', + full_name: 'heroku/some-other-app', + } + + const repositoriesApiHeaders = { + reqheaders: { + accept: 'application/vnd.heroku+json; version=3.repositories-api', + }, } let api: nock.Scope - let kolkrabbiApi: nock.Scope - let githubApi: nock.Scope beforeEach(function () { api = nock('https://api.heroku.com') - kolkrabbiApi = nock('https://kolkrabbi.heroku.com') - githubApi = nock('https://api.github.com') }) afterEach(function () { api.done() - kolkrabbiApi.done() - githubApi.done() nock.cleanAll() }) @@ -173,13 +173,11 @@ describe('pipelines:diff', function () { .reply(200, [targetApp, downstreamApp1, downstreamApp2]) .get(`/pipelines/${targetCoupling.pipeline.id}`) .reply(200, pipelineWithGeneration) - - kolkrabbiApi - .get(`/apps/${targetApp.id}/github`) + .get(`/apps/${targetApp.id}/repo`, undefined, repositoriesApiHeaders) .reply(404, {message: 'Not found.'}) - .get(`/apps/${downstreamApp1.id}/github`) + .get(`/apps/${downstreamApp1.id}/repo`, undefined, repositoriesApiHeaders) .reply(200, downstreamApp1Github) - .get(`/apps/${downstreamApp2.id}/github`) + .get(`/apps/${downstreamApp2.id}/repo`, undefined, repositoriesApiHeaders) .reply(200, downstreamApp2Github) const {error} = await runCommand(PipelinesDiff, [`--app=${targetApp.name}`]) @@ -197,13 +195,11 @@ describe('pipelines:diff', function () { .reply(200, [targetApp, downstreamApp1, downstreamApp2]) .get(`/pipelines/${targetCoupling.pipeline.id}`) .reply(200, pipelineWithGeneration) - - kolkrabbiApi - .get(`/apps/${targetApp.id}/github`) + .get(`/apps/${targetApp.id}/repo`, undefined, repositoriesApiHeaders) .reply(200, targetGithubApp) - .get(`/apps/${downstreamApp1.id}/github`) + .get(`/apps/${downstreamApp1.id}/repo`, undefined, repositoriesApiHeaders) .reply(200, downstreamApp1Github) - .get(`/apps/${downstreamApp2.id}/github`) + .get(`/apps/${downstreamApp2.id}/repo`, undefined, repositoriesApiHeaders) .reply(200, downstreamApp2Github) const {error} = await runCommand(PipelinesDiff, [`--app=${targetApp.name}`]) @@ -223,13 +219,11 @@ describe('pipelines:diff', function () { .reply(200, pipelineWithGeneration) .get(`/apps/${targetApp.id}/releases`) .reply(200, []) - - kolkrabbiApi - .get(`/apps/${targetApp.id}/github`) + .get(`/apps/${targetApp.id}/repo`, undefined, repositoriesApiHeaders) .reply(200, targetGithubApp) - .get(`/apps/${downstreamApp1.id}/github`) + .get(`/apps/${downstreamApp1.id}/repo`, undefined, repositoriesApiHeaders) .reply(200, downstreamApp1Github) - .get(`/apps/${downstreamApp2.id}/github`) + .get(`/apps/${downstreamApp2.id}/repo`, undefined, repositoriesApiHeaders) .reply(200, downstreamApp2Github) const {error} = await runCommand(PipelinesDiff, [`--app=${targetApp.name}`]) @@ -263,16 +257,12 @@ describe('pipelines:diff', function () { .reply(200, {commit: 'COMMIT-HASH'}) .get(`/apps/${downstreamApp1.id}/slugs/${downstreamSlugId}`) .reply(200, {commit: 'COMMIT-HASH'}) - - kolkrabbiApi - .get(`/apps/${targetApp.id}/github`) + .get(`/apps/${targetApp.id}/repo`, undefined, repositoriesApiHeaders) .reply(200, targetGithubApp) - .get(`/apps/${downstreamApp1.id}/github`) + .get(`/apps/${downstreamApp1.id}/repo`, undefined, repositoriesApiHeaders) .reply(200, downstreamApp1Github) - .get(`/apps/${downstreamApp2.id}/github`) + .get(`/apps/${downstreamApp2.id}/repo`, undefined, repositoriesApiHeaders) .reply(200, downstreamApp2Github) - .get('/account/github/token') - .reply(200, {github: {token: 'github-token'}}) const {stdout} = await runCommand(PipelinesDiff, [`--app=${targetApp.name}`]) @@ -303,19 +293,13 @@ describe('pipelines:diff', function () { .reply(200, {commit: hashes[0]}) .get(`/apps/${downstreamApp1.id}/slugs/${downstreamSlugId}`) .reply(200, {commit: hashes[1]}) - - kolkrabbiApi - .get(`/apps/${targetApp.id}/github`) + .get(`/apps/${targetApp.id}/repo`, undefined, repositoriesApiHeaders) .reply(200, targetGithubApp) - .get(`/apps/${downstreamApp1.id}/github`) + .get(`/apps/${downstreamApp1.id}/repo`, undefined, repositoriesApiHeaders) .reply(200, downstreamApp1Github) - .get(`/apps/${downstreamApp2.id}/github`) + .get(`/apps/${downstreamApp2.id}/repo`, undefined, repositoriesApiHeaders) .reply(200, downstreamApp2Github) - .get('/account/github/token') - .reply(200, {github: {token: 'github-token'}}) - - githubApi - .get(`/repos/${targetGithubApp.repo}/compare/${hashes[1]}...${hashes[0]}`) + .get(`/pipelines/${pipeline.id}/repo/compare?base=${hashes[1]}&head=${hashes[0]}`, undefined, repositoriesApiHeaders) .reply(404) const {stdout} = await runCommand(PipelinesDiff, [`--app=${targetApp.name}`]) @@ -349,16 +333,12 @@ describe('pipelines:diff', function () { .reply(200, [{commit: 'COMMIT-HASH'}]) .get(`/apps/${downstreamFirApp1.id}/oci-images/${downstreamOciImageId}`) .reply(200, [{commit: 'COMMIT-HASH'}]) - - kolkrabbiApi - .get(`/apps/${targetFirApp.id}/github`) + .get(`/apps/${targetFirApp.id}/repo`, undefined, repositoriesApiHeaders) .reply(200, targetGithubApp) - .get(`/apps/${downstreamFirApp1.id}/github`) + .get(`/apps/${downstreamFirApp1.id}/repo`, undefined, repositoriesApiHeaders) .reply(200, downstreamApp1Github) - .get(`/apps/${downstreamFirApp2.id}/github`) + .get(`/apps/${downstreamFirApp2.id}/repo`, undefined, repositoriesApiHeaders) .reply(200, downstreamApp2Github) - .get('/account/github/token') - .reply(200, {github: {token: 'github-token'}}) const {stdout} = await runCommand(PipelinesDiff, [`--app=${targetFirApp.name}`]) @@ -389,19 +369,13 @@ describe('pipelines:diff', function () { .reply(200, [{commit: hashes[0]}]) .get(`/apps/${downstreamFirApp1.id}/oci-images/${downstreamOciImageId}`) .reply(200, [{commit: hashes[1]}]) - - kolkrabbiApi - .get(`/apps/${targetFirApp.id}/github`) + .get(`/apps/${targetFirApp.id}/repo`, undefined, repositoriesApiHeaders) .reply(200, targetGithubApp) - .get(`/apps/${downstreamFirApp1.id}/github`) + .get(`/apps/${downstreamFirApp1.id}/repo`, undefined, repositoriesApiHeaders) .reply(200, downstreamApp1Github) - .get(`/apps/${downstreamFirApp2.id}/github`) + .get(`/apps/${downstreamFirApp2.id}/repo`, undefined, repositoriesApiHeaders) .reply(200, downstreamApp2Github) - .get('/account/github/token') - .reply(200, {github: {token: 'github-token'}}) - - githubApi - .get(`/repos/${targetGithubApp.repo}/compare/${hashes[1]}...${hashes[0]}`) + .get(`/pipelines/${firPipeline.id}/repo/compare?base=${hashes[1]}&head=${hashes[0]}`, undefined, repositoriesApiHeaders) .reply(404) const {stdout} = await runCommand(PipelinesDiff, [`--app=${targetFirApp.name}`]) diff --git a/test/unit/commands/pipelines/setup.unit.test.ts b/test/unit/commands/pipelines/setup.unit.test.ts index c7668d5ee6..4168c8d0f4 100644 --- a/test/unit/commands/pipelines/setup.unit.test.ts +++ b/test/unit/commands/pipelines/setup.unit.test.ts @@ -9,38 +9,38 @@ import SetupCommand from '../../../../src/commands/pipelines/setup.js' describe('pipelines:setup', function () { let api: nock.Scope - let kolkrabbiApi: nock.Scope - let githubApi: nock.Scope beforeEach(function () { api = nock('https://api.heroku.com') - kolkrabbiApi = nock('https://kolkrabbi.heroku.com') - githubApi = nock('https://api.github.com') }) afterEach(function () { api.done() - kolkrabbiApi.done() - githubApi.done() nock.cleanAll() restore() }) - it('errors if the user is not linked to GitHub', async function () { - kolkrabbiApi - .get('/account/github/token') - .replyWithError('') + it('errors if the repo cannot be accessed', async function () { + stub(SetupCommand, 'open').resolves() + const promptStub = stub() + promptStub.onFirstCall().resolves('my-pipeline') + promptStub.onSecondCall().resolves('my-org/my-repo') + stub(hux, 'prompt').callsFake(promptStub) + stub(hux, 'confirm').callsFake(stub().resolves(true)) + + api + .get('/repos/my-org/my-repo') + .reply(404, {}) const {error} = await runCommand(SetupCommand, []) - expect(error?.message).to.equal('Account not connected to GitHub.') + expect(error?.message).to.contain('Couldn\'t access that repo') }) context('with an account connected to GitHub', function () { const archiveURL = 'https://example.com/archive.tar.gz' const pipeline = {id: '123-pipeline', name: 'my-pipeline'} - const repo = {default_branch: 'main', id: 123, name: 'my-org/my-repo'} - const kolkrabbiAccount = {github: {token: '123-abc'}} + const repo = {default_branch: 'main', full_name: 'my-org/my-repo', id: 123, name: 'my-repo'} const prodApp = {id: '123-prod-app', name: pipeline.name} const stagingApp = {id: '123-staging-app', name: `${pipeline.name}-staging`} @@ -68,18 +68,22 @@ describe('pipelines:setup', function () { return api } - function setupKolkrabbiNock() { - return kolkrabbiApi - .get('/account/github/token') - .reply(200, kolkrabbiAccount) - .get(`/github/repos/${repo.name}/tarball/${repo.default_branch}`) - .reply(200, { - archive_link: archiveURL, + function setupRepoNock() { + return api + .get(`/repos/${repo.full_name}`) + .reply(200, repo) + .post(`/pipelines/${pipeline.id}/repo`, {repo_url: `https://github.com/${repo.full_name}`}) + .reply(201, {id: '123-repo'}) + .get(`/repos/${repo.full_name}/archives/${repo.default_branch}`) + .reply(200, {archive_link: archiveURL}) + .post(`/pipelines/${pipeline.id}/review-app-config`, { + automatic_review_apps: true, + destroy_stale_apps: true, + pipeline: pipeline.id, + repo: repo.full_name, + wait_for_ci: true, }) - .post(`/pipelines/${pipeline.id}/repository`) .reply(201, {}) - .patch(`/apps/${stagingApp.id}/github`) - .reply(200, {}) } context('when pipeline name is too long', function () { @@ -100,9 +104,9 @@ describe('pipelines:setup', function () { confirmStub = stub() }) - it('creates apps in the personal account with CI enabled', async function () { + it('creates apps in the personal account', async function () { promptStub.onFirstCall().resolves(pipeline.name) - promptStub.onSecondCall().resolves(repo.name) + promptStub.onSecondCall().resolves(repo.full_name) confirmStub.resolves(true) stub(hux, 'prompt').callsFake(promptStub) @@ -110,14 +114,7 @@ describe('pipelines:setup', function () { stub(SetupCommand, 'open').resolves() setupApiNock() - githubApi.get(`/repos/${repo.name}`).reply(200, repo) - - const kolkrabbi = setupKolkrabbiNock() - kolkrabbi - .patch(`/pipelines/${pipeline.id}/repository`, { - ci: true, - }) - .reply(200) + setupRepoNock() await runCommand(SetupCommand, []) @@ -127,7 +124,7 @@ describe('pipelines:setup', function () { it('downcases capitalized pipeline names', async function () { promptStub.reset() - promptStub.onFirstCall().resolves(repo.name) + promptStub.onFirstCall().resolves(repo.full_name) confirmStub.resolves(true) stub(hux, 'prompt').callsFake(promptStub) @@ -135,14 +132,7 @@ describe('pipelines:setup', function () { stub(SetupCommand, 'open').resolves() setupApiNock() - githubApi.get(`/repos/${repo.name}`).reply(200, repo) - - const kolkrabbi = setupKolkrabbiNock() - kolkrabbi - .patch(`/pipelines/${pipeline.id}/repository`, { - ci: true, - }) - .reply(200) + setupRepoNock() await runCommand(SetupCommand, [pipeline.name.toUpperCase()]) @@ -157,16 +147,9 @@ describe('pipelines:setup', function () { stub(SetupCommand, 'open').resolves() setupApiNock() - githubApi.get(`/repos/${repo.name}`).reply(200, repo) + setupRepoNock() - const kolkrabbi = setupKolkrabbiNock() - kolkrabbi - .patch(`/pipelines/${pipeline.id}/repository`, { - ci: true, - }) - .reply(200) - - await runCommand(SetupCommand, ['--yes', pipeline.name, repo.name]) + await runCommand(SetupCommand, ['--yes', pipeline.name, repo.full_name]) // Since we're passing the `yes` flag here, we should always return default settings and // thus never actually call cli.prompt @@ -184,9 +167,9 @@ describe('pipelines:setup', function () { confirmStub = stub() }) - it('creates apps in a team with CI enabled', async function () { + it('creates apps in a team', async function () { promptStub.onFirstCall().resolves(pipeline.name) - promptStub.onSecondCall().resolves(repo.name) + promptStub.onSecondCall().resolves(repo.full_name) confirmStub.resolves(true) stub(hux, 'prompt').callsFake(promptStub) @@ -214,15 +197,7 @@ describe('pipelines:setup', function () { api.get('/teams/test-org').reply(200, {id: '89-0123-456'}) - githubApi.get(`/repos/${repo.name}`).reply(200, repo) - - const kolkrabbi = setupKolkrabbiNock() - kolkrabbi - .patch(`/pipelines/${pipeline.id}/repository`, { - ci: true, - organization: team, - }) - .reply(200) + setupRepoNock() await runCommand(SetupCommand, ['--team', team]) @@ -266,17 +241,13 @@ describe('pipelines:setup', function () { api.get('/teams/test-org').reply(200, {id: '89-0123-456'}) - githubApi.get(`/repos/${repo.name}`).reply(200, repo) - - kolkrabbiApi - .get('/account/github/token') - .reply(200, kolkrabbiAccount) - .get(`/github/repos/${repo.name}/tarball/${repo.default_branch}`) - .reply(200, { - archive_link: archiveURL, - }) - .post(`/pipelines/${pipeline.id}/repository`) - .reply(201, {}) + api + .get(`/repos/${repo.full_name}`) + .reply(200, repo) + .post(`/pipelines/${pipeline.id}/repo`, {repo_url: `https://github.com/${repo.full_name}`}) + .reply(201, {id: '123-repo'}) + .get(`/repos/${repo.full_name}/archives/${repo.default_branch}`) + .reply(200, {archive_link: archiveURL}) const {error} = await runCommand(SetupCommand, ['my-pipeline', 'my-org/my-repo', '--team', team]) @@ -321,17 +292,13 @@ describe('pipelines:setup', function () { api.get('/teams/test-org').reply(200, {id: '89-0123-456'}) - githubApi.get(`/repos/${repo.name}`).reply(200, repo) - - kolkrabbiApi - .get('/account/github/token') - .reply(200, kolkrabbiAccount) - .get(`/github/repos/${repo.name}/tarball/${repo.default_branch}`) - .reply(200, { - archive_link: archiveURL, - }) - .post(`/pipelines/${pipeline.id}/repository`) - .reply(201, {}) + api + .get(`/repos/${repo.full_name}`) + .reply(200, repo) + .post(`/pipelines/${pipeline.id}/repo`, {repo_url: `https://github.com/${repo.full_name}`}) + .reply(201, {id: '123-repo'}) + .get(`/repos/${repo.full_name}/archives/${repo.default_branch}`) + .reply(200, {archive_link: archiveURL}) const {error} = await runCommand(SetupCommand, ['my-pipeline', 'my-org/my-repo', '--team', team]) diff --git a/test/unit/commands/reviewapps/disable.unit.test.ts b/test/unit/commands/reviewapps/disable.unit.test.ts index dfb8e46055..d4aa0ba9c8 100644 --- a/test/unit/commands/reviewapps/disable.unit.test.ts +++ b/test/unit/commands/reviewapps/disable.unit.test.ts @@ -14,209 +14,83 @@ describe('reviewapps:disable', function () { nock.cleanAll() }) - describe('with repos api enabled', function () { - const feature = { - enabled: true, - name: 'dashboard-repositories-api', - } - - const repo = { - full_name: 'james/repo', - } - - it('succeeds with defaults', async function () { - nock('https://api.heroku.com') - .get(`/account/features/${feature.name}`) - .reply(200, feature) - .get(`/pipelines/${pipeline.name}`) - .reply(200, pipeline) - .get(`/pipelines/${pipeline.id}/repo`) - .reply(200, repo) - .delete(`/pipelines/${pipeline.id}/review-app-config`) - .reply(200, {}) - - const {stderr} = await runCommand(ReviewappsDisable, [`--pipeline=${pipeline.name}`]) - - expect(stderr).to.include('done\n') - }) - - it('disables autodeploy', async function () { - nock('https://api.heroku.com') - .get(`/account/features/${feature.name}`) - .reply(200, feature) - .get(`/pipelines/${pipeline.name}`) - .reply(200, pipeline) - .get(`/pipelines/${pipeline.id}/repo`) - .reply(200, repo) - .patch(`/pipelines/${pipeline.id}/review-app-config`) - .reply(200, {}) - - const {stderr, stdout} = await runCommand(ReviewappsDisable, [`--pipeline=${pipeline.name}`, '--no-autodeploy']) - - expect(stdout).to.include('Disabling auto deployment') - expect(stderr).to.include('Configuring pipeline') - }) - - it('disables autodestroy', async function () { - nock('https://api.heroku.com') - .get(`/account/features/${feature.name}`) - .reply(200, feature) - .get(`/pipelines/${pipeline.name}`) - .reply(200, pipeline) - .get(`/pipelines/${pipeline.id}/repo`) - .reply(200, repo) - .patch(`/pipelines/${pipeline.id}/review-app-config`) - .reply(200, {}) - - const {stderr, stdout} = await runCommand(ReviewappsDisable, [`--pipeline=${pipeline.name}`, '--no-autodestroy']) - - expect(stdout).to.include('Disabling auto destroy') - expect(stderr).to.include('Configuring pipeline') - }) - - it('disables wait-for-ci', async function () { - nock('https://api.heroku.com') - .get(`/account/features/${feature.name}`) - .reply(200, feature) - .get(`/pipelines/${pipeline.name}`) - .reply(200, pipeline) - .get(`/pipelines/${pipeline.id}/repo`) - .reply(200, repo) - .patch(`/pipelines/${pipeline.id}/review-app-config`) - .reply(200, {}) - - const {stderr, stdout} = await runCommand(ReviewappsDisable, [`--pipeline=${pipeline.name}`, '--no-wait-for-ci']) - - expect(stdout).to.include('Disabling wait for CI') - expect(stderr).to.include('Configuring pipeline') - }) - - it('disables autodeploy and autodestroy and wait-for-ci', async function () { - nock('https://api.heroku.com') - .get(`/account/features/${feature.name}`) - .reply(200, feature) - .get(`/pipelines/${pipeline.name}`) - .reply(200, pipeline) - .get(`/pipelines/${pipeline.id}/repo`) - .reply(200, repo) - .patch(`/pipelines/${pipeline.id}/review-app-config`) - .reply(200, {}) - - const {stderr, stdout} = await runCommand(ReviewappsDisable, [`--pipeline=${pipeline.name}`, '--no-autodeploy', '--no-autodestroy', '--no-wait-for-ci']) - - expect(stdout).to.include('Disabling auto deployment') - expect(stdout).to.include('Disabling auto destroy') - expect(stdout).to.include('Disabling wait for CI') - expect(stderr).to.include('Configuring pipeline') - }) + const repo = { + full_name: 'james/repo', + } + + it('succeeds with defaults', async function () { + nock('https://api.heroku.com') + .get(`/pipelines/${pipeline.name}`) + .reply(200, pipeline) + .get(`/pipelines/${pipeline.id}/repo`) + .reply(200, repo) + .delete(`/pipelines/${pipeline.id}/review-app-config`) + .reply(200, {}) + + const {stderr} = await runCommand(ReviewappsDisable, [`--pipeline=${pipeline.name}`]) + + expect(stderr).to.include('done\n') + }) + + it('disables autodeploy', async function () { + nock('https://api.heroku.com') + .get(`/pipelines/${pipeline.name}`) + .reply(200, pipeline) + .get(`/pipelines/${pipeline.id}/repo`) + .reply(200, repo) + .patch(`/pipelines/${pipeline.id}/review-app-config`) + .reply(200, {}) + + const {stderr, stdout} = await runCommand(ReviewappsDisable, [`--pipeline=${pipeline.name}`, '--no-autodeploy']) + + expect(stdout).to.include('Disabling auto deployment') + expect(stderr).to.include('Configuring pipeline') + }) + + it('disables autodestroy', async function () { + nock('https://api.heroku.com') + .get(`/pipelines/${pipeline.name}`) + .reply(200, pipeline) + .get(`/pipelines/${pipeline.id}/repo`) + .reply(200, repo) + .patch(`/pipelines/${pipeline.id}/review-app-config`) + .reply(200, {}) + + const {stderr, stdout} = await runCommand(ReviewappsDisable, [`--pipeline=${pipeline.name}`, '--no-autodestroy']) + + expect(stdout).to.include('Disabling auto destroy') + expect(stderr).to.include('Configuring pipeline') + }) + + it('disables wait-for-ci', async function () { + nock('https://api.heroku.com') + .get(`/pipelines/${pipeline.name}`) + .reply(200, pipeline) + .get(`/pipelines/${pipeline.id}/repo`) + .reply(200, repo) + .patch(`/pipelines/${pipeline.id}/review-app-config`) + .reply(200, {}) + + const {stderr, stdout} = await runCommand(ReviewappsDisable, [`--pipeline=${pipeline.name}`, '--no-wait-for-ci']) + + expect(stdout).to.include('Disabling wait for CI') + expect(stderr).to.include('Configuring pipeline') }) - describe('with repos api disabled', function () { - const feature = { - enabled: false, - name: 'dashboard-repositories-api', - } - - const repo = { - repository: { - name: 'james/repo', - }, - } - - it('succeeds with defaults', async function () { - nock('https://api.heroku.com') - .get(`/account/features/${feature.name}`) - .reply(404, {}) - .get(`/pipelines/${pipeline.name}`) - .reply(200, pipeline) - .delete(`/pipelines/${pipeline.id}/review-app-config`) - .reply(200, {}) - - nock('https://kolkrabbi.heroku.com') - .get(`/pipelines/${pipeline.id}/repository`) - .reply(200, repo) - - const {stderr} = await runCommand(ReviewappsDisable, [`--pipeline=${pipeline.name}`]) - - expect(stderr).to.include('Configuring pipeline') - }) - - it('disables autodeploy', async function () { - nock('https://api.heroku.com') - .get(`/account/features/${feature.name}`) - .reply(404, {}) - .get(`/pipelines/${pipeline.name}`) - .reply(200, pipeline) - .patch(`/pipelines/${pipeline.id}/review-app-config`) - .reply(200, {}) - - nock('https://kolkrabbi.heroku.com') - .get(`/pipelines/${pipeline.id}/repository`) - .reply(200, repo) - - const {stderr, stdout} = await runCommand(ReviewappsDisable, [`--pipeline=${pipeline.name}`, '--no-autodeploy']) - - expect(stdout).to.include('Disabling auto deployment') - expect(stderr).to.include('Configuring pipeline') - }) - - it('disables autodestroy', async function () { - nock('https://api.heroku.com') - .get(`/account/features/${feature.name}`) - .reply(404, {}) - .get(`/pipelines/${pipeline.name}`) - .reply(200, pipeline) - .patch(`/pipelines/${pipeline.id}/review-app-config`) - .reply(200, {}) - - nock('https://kolkrabbi.heroku.com') - .get(`/pipelines/${pipeline.id}/repository`) - .reply(200, repo) - - const {stderr, stdout} = await runCommand(ReviewappsDisable, [`--pipeline=${pipeline.name}`, '--no-autodestroy']) - - expect(stdout).to.include('Disabling auto destroy') - expect(stderr).to.include('Configuring pipeline') - }) - - it('disables wait-for-ci', async function () { - nock('https://api.heroku.com') - .get(`/account/features/${feature.name}`) - .reply(404, {}) - .get(`/pipelines/${pipeline.name}`) - .reply(200, pipeline) - .patch(`/pipelines/${pipeline.id}/review-app-config`) - .reply(200, {}) - - nock('https://kolkrabbi.heroku.com') - .get(`/pipelines/${pipeline.id}/repository`) - .reply(200, repo) - - const {stderr, stdout} = await runCommand(ReviewappsDisable, [`--pipeline=${pipeline.name}`, '--no-wait-for-ci']) - - expect(stdout).to.include('Disabling wait for CI') - expect(stderr).to.include('Configuring pipeline') - }) - - it('disables autodeploy and autodestroy and wait-for-ci', async function () { - nock('https://api.heroku.com') - .get(`/account/features/${feature.name}`) - .reply(404, {}) - .get(`/pipelines/${pipeline.name}`) - .reply(200, pipeline) - .patch(`/pipelines/${pipeline.id}/review-app-config`) - .reply(200, {}) - - nock('https://kolkrabbi.heroku.com') - .get(`/pipelines/${pipeline.id}/repository`) - .reply(200, repo) - - const {stderr, stdout} = await runCommand(ReviewappsDisable, [`--pipeline=${pipeline.name}`, '--no-autodeploy', '--no-autodestroy', '--no-wait-for-ci']) - - expect(stdout).to.include('Disabling auto deployment') - expect(stdout).to.include('Disabling auto destroy') - expect(stdout).to.include('Disabling wait for CI') - expect(stderr).to.include('Configuring pipeline') - }) + it('disables autodeploy and autodestroy and wait-for-ci', async function () { + nock('https://api.heroku.com') + .get(`/pipelines/${pipeline.name}`) + .reply(200, pipeline) + .get(`/pipelines/${pipeline.id}/repo`) + .reply(200, repo) + .patch(`/pipelines/${pipeline.id}/review-app-config`) + .reply(200, {}) + + const {stderr, stdout} = await runCommand(ReviewappsDisable, [`--pipeline=${pipeline.name}`, '--no-autodeploy', '--no-autodestroy', '--no-wait-for-ci']) + + expect(stdout).to.include('Disabling auto deployment') + expect(stdout).to.include('Disabling auto destroy') + expect(stdout).to.include('Disabling wait for CI') + expect(stderr).to.include('Configuring pipeline') }) }) diff --git a/test/unit/commands/reviewapps/enable.unit.test.ts b/test/unit/commands/reviewapps/enable.unit.test.ts index 811d11aee3..a7dbad2bfc 100644 --- a/test/unit/commands/reviewapps/enable.unit.test.ts +++ b/test/unit/commands/reviewapps/enable.unit.test.ts @@ -10,222 +10,93 @@ describe('reviewapps:enable', function () { name: 'my-pipeline', } let api: nock.Scope - let kolkrabbiApi: nock.Scope beforeEach(function () { api = nock('https://api.heroku.com') - kolkrabbiApi = nock('https://kolkrabbi.heroku.com') }) afterEach(function () { api.done() - kolkrabbiApi.done() nock.cleanAll() }) - describe('with repos api enabled', function () { - const feature = { - enabled: true, - name: 'dashboard-repositories-api', - } - - const repo = { - full_name: 'james/repo', - } - - it('succeeds with defaults', async function () { - api - .get(`/account/features/${feature.name}`) - .reply(200, feature) - .get(`/pipelines/${pipeline.name}`) - .reply(200, pipeline) - .get(`/pipelines/${pipeline.id}/repo`) - .reply(200, repo) - .post(`/pipelines/${pipeline.id}/review-app-config`) - .reply(200, {}) - - const {stderr} = await runCommand(ReviewappsEnable, [`--pipeline=${pipeline.name}`]) - - expect(stderr).to.include('Configuring pipeline') - }) - - it('succeeds with autodeploy', async function () { - api - .get(`/account/features/${feature.name}`) - .reply(200, feature) - .get(`/pipelines/${pipeline.name}`) - .reply(200, pipeline) - .get(`/pipelines/${pipeline.id}/repo`) - .reply(200, repo) - .patch(`/pipelines/${pipeline.id}/review-app-config`) - .reply(200, {}) - - const {stderr, stdout} = await runCommand(ReviewappsEnable, [`--pipeline=${pipeline.name}`, '--autodeploy']) - - expect(stdout).to.include('Enabling auto deployment') - expect(stderr).to.include('Configuring pipeline') - }) - - it('it succeeds with autodestroy', async function () { - api - .get(`/account/features/${feature.name}`) - .reply(200, feature) - .get(`/pipelines/${pipeline.name}`) - .reply(200, pipeline) - .get(`/pipelines/${pipeline.id}/repo`) - .reply(200, repo) - .patch(`/pipelines/${pipeline.id}/review-app-config`) - .reply(200, {}) - - const {stderr, stdout} = await runCommand(ReviewappsEnable, [`--pipeline=${pipeline.name}`, '--autodestroy']) - - expect(stdout).to.include('Enabling auto destroy') - expect(stderr).to.include('Configuring pipeline') - }) - - it('it succeeds with wait-for-ci', async function () { - api - .get(`/account/features/${feature.name}`) - .reply(200, feature) - .get(`/pipelines/${pipeline.name}`) - .reply(200, pipeline) - .get(`/pipelines/${pipeline.id}/repo`) - .reply(200, repo) - .patch(`/pipelines/${pipeline.id}/review-app-config`) - .reply(200, {}) - - const {stderr, stdout} = await runCommand(ReviewappsEnable, [`--pipeline=${pipeline.name}`, '--wait-for-ci']) - - expect(stdout).to.include('Enabling wait for CI') - expect(stderr).to.include('Configuring pipeline') - }) - - it('it succeeds with autodeploy and autodestroy and wait-for-ci', async function () { - api - .get(`/account/features/${feature.name}`) - .reply(200, feature) - .get(`/pipelines/${pipeline.name}`) - .reply(200, pipeline) - .get(`/pipelines/${pipeline.id}/repo`) - .reply(200, repo) - .patch(`/pipelines/${pipeline.id}/review-app-config`) - .reply(200, {}) - - const {stderr, stdout} = await runCommand(ReviewappsEnable, [`--pipeline=${pipeline.name}`, '--autodeploy', '--autodestroy', '--wait-for-ci']) - - expect(stdout).to.include('Enabling auto deployment') - expect(stdout).to.include('Enabling auto destroy') - expect(stdout).to.include('Enabling wait for CI') - expect(stderr).to.include('Configuring pipeline') - }) + const repo = { + full_name: 'james/repo', + } + + it('succeeds with defaults', async function () { + api + .get(`/pipelines/${pipeline.name}`) + .reply(200, pipeline) + .get(`/pipelines/${pipeline.id}/repo`) + .reply(200, repo) + .post(`/pipelines/${pipeline.id}/review-app-config`) + .reply(200, {}) + + const {stderr} = await runCommand(ReviewappsEnable, [`--pipeline=${pipeline.name}`]) + + expect(stderr).to.include('Configuring pipeline') + }) + + it('succeeds with autodeploy', async function () { + api + .get(`/pipelines/${pipeline.name}`) + .reply(200, pipeline) + .get(`/pipelines/${pipeline.id}/repo`) + .reply(200, repo) + .patch(`/pipelines/${pipeline.id}/review-app-config`) + .reply(200, {}) + + const {stderr, stdout} = await runCommand(ReviewappsEnable, [`--pipeline=${pipeline.name}`, '--autodeploy']) + + expect(stdout).to.include('Enabling auto deployment') + expect(stderr).to.include('Configuring pipeline') + }) + + it('it succeeds with autodestroy', async function () { + api + .get(`/pipelines/${pipeline.name}`) + .reply(200, pipeline) + .get(`/pipelines/${pipeline.id}/repo`) + .reply(200, repo) + .patch(`/pipelines/${pipeline.id}/review-app-config`) + .reply(200, {}) + + const {stderr, stdout} = await runCommand(ReviewappsEnable, [`--pipeline=${pipeline.name}`, '--autodestroy']) + + expect(stdout).to.include('Enabling auto destroy') + expect(stderr).to.include('Configuring pipeline') + }) + + it('it succeeds with wait-for-ci', async function () { + api + .get(`/pipelines/${pipeline.name}`) + .reply(200, pipeline) + .get(`/pipelines/${pipeline.id}/repo`) + .reply(200, repo) + .patch(`/pipelines/${pipeline.id}/review-app-config`) + .reply(200, {}) + + const {stderr, stdout} = await runCommand(ReviewappsEnable, [`--pipeline=${pipeline.name}`, '--wait-for-ci']) + + expect(stdout).to.include('Enabling wait for CI') + expect(stderr).to.include('Configuring pipeline') }) - describe('with repos api disabled', function () { - const feature = { - enabled: false, - name: 'dashboard-repositories-api', - } - - const repo = { - repository: { - name: 'james/repo', - }, - } - - it('succeeds with defaults', async function () { - api - .get(`/account/features/${feature.name}`) - .reply(404, {}) - .get(`/pipelines/${pipeline.name}`) - .reply(200, pipeline) - .post(`/pipelines/${pipeline.id}/review-app-config`) - .reply(200, {}) - - kolkrabbiApi - .get(`/pipelines/${pipeline.id}/repository`) - .reply(200, repo) - - const {stderr} = await runCommand(ReviewappsEnable, [`--pipeline=${pipeline.name}`]) - - expect(stderr).to.include('Configuring pipeline') - }) - - it('succeeds with autodeploy', async function () { - api - .get(`/account/features/${feature.name}`) - .reply(404, {}) - .get(`/pipelines/${pipeline.name}`) - .reply(200, pipeline) - .patch(`/pipelines/${pipeline.id}/review-app-config`) - .reply(200, {}) - - kolkrabbiApi - .get(`/pipelines/${pipeline.id}/repository`) - .reply(200, repo) - - const {stderr, stdout} = await runCommand(ReviewappsEnable, [`--pipeline=${pipeline.name}`, '--autodeploy']) - - expect(stdout).to.include('Enabling auto deployment') - expect(stderr).to.include('Configuring pipeline') - }) - - it('it succeeds with autodestroy', async function () { - api - .get(`/account/features/${feature.name}`) - .reply(404, {}) - .get(`/pipelines/${pipeline.name}`) - .reply(200, pipeline) - .patch(`/pipelines/${pipeline.id}/review-app-config`) - .reply(200, {}) - - kolkrabbiApi - .get(`/pipelines/${pipeline.id}/repository`) - .reply(200, repo) - - const {stderr, stdout} = await runCommand(ReviewappsEnable, [`--pipeline=${pipeline.name}`, '--autodestroy']) - - expect(stdout).to.include('Enabling auto destroy') - expect(stderr).to.include('Configuring pipeline') - }) - - it('it succeeds with wait-for-ci', async function () { - api - .get(`/account/features/${feature.name}`) - .reply(404, {}) - .get(`/pipelines/${pipeline.name}`) - .reply(200, pipeline) - .patch(`/pipelines/${pipeline.id}/review-app-config`) - .reply(200, {}) - - kolkrabbiApi - .get(`/pipelines/${pipeline.id}/repository`) - .reply(200, repo) - - const {stderr, stdout} = await runCommand(ReviewappsEnable, [`--pipeline=${pipeline.name}`, '--wait-for-ci']) - - expect(stdout).to.include('Enabling wait for CI') - expect(stderr).to.include('Configuring pipeline') - }) - - it('it succeeds with autodeploy and autodestroy and wait-for-ci', async function () { - api - .get(`/account/features/${feature.name}`) - .reply(404, {}) - .get(`/pipelines/${pipeline.name}`) - .reply(200, pipeline) - .patch(`/pipelines/${pipeline.id}/review-app-config`) - .reply(200, {}) - - kolkrabbiApi - .get(`/pipelines/${pipeline.id}/repository`) - .reply(200, repo) - - const {stderr, stdout} = await runCommand(ReviewappsEnable, [`--pipeline=${pipeline.name}`, '--autodeploy', '--autodestroy', '--wait-for-ci']) - - expect(stdout).to.include('Enabling auto deployment') - expect(stdout).to.include('Enabling auto destroy') - expect(stdout).to.include('Enabling wait for CI') - expect(stderr).to.include('Configuring pipeline') - }) + it('it succeeds with autodeploy and autodestroy and wait-for-ci', async function () { + api + .get(`/pipelines/${pipeline.name}`) + .reply(200, pipeline) + .get(`/pipelines/${pipeline.id}/repo`) + .reply(200, repo) + .patch(`/pipelines/${pipeline.id}/review-app-config`) + .reply(200, {}) + + const {stderr, stdout} = await runCommand(ReviewappsEnable, [`--pipeline=${pipeline.name}`, '--autodeploy', '--autodestroy', '--wait-for-ci']) + + expect(stdout).to.include('Enabling auto deployment') + expect(stdout).to.include('Enabling auto destroy') + expect(stdout).to.include('Enabling wait for CI') + expect(stderr).to.include('Configuring pipeline') }) })