Skip to content

Commit

Permalink
refactor: convert organizations:current command to yargs
Browse files Browse the repository at this point in the history
  • Loading branch information
rossiam committed Jan 21, 2025
1 parent 57e2a7b commit dfcf065
Show file tree
Hide file tree
Showing 5 changed files with 243 additions and 78 deletions.
47 changes: 0 additions & 47 deletions packages/cli/src/__tests__/commands/organizations.test.ts

This file was deleted.

31 changes: 0 additions & 31 deletions packages/cli/src/commands/organizations/current.ts

This file was deleted.

173 changes: 173 additions & 0 deletions src/__tests__/commands/organizations/current.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import { jest } from '@jest/globals'

import type { ArgumentsCamelCase, Argv } from 'yargs'

import type {
OrganizationResponse,
OrganizationsEndpoint,
SmartThingsClient,
} from '@smartthings/core-sdk'

import type { CommandArgs } from '../../../commands/organizations/current.js'
import type {
APIOrganizationCommand,
APIOrganizationCommandFlags,
apiOrganizationCommand,
apiOrganizationCommandBuilder,
} from '../../../lib/command/api-organization-command.js'
import type {
formatAndWriteItem,
formatAndWriteItemBuilder,
} from '../../../lib/command/format.js'
import {
tableFieldDefinitions,
} from '../../../lib/command/util/organizations-util.js'
import { buildArgvMock, buildArgvMockStub } from '../../test-lib/builder-mock.js'
import { CLIConfig } from '../../../lib/cli-config.js'


const apiOrganizationCommandMock = jest.fn<typeof apiOrganizationCommand>()
const apiOrganizationCommandBuilderMock = jest.fn<typeof apiOrganizationCommandBuilder>()
jest.unstable_mockModule('../../../lib/command/api-organization-command.js', () => ({
apiOrganizationCommand: apiOrganizationCommandMock,
apiOrganizationCommandBuilder: apiOrganizationCommandBuilderMock,
}))

const formatAndWriteItemMock = jest.fn<typeof formatAndWriteItem>()
const formatAndWriteItemBuilderMock = jest.fn<typeof formatAndWriteItemBuilder>()
jest.unstable_mockModule('../../../lib/command/format.js', () => ({
formatAndWriteItem: formatAndWriteItemMock,
formatAndWriteItemBuilder: formatAndWriteItemBuilderMock,
}))

jest.unstable_mockModule('../../../lib/command/util/organizations-util.js', () => ({
tableFieldDefinitions,
}))

const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => { /*no-op*/ })


const {
default: cmd,
} = await import('../../../commands/organizations/current.js')


test('builder', () => {
const yargsMock = buildArgvMockStub<object>()
const {
yargsMock: apiOrganizationCommandBuilderArgvMock,
exampleMock,
argvMock,
} = buildArgvMock<APIOrganizationCommandFlags, CommandArgs>()

apiOrganizationCommandBuilderMock.mockReturnValueOnce(apiOrganizationCommandBuilderArgvMock)
formatAndWriteItemBuilderMock.mockReturnValueOnce(argvMock)

const builder = cmd.builder as (yargs: Argv<object>) => Argv<CommandArgs>

expect(builder(yargsMock)).toBe(argvMock)

expect(apiOrganizationCommandBuilderMock).toHaveBeenCalledExactlyOnceWith(yargsMock)
expect(formatAndWriteItemBuilderMock)
.toHaveBeenCalledExactlyOnceWith(apiOrganizationCommandBuilderArgvMock)

expect(exampleMock).toHaveBeenCalledTimes(1)
})

