Skip to content

Commit

Permalink
Completed unit test conversion
Browse files Browse the repository at this point in the history
  • Loading branch information
justinwilaby committed Mar 15, 2024
1 parent 657b20f commit 7191817
Show file tree
Hide file tree
Showing 5 changed files with 229 additions and 90 deletions.
18 changes: 9 additions & 9 deletions packages/cli/src/commands/redis/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,14 @@ async function redisCLI(uri: ClientRequestArgs, client: Writable): Promise<void>

async function bastionConnect(uri: URL, bastions: string, config: Record<string, unknown>, preferNativeTls: boolean) {
const tunnel: Client = await new Promise(resolve => {
const tunnel = new Client()
tunnel.on('ready', () => resolve(tunnel))
const ssh2 = new Client()
resolve(ssh2)
ssh2.once('ready', () => resolve(ssh2))
ssh2.connect({
host: bastions.split(',')[0],
username: 'bastion',
privateKey: match(config, /_BASTION_KEY/) ?? '',
})
})
const localPort = await portfinder.getPortPromise({startPort: 49152, stopPort: 65535})
const stream: Duplex = await promisify(tunnel.forwardOut)('localhost', localPort, uri.hostname, Number.parseInt(uri.port, 10))
Expand All @@ -127,12 +133,6 @@ async function bastionConnect(uri: URL, bastions: string, config: Record<string,
stream.on('close', () => tunnel.end())
stream.on('end', () => client.end())

tunnel.connect({
host: bastions.split(',')[0],
username: 'bastion',
privateKey: match(config, /_BASTION_KEY/) ?? '',
})

return redisCLI(urlToHttpOptions(uri), client)
}

Expand All @@ -146,7 +146,7 @@ function match(config: Record<string, unknown>, lookup: RegExp): string | null {
return null
}

function maybeTunnel(redis: RedisFormation, config: Record<string, unknown>) {
async function maybeTunnel(redis: RedisFormation, config: Record<string, unknown>) {
const bastions = match(config, /_BASTIONS/)
const hobby = redis.plan.indexOf('hobby') === 0
const preferNativeTls = redis.prefer_native_tls
Expand Down
6 changes: 2 additions & 4 deletions packages/cli/src/lib/redis/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export interface RedisFormation {
}

export function makeAddonsFilter(filter = '') {
const matcher = new RegExp(`/(${filter})/ig`)
const matcher = new RegExp(`(${filter})`, 'ig')

function matches(addon: Required<AddOn>) {
for (let i = 0; i < addon.config_vars.length; i++) {
Expand All @@ -39,9 +39,7 @@ export function makeAddonsFilter(filter = '') {

function onResponse(addons: Required<AddOn>[]) {
const redisAddons = []
// eslint-disable-next-line unicorn/no-for-loop
for (let i = 0; i < addons.length; i++) {
const addon = addons[i]
for (const addon of addons) {
const service = addon.addon_service.name ?? ''

if (service.indexOf(ADDON) === 0 && (!filter || matches(addon))) {
Expand Down
114 changes: 67 additions & 47 deletions packages/cli/test/unit/commands/redis/cli.unit.test.ts
Original file line number Diff line number Diff line change
@@ -1,56 +1,59 @@
import {stdout, stderr} from 'stdout-stderr'
import runCommand, {GenericCmd} from '../../../helpers/runCommand'
import {SinonStub} from 'sinon'
import {CLIError} from '@oclif/core/lib/errors'

import * as nock from 'nock'
import * as sinon from 'sinon'
import {noCallThru} from 'proxyquire'
import {expect} from 'chai'
import {Duplex} from 'node:stream'

const nock = require('nock')
const sinon = require('sinon')
const proxyquire = require('proxyquire')
.noCallThru()
const expect = require('chai').expect
const Duplex = require('stream').Duplex
const EventEmitter = require('events').EventEmitter

describe('heroku redis:cli', async () => {
const command = proxyquire('../../../commands/cli.js', {net: {}, tls: {}, ssh2: {}})
require('./shared.unit.test.ts')
.shouldHandleArgs(command)
})
class Client extends Duplex {
_write() {}

_read() {
this.emit('end')
}
}

class Tunnel extends EventEmitter {
forwardOut = sinon.stub().yields(null, new Client())
connect = sinon.stub().callsFake(() => this.emit('ready'))
end = sinon.stub()
}

describe('heroku redis:cli', async () => {
describe('heroku redis:cli', async () => {
const proxyquire = noCallThru()
const command = proxyquire('../../../commands/cli.js', {net: {}, tls: {}, ssh2: {}})
require('./shared.unit.test.ts').shouldHandleArgs(command)
})

let command: GenericCmd
let net: { connect: SinonStub }
let tls: { connect: SinonStub }
let tunnel: { forwardOut: SinonStub, connect: SinonStub, end: SinonStub }
let net: {connect: SinonStub}
let tls: {connect: SinonStub}
let tunnel: {forwardOut: SinonStub, connect: SinonStub, end: SinonStub}
const addonId = '1dcb269b-8be5-4132-8aeb-e3f3c7364958'
const appId = '7b0ae612-8775-4502-a5b5-2b45a4d18b2d'

beforeEach(function () {
class Client extends Duplex {
_write() {}
_read() {
this.emit('end')
}
}

net = {
connect: sinon.stub().returns(new Client()),
}
tls = {
connect: sinon.stub().returns(new Client()),
}

class Tunnel extends EventEmitter {
forwardOut = sinon.stub().yields(null, new Client())
connect = sinon.stub().callsFake(() => this.emit('ready'))
end = sinon.stub()
}

const ssh2 = {
Client: function () {
tunnel = new Tunnel()
return tunnel
},
}
const proxyquire = noCallThru()
const {default: Cmd} = proxyquire('../../../../src/commands/redis/cli', {net, tls, ssh2})
command = Cmd
})
Expand Down Expand Up @@ -86,8 +89,10 @@ describe('heroku redis:cli', async () => {
app.done()
configVars.done()
redis.done()
expect(stdout.output).to.equal('Connecting to redis-haiku (REDIS_FOO, REDIS_BAR):\n')
expect(stderr.output).to.equal('')
const outputParts = stdout.output.split('\n')
expect(outputParts[0]).to.equal('Connecting to redis-haiku (REDIS_FOO, REDIS_BAR):')
expect(outputParts[1]).to.equal('')
expect(outputParts[2]).to.equal('Disconnected from instance.')
expect(net.connect.called).to.equal(true)
})

Expand Down Expand Up @@ -122,8 +127,10 @@ describe('heroku redis:cli', async () => {
app.done()
configVars.done()
redis.done()
expect(stdout.output).to.equal('Connecting to redis-haiku (REDIS_FOO, REDIS_BAR, REDIS_TLS_URL):\n')
expect(stderr.output).to.equal('')
const outputParts = stdout.output.split('\n')
expect(outputParts[0]).to.equal('Connecting to redis-haiku (REDIS_FOO, REDIS_BAR, REDIS_TLS_URL):')
expect(outputParts[1]).to.equal('')
expect(outputParts[2]).to.equal('Disconnected from instance.')
expect(tls.connect.called).to.equal(true)
})

Expand Down Expand Up @@ -158,8 +165,10 @@ describe('heroku redis:cli', async () => {
app.done()
configVars.done()
redis.done()
expect(stdout.output).to.equal('Connecting to redis-haiku (REDIS_FOO, REDIS_BAR):\n')
expect(stderr.output).to.equal('')
const outputParts = stdout.output.split('\n')
expect(outputParts[0]).to.equal('Connecting to redis-haiku (REDIS_FOO, REDIS_BAR):')
expect(outputParts[1]).to.equal('')
expect(outputParts[2]).to.equal('Disconnected from instance.')
expect(tls.connect.called).to.equal(true)
})

Expand Down Expand Up @@ -194,15 +203,18 @@ describe('heroku redis:cli', async () => {
])
expect(true, 'cli command should fail!').to.equal(false)
} catch (error) {
const {code} = error as { code: number }
expect(error).to.be.an.instanceof(Error)
expect(code).to.equal(1)
expect(error).to.be.an.instanceof(CLIError)

if (error instanceof CLIError) {
const {oclif: {exit}, message} = error
expect(exit).to.equal(1)
expect(message).to.contain('Using redis:cli on Heroku Redis shield plans is not supported.')
}
}

await app.done()
await redis.done()
await configVars.done()
expect(stderr.output).to.contain('Using redis:cli on Heroku Redis shield plans is not supported.')
app.done()
redis.done()
configVars.done()
})

it('# for bastion it uses tunnel.connect', async () => {
Expand All @@ -227,17 +239,21 @@ describe('heroku redis:cli', async () => {
.reply(200, {
resource_url: 'redis://foobar:password@example.com:8649', plan: 'premium-0',
})

await runCommand(command, [
'--app',
'example',
'--confirm',
'example',
])

app.done()
configVars.done()
redis.done()
expect(stdout.output).to.equal('Connecting to redis-haiku (REDIS_URL):\n')
expect(stderr.output).to.equal('')
const outputParts = stdout.output.split('\n')
expect(outputParts[0]).to.equal('Connecting to redis-haiku (REDIS_URL):')
expect(outputParts[1]).to.equal('')
expect(outputParts[2]).to.equal('Disconnected from instance.')
})

it('# for private spaces bastion with prefer_native_tls, it uses tls.connect', async () => {
Expand Down Expand Up @@ -271,8 +287,10 @@ describe('heroku redis:cli', async () => {
app.done()
configVars.done()
redis.done()
expect(stdout.output).to.equal('Connecting to redis-haiku (REDIS_URL):\n')
expect(stderr.output).to.equal('')
const outputParts = stdout.output.split('\n')
expect(outputParts[0]).to.equal('Connecting to redis-haiku (REDIS_URL):')
expect(outputParts[1]).to.equal('')
expect(outputParts[2]).to.equal('Disconnected from instance.')
expect(tls.connect.called).to.equal(true)
})

Expand Down Expand Up @@ -320,8 +338,10 @@ describe('heroku redis:cli', async () => {
app.done()
configVars.done()
redis.done()
expect(stdout.output).to.equal('Connecting to redis-sonnet (REDIS_6_URL):\n')
expect(stderr.output).to.equal('')
const outputParts = stdout.output.split('\n')
expect(outputParts[0]).to.equal('Connecting to redis-sonnet (REDIS_6_URL):')
expect(outputParts[1]).to.equal('')
expect(outputParts[2]).to.equal('Disconnected from instance.')
const connectArgs = tunnel.connect.args[0]
expect(connectArgs).to.have.length(1)
expect(connectArgs[0]).to.deep.equal({
Expand All @@ -332,7 +352,7 @@ describe('heroku redis:cli', async () => {
expect(localAddr).to.equal('localhost')
expect(localPort).to.be.a('number')
expect(remoteAddr).to.equal('redis-6.example.com')
expect(remotePort).to.equal('8649')
expect(remotePort).to.equal(8649)
const tlsConnectArgs = tls.connect.args[0]
expect(tlsConnectArgs).to.have.length(1)
const tlsConnectOptions = {
Expand Down
73 changes: 45 additions & 28 deletions packages/cli/test/unit/commands/redis/shared.unit.test.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,59 @@
import {CLIError} from '@oclif/core/lib/errors'
import {stdout, stderr} from 'stdout-stderr'
import Cmd from 'REPLACE_WITH_PATH_TO_COMMAND'
import runCommand from '../../helpers/runCommand'
let expect = require('chai').expect
let nock = require('nock')
let exit = require('heroku-cli-util').exit
const unwrap = require('../../unwrap')
exports.shouldHandleArgs = function (command, flags = {}) {

import * as nock from 'nock'
import {expect} from 'chai'
import runCommand, {type GenericCmd} from '../../../helpers/runCommand'
import {unwrap} from '../../../helpers/utils/unwrap'

exports.shouldHandleArgs = function (command: GenericCmd) {
describe('', function () {
beforeEach(function () {
cli.mockConsole()
exit.mock()
nock.cleanAll()
})
it('# shows an error if an app has no addons', function () {
let app = nock('https://api.heroku.com:443')

it('# shows an error if an app has no addons', async () => {
const app = nock('https://api.heroku.com:443')
.get('/apps/example/addons')
.reply(200, [])
return expect(runCommand(Cmd, [
'--app',
'example',
])).to.be.rejectedWith(exit.ErrorExit)
.then(() => app.done())
.then(() => expect(stdout.output).to.equal(''))
.then(() => expect(unwrap(stderr.output)).to.equal('No Redis instances found.\n'))

try {
await runCommand(command, [
'--app',
'example',
])
} catch (error) {
expect(error).to.be.an.instanceof(CLIError)
}

app.done()
expect(stdout.output).to.equal('')
expect(unwrap(stderr.output)).to.equal('No Redis instances found.\n')
})
it('# shows an error if the addon is ambiguous', function () {
let app = nock('https://api.heroku.com:443')

it('# shows an error if the addon is ambiguous', async () => {
const app = nock('https://api.heroku.com:443')
.get('/apps/example/addons')
.reply(200, [
{name: 'redis-haiku-a', addon_service: {name: 'heroku-redis'}, config_vars: ['REDIS_FOO']}, {name: 'redis-haiku-b', addon_service: {name: 'heroku-redis'}, config_vars: ['REDIS_BAR']},
{
name: 'redis-haiku-a',
addon_service: {name: 'heroku-redis'},
config_vars: ['REDIS_FOO'],
}, {name: 'redis-haiku-b', addon_service: {name: 'heroku-redis'}, config_vars: ['REDIS_BAR']},
])

try {
await runCommand(command, [
'--app',
'example',
])
return expect(runCommand(Cmd, [
'--app',
'example',
])).to.be.rejectedWith(exit.ErrorExit)
.then(() => app.done())
.then(() => expect(stdout.output).to.equal(''))
.then(() => expect(unwrap(stderr.output)).to.equal('Please specify a single instance. Found: redis-haiku-a, redis-haiku-b\n'))
} catch (error) {
expect(error).to.be.an.instanceof(CLIError)
}

app.done()
expect(stdout.output).to.equal('')
expect(unwrap(stderr.output)).to.equal('Please specify a single instance. Found: redis-haiku-a, redis-haiku-b\n')
})
})
}
Loading

0 comments on commit 7191817

Please sign in to comment.