diff --git a/packages/cli/test/unit/commands/buildpacks/add.unit.test.ts b/packages/cli/test/unit/commands/buildpacks/add.unit.test.ts new file mode 100644 index 0000000000..c20748b678 --- /dev/null +++ b/packages/cli/test/unit/commands/buildpacks/add.unit.test.ts @@ -0,0 +1,216 @@ +import {Fixture} from '@heroku/buildpack-registry' +import {runCommand} from '@oclif/test' +import {expect} from 'chai' +import nock from 'nock' + +import {BuildpackInstallationsStub as Stubber} from '../../../helpers/buildpacks/buildpack-installations-stub.js' + +describe('buildpacks:add', function () { + let api: nock.Scope + + beforeEach(function () { + api = nock('https://api.heroku.com') + }) + + afterEach(function () { + api.done() + nock.cleanAll() + }) + + describe('URL', function () { + it('# maps buildpack names', async function () { + const registry = new Map() + registry.set('https://buildpack-registry.s3.amazonaws.com/buildpacks/hone/test.tgz', + {name: 'hone/test', url: 'urn:buildpack:hone/test'}) + + Stubber.get(api) + Stubber.put(api, ['https://buildpack-registry.s3.amazonaws.com/buildpacks/hone/test.tgz'], registry) + + const buildpack = Fixture.buildpack({ + blob_url: 'https://buildpack-registry.s3.amazonaws.com/buildpacks/hone/test.tgz', + name: 'test', + namespace: 'hone', + }) + + nock('https://buildpack-registry.heroku.com') + .get('/buildpacks/hone%2Ftest') + .times(2) + .reply(200, buildpack) + + const {stderr, stdout} = await runCommand(['buildpacks:add', 'hone/test', '-a', 'example']) + + expect(stderr).to.equal('') + expect(stdout).to.equal( + `Buildpack added. Next release on example will use hone/test. +Run git push heroku main to create a new release using this buildpack. +`) + }) + + it('# with no buildpacks adds the buildpack URL', async function () { + Stubber.get(api) + Stubber.put(api, ['https://github.com/heroku/heroku-buildpack-ruby']) + + const {stderr, stdout} = await runCommand(['buildpacks:add', 'https://github.com/heroku/heroku-buildpack-ruby', '-a', 'example']) + + expect(stderr).to.equal('') + expect(stdout).to.equal( + `Buildpack added. Next release on example will use https://github.com/heroku/heroku-buildpack-ruby. +Run git push heroku main to create a new release using this buildpack. +`) + }) + + it('# with one existing buildpack adds a buildpack URL to the end of the list', async function () { + Stubber.get(api, ['https://github.com/heroku/heroku-buildpack-java']) + Stubber.put(api, [ + 'https://github.com/heroku/heroku-buildpack-java', + 'https://github.com/heroku/heroku-buildpack-ruby', + ]) + + const {stderr, stdout} = await runCommand(['buildpacks:add', 'https://github.com/heroku/heroku-buildpack-ruby', '-a', 'example']) + + expect(stderr).to.equal('') + expect(stdout).to.equal( + `Buildpack added. Next release on example will use: + 1. https://github.com/heroku/heroku-buildpack-java + 2. https://github.com/heroku/heroku-buildpack-ruby +Run git push heroku main to create a new release using these buildpacks. +`) + }) + + it('# with two existing buildpacks successfully adds a buildpack URL to the end of the list', async function () { + Stubber.get(api, [ + 'https://github.com/heroku/heroku-buildpack-java', + 'https://github.com/heroku/heroku-buildpack-nodejs', + ]) + Stubber.put(api, [ + 'https://github.com/heroku/heroku-buildpack-java', + 'https://github.com/heroku/heroku-buildpack-nodejs', + 'https://github.com/heroku/heroku-buildpack-ruby', + ]) + + const {stderr, stdout} = await runCommand(['buildpacks:add', 'https://github.com/heroku/heroku-buildpack-ruby', '-a', 'example']) + + expect(stderr).to.equal('') + expect(stdout).to.equal( + `Buildpack added. Next release on example will use: + 1. https://github.com/heroku/heroku-buildpack-java + 2. https://github.com/heroku/heroku-buildpack-nodejs + 3. https://github.com/heroku/heroku-buildpack-ruby +Run git push heroku main to create a new release using these buildpacks. +`) + }) + + it('# errors out when already exists', async function () { + Stubber.get(api, ['https://github.com/foobar/foobar']) + + const {error} = await runCommand(['buildpacks:add', 'https://github.com/foobar/foobar', '-a', 'example']) + + expect(error?.message).to.include('The buildpack https://github.com/foobar/foobar is already set on your app.') + }) + + it('# errors out on unmapped codon urls', async function () { + Stubber.get(api, ['https://codon-buildpacks.s3.amazonaws.com/buildpacks/heroku/jvm-common.tgz']) + + const {error} = await runCommand(['buildpacks:add', 'https://codon-buildpacks.s3.amazonaws.com/buildpacks/heroku/jvm-common.tgz', '-a', 'example']) + + expect(error?.message).to.include('The buildpack https://codon-buildpacks.s3.amazonaws.com/buildpacks/heroku/jvm-common.tgz is already set on your app.') + }) + + it('# errors out when already exists urn', async function () { + Stubber.get(api, [ + { + name: 'heroku/ruby', + url: 'urn:buildpack:heroku/ruby', + }, + ]) + + const buildpack = Fixture.buildpack({ + blob_url: 'https://buildpack-registry.s3.amazonaws.com/buildpacks/heroku/ruby.tgz', + name: 'ruby', + namespace: 'heroku', + }) + + nock('https://buildpack-registry.heroku.com') + .get('/buildpacks/heroku%2Fruby') + .reply(200, buildpack) + + const {error} = await runCommand(['buildpacks:add', 'heroku/ruby', '-a', 'example']) + + expect(error?.message).to.include('The buildpack heroku/ruby is already set on your app.') + }) + }) + + describe('-i INDEX URL', function () { + it('# with no buildpacks adds the buildpack URL with index', async function () { + Stubber.get(api) + Stubber.put(api, ['https://github.com/heroku/heroku-buildpack-ruby']) + + const {stderr, stdout} = await runCommand(['buildpacks:add', 'https://github.com/heroku/heroku-buildpack-ruby', '-i', '1', '-a', 'example']) + + expect(stderr).to.equal('') + expect(stdout).to.equal( + `Buildpack added. Next release on example will use https://github.com/heroku/heroku-buildpack-ruby. +Run git push heroku main to create a new release using this buildpack. +`) + }) + + it('# with one existing buildpack inserts a buildpack URL at index', async function () { + Stubber.get(api, ['https://github.com/heroku/heroku-buildpack-java']) + Stubber.put(api, [ + 'https://github.com/heroku/heroku-buildpack-ruby', + 'https://github.com/heroku/heroku-buildpack-java', + ]) + + const {stderr, stdout} = await runCommand(['buildpacks:add', 'https://github.com/heroku/heroku-buildpack-ruby', '-i', '1', '-a', 'example']) + + expect(stderr).to.equal('') + expect(stdout).to.equal( + `Buildpack added. Next release on example will use: + 1. https://github.com/heroku/heroku-buildpack-ruby + 2. https://github.com/heroku/heroku-buildpack-java +Run git push heroku main to create a new release using these buildpacks. +`) + }) + + it('# with two existing buildpacks successfully inserts a buildpack URL at index', async function () { + Stubber.get(api, [ + 'https://github.com/heroku/heroku-buildpack-java', + 'https://github.com/heroku/heroku-buildpack-nodejs', + ]) + Stubber.put(api, [ + 'https://github.com/heroku/heroku-buildpack-java', + 'https://github.com/heroku/heroku-buildpack-ruby', + 'https://github.com/heroku/heroku-buildpack-nodejs', + ]) + + const {stderr, stdout} = await runCommand(['buildpacks:add', 'https://github.com/heroku/heroku-buildpack-ruby', '-i', '2', '-a', 'example']) + + expect(stderr).to.equal('') + expect(stdout).to.equal( + `Buildpack added. Next release on example will use: + 1. https://github.com/heroku/heroku-buildpack-java + 2. https://github.com/heroku/heroku-buildpack-ruby + 3. https://github.com/heroku/heroku-buildpack-nodejs +Run git push heroku main to create a new release using these buildpacks. +`) + }) + + it('# returns an error message when i is not an integer', async function () { + const {error} = await runCommand(['buildpacks:add', 'https://github.com/heroku/heroku-buildpack-ruby', '-i', 'notinteger', '-a', 'example']) + + expect(error?.message).to.include('Parsing --index \n\tExpected an integer but received: notinteger\nSee more help with --help') + }) + + it('# returns an error message when i < 0', async function () { + const {error} = await runCommand(['buildpacks:add', 'https://github.com/heroku/heroku-buildpack-ruby', '-i', '-1', '-a', 'example']) + + expect(error?.message).to.include('Invalid index. Must be greater than 0.') + }) + + it('# returns an error message when i == 0', async function () { + const {error} = await runCommand(['buildpacks:add', 'https://github.com/heroku/heroku-buildpack-ruby', '-i', '0', '-a', 'example']) + + expect(error?.message).to.include('Invalid index. Must be greater than 0.') + }) + }) +}) diff --git a/packages/cli/test/unit/commands/buildpacks/add.unit.test.ts.skip b/packages/cli/test/unit/commands/buildpacks/add.unit.test.ts.skip deleted file mode 100644 index 05f24ccd0c..0000000000 --- a/packages/cli/test/unit/commands/buildpacks/add.unit.test.ts.skip +++ /dev/null @@ -1,229 +0,0 @@ -import {Fixture} from '@heroku/buildpack-registry' -import {expect, test} from '@oclif/test' -import nock from 'nock' - -import {BuildpackInstallationsStub as Stubber} from '../../../helpers/buildpacks/buildpack-installations-stub.js' - -describe('buildpacks:add', function () { - describe('URL', function () { - test - .nock('https://api.heroku.com', (api: nock.Scope) => { - const registry = new Map() - registry.set('https://buildpack-registry.s3.amazonaws.com/buildpacks/hone/test.tgz', {url: 'urn:buildpack:hone/test', name: 'hone/test'}) - - Stubber.get(api) - Stubber.put(api, ['https://buildpack-registry.s3.amazonaws.com/buildpacks/hone/test.tgz'], registry) - }) - .nock('https://buildpack-registry.heroku.com', (api: nock.Scope) => { - const buildpack = Fixture.buildpack({ - namespace: 'hone', - name: 'test', - blob_url: 'https://buildpack-registry.s3.amazonaws.com/buildpacks/hone/test.tgz', - }) - - api - .get('/buildpacks/hone%2Ftest') - .times(2) - .reply(200, buildpack) - }) - .stdout() - .stderr() - .command(['buildpacks:add', 'hone/test', '-a', 'example']) - .it('# maps buildpack names', ctx => { - // TODO: On the upgrade to Node 12 this produced an error related to - // an older version of nock used by fancy-nock - // ctx.stderr contained: 'OutgoingMessage.prototype._headers is deprecated' - expect(ctx.stderr).to.equal('') - - expect(ctx.stdout).to.equal( - `Buildpack added. Next release on example will use hone/test. -Run git push heroku main to create a new release using this buildpack. -`) - }) - - test - .nock('https://api.heroku.com', (api: nock.Scope) => { - Stubber.get(api) - Stubber.put(api, ['https://github.com/heroku/heroku-buildpack-ruby']) - }) - .stdout() - .stderr() - .command(['buildpacks:add', 'https://github.com/heroku/heroku-buildpack-ruby', '-a', 'example']) - .it('# with no buildpacks adds the buildpack URL', ctx => { - expect(ctx.stderr).to.equal('') - expect(ctx.stdout).to.equal( - `Buildpack added. Next release on example will use https://github.com/heroku/heroku-buildpack-ruby. -Run git push heroku main to create a new release using this buildpack. -`) - }) - - test - .nock('https://api.heroku.com', (api: nock.Scope) => { - Stubber.get(api, ['https://github.com/heroku/heroku-buildpack-java']) - Stubber.put(api, [ - 'https://github.com/heroku/heroku-buildpack-java', - 'https://github.com/heroku/heroku-buildpack-ruby', - ]) - }) - .stdout() - .stderr() - .command(['buildpacks:add', 'https://github.com/heroku/heroku-buildpack-ruby', '-a', 'example']) - .it('# with one existing buildpack adds a buildpack URL to the end of the list', ctx => { - expect(ctx.stderr).to.equal('') - expect(ctx.stdout).to.equal( - `Buildpack added. Next release on example will use: - 1. https://github.com/heroku/heroku-buildpack-java - 2. https://github.com/heroku/heroku-buildpack-ruby -Run git push heroku main to create a new release using these buildpacks. -`) - }) - - test - .nock('https://api.heroku.com', (api: nock.Scope) => { - Stubber.get(api, [ - 'https://github.com/heroku/heroku-buildpack-java', - 'https://github.com/heroku/heroku-buildpack-nodejs', - ]) - Stubber.put(api, [ - 'https://github.com/heroku/heroku-buildpack-java', - 'https://github.com/heroku/heroku-buildpack-nodejs', - 'https://github.com/heroku/heroku-buildpack-ruby', - ]) - }) - .stdout() - .stderr() - .command(['buildpacks:add', 'https://github.com/heroku/heroku-buildpack-ruby', '-a', 'example']) - .it('# with two existing buildpacks successfully adds a buildpack URL to the end of the list', ctx => { - expect(ctx.stderr).to.equal('') - expect(ctx.stdout).to.equal( - `Buildpack added. Next release on example will use: - 1. https://github.com/heroku/heroku-buildpack-java - 2. https://github.com/heroku/heroku-buildpack-nodejs - 3. https://github.com/heroku/heroku-buildpack-ruby -Run git push heroku main to create a new release using these buildpacks. -`) - }) - - test - .nock('https://api.heroku.com', (api: nock.Scope) => { - Stubber.get(api, ['https://github.com/foobar/foobar']) - }) - .stderr() - .command(['buildpacks:add', 'https://github.com/foobar/foobar', '-a', 'example']) - .catch('The buildpack https://github.com/foobar/foobar is already set on your app.') - .it('# errors out when already exists') - - test - .nock('https://api.heroku.com', (api: nock.Scope) => { - Stubber.get(api, ['https://codon-buildpacks.s3.amazonaws.com/buildpacks/heroku/jvm-common.tgz']) - }) - .stderr() - .command(['buildpacks:add', 'https://codon-buildpacks.s3.amazonaws.com/buildpacks/heroku/jvm-common.tgz', '-a', 'example']) - .catch('The buildpack https://codon-buildpacks.s3.amazonaws.com/buildpacks/heroku/jvm-common.tgz is already set on your app.') - .it('# errors out on unmapped codon urls') - - test - .nock('https://api.heroku.com', (api: nock.Scope) => { - Stubber.get(api, [ - { - url: 'urn:buildpack:heroku/ruby', - name: 'heroku/ruby', - }, - ]) - }) - .nock('https://buildpack-registry.heroku.com', (api: nock.Scope) => { - const buildpack = Fixture.buildpack({ - namespace: 'heroku', - name: 'ruby', - blob_url: 'https://buildpack-registry.s3.amazonaws.com/buildpacks/heroku/ruby.tgz', - }) - - api - .get('/buildpacks/heroku%2Fruby') - .reply(200, buildpack) - }) - .command(['buildpacks:add', 'heroku/ruby', '-a', 'example']) - .catch('The buildpack heroku/ruby is already set on your app.') - .it('# errors out when already exists urn') - }) - - describe('-i INDEX URL', function () { - test - .nock('https://api.heroku.com', (api: nock.Scope) => { - Stubber.get(api) - Stubber.put(api, ['https://github.com/heroku/heroku-buildpack-ruby']) - }) - .stdout() - .stderr() - .command(['buildpacks:add', 'https://github.com/heroku/heroku-buildpack-ruby', '-i', '1', '-a', 'example']) - .it('# with no buildpacks adds the buildpack URL with index', ctx => { - expect(ctx.stderr).to.equal('') - expect(ctx.stdout).to.equal( - `Buildpack added. Next release on example will use https://github.com/heroku/heroku-buildpack-ruby. -Run git push heroku main to create a new release using this buildpack. -`) - }) - - test - .nock('https://api.heroku.com', (api: nock.Scope) => { - Stubber.get(api, ['https://github.com/heroku/heroku-buildpack-java']) - Stubber.put(api, [ - 'https://github.com/heroku/heroku-buildpack-ruby', - 'https://github.com/heroku/heroku-buildpack-java', - ]) - }) - .stdout() - .stderr() - .command(['buildpacks:add', 'https://github.com/heroku/heroku-buildpack-ruby', '-i', '1', '-a', 'example']) - .it('# with one existing buildpack inserts a buildpack URL at index', ctx => { - expect(ctx.stderr).to.equal('') - expect(ctx.stdout).to.equal( - `Buildpack added. Next release on example will use: - 1. https://github.com/heroku/heroku-buildpack-ruby - 2. https://github.com/heroku/heroku-buildpack-java -Run git push heroku main to create a new release using these buildpacks. -`) - }) - - test - .nock('https://api.heroku.com', (api: nock.Scope) => { - Stubber.get(api, [ - 'https://github.com/heroku/heroku-buildpack-java', - 'https://github.com/heroku/heroku-buildpack-nodejs', - ]) - Stubber.put(api, [ - 'https://github.com/heroku/heroku-buildpack-java', - 'https://github.com/heroku/heroku-buildpack-ruby', - 'https://github.com/heroku/heroku-buildpack-nodejs', - ]) - }) - .stdout() - .stderr() - .command(['buildpacks:add', 'https://github.com/heroku/heroku-buildpack-ruby', '-i', '2', '-a', 'example']) - .it('# with two existing buildpacks successfully inserts a buildpack URL at index', ctx => { - expect(ctx.stderr).to.equal('') - expect(ctx.stdout).to.equal( - `Buildpack added. Next release on example will use: - 1. https://github.com/heroku/heroku-buildpack-java - 2. https://github.com/heroku/heroku-buildpack-ruby - 3. https://github.com/heroku/heroku-buildpack-nodejs -Run git push heroku main to create a new release using these buildpacks. -`) - }) - - test - .command(['buildpacks:add', 'https://github.com/heroku/heroku-buildpack-ruby', '-i', 'notinteger', '-a', 'example']) - .catch('Parsing --index \n\tExpected an integer but received: notinteger\nSee more help with --help') - .it('# returns an error message when i is not an integer') - - test - .command(['buildpacks:add', 'https://github.com/heroku/heroku-buildpack-ruby', '-i', '-1', '-a', 'example']) - .catch('Invalid index. Must be greater than 0.') - .it('# returns an error message when i < 0') - - test - .command(['buildpacks:add', 'https://github.com/heroku/heroku-buildpack-ruby', '-i', '0', '-a', 'example']) - .catch('Invalid index. Must be greater than 0.') - .it('# returns an error message when i == 0') - }) -}) diff --git a/packages/cli/test/unit/commands/buildpacks/clear.unit.test.ts b/packages/cli/test/unit/commands/buildpacks/clear.unit.test.ts new file mode 100644 index 0000000000..4300b4fae5 --- /dev/null +++ b/packages/cli/test/unit/commands/buildpacks/clear.unit.test.ts @@ -0,0 +1,54 @@ +import {runCommand} from '@oclif/test' +import {expect} from 'chai' +import nock from 'nock' + +import {BuildpackInstallationsStub as Stubber} from '../../../helpers/buildpacks/buildpack-installations-stub.js' +import {unwrap} from '../../../helpers/utils/unwrap.js' + +describe('buildpacks:clear', function () { + let api: nock.Scope + + beforeEach(function () { + api = nock('https://api.heroku.com') + }) + + afterEach(function () { + api.done() + nock.cleanAll() + }) + + it('# clears the buildpack URL', async function () { + Stubber.put(api) + api + .get('/apps/example/config-vars') + .reply(200, {}) + + const {stdout} = await runCommand(['buildpacks:clear', '-a', 'example']) + + expect(stdout).to.equal('Buildpacks cleared. Next release on example will detect buildpacks normally.\n') + }) + + it('# clears and warns about buildpack URL config var', async function () { + Stubber.put(api) + api + .get('/apps/example/config-vars') + .reply(200, {BUILDPACK_URL: 'https://github.com/foo/foo'}) + + const {stderr, stdout} = await runCommand(['buildpacks:clear', '-a', 'example']) + + expect(stdout).to.equal('Buildpacks cleared.\n') + expect(unwrap(stderr)).to.equal('Warning: The BUILDPACK_URL config var is still set and will be used for the next release\n') + }) + + it('# clears and warns about language pack URL config var', async function () { + Stubber.put(api) + api + .get('/apps/example/config-vars') + .reply(200, {LANGUAGE_PACK_URL: 'https://github.com/foo/foo'}) + + const {stderr, stdout} = await runCommand(['buildpacks:clear', '-a', 'example']) + + expect(stdout).to.equal('Buildpacks cleared.\n') + expect(unwrap(stderr)).to.equal('Warning: The LANGUAGE_PACK_URL config var is still set and will be used for the next release\n') + }) +}) diff --git a/packages/cli/test/unit/commands/buildpacks/clear.unit.test.ts.skip b/packages/cli/test/unit/commands/buildpacks/clear.unit.test.ts.skip deleted file mode 100644 index 502b281df2..0000000000 --- a/packages/cli/test/unit/commands/buildpacks/clear.unit.test.ts.skip +++ /dev/null @@ -1,52 +0,0 @@ -import {expect, test} from '@oclif/test' -import nock from 'nock' - -import {BuildpackInstallationsStub as Stubber} from '../../../helpers/buildpacks/buildpack-installations-stub.js' -import {unwrap} from '../../../helpers/utils/unwrap.js' - -describe('buildpacks:clear', function () { - test - .nock('https://api.heroku.com', (api: nock.Scope) => { - Stubber.put(api) - api - .get('/apps/example/config-vars') - .reply(200, {}) - }) - .stdout() - .command(['buildpacks:clear', '-a', 'example']) - .it('# clears the buildpack URL', ctx => { - nock('https://api.heroku.com') - - expect(ctx.stdout).to.equal('Buildpacks cleared. Next release on example will detect buildpacks normally.\n') - }) - - test - .nock('https://api.heroku.com', (api: nock.Scope) => { - Stubber.put(api) - api - .get('/apps/example/config-vars') - .reply(200, {BUILDPACK_URL: 'https://github.com/foo/foo'}) - }) - .stdout() - .stderr() - .command(['buildpacks:clear', '-a', 'example']) - .it('# clears and warns about buildpack URL config var', ctx => { - expect(ctx.stdout).to.equal('Buildpacks cleared.\n') - expect(unwrap(ctx.stderr)).to.equal('Warning: The BUILDPACK_URL config var is still set and will be used for the next release\n') - }) - - test - .nock('https://api.heroku.com', (api: nock.Scope) => { - Stubber.put(api) - api - .get('/apps/example/config-vars') - .reply(200, {LANGUAGE_PACK_URL: 'https://github.com/foo/foo'}) - }) - .stdout() - .stderr() - .command(['buildpacks:clear', '-a', 'example']) - .it('# clears and warns about language pack URL config var', ctx => { - expect(ctx.stdout).to.equal('Buildpacks cleared.\n') - expect(unwrap(ctx.stderr)).to.equal('Warning: The LANGUAGE_PACK_URL config var is still set and will be used for the next release\n') - }) -}) diff --git a/packages/cli/test/unit/commands/buildpacks/index.unit.test.ts b/packages/cli/test/unit/commands/buildpacks/index.unit.test.ts new file mode 100644 index 0000000000..750bdcf7b9 --- /dev/null +++ b/packages/cli/test/unit/commands/buildpacks/index.unit.test.ts @@ -0,0 +1,299 @@ +import {runCommand} from '@oclif/test' +import {expect} from 'chai' +import nock from 'nock' +import tsheredoc from 'tsheredoc' +const heredoc = tsheredoc.default + +import {BuildpackInstallationsStub as Stubber} from '../../../helpers/buildpacks/buildpack-installations-stub.js' + +const cedarApp = { + acm: false, + archived_at: null, + build_stack: {name: 'heroku-24'}, + created_at: '2024-09-06T17:45:29Z', + generation: 'cedar', + git_url: 'https://git.heroku.com', + id: '12345678-aaaa-bbbb-cccc-b2443790f501', + internal_routing: null, + maintenance: false, + name: 'example', + owner: {email: 'example-owner@heroku.com'}, + region: {name: 'virginia'}, + released_at: '2024-11-13T20:07:47Z', + repo_size: null, + slug_size: null, + stack: {name: 'heroku-24'}, + updated_at: '2024-11-13T20:07:47Z', + web_url: 'https://cedar-example-app.herokuapp.com', +} + +const firApp = { + acm: false, + archived_at: null, + build_stack: {name: 'heroku-24'}, + created_at: '2024-09-06T17:45:29Z', + generation: 'fir', + git_url: 'https://git.heroku.com', + id: '12345678-aaaa-bbbb-cccc-b2443790f501', + internal_routing: null, + maintenance: false, + name: 'example', + owner: {email: 'example-owner@heroku.com'}, + region: {name: 'virginia'}, + released_at: '2024-11-13T20:07:47Z', + repo_size: null, + slug_size: null, + stack: {name: 'heroku-24'}, + updated_at: '2024-11-13T20:07:47Z', + web_url: 'https://fir-example-app.herokuapp.com', +} + +const releases = [ + { + addon_plan_names: [ + 'heroku-postgresql:dev', + ], + app: { + id: '01234567-89ab-cdef-0123-456789abcdef', + name: 'example', + }, + artifacts: [ + { + id: '01234567-89ab-cdef-0123-456789abcdef', + type: 'oci-image', + }, + ], + created_at: '2012-01-01T12:00:00Z', + current: true, + description: 'Added new feature', + eligible_for_rollback: true, + id: '01234567-89ab-cdef-0123-456789abcdef', + oci_image: { + id: '01234567-89ab-cdef-0123-456789abcdef', + }, + output_stream_url: 'https://release-output.heroku.com/streams/01234567-89ab-cdef-0123-456789abcdef', + slug: { + id: '01234567-89ab-cdef-0123-456789abcdef', + }, + status: 'succeeded', + updated_at: '2012-01-01T12:00:00Z', + user: { + email: 'username@example.com', + id: '01234567-89ab-cdef-0123-456789abcdef', + }, + version: 11, + }, +] + +const ociImages = [ + { + architecture: 'arm64', + base_image_name: 'heroku/heroku:22-cnb', + base_top_layer: 'sha256:ea36ae5fbc1e7230e0a782bf216fb46500e210382703baa6bab8acf2c6a23f78', + buildpacks: [ + { + homepage: 'https://github.com/heroku/buildpacks-ruby', + id: 'heroku/ruby', + version: '2.0.0', + }, + ], + commit: '60883d9e8947a57e04dc9124f25df004866a2051', + commit_description: 'fixed a bug with API documentation', + created_at: '2012-01-01T12:00:00Z', + digest: 'sha256:dc14ae5fbc1e7230e0a782bf216fb46500e210631703bcc6bab8acf2c6a23f42', + id: '01234567-89ab-cdef-0123-456789abcdef', + image_repo: 'd7ba1ace-b396-4691-968c-37ae53153426/builds', + process_types: { + web: { + command: '/cnb/process/web', + default: true, + display_cmd: 'bundle exec puma -p $PORT', + name: 'web', + working_dir: '/workspace/webapp', + }, + }, + stack: { + id: 'ba46bf09-7bd1-42fd-90df-a1a9a93eb4a2', + name: 'cnb', + }, + updated_at: '2012-01-01T12:00:00Z', + }, +] + +describe('buildpacks', function () { + let api: nock.Scope + + beforeEach(function () { + api = nock('https://api.heroku.com') + }) + + afterEach(function () { + api.done() + nock.cleanAll() + }) + + it('# displays the buildpack URL', async function () { + api.get(`/apps/${cedarApp.name}`).reply(200, cedarApp) + Stubber.get(api, ['https://github.com/heroku/heroku-buildpack-ruby']) + + const {stderr, stdout} = await runCommand(['buildpacks', '-a', cedarApp.name]) + + expect(stderr).to.equal('') + expect(stdout).to.equal(heredoc(` + === ⬢ ${cedarApp.name} Classic Buildpack (from the Heroku Buildpack Registry) + + https://github.com/heroku/heroku-buildpack-ruby + `)) + }) + + it('# maps buildpack urns to names', async function () { + api.get(`/apps/${cedarApp.name}`).reply(200, cedarApp) + Stubber.get(api, [{name: 'heroku/ruby', url: 'urn:buildpack:heroku/ruby'}]) + + const {stderr, stdout} = await runCommand(['buildpacks', '-a', cedarApp.name]) + + expect(stderr).to.equal('') + expect(stdout).to.equal(heredoc(` + === ⬢ ${cedarApp.name} Classic Buildpack (from the Heroku Buildpack Registry) + + heroku/ruby + `)) + }) + + it('# does not map buildpack s3 to names', async function () { + api.get(`/apps/${cedarApp.name}`).reply(200, cedarApp) + Stubber.get(api, ['https://codon-buildpacks.s3.amazonaws.com/buildpacks/heroku/ruby.tgz']) + + const {stderr, stdout} = await runCommand(['buildpacks', '-a', cedarApp.name]) + + expect(stderr).to.equal('') + expect(stdout).to.equal(heredoc(` + === ⬢ ${cedarApp.name} Classic Buildpack (from the Heroku Buildpack Registry) + + https://codon-buildpacks.s3.amazonaws.com/buildpacks/heroku/ruby.tgz + `)) + }) + + it('# with no buildpack URL set does not display a buildpack URL', async function () { + const api = nock('https://api.heroku.com') + api.get(`/apps/${cedarApp.name}`).reply(200, cedarApp) + Stubber.get(api) + + const {stderr, stdout} = await runCommand(['buildpacks', '-a', cedarApp.name]) + + expect(stderr).to.equal('') + expect(stdout).to.equal(heredoc(` + ⬢ ${cedarApp.name} has no Buildpacks. + `)) + }) + + it('# with two buildpack URLs set displays the buildpack URL', async function () { + const api = nock('https://api.heroku.com') + api.get(`/apps/${cedarApp.name}`).reply(200, cedarApp) + Stubber.get(api, [ + 'https://github.com/heroku/heroku-buildpack-java', + 'https://github.com/heroku/heroku-buildpack-ruby', + ]) + + const {stderr, stdout} = await runCommand(['buildpacks', '-a', cedarApp.name]) + + expect(stderr).to.equal('') + expect(stdout).to.equal(heredoc(` + === ⬢ ${cedarApp.name} Classic Buildpacks (from the Heroku Buildpack Registry) + + 1. https://github.com/heroku/heroku-buildpack-java + 2. https://github.com/heroku/heroku-buildpack-ruby + `)) + }) + + it('# returns the buildpack registry name back', async function () { + api.get(`/apps/${cedarApp.name}`).reply(200, cedarApp) + Stubber.get(api, [ + 'https://buildpack-registry.s3.amazonaws.com/buildpacks/heroku/java.tgz', + 'https://buildpack-registry.s3.amazonaws.com/buildpacks/rust-lang/rust.tgz', + ]) + + const {stderr, stdout} = await runCommand(['buildpacks', '-a', cedarApp.name]) + + expect(stderr).to.equal('') + expect(stdout).to.equal(heredoc(` + === ⬢ ${cedarApp.name} Classic Buildpacks (from the Heroku Buildpack Registry) + + 1. heroku/java + 2. rust-lang/rust + `)) + }) + + it('# displays the buildpack URL with classic buildpack source', async function () { + api.get(`/apps/${cedarApp.name}`).reply(200, cedarApp) + Stubber.get(api, ['https://github.com/heroku/heroku-buildpack-ruby']) + + const {stderr, stdout} = await runCommand(['buildpacks', '-a', cedarApp.name]) + + expect(stderr).to.equal('') + expect(stdout).to.equal(heredoc(` + === ⬢ ${cedarApp.name} Classic Buildpack (from the Heroku Buildpack Registry) + + https://github.com/heroku/heroku-buildpack-ruby + `)) + }) + + it('# returns cnb buildpack ids for fir apps with OCI source', async function () { + nock('https://api.heroku.com', { + reqheaders: {accept: 'application/vnd.heroku+json; version=3.sdk'}, + }) + .get(`/apps/${firApp.name}`).reply(200, firApp) + .get(`/apps/${firApp.name}/releases`).reply(200, releases) + .get(`/apps/${firApp.name}/oci-images/${releases[0].id}`).reply(200, ociImages) + + const {stderr, stdout} = await runCommand(['buildpacks', '-a', firApp.name]) + + expect(stderr).to.equal('') + expect(stdout).to.equal(heredoc(` + === ⬢ ${firApp.name} Cloud Native Buildpack (from the latest release's OCI image) + + heroku/ruby + `)) + }) + + it('# with multiple buildpack URLs shows plural form and source', async function () { + api.get(`/apps/${cedarApp.name}`).reply(200, cedarApp) + Stubber.get(api, [ + 'https://github.com/heroku/heroku-buildpack-java', + 'https://github.com/heroku/heroku-buildpack-ruby', + ]) + + const {stderr, stdout} = await runCommand(['buildpacks', '-a', cedarApp.name]) + + expect(stderr).to.equal('') + expect(stdout).to.equal(heredoc(` + === ⬢ ${cedarApp.name} Classic Buildpacks (from the Heroku Buildpack Registry) + + 1. https://github.com/heroku/heroku-buildpack-java + 2. https://github.com/heroku/heroku-buildpack-ruby + `)) + }) + + it('# with no buildpack URL set shows appropriate message', async function () { + api.get(`/apps/${cedarApp.name}`).reply(200, cedarApp) + Stubber.get(api) + + const {stderr, stdout} = await runCommand(['buildpacks', '-a', cedarApp.name]) + + expect(stderr).to.equal('') + expect(stdout).to.equal(`⬢ ${cedarApp.name} has no Buildpacks.\n`) + }) + + it('# returns nothing when no releases for fir app', async function () { + nock('https://api.heroku.com', { + reqheaders: {accept: 'application/vnd.heroku+json; version=3.sdk'}, + }) + .get(`/apps/${firApp.name}`).reply(200, firApp) + .get(`/apps/${firApp.name}/releases`).reply(200, []) + + const {stderr, stdout} = await runCommand(['buildpacks', '-a', firApp.name]) + + expect(stderr).to.equal('') + expect(stdout).to.equal(`⬢ ${firApp.name} has no Buildpacks.\n`) + }) +}) diff --git a/packages/cli/test/unit/commands/buildpacks/index.unit.test.ts.skip b/packages/cli/test/unit/commands/buildpacks/index.unit.test.ts.skip deleted file mode 100644 index f6b2f04d19..0000000000 --- a/packages/cli/test/unit/commands/buildpacks/index.unit.test.ts.skip +++ /dev/null @@ -1,318 +0,0 @@ -/* eslint-disable mocha/no-setup-in-describe */ -import {expect, test} from '@oclif/test' -import nock from 'nock' - -import tsheredoc from 'tsheredoc' -const heredoc = tsheredoc.default - -import {BuildpackInstallationsStub as Stubber} from '../../../helpers/buildpacks/buildpack-installations-stub.js' - -const cedarApp = { - acm: false, - archived_at: null, - build_stack: {name: 'heroku-24'}, - created_at: '2024-09-06T17:45:29Z', - git_url: 'https://git.heroku.com', - id: '12345678-aaaa-bbbb-cccc-b2443790f501', - generation: 'cedar', - maintenance: false, - name: 'example', - owner: {email: 'example-owner@heroku.com'}, - internal_routing: null, - region: {name: 'virginia'}, - released_at: '2024-11-13T20:07:47Z', - repo_size: null, - slug_size: null, - stack: {name: 'heroku-24'}, - updated_at: '2024-11-13T20:07:47Z', - web_url: 'https://cedar-example-app.herokuapp.com', -} - -const firApp = { - acm: false, - archived_at: null, - build_stack: {name: 'heroku-24'}, - created_at: '2024-09-06T17:45:29Z', - generation: 'fir', - git_url: 'https://git.heroku.com', - id: '12345678-aaaa-bbbb-cccc-b2443790f501', - maintenance: false, - name: 'example', - owner: {email: 'example-owner@heroku.com'}, - internal_routing: null, - region: {name: 'virginia'}, - released_at: '2024-11-13T20:07:47Z', - repo_size: null, - slug_size: null, - stack: {name: 'heroku-24'}, - updated_at: '2024-11-13T20:07:47Z', - web_url: 'https://fir-example-app.herokuapp.com', -} - -const releases = [ - { - addon_plan_names: [ - 'heroku-postgresql:dev', - ], - artifacts: [ - { - type: 'oci-image', - id: '01234567-89ab-cdef-0123-456789abcdef', - }, - ], - app: { - name: 'example', - id: '01234567-89ab-cdef-0123-456789abcdef', - }, - created_at: '2012-01-01T12:00:00Z', - description: 'Added new feature', - id: '01234567-89ab-cdef-0123-456789abcdef', - updated_at: '2012-01-01T12:00:00Z', - oci_image: { - id: '01234567-89ab-cdef-0123-456789abcdef', - }, - slug: { - id: '01234567-89ab-cdef-0123-456789abcdef', - }, - status: 'succeeded', - user: { - id: '01234567-89ab-cdef-0123-456789abcdef', - email: 'username@example.com', - }, - version: 11, - current: true, - output_stream_url: 'https://release-output.heroku.com/streams/01234567-89ab-cdef-0123-456789abcdef', - eligible_for_rollback: true, - }, -] - -const ociImages = [ - { - id: '01234567-89ab-cdef-0123-456789abcdef', - base_image_name: 'heroku/heroku:22-cnb', - base_top_layer: 'sha256:ea36ae5fbc1e7230e0a782bf216fb46500e210382703baa6bab8acf2c6a23f78', - commit: '60883d9e8947a57e04dc9124f25df004866a2051', - commit_description: 'fixed a bug with API documentation', - image_repo: 'd7ba1ace-b396-4691-968c-37ae53153426/builds', - digest: 'sha256:dc14ae5fbc1e7230e0a782bf216fb46500e210631703bcc6bab8acf2c6a23f42', - stack: { - id: 'ba46bf09-7bd1-42fd-90df-a1a9a93eb4a2', - name: 'cnb', - }, - process_types: { - web: { - name: 'web', - display_cmd: 'bundle exec puma -p $PORT', - command: '/cnb/process/web', - working_dir: '/workspace/webapp', - default: true, - }, - }, - buildpacks: [ - { - id: 'heroku/ruby', - version: '2.0.0', - homepage: 'https://github.com/heroku/buildpacks-ruby', - }, - ], - created_at: '2012-01-01T12:00:00Z', - updated_at: '2012-01-01T12:00:00Z', - architecture: 'arm64', - }, -] - -describe('buildpacks', function () { - test - .nock('https://api.heroku.com', (api: nock.Scope) => { - api.get(`/apps/${cedarApp.name}`).reply(200, cedarApp) - Stubber.get(api, ['https://github.com/heroku/heroku-buildpack-ruby']) - }) - .stdout() - .stderr() - .command(['buildpacks', '-a', cedarApp.name]) - .it('# displays the buildpack URL', ctx => { - expect(ctx.stderr).to.equal('') - expect(ctx.stdout).to.equal(heredoc(` - === ⬢ ${cedarApp.name} Classic Buildpack (from the Heroku Buildpack Registry) - - https://github.com/heroku/heroku-buildpack-ruby - `)) - }) - - test - .nock('https://api.heroku.com', (api: nock.Scope) => { - api.get(`/apps/${cedarApp.name}`).reply(200, cedarApp) - Stubber.get(api, [{url: 'urn:buildpack:heroku/ruby', name: 'heroku/ruby'}]) - }) - .stdout() - .stderr() - .command(['buildpacks', '-a', cedarApp.name]) - .it('# maps buildpack urns to names', ctx => { - expect(ctx.stderr).to.equal('') - expect(ctx.stdout).to.equal(heredoc(` - === ⬢ ${cedarApp.name} Classic Buildpack (from the Heroku Buildpack Registry) - - heroku/ruby - `)) - }) - - test - .nock('https://api.heroku.com', (api: nock.Scope) => { - api.get(`/apps/${cedarApp.name}`).reply(200, cedarApp) - Stubber.get(api, ['https://codon-buildpacks.s3.amazonaws.com/buildpacks/heroku/ruby.tgz']) - }) - .stdout() - .stderr() - .command(['buildpacks', '-a', cedarApp.name]) - .it('# does not map buildpack s3 to names', ctx => { - expect(ctx.stderr).to.equal('') - expect(ctx.stdout).to.equal(heredoc(` - === ⬢ ${cedarApp.name} Classic Buildpack (from the Heroku Buildpack Registry) - - https://codon-buildpacks.s3.amazonaws.com/buildpacks/heroku/ruby.tgz - `)) - }) - - test - .nock('https://api.heroku.com', (api: nock.Scope) => { - api.get(`/apps/${cedarApp.name}`).reply(200, cedarApp) - Stubber.get(api) - }) - .stdout() - .stderr() - .command(['buildpacks', '-a', cedarApp.name]) - .it('# with no buildpack URL set does not display a buildpack URL', ctx => { - expect(ctx.stderr).to.equal('') - expect(ctx.stdout).to.equal(heredoc(` - ⬢ ${cedarApp.name} has no Buildpacks. - `)) - }) - - test - .nock('https://api.heroku.com', (api: nock.Scope) => { - api.get(`/apps/${cedarApp.name}`).reply(200, cedarApp) - Stubber.get(api, [ - 'https://github.com/heroku/heroku-buildpack-java', - 'https://github.com/heroku/heroku-buildpack-ruby', - ]) - }) - .stdout() - .stderr() - .command(['buildpacks', '-a', cedarApp.name]) - .it('# with two buildpack URLs set displays the buildpack URL', ctx => { - expect(ctx.stderr).to.equal('') - expect(ctx.stdout).to.equal(heredoc(` - === ⬢ ${cedarApp.name} Classic Buildpacks (from the Heroku Buildpack Registry) - - 1. https://github.com/heroku/heroku-buildpack-java - 2. https://github.com/heroku/heroku-buildpack-ruby - `)) - }) - - test - .nock('https://api.heroku.com', (api: nock.Scope) => { - api.get(`/apps/${cedarApp.name}`).reply(200, cedarApp) - Stubber.get(api, [ - 'https://buildpack-registry.s3.amazonaws.com/buildpacks/heroku/java.tgz', - 'https://buildpack-registry.s3.amazonaws.com/buildpacks/rust-lang/rust.tgz', - ]) - }) - .stdout() - .stderr() - .command(['buildpacks', '-a', cedarApp.name]) - .it('# returns the buildpack registry name back', ctx => { - expect(ctx.stderr).to.equal('') - expect(ctx.stdout).to.equal(heredoc(` - === ⬢ ${cedarApp.name} Classic Buildpacks (from the Heroku Buildpack Registry) - - 1. heroku/java - 2. rust-lang/rust - `)) - }) - - test - .nock('https://api.heroku.com', (api: nock.Scope) => { - api.get(`/apps/${cedarApp.name}`).reply(200, cedarApp) - Stubber.get(api, ['https://github.com/heroku/heroku-buildpack-ruby']) - }) - .stdout() - .stderr() - .command(['buildpacks', '-a', cedarApp.name]) - .it('# displays the buildpack URL with classic buildpack source', ctx => { - expect(ctx.stderr).to.equal('') - expect(ctx.stdout).to.equal(heredoc(` - === ⬢ ${cedarApp.name} Classic Buildpack (from the Heroku Buildpack Registry) - - https://github.com/heroku/heroku-buildpack-ruby - `)) - }) - - test - .nock('https://api.heroku.com', { - reqheaders: {accept: 'application/vnd.heroku+json; version=3.sdk'}, - }, (api: nock.Scope) => { - api.get(`/apps/${firApp.name}`).reply(200, firApp) - api.get(`/apps/${firApp.name}/releases`).reply(200, releases) - api.get(`/apps/${firApp.name}/oci-images/${releases[0].id}`).reply(200, ociImages) - }) - .stdout() - .stderr() - .command(['buildpacks', '-a', firApp.name]) - .it('# returns cnb buildpack ids for fir apps with OCI source', ctx => { - expect(ctx.stderr).to.equal('') - expect(ctx.stdout).to.equal(heredoc(` - === ⬢ ${firApp.name} Cloud Native Buildpack (from the latest release's OCI image) - - heroku/ruby - `)) - }) - - test - .nock('https://api.heroku.com', (api: nock.Scope) => { - api.get(`/apps/${cedarApp.name}`).reply(200, cedarApp) - Stubber.get(api, [ - 'https://github.com/heroku/heroku-buildpack-java', - 'https://github.com/heroku/heroku-buildpack-ruby', - ]) - }) - .stdout() - .stderr() - .command(['buildpacks', '-a', cedarApp.name]) - .it('# with multiple buildpack URLs shows plural form and source', ctx => { - expect(ctx.stderr).to.equal('') - expect(ctx.stdout).to.equal(heredoc(` - === ⬢ ${cedarApp.name} Classic Buildpacks (from the Heroku Buildpack Registry) - - 1. https://github.com/heroku/heroku-buildpack-java - 2. https://github.com/heroku/heroku-buildpack-ruby - `)) - }) - - test - .nock('https://api.heroku.com', (api: nock.Scope) => { - api.get(`/apps/${cedarApp.name}`).reply(200, cedarApp) - Stubber.get(api) - }) - .stdout() - .stderr() - .command(['buildpacks', '-a', cedarApp.name]) - .it('# with no buildpack URL set shows appropriate message', ctx => { - expect(ctx.stderr).to.equal('') - expect(ctx.stdout).to.equal(`⬢ ${cedarApp.name} has no Buildpacks.\n`) - }) - - test - .nock('https://api.heroku.com', { - reqheaders: {accept: 'application/vnd.heroku+json; version=3.sdk'}, - }, (api: nock.Scope) => { - api.get(`/apps/${firApp.name}`).reply(200, firApp) - api.get(`/apps/${firApp.name}/releases`).reply(200, []) - }) - .stdout() - .stderr() - .command(['buildpacks', '-a', firApp.name]) - .it('# returns nothing when no releases for fir app', ctx => { - expect(ctx.stderr).to.equal('') - expect(ctx.stdout).to.equal(`⬢ ${firApp.name} has no Buildpacks.\n`) - }) -}) diff --git a/packages/cli/test/unit/commands/buildpacks/info.unit.test.ts b/packages/cli/test/unit/commands/buildpacks/info.unit.test.ts new file mode 100644 index 0000000000..13769b791a --- /dev/null +++ b/packages/cli/test/unit/commands/buildpacks/info.unit.test.ts @@ -0,0 +1,60 @@ +import {Fixture} from '@heroku/buildpack-registry' +import {runCommand} from '@oclif/test' +import {expect} from 'chai' +import nock from 'nock' + +describe('buildpacks:info', function () { + let registryApi: nock.Scope + + beforeEach(function () { + registryApi = nock('https://buildpack-registry.heroku.com') + }) + + afterEach(function () { + registryApi.done() + nock.cleanAll() + }) + + it('shows info about the buildpack', async function () { + registryApi + .get('/buildpacks/heroku%2Fruby') + .reply(200, Fixture.buildpack({ + name: 'ruby', + source: { + owner: 'heroku', + repo: 'heroku-buildpack-ruby', + type: 'github', + }, + })) + .get('/buildpacks/heroku%2Fruby/revisions') + .reply(200, [Fixture.revision()]) + .get('/buildpacks/heroku%2Fruby/readme') + .reply(200, Fixture.readme()) + + const {stdout} = await runCommand(['buildpacks:info', 'heroku/ruby']) + + expect(stdout).to.contain('=== heroku/ruby') + expect(stdout).to.contain('languages') + expect(stdout).to.match(/source:\s+https:\/\/github\.com\/heroku\/heroku-buildpack-ruby/) + }) + + it("handles if the buildpack doesn't exist", async function () { + registryApi + .get('/buildpacks/hone%2Ftest') + .reply(404, {}) + + const {error} = await runCommand(['buildpacks:info', 'hone/test']) + + expect(error?.message).to.include("Could not find the buildpack 'hone/test'") + }) + + it('handles case if there are errors from the API', async function () { + registryApi + .get('/buildpacks/hone%2Ftest') + .reply(500, 'some error') + + const {error} = await runCommand(['buildpacks:info', 'hone/test']) + + expect(error?.message).to.include('Problems finding buildpack info: some error') + }) +}) diff --git a/packages/cli/test/unit/commands/buildpacks/info.unit.test.ts.skip b/packages/cli/test/unit/commands/buildpacks/info.unit.test.ts.skip deleted file mode 100644 index d237ea67e6..0000000000 --- a/packages/cli/test/unit/commands/buildpacks/info.unit.test.ts.skip +++ /dev/null @@ -1,50 +0,0 @@ -import {Fixture} from '@heroku/buildpack-registry' -import {expect, test} from '@oclif/test' -import nock from 'nock' - -describe('buildpacks:info', function () { - test - .nock('https://buildpack-registry.heroku.com', (api: nock.Scope) => { - api - .get('/buildpacks/heroku%2Fruby') - .reply(200, Fixture.buildpack({ - name: 'ruby', - source: { - type: 'github', - owner: 'heroku', - repo: 'heroku-buildpack-ruby', - }, - })) - .get('/buildpacks/heroku%2Fruby/revisions') - .reply(200, [Fixture.revision()]) - .get('/buildpacks/heroku%2Fruby/readme') - .reply(200, Fixture.readme()) - }) - .stdout() - .command(['buildpacks:info', 'heroku/ruby']) - .it('shows info about the buildpack', ctx => { - expect(ctx.stdout).to.contain('=== heroku/ruby') - expect(ctx.stdout).to.contain('languages') - expect(ctx.stdout).to.match(/source:\s+https:\/\/github\.com\/heroku\/heroku-buildpack-ruby/) - }) - - test - .nock('https://buildpack-registry.heroku.com', (api: nock.Scope) => { - api - .get('/buildpacks/hone%2Ftest') - .reply(404, {}) - }) - .command(['buildpacks:info', 'hone/test']) - .catch("Could not find the buildpack 'hone/test'") - .it("handles if the buildpack doesn't exist") - - test - .nock('https://buildpack-registry.heroku.com', (api: nock.Scope) => { - api - .get('/buildpacks/hone%2Ftest') - .reply(500, 'some error') - }) - .command(['buildpacks:info', 'hone/test']) - .catch('Problems finding buildpack info: some error') - .it('handles case if there are errors from the API') -}) diff --git a/packages/cli/test/unit/commands/buildpacks/remove.unit.test.ts b/packages/cli/test/unit/commands/buildpacks/remove.unit.test.ts new file mode 100644 index 0000000000..6f38ae6a45 --- /dev/null +++ b/packages/cli/test/unit/commands/buildpacks/remove.unit.test.ts @@ -0,0 +1,273 @@ +import {Fixture} from '@heroku/buildpack-registry' +import {runCommand} from '@oclif/test' +import {expect} from 'chai' +import nock from 'nock' + +import {BuildpackInstallationsStub as Stubber} from '../../../helpers/buildpacks/buildpack-installations-stub.js' +import {unwrap} from '../../../helpers/utils/unwrap.js' + +describe('buildpacks:remove', function () { + let api: nock.Scope + + beforeEach(function () { + api = nock('https://api.heroku.com') + }) + + afterEach(function () { + api.done() + nock.cleanAll() + }) + + describe('-i INDEX', function () { + it('# with one buildpack successfully removes index', async function () { + Stubber.get(api, ['https://github.com/heroku/heroku-buildpack-ruby']) + Stubber.put(api) + api + .get('/apps/example/config-vars') + .reply(200, {}) + + const {stdout} = await runCommand(['buildpacks:remove', '-i', '1', '-a', 'example']) + + expect(stdout).to.equal( + `Buildpack removed. Next release on example will detect buildpacks normally. +`) + }) + + it('# with one buildpack successfully removes index with language warn', async function () { + Stubber.get(api, [ + 'https://github.com/heroku/heroku-buildpack-ruby', + ]) + Stubber.put(api) + api + .get('/apps/example/config-vars') + .reply(200, {LANGUAGE_PACK_URL: 'https://github.com/foo/foo'}) + + const {stderr, stdout} = await runCommand(['buildpacks:remove', '-i', '1', '-a', 'example']) + + expect(stdout).to.equal( + `Buildpack removed. +`) + expect(unwrap(stderr)).to.equal('Warning: The LANGUAGE_PACK_URL config var is still set and will be used for the next release\n') + }) + + it('# with one buildpack successfully removes index with buildpack warn', async function () { + Stubber.get(api, [ + 'https://github.com/heroku/heroku-buildpack-ruby', + ]) + Stubber.put(api) + api + .get('/apps/example/config-vars') + .reply(200, {BUILDPACK_URL: 'https://github.com/foo/foo'}) + + const {stderr, stdout} = await runCommand(['buildpacks:remove', '-i', '1', '-a', 'example']) + + expect(stdout).to.equal( + `Buildpack removed. +`) + expect(unwrap(stderr)).to.equal('Warning: The BUILDPACK_URL config var is still set and will be used for the next release\n') + }) + + it('# with two buildpacks successfully removes index - java', async function () { + Stubber.get(api, [ + 'https://github.com/heroku/heroku-buildpack-java', + 'https://github.com/heroku/heroku-buildpack-ruby', + ]) + Stubber.put(api, [ + 'https://github.com/heroku/heroku-buildpack-java', + ]) + + const {stdout} = await runCommand(['buildpacks:remove', '-i', '2', '-a', 'example']) + + expect(stdout).to.equal( + `Buildpack removed. Next release on example will use https://github.com/heroku/heroku-buildpack-java. +Run git push heroku main to create a new release using this buildpack. +`) + }) + + it('# with two buildpacks successfully removes index - ruby', async function () { + Stubber.get(api, [ + 'https://github.com/heroku/heroku-buildpack-java', + 'https://github.com/heroku/heroku-buildpack-ruby', + ]) + Stubber.put(api, [ + 'https://github.com/heroku/heroku-buildpack-ruby', + ]) + + const {stdout} = await runCommand(['buildpacks:remove', '-i', '1', '-a', 'example']) + + expect(stdout).to.equal( + `Buildpack removed. Next release on example will use https://github.com/heroku/heroku-buildpack-ruby. +Run git push heroku main to create a new release using this buildpack. +`) + }) + + it('# with three buildpacks successfully removes index', async function () { + Stubber.get(api, [ + 'https://github.com/heroku/heroku-buildpack-java', + 'https://github.com/heroku/heroku-buildpack-nodejs', + 'https://github.com/heroku/heroku-buildpack-ruby', + ]) + Stubber.put(api, [ + 'https://github.com/heroku/heroku-buildpack-java', + 'https://github.com/heroku/heroku-buildpack-ruby', + ]) + + const {stdout} = await runCommand(['buildpacks:remove', '-i', '2', '-a', 'example']) + + expect(stdout).to.equal( + `Buildpack removed. Next release on example will use: + 1. https://github.com/heroku/heroku-buildpack-java + 2. https://github.com/heroku/heroku-buildpack-ruby +Run git push heroku main to create a new release using these buildpacks. +`) + }) + + it('# returns an error message when i is not an integer', async function () { + const {error} = await runCommand(['buildpacks:remove', '-i', 'notaninteger', '-a', 'example']) + + expect(error?.message).to.include('Parsing --index \n\tExpected an integer but received: notaninteger\nSee more help with --help') + }) + + it('# with no buildpacks reports an error removing index', async function () { + Stubber.get(api) + + const {error} = await runCommand(['buildpacks:remove', '-i', '1', '-a', 'example']) + + expect(error?.message).to.include('No buildpacks were found. Next release on example will detect buildpack normally.') + }) + + it('# returns an error when the index > 1 and the size is one', async function () { + Stubber.get(api, [ + 'https://github.com/foo/foo', + ]) + + const {error} = await runCommand(['buildpacks:remove', '-i', '2', '-a', 'example']) + + expect(error?.message).to.include('Invalid index. Only valid value is 1.') + }) + + it('# returns an error when the index > size and the size > one', async function () { + Stubber.get(api, [ + 'https://github.com/foo/foo', + 'https://github.com/bar/bar', + ]) + + const {error} = await runCommand(['buildpacks:remove', '-i', '3', '-a', 'example']) + + expect(error?.message).to.include('Invalid index. Please choose a value between 1 and 2') + }) + }) + + describe('URL', function () { + it('# with one buildpack successfully removes url', async function () { + Stubber.get(api, [ + 'https://github.com/heroku/heroku-buildpack-ruby', + ]) + Stubber.put(api) + api + .get('/apps/example/config-vars') + .reply(200, {}) + + const {stdout} = await runCommand(['buildpacks:remove', 'https://github.com/heroku/heroku-buildpack-ruby', '-a', 'example']) + + expect(stdout).to.equal( + `Buildpack removed. Next release on example will detect buildpacks normally. +`) + }) + + it('# with two buildpacks successfully removes url', async function () { + Stubber.get(api, [ + 'https://github.com/heroku/heroku-buildpack-java', + 'https://github.com/heroku/heroku-buildpack-ruby', + ]) + Stubber.put(api, [ + 'https://github.com/heroku/heroku-buildpack-java', + ]) + + const {stderr, stdout} = await runCommand(['buildpacks:remove', 'https://github.com/heroku/heroku-buildpack-ruby', '-a', 'example']) + + expect(stderr).to.equal('') + expect(stdout).to.equal( + `Buildpack removed. Next release on example will use https://github.com/heroku/heroku-buildpack-java. +Run git push heroku main to create a new release using this buildpack. +`) + }) + + it('# remove by name should work', async function () { + Stubber.get(api, [ + {name: 'heroku/ruby', url: 'urn:buildpack:heroku/ruby'}, + ]) + Stubber.put(api) + api + .get('/apps/example/config-vars') + .reply(200, {}) + + const buildpack = Fixture.buildpack({ + name: 'ruby', + }) + nock('https://buildpack-registry.heroku.com') + .get('/buildpacks/heroku%2Fruby') + .reply(200, buildpack) + + const {stderr, stdout} = await runCommand(['buildpacks:remove', 'heroku/ruby', '-a', 'example']) + + expect(stderr).to.equal('') + expect(stdout).to.equal( + `Buildpack removed. Next release on example will detect buildpacks normally. +`) + }) + + it('# with no buildpacks reports an error removing buildpack_url', async function () { + Stubber.get(api) + + const {error} = await runCommand(['buildpacks:remove', 'https://github.com/bar/bar', '-a', 'example']) + + expect(error?.message).to.include('No buildpacks were found. Next release on example will detect buildpack normally.') + }) + + it('# returns an error when the url is not found', async function () { + Stubber.get(api, [ + 'https://github.com/foo/foo', + ]) + + const {error} = await runCommand(['buildpacks:remove', 'https://github.com/bar/bar', '-a', 'example']) + + expect(error?.message).to.include('Buildpack not found. Nothing was removed.') + }) + + it('# with three buildpacks successfully removes url', async function () { + Stubber.get(api, [ + 'https://github.com/heroku/heroku-buildpack-java', + 'https://github.com/heroku/heroku-buildpack-nodejs', + 'https://github.com/heroku/heroku-buildpack-ruby', + ]) + Stubber.put(api, [ + 'https://github.com/heroku/heroku-buildpack-java', + 'https://github.com/heroku/heroku-buildpack-nodejs', + ]) + + const {stdout} = await runCommand(['buildpacks:remove', 'https://github.com/heroku/heroku-buildpack-ruby', '-a', 'example']) + + expect(stdout).to.equal( + `Buildpack removed. Next release on example will use: + 1. https://github.com/heroku/heroku-buildpack-java + 2. https://github.com/heroku/heroku-buildpack-nodejs +Run git push heroku main to create a new release using these buildpacks. +`) + }) + }) + + describe('-i INDEX URL', function () { + it('# returns an error message when i and url are specified', async function () { + const {error} = await runCommand(['buildpacks:remove', 'https://github.com/heroku/heroku-buildpack-ruby', '-i', '1', '-a', 'example']) + + expect(error?.message).to.include('Please choose either index or Buildpack, but not both.') + }) + + it('# returns an error message neither i or url are specified', async function () { + const {error} = await runCommand(['buildpacks:remove', '-a', 'example']) + + expect(error?.message).to.include('Usage: heroku buildpacks:remove [BUILDPACK_URL]. Must specify a buildpack to remove, either by index or URL.') + }) + }) +}) diff --git a/packages/cli/test/unit/commands/buildpacks/remove.unit.test.ts.skip b/packages/cli/test/unit/commands/buildpacks/remove.unit.test.ts.skip deleted file mode 100644 index a519d787b6..0000000000 --- a/packages/cli/test/unit/commands/buildpacks/remove.unit.test.ts.skip +++ /dev/null @@ -1,284 +0,0 @@ -import {Fixture} from '@heroku/buildpack-registry' -import {expect, test} from '@oclif/test' -import nock from 'nock' - -import {BuildpackInstallationsStub as Stubber} from '../../../helpers/buildpacks/buildpack-installations-stub.js' -import {unwrap} from '../../../helpers/utils/unwrap.js' - -describe('buildpacks:remove', function () { - describe('-i INDEX', function () { - test - .nock('https://api.heroku.com', (api: nock.Scope) => { - Stubber.get(api, ['https://github.com/heroku/heroku-buildpack-ruby']) - Stubber.put(api) - api - .get('/apps/example/config-vars') - .reply(200, {}) - }) - .stdout() - .stderr() - .command(['buildpacks:remove', '-i', '1', '-a', 'example']) - .it('# with one buildpack successfully removes index', ctx => { - expect(ctx.stdout).to.equal( - `Buildpack removed. Next release on example will detect buildpacks normally. -`) - }) - - test - .nock('https://api.heroku.com', (api: nock.Scope) => { - Stubber.get(api, [ - 'https://github.com/heroku/heroku-buildpack-ruby', - ]) - Stubber.put(api) - api - .get('/apps/example/config-vars') - .reply(200, {LANGUAGE_PACK_URL: 'https://github.com/foo/foo'}) - }) - .stdout() - .stderr() - .command(['buildpacks:remove', '-i', '1', '-a', 'example']) - .it('# with one buildpack successfully removes index with language warn', ctx => { - expect(ctx.stdout).to.equal( - `Buildpack removed. -`) - expect(unwrap(ctx.stderr)).to.equal('Warning: The LANGUAGE_PACK_URL config var is still set and will be used for the next release\n') - }) - - test - .nock('https://api.heroku.com', (api: nock.Scope) => { - Stubber.get(api, [ - 'https://github.com/heroku/heroku-buildpack-ruby', - ]) - Stubber.put(api) - api - .get('/apps/example/config-vars') - .reply(200, {BUILDPACK_URL: 'https://github.com/foo/foo'}) - }) - .stdout() - .stderr() - .command(['buildpacks:remove', '-i', '1', '-a', 'example']) - .it('# with one buildpack successfully removes index with buildpack warn', ctx => { - expect(ctx.stdout).to.equal( - `Buildpack removed. -`) - expect(unwrap(ctx.stderr)).to.equal('Warning: The BUILDPACK_URL config var is still set and will be used for the next release\n') - }) - - test - .nock('https://api.heroku.com', (api: nock.Scope) => { - Stubber.get(api, [ - 'https://github.com/heroku/heroku-buildpack-java', - 'https://github.com/heroku/heroku-buildpack-ruby', - ]) - Stubber.put(api, [ - 'https://github.com/heroku/heroku-buildpack-java', - ]) - }) - .stdout() - .command(['buildpacks:remove', '-i', '2', '-a', 'example']) - .it('# with two buildpacks successfully removes index - java', ctx => { - expect(ctx.stdout).to.equal( - `Buildpack removed. Next release on example will use https://github.com/heroku/heroku-buildpack-java. -Run git push heroku main to create a new release using this buildpack. -`) - }) - - test - .nock('https://api.heroku.com', (api: nock.Scope) => { - Stubber.get(api, [ - 'https://github.com/heroku/heroku-buildpack-java', - 'https://github.com/heroku/heroku-buildpack-ruby', - ]) - Stubber.put(api, [ - 'https://github.com/heroku/heroku-buildpack-ruby', - ]) - }) - .stdout() - .command(['buildpacks:remove', '-i', '1', '-a', 'example']) - .it('# with two buildpacks successfully removes index - ruby', ctx => { - expect(ctx.stdout).to.equal( - `Buildpack removed. Next release on example will use https://github.com/heroku/heroku-buildpack-ruby. -Run git push heroku main to create a new release using this buildpack. -`) - }) - - test - .nock('https://api.heroku.com', (api: nock.Scope) => { - Stubber.get(api, [ - 'https://github.com/heroku/heroku-buildpack-java', - 'https://github.com/heroku/heroku-buildpack-nodejs', - 'https://github.com/heroku/heroku-buildpack-ruby', - ]) - Stubber.put(api, [ - 'https://github.com/heroku/heroku-buildpack-java', - 'https://github.com/heroku/heroku-buildpack-ruby', - ]) - }) - .stdout() - .command(['buildpacks:remove', '-i', '2', '-a', 'example']) - .it('# with three buildpacks successfully removes index', ctx => { - expect(ctx.stdout).to.equal( - `Buildpack removed. Next release on example will use: - 1. https://github.com/heroku/heroku-buildpack-java - 2. https://github.com/heroku/heroku-buildpack-ruby -Run git push heroku main to create a new release using these buildpacks. -`) - }) - - test - .command(['buildpacks:remove', '-i', 'notaninteger', '-a', 'example']) - .catch('Parsing --index \n\tExpected an integer but received: notaninteger\nSee more help with --help') - .it('# returns an error message when i is not an integer') - - test - .nock('https://api.heroku.com', (api: nock.Scope) => { - Stubber.get(api) - }) - .command(['buildpacks:remove', '-i', '1', '-a', 'example']) - .catch('No buildpacks were found. Next release on example will detect buildpack normally.') - .it('# with no buildpacks reports an error removing index') - - test - .nock('https://api.heroku.com', (api: nock.Scope) => { - Stubber.get(api, [ - 'https://github.com/foo/foo', - ]) - }) - .command(['buildpacks:remove', '-i', '2', '-a', 'example']) - .catch('Invalid index. Only valid value is 1.') - .it('# returns an error when the index > 1 and the size is one') - - test - .nock('https://api.heroku.com', (api: nock.Scope) => { - Stubber.get(api, [ - 'https://github.com/foo/foo', - 'https://github.com/bar/bar', - ]) - }) - .command(['buildpacks:remove', '-i', '3', '-a', 'example']) - .catch('Invalid index. Please choose a value between 1 and 2') - .it('# returns an error when the index > size and the size > one') - }) - - describe('URL', function () { - test - .nock('https://api.heroku.com', (api: nock.Scope) => { - Stubber.get(api, [ - 'https://github.com/heroku/heroku-buildpack-ruby', - ]) - Stubber.put(api) - api - .get('/apps/example/config-vars') - .reply(200, {}) - }) - .stdout() - .command(['buildpacks:remove', 'https://github.com/heroku/heroku-buildpack-ruby', '-a', 'example']) - .it('# with one buildpack successfully removes url', ctx => { - expect(ctx.stdout).to.equal( - `Buildpack removed. Next release on example will detect buildpacks normally. -`) - }) - - test - .nock('https://api.heroku.com', (api: nock.Scope) => { - Stubber.get(api, [ - 'https://github.com/heroku/heroku-buildpack-java', - 'https://github.com/heroku/heroku-buildpack-ruby', - ]) - Stubber.put(api, [ - 'https://github.com/heroku/heroku-buildpack-java', - ]) - }) - .stdout() - .stderr() - .command(['buildpacks:remove', 'https://github.com/heroku/heroku-buildpack-ruby', '-a', 'example']) - .it('# with two buildpacks successfully removes url', ctx => { - expect(ctx.stderr).to.equal('') - expect(ctx.stdout).to.equal( - `Buildpack removed. Next release on example will use https://github.com/heroku/heroku-buildpack-java. -Run git push heroku main to create a new release using this buildpack. -`) - }) - - test - .nock('https://api.heroku.com', (api: nock.Scope) => { - Stubber.get(api, [ - {url: 'urn:buildpack:heroku/ruby', name: 'heroku/ruby'}, - ]) - Stubber.put(api) - api - .get('/apps/example/config-vars') - .reply(200, {}) - }) - .nock('https://buildpack-registry.heroku.com', (api: nock.Scope) => { - const buildpack = Fixture.buildpack({ - name: 'ruby', - }) - api - .get('/buildpacks/heroku%2Fruby') - .reply(200, buildpack) - }) - .stdout() - .stderr() - .command(['buildpacks:remove', 'heroku/ruby', '-a', 'example']) - .it('# remove by name should work', ctx => { - expect(ctx.stderr).to.equal('') - expect(ctx.stdout).to.equal( - `Buildpack removed. Next release on example will detect buildpacks normally. -`) - }) - - test - .nock('https://api.heroku.com', (api: nock.Scope) => { - Stubber.get(api) - }) - .command(['buildpacks:remove', 'https://github.com/bar/bar', '-a', 'example']) - .catch('No buildpacks were found. Next release on example will detect buildpack normally.') - .it('# with no buildpacks reports an error removing buildpack_url') - - test - .nock('https://api.heroku.com', (api: nock.Scope) => { - Stubber.get(api, [ - 'https://github.com/foo/foo', - ]) - }) - .command(['buildpacks:remove', 'https://github.com/bar/bar', '-a', 'example']) - .catch('Buildpack not found. Nothing was removed.') - .it('# returns an error when the url is not found') - - test - .nock('https://api.heroku.com', (api: nock.Scope) => { - Stubber.get(api, [ - 'https://github.com/heroku/heroku-buildpack-java', - 'https://github.com/heroku/heroku-buildpack-nodejs', - 'https://github.com/heroku/heroku-buildpack-ruby', - ]) - Stubber.put(api, [ - 'https://github.com/heroku/heroku-buildpack-java', - 'https://github.com/heroku/heroku-buildpack-nodejs', - ]) - }) - .stdout() - .command(['buildpacks:remove', 'https://github.com/heroku/heroku-buildpack-ruby', '-a', 'example']) - .it('# with three buildpacks successfully removes url', ctx => { - expect(ctx.stdout).to.equal( - `Buildpack removed. Next release on example will use: - 1. https://github.com/heroku/heroku-buildpack-java - 2. https://github.com/heroku/heroku-buildpack-nodejs -Run git push heroku main to create a new release using these buildpacks. -`) - }) - }) - - describe('-i INDEX URL', function () { - test - .command(['buildpacks:remove', 'https://github.com/heroku/heroku-buildpack-ruby', '-i', '1', '-a', 'example']) - .catch('Please choose either index or Buildpack, but not both.') - .it('# returns an error message when i and url are specified') - - test - .command(['buildpacks:remove', '-a', 'example']) - .catch('Usage: heroku buildpacks:remove [BUILDPACK_URL]. Must specify a buildpack to remove, either by index or URL.') - .it('# returns an error message neither i or url are specified') - }) -}) diff --git a/packages/cli/test/unit/commands/buildpacks/search.unit.test.ts b/packages/cli/test/unit/commands/buildpacks/search.unit.test.ts new file mode 100644 index 0000000000..1a2c6b0583 --- /dev/null +++ b/packages/cli/test/unit/commands/buildpacks/search.unit.test.ts @@ -0,0 +1,52 @@ +import {Fixture} from '@heroku/buildpack-registry' +import {runCommand} from '@oclif/test' +import {expect} from 'chai' +import nock from 'nock' + +describe('buildpacks:search', function () { + let registryApi: nock.Scope + + beforeEach(function () { + registryApi = nock('https://buildpack-registry.heroku.com') + }) + + afterEach(function () { + registryApi.done() + nock.cleanAll() + }) + + it('searches using the namespace', async function () { + registryApi + .get('/buildpacks?in[namespace][]=heroku') + .reply(200, [ + Fixture.buildpack({ + description: 'Official Heroku Buildpack for Ruby', + name: 'ruby', + }), + ]) + + const {stdout} = await runCommand(['buildpacks:search', '--namespace', 'heroku']) + + expect(stdout).to.contain('heroku/ruby') + expect(stdout).to.contain('1 buildpack found') + }) + + it('searches only returns unique buildpacks', async function () { + const rubyBuildpack = Fixture.buildpack({ + description: 'Official Heroku Buildpack for Ruby', + name: 'ruby', + }) + + registryApi + .get('/buildpacks?in[namespace][]=ruby') + .reply(200, []) + .get('/buildpacks?in[name][]=ruby') + .reply(200, [rubyBuildpack]) + .get('/buildpacks?like[description]=ruby') + .reply(200, [rubyBuildpack]) + + const {stdout} = await runCommand(['buildpacks:search', 'ruby']) + + expect(stdout).to.contain('1 buildpack found') + }) +}) diff --git a/packages/cli/test/unit/commands/buildpacks/search.unit.test.ts.skip b/packages/cli/test/unit/commands/buildpacks/search.unit.test.ts.skip deleted file mode 100644 index 669c7e9cc7..0000000000 --- a/packages/cli/test/unit/commands/buildpacks/search.unit.test.ts.skip +++ /dev/null @@ -1,44 +0,0 @@ -import {Fixture} from '@heroku/buildpack-registry' -import {expect, test} from '@oclif/test' -import nock from 'nock' - -describe('buildpacks:search', function () { - test - .nock('https://buildpack-registry.heroku.com', (api: nock.Scope) => { - api - .get('/buildpacks?in[namespace][]=heroku') - .reply(200, [ - Fixture.buildpack({ - name: 'ruby', - description: 'Official Heroku Buildpack for Ruby', - }), - ]) - }) - .stdout() - .command(['buildpacks:search', '--namespace', 'heroku']) - .it('searches using the namespace', ctx => { - expect(ctx.stdout).to.contain('heroku/ruby') - expect(ctx.stdout).to.contain('1 buildpack found') - }) - - test - .nock('https://buildpack-registry.heroku.com', (api: nock.Scope) => { - const rubyBuildpack = Fixture.buildpack({ - name: 'ruby', - description: 'Official Heroku Buildpack for Ruby', - }) - - api - .get('/buildpacks?in[namespace][]=ruby') - .reply(200, []) - .get('/buildpacks?in[name][]=ruby') - .reply(200, [rubyBuildpack]) - .get('/buildpacks?like[description]=ruby') - .reply(200, [rubyBuildpack]) - }) - .stdout() - .command(['buildpacks:search', 'ruby']) - .it('searches only returns unique buildpacks', ctx => { - expect(ctx.stdout).to.contain('1 buildpack found') - }) -}) diff --git a/packages/cli/test/unit/commands/buildpacks/set.unit.test.ts b/packages/cli/test/unit/commands/buildpacks/set.unit.test.ts new file mode 100644 index 0000000000..f6f1e0bd69 --- /dev/null +++ b/packages/cli/test/unit/commands/buildpacks/set.unit.test.ts @@ -0,0 +1,210 @@ +import {runCommand} from '@oclif/test' +import {expect} from 'chai' +import nock from 'nock' + +import {BuildpackInstallationsStub as Stubber} from '../../../helpers/buildpacks/buildpack-installations-stub.js' + +describe('buildpacks:set', function () { + let api: nock.Scope + + beforeEach(function () { + api = nock('https://api.heroku.com') + }) + + afterEach(function () { + api.done() + nock.cleanAll() + }) + + describe('URL', function () { + it('# with no buildpacks sets the buildpack URL', async function () { + Stubber.get(api) + Stubber.put(api, [ + 'https://github.com/heroku/heroku-buildpack-ruby', + ]) + + const {stderr, stdout} = await runCommand(['buildpacks:set', 'https://github.com/heroku/heroku-buildpack-ruby', '-a', 'example']) + + expect(stderr).to.equal('') + expect(stdout).to.equal( + `Buildpack set. Next release on example will use https://github.com/heroku/heroku-buildpack-ruby. +Run git push heroku main to create a new release using this buildpack. +`) + }) + + it('# errors out when already exists', async function () { + Stubber.get(api, [ + 'https://github.com/foobar/foobar', + ]) + + const {error} = await runCommand(['buildpacks:set', 'https://github.com/foobar/foobar', '-a', 'example']) + + expect(error?.message).to.include('The buildpack https://github.com/foobar/foobar is already set on your app.') + }) + + it('# overwrites in the first when no i is passed', async function () { + Stubber.get(api, [ + 'https://github.com/foo/foo', + 'https://github.com/baz/baz', + 'https://github.com/biz/biz', + ]) + Stubber.put(api, [ + 'https://github.com/bar/bar', + 'https://github.com/baz/baz', + 'https://github.com/biz/biz', + ]) + + const {stdout} = await runCommand(['buildpacks:set', 'https://github.com/bar/bar', '-a', 'example']) + + expect(stdout).to.equal( + `Buildpack set. Next release on example will use: + 1. https://github.com/bar/bar + 2. https://github.com/baz/baz + 3. https://github.com/biz/biz +Run git push heroku main to create a new release using these buildpacks. +`) + }) + }) + + describe('-i INDEX URL', function () { + it('# with no buildpacks sets the buildpack URL with index', async function () { + Stubber.get(api) + Stubber.put(api, [ + 'https://github.com/heroku/heroku-buildpack-ruby', + ]) + + const {stderr, stdout} = await runCommand(['buildpacks:set', 'https://github.com/heroku/heroku-buildpack-ruby', '-i', '1', '-a', 'example']) + + expect(stderr).to.equal('') + expect(stdout).to.equal( + `Buildpack set. Next release on example will use https://github.com/heroku/heroku-buildpack-ruby. +Run git push heroku main to create a new release using this buildpack. +`) + }) + + it('# with one existing buildpack successfully overwrites an existing buildpack URL at index', async function () { + Stubber.get(api, [ + 'https://github.com/heroku/heroku-buildpack-java', + ]) + Stubber.put(api, [ + 'https://github.com/heroku/heroku-buildpack-ruby', + ]) + + const {stderr, stdout} = await runCommand(['buildpacks:set', 'https://github.com/heroku/heroku-buildpack-ruby', '-i', '1', '-a', 'example']) + + expect(stderr).to.equal('') + expect(stdout).to.equal( + `Buildpack set. Next release on example will use https://github.com/heroku/heroku-buildpack-ruby. +Run git push heroku main to create a new release using this buildpack. +`) + }) + + it('# with one existing buildpack unsuccessfully fails if buildpack is already set', async function () { + Stubber.get(api, [ + 'https://github.com/heroku/heroku-buildpack-ruby', + ]) + + const {error} = await runCommand(['buildpacks:set', 'https://github.com/heroku/heroku-buildpack-ruby', '-i', '1', '-a', 'example']) + + expect(error?.message).to.include('The buildpack https://github.com/heroku/heroku-buildpack-ruby is already set on your app.') + }) + + it('# with two existing buildpacks successfully overwrites an existing buildpack URL at index', async function () { + Stubber.get(api, [ + 'https://github.com/heroku/heroku-buildpack-java', + 'https://github.com/heroku/heroku-buildpack-nodejs', + ]) + Stubber.put(api, [ + 'https://github.com/heroku/heroku-buildpack-ruby', + 'https://github.com/heroku/heroku-buildpack-nodejs', + ]) + + const {stderr, stdout} = await runCommand(['buildpacks:set', 'https://github.com/heroku/heroku-buildpack-ruby', '-i', '1', '-a', 'example']) + + expect(stderr).to.equal('') + expect(stdout).to.equal( + `Buildpack set. Next release on example will use: + 1. https://github.com/heroku/heroku-buildpack-ruby + 2. https://github.com/heroku/heroku-buildpack-nodejs +Run git push heroku main to create a new release using these buildpacks. +`) + }) + + it('# with two existing buildpacks successfully adds buildpack URL to the end of list', async function () { + Stubber.get(api, [ + 'https://github.com/heroku/heroku-buildpack-java', + 'https://github.com/heroku/heroku-buildpack-nodejs', + ]) + Stubber.put(api, [ + 'https://github.com/heroku/heroku-buildpack-java', + 'https://github.com/heroku/heroku-buildpack-nodejs', + 'https://github.com/heroku/heroku-buildpack-ruby', + ]) + + const {stderr, stdout} = await runCommand(['buildpacks:set', 'https://github.com/heroku/heroku-buildpack-ruby', '-i', '3', '-a', 'example']) + + expect(stderr).to.equal('') + expect(stdout).to.equal( + `Buildpack set. Next release on example will use: + 1. https://github.com/heroku/heroku-buildpack-java + 2. https://github.com/heroku/heroku-buildpack-nodejs + 3. https://github.com/heroku/heroku-buildpack-ruby +Run git push heroku main to create a new release using these buildpacks. +`) + }) + + it('# with two existing buildpacks successfully adds buildpack URL to the very end of list', async function () { + Stubber.get(api, [ + 'https://github.com/heroku/heroku-buildpack-java', + 'https://github.com/heroku/heroku-buildpack-nodejs', + ]) + Stubber.put(api, [ + 'https://github.com/heroku/heroku-buildpack-java', + 'https://github.com/heroku/heroku-buildpack-nodejs', + 'https://github.com/heroku/heroku-buildpack-ruby', + ]) + + const {stderr, stdout} = await runCommand(['buildpacks:set', 'https://github.com/heroku/heroku-buildpack-ruby', '-i', '99', '-a', 'example']) + + expect(stderr).to.equal('') + expect(stdout).to.equal( + `Buildpack set. Next release on example will use: + 1. https://github.com/heroku/heroku-buildpack-java + 2. https://github.com/heroku/heroku-buildpack-nodejs + 3. https://github.com/heroku/heroku-buildpack-ruby +Run git push heroku main to create a new release using these buildpacks. +`) + }) + + it('# with two existing buildpacks unsuccessfully fails if buildpack is already set', async function () { + Stubber.get(api, [ + 'https://github.com/heroku/heroku-buildpack-java', + 'https://github.com/heroku/heroku-buildpack-nodejs', + ]) + + const {error} = await runCommand(['buildpacks:set', 'https://github.com/heroku/heroku-buildpack-java', '-i', '2', '-a', 'example']) + + expect(error?.message).to.include('The buildpack https://github.com/heroku/heroku-buildpack-java is already set on your app.') + }) + + it('# returns an error message when i is not an integer', async function () { + const {error} = await runCommand(['buildpacks:set', 'https://github.com/bar/bar', '-i', 'notaninteger', '-a', 'example']) + + expect(error?.message).to.include('Parsing --index \n\tExpected an integer but received: notaninteger\nSee more help with --help') + }) + + it('# returns an error message when i < 0', async function () { + const {error} = await runCommand(['buildpacks:set', 'https://github.com/bar/bar', '-i', '-1', '-a', 'example']) + + expect(error?.message).to.include('Invalid index. Must be greater than 0.') + }) + + it('# handles a missing buildpack URL arg', async function () { + const {error} = await runCommand(['buildpacks:set', '-a', 'example']) + + expect(error?.message).to.include(`Missing 1 required arg: +buildpack namespace/name of the buildpack +See more help with --help`) + }) + }) +}) diff --git a/packages/cli/test/unit/commands/buildpacks/set.unit.test.ts.skip b/packages/cli/test/unit/commands/buildpacks/set.unit.test.ts.skip deleted file mode 100644 index 53a0c14569..0000000000 --- a/packages/cli/test/unit/commands/buildpacks/set.unit.test.ts.skip +++ /dev/null @@ -1,219 +0,0 @@ -import {expect, test} from '@oclif/test' -import nock from 'nock' - -import {BuildpackInstallationsStub as Stubber} from '../../../helpers/buildpacks/buildpack-installations-stub.js' - -describe('buildpacks:set', function () { - describe('URL', function () { - test - .nock('https://api.heroku.com', (api: nock.Scope) => { - Stubber.get(api) - Stubber.put(api, [ - 'https://github.com/heroku/heroku-buildpack-ruby', - ]) - }) - .stdout() - .stderr() - .command(['buildpacks:set', 'https://github.com/heroku/heroku-buildpack-ruby', '-a', 'example']) - .it('# with no buildpacks sets the buildpack URL', ctx => { - expect(ctx.stderr).to.equal('') - expect(ctx.stdout).to.equal( - `Buildpack set. Next release on example will use https://github.com/heroku/heroku-buildpack-ruby. -Run git push heroku main to create a new release using this buildpack. -`) - }) - - test - .nock('https://api.heroku.com', (api: nock.Scope) => { - Stubber.get(api, [ - 'https://github.com/foobar/foobar', - ]) - }) - .command(['buildpacks:set', 'https://github.com/foobar/foobar', '-a', 'example']) - .catch('The buildpack https://github.com/foobar/foobar is already set on your app.') - .it('# errors out when already exists') - - test - .nock('https://api.heroku.com', (api: nock.Scope) => { - Stubber.get(api, [ - 'https://github.com/foo/foo', - 'https://github.com/baz/baz', - 'https://github.com/biz/biz', - ]) - Stubber.put(api, [ - 'https://github.com/bar/bar', - 'https://github.com/baz/baz', - 'https://github.com/biz/biz', - ]) - }) - .stdout() - .stderr() - .command(['buildpacks:set', 'https://github.com/bar/bar', '-a', 'example']) - .it('# overwrites in the first when no i is passed', ctx => { - expect(ctx.stdout).to.equal( - `Buildpack set. Next release on example will use: - 1. https://github.com/bar/bar - 2. https://github.com/baz/baz - 3. https://github.com/biz/biz -Run git push heroku main to create a new release using these buildpacks. -`) - }) - }) - - describe('-i INDEX URL', function () { - test - .nock('https://api.heroku.com', (api: nock.Scope) => { - Stubber.get(api) - Stubber.put(api, [ - 'https://github.com/heroku/heroku-buildpack-ruby', - ]) - }) - .stdout() - .stderr() - .command(['buildpacks:set', 'https://github.com/heroku/heroku-buildpack-ruby', '-i', '1', '-a', 'example']) - .it('# with no buildpacks sets the buildpack URL with index', ctx => { - expect(ctx.stderr).to.equal('') - expect(ctx.stdout).to.equal( - `Buildpack set. Next release on example will use https://github.com/heroku/heroku-buildpack-ruby. -Run git push heroku main to create a new release using this buildpack. -`) - }) - - test - .nock('https://api.heroku.com', (api: nock.Scope) => { - Stubber.get(api, [ - 'https://github.com/heroku/heroku-buildpack-java', - ]) - Stubber.put(api, [ - 'https://github.com/heroku/heroku-buildpack-ruby', - ]) - }) - .stdout() - .stderr() - .command(['buildpacks:set', 'https://github.com/heroku/heroku-buildpack-ruby', '-i', '1', '-a', 'example']) - .it('# with one existing buildpack successfully overwrites an existing buildpack URL at index', ctx => { - expect(ctx.stderr).to.equal('') - expect(ctx.stdout).to.equal( - `Buildpack set. Next release on example will use https://github.com/heroku/heroku-buildpack-ruby. -Run git push heroku main to create a new release using this buildpack. -`) - }) - test - .nock('https://api.heroku.com', (api: nock.Scope) => { - Stubber.get(api, [ - 'https://github.com/heroku/heroku-buildpack-ruby', - ]) - }) - .stdout() - .stderr() - .command(['buildpacks:set', 'https://github.com/heroku/heroku-buildpack-ruby', '-i', '1', '-a', 'example']) - .catch('The buildpack https://github.com/heroku/heroku-buildpack-ruby is already set on your app.') - .it('# with one existing buildpack unsuccessfully fails if buildpack is already set') - - test - .nock('https://api.heroku.com', (api: nock.Scope) => { - Stubber.get(api, [ - 'https://github.com/heroku/heroku-buildpack-java', - 'https://github.com/heroku/heroku-buildpack-nodejs', - ]) - Stubber.put(api, [ - 'https://github.com/heroku/heroku-buildpack-ruby', - 'https://github.com/heroku/heroku-buildpack-nodejs', - ]) - }) - .stdout() - .stderr() - .command(['buildpacks:set', 'https://github.com/heroku/heroku-buildpack-ruby', '-i', '1', '-a', 'example']) - .it('# with two existing buildpacks successfully overwrites an existing buildpack URL at index', ctx => { - expect(ctx.stderr).to.equal('') - expect(ctx.stdout).to.equal( - `Buildpack set. Next release on example will use: - 1. https://github.com/heroku/heroku-buildpack-ruby - 2. https://github.com/heroku/heroku-buildpack-nodejs -Run git push heroku main to create a new release using these buildpacks. -`) - }) - - test - .nock('https://api.heroku.com', (api: nock.Scope) => { - Stubber.get(api, [ - 'https://github.com/heroku/heroku-buildpack-java', - 'https://github.com/heroku/heroku-buildpack-nodejs', - ]) - Stubber.put(api, [ - 'https://github.com/heroku/heroku-buildpack-java', - 'https://github.com/heroku/heroku-buildpack-nodejs', - 'https://github.com/heroku/heroku-buildpack-ruby', - ]) - }) - .stdout() - .stderr() - .command(['buildpacks:set', 'https://github.com/heroku/heroku-buildpack-ruby', '-i', '3', '-a', 'example']) - .it('# with two existing buildpacks successfully adds buildpack URL to the end of list', ctx => { - expect(ctx.stderr).to.equal('') - expect(ctx.stdout).to.equal( - `Buildpack set. Next release on example will use: - 1. https://github.com/heroku/heroku-buildpack-java - 2. https://github.com/heroku/heroku-buildpack-nodejs - 3. https://github.com/heroku/heroku-buildpack-ruby -Run git push heroku main to create a new release using these buildpacks. -`) - }) - - test - .nock('https://api.heroku.com', (api: nock.Scope) => { - Stubber.get(api, [ - 'https://github.com/heroku/heroku-buildpack-java', - 'https://github.com/heroku/heroku-buildpack-nodejs', - ]) - Stubber.put(api, [ - 'https://github.com/heroku/heroku-buildpack-java', - 'https://github.com/heroku/heroku-buildpack-nodejs', - 'https://github.com/heroku/heroku-buildpack-ruby', - ]) - }) - .stdout() - .stderr() - .command(['buildpacks:set', 'https://github.com/heroku/heroku-buildpack-ruby', '-i', '99', '-a', 'example']) - .it('# with two existing buildpacks successfully adds buildpack URL to the very end of list', ctx => { - expect(ctx.stderr).to.equal('') - expect(ctx.stdout).to.equal( - `Buildpack set. Next release on example will use: - 1. https://github.com/heroku/heroku-buildpack-java - 2. https://github.com/heroku/heroku-buildpack-nodejs - 3. https://github.com/heroku/heroku-buildpack-ruby -Run git push heroku main to create a new release using these buildpacks. -`) - }) - - test - .nock('https://api.heroku.com', (api: nock.Scope) => { - Stubber.get(api, [ - 'https://github.com/heroku/heroku-buildpack-java', - 'https://github.com/heroku/heroku-buildpack-nodejs', - ]) - }) - .stdout() - .stderr() - .command(['buildpacks:set', 'https://github.com/heroku/heroku-buildpack-java', '-i', '2', '-a', 'example']) - .catch('The buildpack https://github.com/heroku/heroku-buildpack-java is already set on your app.') - .it('# with two existing buildpacks unsuccessfully fails if buildpack is already set') - - test - .command(['buildpacks:set', 'https://github.com/bar/bar', '-i', 'notaninteger', '-a', 'example']) - .catch('Parsing --index \n\tExpected an integer but received: notaninteger\nSee more help with --help') - .it('# returns an error message when i is not an integer') - - test - .command(['buildpacks:set', 'https://github.com/bar/bar', '-i', '-1', '-a', 'example']) - .catch('Invalid index. Must be greater than 0.') - .it('# returns an error message when i < 0') - - test - .command(['buildpacks:set', '-a', 'example']) - .catch(`Missing 1 required arg: -buildpack namespace/name of the buildpack -See more help with --help`) - .it('# handles a missing buildpack URL arg') - }) -}) diff --git a/packages/cli/test/unit/commands/buildpacks/versions.unit.test.ts b/packages/cli/test/unit/commands/buildpacks/versions.unit.test.ts new file mode 100644 index 0000000000..7b854182cc --- /dev/null +++ b/packages/cli/test/unit/commands/buildpacks/versions.unit.test.ts @@ -0,0 +1,59 @@ +import {Fixture} from '@heroku/buildpack-registry' +import {runCommand} from '@oclif/test' +import {expect} from 'chai' +import nock from 'nock' + +describe('buildpacks:versions', function () { + let originalApiKey: string | undefined + let registryApi: nock.Scope + + beforeEach(function () { + registryApi = nock('https://buildpack-registry.heroku.com') + originalApiKey = process.env.HEROKU_API_KEY + process.env.HEROKU_API_KEY = 'authtoken' + }) + + afterEach(function () { + registryApi.done() + nock.cleanAll() + if (originalApiKey) { + process.env.HEROKU_API_KEY = originalApiKey + } else { + delete process.env.HEROKU_API_KEY + } + }) + + it('shows info about the buildpack', async function () { + registryApi + .get('/buildpacks/heroku%2Fruby/revisions') + .reply(200, [ + Fixture.revision({ + release: 138, + }), + ]) + + const {stdout} = await runCommand(['buildpacks:versions', 'heroku/ruby']) + + expect(stdout).to.contain('138') + }) + + it('handles buildpack not existing', async function () { + registryApi + .get('/buildpacks/hone%2Ftest/revisions') + .reply(404, '') + + const {error} = await runCommand(['buildpacks:versions', 'hone/test']) + + expect(error?.message).to.include("Could not find 'hone/test'") + }) + + it('handles server error', async function () { + registryApi + .get('/buildpacks/hone%2Ftest/revisions') + .reply(500, 'some error') + + const {error} = await runCommand(['buildpacks:versions', 'hone/test']) + + expect(error?.message).to.include('Problem fetching versions, 500: some error') + }) +}) diff --git a/packages/cli/test/unit/commands/buildpacks/versions.unit.test.ts.skip b/packages/cli/test/unit/commands/buildpacks/versions.unit.test.ts.skip deleted file mode 100644 index 6a8be864e1..0000000000 --- a/packages/cli/test/unit/commands/buildpacks/versions.unit.test.ts.skip +++ /dev/null @@ -1,44 +0,0 @@ -import {Fixture} from '@heroku/buildpack-registry' -import {expect, test} from '@oclif/test' -import nock from 'nock' - -describe('buildpacks:versions', function () { - test - .env({HEROKU_API_KEY: 'authtoken'}) - .nock('https://buildpack-registry.heroku.com', (api: nock.Scope) => { - api - .get('/buildpacks/heroku%2Fruby/revisions') - .reply(200, [ - Fixture.revision({ - release: 138, - }), - ]) - }) - .stdout() - .command(['buildpacks:versions', 'heroku/ruby']) - .it('shows info about the buildpack', ctx => { - expect(ctx.stdout).to.contain('138') - }) - - test - .env({HEROKU_API_KEY: 'authtoken'}) - .nock('https://buildpack-registry.heroku.com', (api: nock.Scope) => { - api - .get('/buildpacks/hone%2Ftest/revisions') - .reply(404, '') - }) - .command(['buildpacks:versions', 'hone/test']) - .catch("Could not find 'hone/test'") - .it('handles buildpack not existing') - - test - .env({HEROKU_API_KEY: 'authtoken'}) - .nock('https://buildpack-registry.heroku.com', (api: nock.Scope) => { - api - .get('/buildpacks/hone%2Ftest/revisions') - .reply(500, 'some error') - }) - .command(['buildpacks:versions', 'hone/test']) - .catch('Problem fetching versions, 500: some error') - .it('handles server error') -}) diff --git a/packages/cli/test/unit/commands/domains/add.unit.test.ts.skip b/packages/cli/test/unit/commands/domains/add.unit.test.ts similarity index 68% rename from packages/cli/test/unit/commands/domains/add.unit.test.ts.skip rename to packages/cli/test/unit/commands/domains/add.unit.test.ts index 19fe54096a..75fb75264f 100644 --- a/packages/cli/test/unit/commands/domains/add.unit.test.ts.skip +++ b/packages/cli/test/unit/commands/domains/add.unit.test.ts @@ -1,22 +1,32 @@ -import {expect, test} from '@oclif/test' -import DomainsAdd from '../../../../src/commands/domains/add.js' +import {expect} from 'chai' +import nock from 'nock' import sinon from 'sinon' +import {stderr, stdout} from 'stdout-stderr' + +import DomainsAdd from '../../../../src/commands/domains/add.js' +import runCommand from '../../../helpers/runCommand.js' describe('domains:add', function () { + afterEach(function () { + nock.cleanAll() + stdout.stop() + stderr.stop() + }) + const domainsResponse = { acm_status: null, acm_status_reason: null, app: { - name: 'myapp', id: '01234567-89ab-cdef-0123-456789abcdef', + name: 'myapp', }, cname: null, created_at: '2012-01-01T12:00:00Z', hostname: 'example.com', id: '01234567-89ab-cdef-0123-456789abcdef', kind: 'custom', - updated_at: '2012-01-01T12:00:00Z', status: 'pending', + updated_at: '2012-01-01T12:00:00Z', } describe('adding a domain to an app with multiple certs', function () { @@ -28,19 +38,18 @@ describe('domains:add', function () { } describe('using the --cert flag', function () { - test - .stderr() - .nock('https://api.heroku.com', api => api + it('adds the domain to the app', async function () { + nock('https://api.heroku.com') .post('/apps/myapp/domains', { hostname: 'example.com', sni_endpoint: 'my-cert', }) - .reply(200, domainsResponseWithEndpoint), - ) - .command(['domains:add', 'example.com', '--app', 'myapp', '--cert', 'my-cert']) - .it('adds the domain to the app', ctx => { - expect(ctx.stderr).to.contain('Adding example.com to ⬢ myapp... done') - }) + .reply(200, domainsResponseWithEndpoint) + + await runCommand(DomainsAdd, ['example.com', '--app', 'myapp', '--cert', 'my-cert']) + + expect(stderr.output).to.contain('Adding example.com to ⬢ myapp... done') + }) }) describe('without passing a cert', function () { @@ -49,8 +58,8 @@ describe('domains:add', function () { app: { name: 'myapp', }, - name: 'cert1', displayName: 'Best Cert Ever', + name: 'cert1', ssl_cert: { cert_domains: ['foo.com', 'bar.com', 'baz.com', 'baq.com', 'blah.com', 'rejairieja.com'], }, @@ -68,7 +77,7 @@ describe('domains:add', function () { let promptForCertStub: sinon.SinonStub - beforeEach(async function () { + beforeEach(function () { promptForCertStub = sinon.stub(DomainsAdd.prototype, 'promptForCert').resolves('my-cert') }) @@ -76,21 +85,20 @@ describe('domains:add', function () { promptForCertStub.restore() }) - test - .stderr() - .nock('https://api.heroku.com', api => api + it('adds the domain to the app', async function () { + nock('https://api.heroku.com') .post('/apps/myapp/domains', { hostname: 'example.com', sni_endpoint: 'my-cert', }) .reply(200, domainsResponseWithEndpoint) .get('/apps/myapp/sni-endpoints') - .reply(200, certsResponse), - ) - .command(['domains:add', 'example.com', '--app', 'myapp']) - .it('adds the domain to the app', ctx => { - expect(ctx.stderr).to.contain('Adding example.com to ⬢ myapp... done') - }) + .reply(200, certsResponse) + + await runCommand(DomainsAdd, ['example.com', '--app', 'myapp']) + + expect(stderr.output).to.contain('Adding example.com to ⬢ myapp... done') + }) }) }) }) diff --git a/packages/cli/test/unit/commands/domains/clear.unit.test.ts b/packages/cli/test/unit/commands/domains/clear.unit.test.ts new file mode 100644 index 0000000000..f622c929c6 --- /dev/null +++ b/packages/cli/test/unit/commands/domains/clear.unit.test.ts @@ -0,0 +1,23 @@ +import {runCommand} from '@oclif/test' +import {expect} from 'chai' +import nock from 'nock' + +describe('domains:clear', function () { + let api: nock.Scope + + beforeEach(function () { + api = nock('https://api.heroku.com') + }) + + it('clears domains all domains', async function () { + api + .get('/apps/myapp/domains') + .reply(200, [{hostname: 'example.com', kind: 'custom'}]) + .delete('/apps/myapp/domains/example.com') + .reply(200, {}) + + const {stderr} = await runCommand(['domains:clear', '--app', 'myapp']) + + expect(stderr).to.contain('Removing all domains from ⬢ myapp... done') + }) +}) diff --git a/packages/cli/test/unit/commands/domains/clear.unit.test.ts.skip b/packages/cli/test/unit/commands/domains/clear.unit.test.ts.skip deleted file mode 100644 index fcf0c85c01..0000000000 --- a/packages/cli/test/unit/commands/domains/clear.unit.test.ts.skip +++ /dev/null @@ -1,18 +0,0 @@ -import {expect, test} from '@oclif/test' - -describe('domains:clear', function () { - test - .stderr() - .nock('https://api.heroku.com', api => api - .get('/apps/myapp/domains') - .reply(200, [{hostname: 'example.com', kind: 'custom'}]), - ) - .nock('https://api.heroku.com', api => api - .delete('/apps/myapp/domains/example.com') - .reply(200, {}), - ) - .command(['domains:clear', '--app', 'myapp']) - .it('clears domains all domains', ctx => { - expect(ctx.stderr).to.contain('Removing all domains from ⬢ myapp... done') - }) -}) diff --git a/packages/cli/test/unit/commands/domains/index.unit.test.ts.skip b/packages/cli/test/unit/commands/domains/index.unit.test.ts similarity index 58% rename from packages/cli/test/unit/commands/domains/index.unit.test.ts.skip rename to packages/cli/test/unit/commands/domains/index.unit.test.ts index 63f631f7b5..528e1de529 100644 --- a/packages/cli/test/unit/commands/domains/index.unit.test.ts.skip +++ b/packages/cli/test/unit/commands/domains/index.unit.test.ts @@ -1,24 +1,44 @@ -import {expect, test} from '@oclif/test' -import {unwrap} from '../../../helpers/utils/unwrap.js' -import removeAllWhitespace from '../../../helpers/utils/remove-whitespaces.js' -import DomainsIndex from '../../../../src/commands/domains/index.js' +import {expect} from 'chai' +import nock from 'nock' import sinon from 'sinon' +import {stderr, stdout} from 'stdout-stderr' + +import DomainsIndex from '../../../../src/commands/domains/index.js' +import runCommand from '../../../helpers/runCommand.js' +import removeAllWhitespace from '../../../helpers/utils/remove-whitespaces.js' +import {unwrap} from '../../../helpers/utils/unwrap.js' describe('domains', function () { + let confirmStub: sinon.SinonStub + let api: nock.Scope + + beforeEach(function () { + api = nock('https://api.heroku.com') + confirmStub = sinon.stub(DomainsIndex.prototype, 'confirmDisplayAllDomains').resolves(true) + }) + + afterEach(function () { + api.done() + confirmStub.restore() + nock.cleanAll() + stdout.stop() + stderr.stop() + }) + const herokuOnlyDomainsResponse = [{ acm_status: null, acm_status_reason: null, app: { - name: 'myapp', id: '01234567-89ab-cdef-0123-456789abcdef', + name: 'myapp', }, cname: null, created_at: '2012-01-01T12:00:00Z', hostname: 'myapp.herokuapp.com', id: '01234567-89ab-cdef-0123-456789abcdef', kind: 'heroku', - updated_at: '2012-01-01T12:00:00Z', status: 'pending', + updated_at: '2012-01-01T12:00:00Z', }] const herokuAndCustomDomainsResponse = [ @@ -27,46 +47,46 @@ describe('domains', function () { acm_status: 'failing', acm_status_reason: 'Failing CCA check', app: { - name: 'myapp', id: '01234567-89ab-cdef-0123-456789abcdef', + name: 'myapp', }, cname: 'foo.herokudns.com', created_at: '2012-01-01T12:00:00Z', hostname: 'example.com', id: '11434567-89ab-cdef-0123-456789abcdef', kind: 'custom', - updated_at: '2012-01-01T12:00:00Z', status: 'succeeded', + updated_at: '2012-01-01T12:00:00Z', }, { acm_status: 'failing', acm_status_reason: 'Failing CCA check', app: { - name: 'myapp', id: '01234567-89ab-cdef-0123-456789abcdef', + name: 'myapp', }, cname: 'bar.herokudns.com', created_at: '2012-01-01T12:00:00Z', hostname: 'www.example.com', id: '11234567-89ab-cdef-0123-456789abcdef', kind: 'custom', - updated_at: '2012-01-01T12:00:00Z', status: 'succeeded', + updated_at: '2012-01-01T12:00:00Z', }, { acm_status: 'failing', acm_status_reason: 'Failing CCA check', app: { - name: 'myapp', id: '01234567-89ab-cdef-0123-456789abcdef', + name: 'myapp', }, cname: 'buzz.herokudns.com', created_at: '2012-01-01T12:00:00Z', hostname: '*.example.com', id: '12234567-89ab-cdef-0123-456789abcdef', kind: 'custom', - updated_at: '2012-01-01T12:00:00Z', status: 'succeeded', + updated_at: '2012-01-01T12:00:00Z', }, ] @@ -75,104 +95,90 @@ describe('domains', function () { acm_status: 'failing', acm_status_reason: 'Failing CCA check', app: { - name: 'myapp', id: '01234567-89ab-cdef-0123-456789abcdef', - }, - sni_endpoint: { - id: 1, - name: 'some haiku', + name: 'myapp', }, cname: 'buzz.herokudns.com', created_at: '2012-01-01T12:00:00Z', hostname: '*.example.com', id: '12234567-89ab-cdef-0123-456789abcdef', kind: 'custom', - updated_at: '2012-01-01T12:00:00Z', + sni_endpoint: { + id: 1, + name: 'some haiku', + }, status: 'succeeded', + updated_at: '2012-01-01T12:00:00Z', }, ] - let confirmStub: sinon.SinonStub - beforeEach(async function () { - confirmStub = sinon.stub(DomainsIndex.prototype, 'confirmDisplayAllDomains').resolves(true) - }) + it('does not show the custom domain header if there are no custom domains', async function () { + api + .get('/apps/myapp/domains') + .reply(200, herokuOnlyDomainsResponse) - afterEach(function () { - confirmStub.restore() + await runCommand(DomainsIndex, ['--app', 'myapp']) + + expect(stdout.output).to.contain('=== myapp Heroku Domain\n\nmyapp.herokuapp.com') + expect(stdout.output).to.contain('myapp.herokuapp.com') + expect(stdout.output).to.not.contain('=== myapp Custom Domains') }) - test - .nock('https://api.heroku.com', api => api + it('shows a list of domains and their DNS targets when there are custom domains', async function () { + api .get('/apps/myapp/domains') - .reply(200, herokuOnlyDomainsResponse), - ) - .stdout() - .command(['domains', '--app', 'myapp']) - .it('does not show the custom domain header if there are no custom domains', ctx => { - expect(ctx.stdout).to.contain('=== myapp Heroku Domain\n\nmyapp.herokuapp.com') - expect(ctx.stdout).to.contain('myapp.herokuapp.com') - expect(ctx.stdout).to.not.contain('=== myapp Custom Domains') - }) - - test - .nock('https://api.heroku.com', api => api - .get('/apps/myapp/domains') - .reply(200, herokuAndCustomDomainsResponse), - ) - .stdout() - .command(['domains', '--app', 'myapp']) - .it('shows a list of domains and their DNS targets when there are custom domains', ctx => { - const actual = removeAllWhitespace(ctx.stdout) - expect(ctx.stdout).to.contain('=== myapp Heroku Domain\n\nmyapp.herokuapp.com') - expect(ctx.stdout).to.contain('myapp.herokuapp.com') - expect(ctx.stdout).to.contain('=== myapp Custom Domains') - expect(actual).to.contain(removeAllWhitespace('Domain Name DNS Record Type DNS Target')) - expect(actual).to.contain(removeAllWhitespace('example.com ALIAS or ANAME foo.herokudns.com')) - expect(actual).to.contain(removeAllWhitespace('www.example.com CNAME bar.herokudns.com')) - expect(actual).to.contain(removeAllWhitespace('*.example.com CNAME buzz.herokudns.com')) - }) - - test - .nock('https://api.heroku.com', api => api + .reply(200, herokuAndCustomDomainsResponse) + + await runCommand(DomainsIndex, ['--app', 'myapp']) + + const actual = removeAllWhitespace(stdout.output) + expect(stdout.output).to.contain('=== myapp Heroku Domain\n\nmyapp.herokuapp.com') + expect(stdout.output).to.contain('myapp.herokuapp.com') + expect(stdout.output).to.contain('=== myapp Custom Domains') + expect(actual).to.contain(removeAllWhitespace('Domain Name DNS Record Type DNS Target')) + expect(actual).to.contain(removeAllWhitespace('example.com ALIAS or ANAME foo.herokudns.com')) + expect(actual).to.contain(removeAllWhitespace('www.example.com CNAME bar.herokudns.com')) + expect(actual).to.contain(removeAllWhitespace('*.example.com CNAME buzz.herokudns.com')) + }) + + it('shows the SNI endpoint column when multiple sni endpoints are enabled', async function () { + api .get('/apps/myapp/domains') - .reply(200, herokuDomainWithSniEndpoint), - ) - .stdout() - .command(['domains', '--app', 'myapp']) - .it('shows the SNI endpoint column when multiple sni endpoints are enabled', ctx => { - const actual = removeAllWhitespace(ctx.stdout) - expect(actual).to.contain(removeAllWhitespace('Domain Name DNS Record Type DNS Target SNI Endpoint')) - expect(actual).to.contain(removeAllWhitespace('*.example.com CNAME buzz.herokudns.com some haiku')) - }) - - test - .stdout() - .stderr() - .nock('https://api.heroku.com', api => api + .reply(200, herokuDomainWithSniEndpoint) + + await runCommand(DomainsIndex, ['--app', 'myapp']) + + const actual = removeAllWhitespace(stdout.output) + expect(actual).to.contain(removeAllWhitespace('Domain Name DNS Record Type DNS Target SNI Endpoint')) + expect(actual).to.contain(removeAllWhitespace('*.example.com CNAME buzz.herokudns.com some haiku')) + }) + + it('shows warning message for over 100 domains', async function () { + api .get('/apps/myapp/domains') .reply(200, () => { const domainData = { acm_status: null, acm_status_reason: null, app: { - name: 'myapp', id: '01234567-89ab-cdef-0123-456789abcdef', + name: 'myapp', }, cname: null, created_at: '2012-01-01T12:00:00Z', hostname: 'example.com', id: '11434567-89ab-cdef-0123-456789abcdef', kind: 'custom', - updated_at: '2012-01-01T12:00:00Z', status: 'succeeded', + updated_at: '2012-01-01T12:00:00Z', } return new Array(1000).fill(domainData) // eslint-disable-line unicorn/no-new-array - }), - ) - .command(['domains', '--app', 'myapp']) - .it('shows warning message for over 100 domains', ctx => { - expect(ctx.stdout).to.contain('=== myapp Heroku Domain') - expect(unwrap(ctx.stderr)).to.contain('Warning: This app has over 100 domains. Your terminal may not be configured to display the total amount of domains.') - }) + }) + + await runCommand(DomainsIndex, ['--app', 'myapp']) + + expect(stdout.output).to.contain('=== myapp Heroku Domain') + expect(unwrap(stderr.output)).to.contain('Warning: This app has over 100 domains. Your terminal may not be configured to display the total amount of domains.') + }) }) diff --git a/packages/cli/test/unit/commands/domains/info.unit.test.ts b/packages/cli/test/unit/commands/domains/info.unit.test.ts new file mode 100644 index 0000000000..7729fca30f --- /dev/null +++ b/packages/cli/test/unit/commands/domains/info.unit.test.ts @@ -0,0 +1,50 @@ +import {runCommand} from '@oclif/test' +import {expect} from 'chai' +import nock from 'nock' + +describe('domains:info', function () { + let api: nock.Scope + + beforeEach(function () { + api = nock('https://api.heroku.com') + }) + + afterEach(function () { + api.done() + nock.cleanAll() + }) + + const domainInfoResponse = { + acm_status: 'pending', + acm_status_reason: 'Failing CCA check', + app: { + id: '01234567-89ab-cdef-0123-456789abcdef', + name: 'myapp', + }, + cname: 'example.herokudns.com', + created_at: '2012-01-01T12:00:00Z', + hostname: 'www.example.com', + id: '01234567-89ab-cdef-0123-456789abcdef', + kind: 'custom', + status: 'pending', + updated_at: '2012-01-01T12:00:00Z', + } + + it('shows detailed information about a domain', async function () { + api + .get('/apps/myapp/domains/www.example.com') + .reply(200, domainInfoResponse) + + const {stdout} = await runCommand(['domains:info', 'www.example.com', '--app', 'myapp']) + + expect(stdout).to.contain('acm_status: pending') + expect(stdout).to.contain('acm_status_reason: Failing CCA check') + expect(stdout).to.contain('app: myapp') + expect(stdout).to.contain('cname: example.herokudns.com') + expect(stdout).to.contain('created_at: 2012-01-01T12:00:00Z') + expect(stdout).to.contain('hostname: www.example.com') + expect(stdout).to.contain('kind: custom') + expect(stdout).to.contain('status: pending') + expect(stdout).to.contain('updated_at: 2012-01-01T12:00:00Z') + }) +}) diff --git a/packages/cli/test/unit/commands/domains/info.unit.test.ts.skip b/packages/cli/test/unit/commands/domains/info.unit.test.ts.skip deleted file mode 100644 index 597a8f8319..0000000000 --- a/packages/cli/test/unit/commands/domains/info.unit.test.ts.skip +++ /dev/null @@ -1,38 +0,0 @@ -import {expect, test} from '@oclif/test' - -describe('domains:info', function () { - const domainInfoResponse = { - acm_status: 'pending', - acm_status_reason: 'Failing CCA check', - app: { - name: 'myapp', - id: '01234567-89ab-cdef-0123-456789abcdef', - }, - cname: 'example.herokudns.com', - created_at: '2012-01-01T12:00:00Z', - hostname: 'www.example.com', - id: '01234567-89ab-cdef-0123-456789abcdef', - kind: 'custom', - updated_at: '2012-01-01T12:00:00Z', - status: 'pending', - } - - test - .nock('https://api.heroku.com', api => api - .get('/apps/myapp/domains/www.example.com') - .reply(200, domainInfoResponse), - ) - .stdout() - .command(['domains:info', 'www.example.com', '--app', 'myapp']) - .it('shows detailed information about a domain', ctx => { - expect(ctx.stdout).to.contain('acm_status: pending') - expect(ctx.stdout).to.contain('acm_status_reason: Failing CCA check') - expect(ctx.stdout).to.contain('app: myapp') - expect(ctx.stdout).to.contain('cname: example.herokudns.com') - expect(ctx.stdout).to.contain('created_at: 2012-01-01T12:00:00Z') - expect(ctx.stdout).to.contain('hostname: www.example.com') - expect(ctx.stdout).to.contain('kind: custom') - expect(ctx.stdout).to.contain('status: pending') - expect(ctx.stdout).to.contain('updated_at: 2012-01-01T12:00:00Z') - }) -}) diff --git a/packages/cli/test/unit/commands/domains/remove.unit.test.ts b/packages/cli/test/unit/commands/domains/remove.unit.test.ts new file mode 100644 index 0000000000..271e130d84 --- /dev/null +++ b/packages/cli/test/unit/commands/domains/remove.unit.test.ts @@ -0,0 +1,26 @@ +import {runCommand} from '@oclif/test' +import {expect} from 'chai' +import nock from 'nock' + +describe('domains:remove', function () { + let api: nock.Scope + + beforeEach(function () { + api = nock('https://api.heroku.com') + }) + + afterEach(function () { + api.done() + nock.cleanAll() + }) + + it('removes a single domain provided by an argument', async function () { + api + .delete('/apps/myapp/domains/example.com') + .reply(200, {}) + + const {stderr} = await runCommand(['domains:remove', 'example.com', '--app', 'myapp']) + + expect(stderr).to.contain('Removing example.com from ⬢ myapp... done') + }) +}) diff --git a/packages/cli/test/unit/commands/domains/remove.unit.test.ts.skip b/packages/cli/test/unit/commands/domains/remove.unit.test.ts.skip deleted file mode 100644 index 005be89a9b..0000000000 --- a/packages/cli/test/unit/commands/domains/remove.unit.test.ts.skip +++ /dev/null @@ -1,14 +0,0 @@ -import {expect, test} from '@oclif/test' - -describe('domains:remove', function () { - test - .stderr() - .nock('https://api.heroku.com', api => api - .delete('/apps/myapp/domains/example.com') - .reply(200, {}), - ) - .command(['domains:remove', 'example.com', '--app', 'myapp']) - .it('removes a single domain provided by an argument', ctx => { - expect(ctx.stderr).to.contain('Removing example.com from ⬢ myapp... done') - }) -}) diff --git a/packages/cli/test/unit/commands/domains/update.unit.test.ts.skip b/packages/cli/test/unit/commands/domains/update.unit.test.ts similarity index 50% rename from packages/cli/test/unit/commands/domains/update.unit.test.ts.skip rename to packages/cli/test/unit/commands/domains/update.unit.test.ts index dfc2014d6b..9e559c423a 100644 --- a/packages/cli/test/unit/commands/domains/update.unit.test.ts.skip +++ b/packages/cli/test/unit/commands/domains/update.unit.test.ts @@ -1,6 +1,19 @@ -import {expect, test} from '@oclif/test' +import {runCommand} from '@oclif/test' +import {expect} from 'chai' +import nock from 'nock' describe('domains:update', function () { + let api: nock.Scope + + beforeEach(function () { + api = nock('https://api.heroku.com') + }) + + afterEach(function () { + api.done() + nock.cleanAll() + }) + const responseBody = { acm_status: null, acm_status_reason: null, @@ -10,20 +23,19 @@ describe('domains:update', function () { hostname: 'example.com', id: '7ac15e30-6460-48e1-919a-e794bf3512ac', kind: 'custom', - status: 'succeeded', sni_endpoint: { id: '8cae023a-d8f1-4aca-9929-e516dc011694', }, + status: 'succeeded', } - test - .stderr() - .nock('https://api.heroku.com', api => api + it('updates the domain to use a different certificate', async function () { + api .patch('/apps/myapp/domains/example.com', {sni_endpoint: 'sniendpoint-id'}) - .reply(200, responseBody), - ) - .command(['domains:update', 'example.com', '--cert', 'sniendpoint-id', '--app', 'myapp']) - .it('updates the domain to use a different certificate', ctx => { - expect(ctx.stderr).to.contain('Updating example.com to use sniendpoint-id certificate... done') - }) + .reply(200, responseBody) + + const {stderr} = await runCommand(['domains:update', 'example.com', '--cert', 'sniendpoint-id', '--app', 'myapp']) + + expect(stderr).to.contain('Updating example.com to use sniendpoint-id certificate... done') + }) }) diff --git a/packages/cli/test/unit/commands/domains/wait.unit.test.ts b/packages/cli/test/unit/commands/domains/wait.unit.test.ts new file mode 100644 index 0000000000..a0f88787d2 --- /dev/null +++ b/packages/cli/test/unit/commands/domains/wait.unit.test.ts @@ -0,0 +1,40 @@ +import {runCommand} from '@oclif/test' +import {expect} from 'chai' +import nock from 'nock' + +describe('domains:wait', function () { + let api: nock.Scope + + beforeEach(function () { + api = nock('https://api.heroku.com') + }) + + afterEach(function () { + api.done() + nock.cleanAll() + }) + + it('waits on domain status succeeded', async function () { + api + .get('/apps/myapp/domains/example.com') + .reply(200, {hostname: 'example.com', id: 123, status: 'pending'}) + .get('/apps/myapp/domains/123') + .reply(200, {hostname: 'example.com', id: 123, status: 'succeeded'}) + + const {stderr} = await runCommand(['domains:wait', 'example.com', '--app', 'myapp']) + + expect(stderr).to.contain('Waiting for example.com... done') + }) + + it('waits on domains when no hostname is provided', async function () { + api + .get('/apps/myapp/domains') + .reply(200, [{hostname: 'example.com', id: 123, status: 'pending'}]) + .get('/apps/myapp/domains/123') + .reply(200, {hostname: 'example.com', id: 123, status: 'succeeded'}) + + const {stderr} = await runCommand(['domains:wait', '--app', 'myapp']) + + expect(stderr).to.contain('Waiting for example.com... done') + }) +}) diff --git a/packages/cli/test/unit/commands/domains/wait.unit.test.ts.skip b/packages/cli/test/unit/commands/domains/wait.unit.test.ts.skip deleted file mode 100644 index 8724f4e4a5..0000000000 --- a/packages/cli/test/unit/commands/domains/wait.unit.test.ts.skip +++ /dev/null @@ -1,33 +0,0 @@ -import {expect, test} from '@oclif/test' - -describe('domains:wait', function () { - test - .stderr() - .nock('https://api.heroku.com', api => api - .get('/apps/myapp/domains/example.com') - .reply(200, {id: 123, hostname: 'example.com', status: 'pending'}), - ) - .nock('https://api.heroku.com', api => api - .get('/apps/myapp/domains/123') - .reply(200, {id: 123, hostname: 'example.com', status: 'succeeded'}), - ) - .command(['domains:wait', 'example.com', '--app', 'myapp']) - .it('waits on domain status succeeded', ctx => { - expect(ctx.stderr).to.contain('Waiting for example.com... done') - }) - - test - .stderr() - .nock('https://api.heroku.com', (api: any) => api - .get('/apps/myapp/domains') - .reply(200, [{id: 123, hostname: 'example.com', status: 'pending'}]), - ) - .nock('https://api.heroku.com', (api: any) => api - .get('/apps/myapp/domains/123') - .reply(200, {id: 123, hostname: 'example.com', status: 'succeeded'}), - ) - .command(['domains:wait', '--app', 'myapp']) - .it('waits on domains when no hostname is provided', ctx => { - expect(ctx.stderr).to.contain('Waiting for example.com... done') - }) -})