describe('handler', () => {
const organization1 = { organizationId: 'organization-id-1' } as OrganizationResponse
const organization2 = { organizationId: 'organization-id-2' } as OrganizationResponse
const defaultOrganization = {
organizationId: 'default-organization-id',
isDefaultUserOrg: true,
} as OrganizationResponse
const organizationList = [organization1, organization2, defaultOrganization] as OrganizationResponse[]

const apiOrganizationsGetMock = jest.fn<typeof OrganizationsEndpoint.prototype.get>()
.mockResolvedValue(organization2)
const apiOrganizationsListMock = jest.fn<typeof OrganizationsEndpoint.prototype.list>()
.mockResolvedValue(organizationList)
const clientMock = {
organizations: {
get: apiOrganizationsGetMock,
list: apiOrganizationsListMock,
},
} as unknown as SmartThingsClient
const stringConfigValueMock = jest.fn<CLIConfig['stringConfigValue']>()
.mockReturnValue(undefined)
const cliConfigMock = {
stringConfigValue: stringConfigValueMock,
} as unknown as CLIConfig
const command = {
client: clientMock,
cliConfig: cliConfigMock,
} as APIOrganizationCommand<ArgumentsCamelCase<CommandArgs>>
apiOrganizationCommandMock.mockResolvedValue(command)

const inputArgv = {
profile: 'default',
} as unknown as ArgumentsCamelCase<CommandArgs>

it('finds user default organization', async () => {
await expect(cmd.handler(inputArgv)).resolves.not.toThrow()

expect(apiOrganizationCommandMock).toHaveBeenCalledExactlyOnceWith(inputArgv)
expect(apiOrganizationsListMock).toHaveBeenCalledExactlyOnceWith()
expect(formatAndWriteItemMock)
.toHaveBeenCalledExactlyOnceWith(command, { tableFieldDefinitions }, defaultOrganization)

expect(apiOrganizationsGetMock).not.toHaveBeenCalled()
})

it('displays configured default organization', async () => {
stringConfigValueMock.mockReturnValueOnce('config-organization-id')

await expect(cmd.handler(inputArgv)).resolves.not.toThrow()

expect(apiOrganizationCommandMock).toHaveBeenCalledExactlyOnceWith(inputArgv)
expect(apiOrganizationsGetMock).toHaveBeenCalledExactlyOnceWith('config-organization-id')
expect(formatAndWriteItemMock)
.toHaveBeenCalledExactlyOnceWith(command, { tableFieldDefinitions }, organization2)

expect(apiOrganizationsListMock).not.toHaveBeenCalled()
})

it('displays proper error message when a configured organization does not exist', async () => {
stringConfigValueMock.mockReturnValueOnce('config-organization-id')
apiOrganizationsGetMock.mockRejectedValueOnce({ response: { status: 403 } })

await expect(cmd.handler(inputArgv)).resolves.not.toThrow()

expect(apiOrganizationCommandMock).toHaveBeenCalledExactlyOnceWith(inputArgv)
expect(apiOrganizationsGetMock).toHaveBeenCalledExactlyOnceWith('config-organization-id')
expect(consoleErrorSpy).toHaveBeenCalledWith('Organization \'config-organization-id\' not found')

expect(apiOrganizationsListMock).not.toHaveBeenCalled()
expect(formatAndWriteItemMock).not.toHaveBeenCalled()
})

it('displays proper error message when no default organization configured or found', async () => {
apiOrganizationsListMock.mockResolvedValueOnce([organization1, organization2])

await expect(cmd.handler(inputArgv)).resolves.not.toThrow()

expect(apiOrganizationCommandMock).toHaveBeenCalledExactlyOnceWith(inputArgv)
expect(consoleErrorSpy).toHaveBeenCalledWith('Could not find an active organization.')

expect(apiOrganizationsGetMock).not.toHaveBeenCalled()
expect(formatAndWriteItemMock).not.toHaveBeenCalled()
})

it('rethrows unexpected error getting organization', async () => {
stringConfigValueMock.mockReturnValueOnce('config-organization-id')
apiOrganizationsGetMock.mockRejectedValueOnce(Error('some bad thing happened'))

await expect(cmd.handler(inputArgv)).rejects.toThrow('some bad thing happened')

expect(apiOrganizationCommandMock).toHaveBeenCalledExactlyOnceWith(inputArgv)
expect(apiOrganizationsGetMock).toHaveBeenCalledExactlyOnceWith('config-organization-id')

expect(apiOrganizationsListMock).not.toHaveBeenCalled()
expect(formatAndWriteItemMock).not.toHaveBeenCalled()
})
})
2 changes: 2 additions & 0 deletions src/commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import locationsCreateCommand from './locations/create.js'
import locationsDeleteCommand from './locations/delete.js'
import locationsUpdateCommand from './locations/update.js'
import organizationsCommand from './organizations.js'
import organizationsCurrentCommand from './organizations/current.js'
import scenesCommand from './scenes.js'
import schemaCommand from './schema.js'
import schemaCreateCommand from './schema/create.js'
Expand Down Expand Up @@ -70,6 +71,7 @@ export const commands: CommandModule<object, any>[] = [
locationsDeleteCommand,
locationsUpdateCommand,
organizationsCommand,
organizationsCurrentCommand,
scenesCommand,
schemaCommand,
schemaCreateCommand,
Expand Down
68 changes: 68 additions & 0 deletions src/commands/organizations/current.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { type ArgumentsCamelCase, type Argv, type CommandModule } from 'yargs'

import {
apiOrganizationCommand,
apiOrganizationCommandBuilder,
type APIOrganizationCommandFlags,
} from '../../lib/command/api-organization-command.js'
import {
formatAndWriteItem,
formatAndWriteItemBuilder,
type FormatAndWriteItemFlags,
} from '../../lib/command/format.js'
import { tableFieldDefinitions } from '../../lib/command/util/organizations-util.js'
import { OrganizationResponse } from '@smartthings/core-sdk'


export type CommandArgs =
& APIOrganizationCommandFlags
& FormatAndWriteItemFlags

const command = 'organizations:current'

const describe = 'get the currently active organization'

export const builder = (yargs: Argv): Argv<CommandArgs> =>
formatAndWriteItemBuilder(apiOrganizationCommandBuilder(yargs))
.example([
[
'$0 organizations:current',
'display the currently active organization',
],
[
'$0 organizations:current --profile org2',
'display the currently active organization for the profile "org2',
],
])

const handler = async (argv: ArgumentsCamelCase<CommandArgs>): Promise<void> => {
const command = await apiOrganizationCommand(argv)

const currentOrganizationId = command.cliConfig.stringConfigValue('organization')
const getOrganization = async (id: string): Promise<OrganizationResponse | undefined> => {
try {
return await command.client.organizations.get(id)
} catch (error) {
if (error.response?.status === 403) {
return undefined
}
throw error
}
}
const currentOrganization = currentOrganizationId
? await getOrganization(currentOrganizationId)
: (await command.client.organizations.list()).find(org => org.isDefaultUserOrg)

if (currentOrganization) {
await formatAndWriteItem(command, { tableFieldDefinitions }, currentOrganization)
} else {
if (currentOrganizationId) {
console.error(`Organization '${currentOrganizationId}' not found`)
} else {
console.error('Could not find an active organization.')
}
}
}

const cmd: CommandModule<object, CommandArgs> = { command, describe, builder, handler }
export default cmd

0 comments on commit dfcf065

Please sign in to comment.