diff --git a/packages/edge/src/__tests__/commands/edge/channels/delete.test.ts b/packages/edge/src/__tests__/commands/edge/channels/delete.test.ts deleted file mode 100644 index 033090be..00000000 --- a/packages/edge/src/__tests__/commands/edge/channels/delete.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { resetManagedConfigKey } from '@smartthings/cli-lib' -import { ChannelsEndpoint } from '@smartthings/core-sdk' - -import ChannelsDeleteCommand from '../../../../commands/edge/channels/delete.js' -import { chooseChannel } from '../../../../lib/commands/channels-util.js' - - -jest.mock('../../../../../src/lib/commands/channels-util') - -describe('ChannelsDeleteCommand', () => { - const chooseDriverMock = jest.mocked(chooseChannel).mockResolvedValue('chosen-channel-id') - const apiChannelsDeleteSpy = jest.spyOn(ChannelsEndpoint.prototype, 'delete').mockImplementation() - const resetManagedConfigKeyMock = jest.mocked(resetManagedConfigKey) - - it('deletes a channel', async () => { - await expect(ChannelsDeleteCommand.run(['cmd-line-channel-id'])).resolves.not.toThrow() - - expect(chooseDriverMock).toHaveBeenCalledTimes(1) - expect(chooseDriverMock).toHaveBeenCalledWith(expect.any(ChannelsDeleteCommand), - 'Choose a channel to delete.', 'cmd-line-channel-id') - - expect(apiChannelsDeleteSpy).toHaveBeenCalledTimes(1) - expect(apiChannelsDeleteSpy).toHaveBeenCalledWith('chosen-channel-id') - expect(resetManagedConfigKeyMock).toHaveBeenCalledTimes(1) - expect(resetManagedConfigKeyMock).toHaveBeenCalledWith( - expect.objectContaining({ profileName: 'default' }), - 'defaultChannel', - expect.any(Function), - ) - - const predicate = resetManagedConfigKeyMock.mock.calls[0][2] - expect(predicate).toBeDefined() - expect(predicate?.('another-channel-id')).toBeFalsy() - expect(predicate?.('chosen-channel-id')).toBeTruthy() - }) -}) diff --git a/packages/edge/src/commands/edge/channels/create.ts b/packages/edge/src/commands/edge/channels/create.ts deleted file mode 100644 index 5f9f85b0..00000000 --- a/packages/edge/src/commands/edge/channels/create.ts +++ /dev/null @@ -1,52 +0,0 @@ -import inquirer from 'inquirer' - -import { Channel, ChannelCreate } from '@smartthings/core-sdk' - -import { inputAndOutputItem, TableFieldDefinition, userInputProcessor } from '@smartthings/cli-lib' - -import { EdgeCommand } from '../../../lib/edge-command.js' - - -const tableFieldDefinitions: TableFieldDefinition[] = ['channelId', 'name', 'description', - 'type', 'termsOfServiceUrl', 'createdDate', 'lastModifiedDate'] - -export default class ChannelsCreateCommand extends EdgeCommand { - static description = 'create a channel' + - this.apiDocsURL('createChannel') - - static flags = { - ...EdgeCommand.flags, - ...inputAndOutputItem.flags, - } - - async getInputFromUser(): Promise { - const name = (await inquirer.prompt({ - type: 'input', - name: 'name', - message: 'Channel name:', - validate: input => input ? true : 'name is required', - })).name as string - - const description = (await inquirer.prompt({ - type: 'input', - name: 'description', - message: 'Channel description:', - validate: input => input ? true : 'description is required', - })).description as string - - const termsOfServiceUrl = (await inquirer.prompt({ - type: 'input', - name: 'termsOfServiceUrl', - message: 'Channel terms of service URL:', - validate: input => input ? true : 'termsOfServiceUrl is required', - })).termsOfServiceUrl as string - - return { name, description, termsOfServiceUrl, type: 'DRIVER' } - } - - async run(): Promise { - await inputAndOutputItem(this, { tableFieldDefinitions }, - (_, input: ChannelCreate) => this.client.channels.create(input), - userInputProcessor(this)) - } -} diff --git a/packages/edge/src/commands/edge/channels/delete.ts b/packages/edge/src/commands/edge/channels/delete.ts deleted file mode 100644 index addbe7b3..00000000 --- a/packages/edge/src/commands/edge/channels/delete.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { resetManagedConfigKey } from '@smartthings/cli-lib' - -import { chooseChannel } from '../../../lib/commands/channels-util.js' -import { EdgeCommand } from '../../../lib/edge-command.js' - - -export default class ChannelsDeleteCommand extends EdgeCommand { - static description = 'delete a channel' + - this.apiDocsURL('deleteChannel') - - static flags = EdgeCommand.flags - - static args = [{ - name: 'id', - description: 'channel id', - }] - - async run(): Promise { - const id = await chooseChannel(this, 'Choose a channel to delete.', this.args.id) - await this.client.channels.delete(id) - await resetManagedConfigKey(this.cliConfig, 'defaultChannel', value => value === id) - this.log(`Channel ${id} deleted.`) - } -} diff --git a/src/__tests__/commands/edge/channels/delete.test.ts b/src/__tests__/commands/edge/channels/delete.test.ts new file mode 100644 index 00000000..8d43ebab --- /dev/null +++ b/src/__tests__/commands/edge/channels/delete.test.ts @@ -0,0 +1,86 @@ +import { jest } from '@jest/globals' + +import type { ArgumentsCamelCase, Argv } from 'yargs' + +import { ChannelsEndpoint } from '@smartthings/core-sdk' + +import type { CommandArgs } from '../../../../commands/edge/channels/delete.js' +import type { APICommand, APICommandFlags } from '../../../../lib/command/api-command.js' +import type { CLIConfig, resetManagedConfigKey } from '../../../../lib/cli-config.js' +import type { chooseChannel } from '../../../../lib/command/util/edge/channels-choose.js' +import { apiCommandMocks } from '../../../test-lib/api-command-mock.js' +import { buildArgvMock } from '../../../test-lib/builder-mock.js' + + +const resetManagedConfigKeyMock = jest.fn() +jest.unstable_mockModule('../../../../lib/cli-config.js', () => ({ + resetManagedConfigKey: resetManagedConfigKeyMock, +})) + +const { apiCommandMock, apiCommandBuilderMock, apiDocsURLMock } = apiCommandMocks('../../../..') + +const chooseChannelMock = jest.fn().mockResolvedValue('chosen-channel-id') +jest.unstable_mockModule('../../../../lib/command/util/edge/channels-choose.js', () => ({ + chooseChannel: chooseChannelMock, +})) + +const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(() => { /* do nothing */ }) + + +const { default: cmd } = await import('../../../../commands/edge/channels/delete.js') + + +test('builder', () => { + const { + yargsMock, + positionalMock, + optionMock, + exampleMock, + epilogMock, + argvMock, + } = buildArgvMock() + + apiCommandBuilderMock.mockReturnValue(argvMock) + + const builder = cmd.builder as (yargs: Argv) => Argv + expect(builder(yargsMock)).toBe(argvMock) + + expect(apiCommandBuilderMock).toHaveBeenCalledExactlyOnceWith(yargsMock) + + expect(positionalMock).toHaveBeenCalledTimes(1) + expect(optionMock).toHaveBeenCalledTimes(0) + expect(exampleMock).toHaveBeenCalledTimes(1) + expect(apiDocsURLMock).toHaveBeenCalledTimes(1) + expect(epilogMock).toHaveBeenCalledTimes(1) +}) + +test('handler', async () => { + const cliConfig = { profile: {} } as CLIConfig + const inputArgv = { + profile: 'default', + id: 'cmd-line-id', + } as ArgumentsCamelCase + const apiChannelsDeleteMock = jest.fn() + const command = { + client: { + channels: { + delete: apiChannelsDeleteMock, + }, + }, + cliConfig, + } as unknown as APICommand + apiCommandMock.mockResolvedValueOnce(command) + + await expect(cmd.handler(inputArgv)).resolves.not.toThrow() + + expect(apiCommandMock).toHaveBeenCalledExactlyOnceWith(inputArgv) + expect(chooseChannelMock).toHaveBeenCalledExactlyOnceWith(command, 'Choose a channel to delete.', 'cmd-line-id') + expect(apiChannelsDeleteMock).toHaveBeenCalledExactlyOnceWith('chosen-channel-id') + expect(resetManagedConfigKeyMock).toHaveBeenCalledExactlyOnceWith(cliConfig, 'defaultChannel', expect.any(Function)) + expect(consoleLogSpy).toHaveBeenCalledWith('Channel chosen-channel-id deleted.') + + const resetPredicate = resetManagedConfigKeyMock.mock.calls[0][2] + + expect(resetPredicate?.('chosen-channel-id')).toBe(true) + expect(resetPredicate?.('different-channel-id')).toBe(false) +}) diff --git a/src/commands/edge/channels/create.ts b/src/commands/edge/channels/create.ts new file mode 100644 index 00000000..87ec4e82 --- /dev/null +++ b/src/commands/edge/channels/create.ts @@ -0,0 +1,78 @@ +import inquirer from 'inquirer' +import { type ArgumentsCamelCase, type Argv, type CommandModule } from 'yargs' + +import { type Channel, type ChannelCreate } from '@smartthings/core-sdk' + +import { apiCommand, apiCommandBuilder, type APICommandFlags, apiDocsURL } from '../../../lib/command/api-command.js' +import { + inputAndOutputItem, + inputAndOutputItemBuilder, + type InputAndOutputItemFlags, +} from '../../../lib/command/input-and-output-item.js' +import { type TableFieldDefinition } from '../../../lib/table-generator.js' +import { userInputProcessor } from '../../../lib/command/input-processor.js' + + +export type CommandArgs = + & APICommandFlags + & InputAndOutputItemFlags + +const command = 'edge:channels:create' + +const describe = 'create a channel' + +const tableFieldDefinitions: TableFieldDefinition[] = ['channelId', 'name', 'description', + 'type', 'termsOfServiceUrl', 'createdDate', 'lastModifiedDate'] + +const builder = (yargs: Argv): Argv => + inputAndOutputItemBuilder(apiCommandBuilder(yargs)) + .example([ + [ + '$0 edge:channels:create', + 'create a channel from prompted input', + ], + [ + '$0 edge:channels:create --input channel.json', + 'create a channel as defined in channel.json', + ], + ]) + .epilog(apiDocsURL('createChannel')) + +const handler = async (argv: ArgumentsCamelCase): Promise => { + const command = await apiCommand(argv) + + const getInputFromUser = async (): Promise => { + const name = (await inquirer.prompt({ + type: 'input', + name: 'name', + message: 'Channel name:', + validate: input => input ? true : 'name is required', + })).name as string + + const description = (await inquirer.prompt({ + type: 'input', + name: 'description', + message: 'Channel description:', + validate: input => input ? true : 'description is required', + })).description as string + + const termsOfServiceUrl = (await inquirer.prompt({ + type: 'input', + name: 'termsOfServiceUrl', + message: 'Channel terms of service URL:', + validate: input => input ? true : 'termsOfServiceUrl is required', + })).termsOfServiceUrl as string + + return { name, description, termsOfServiceUrl, type: 'DRIVER' } + } + + await inputAndOutputItem( + command, + { tableFieldDefinitions }, + (_, input: ChannelCreate) => command.client.channels.create(input), + userInputProcessor(() => getInputFromUser()), + ) +} + +const cmd: CommandModule = { command, describe, builder, handler } +export default cmd diff --git a/src/commands/edge/channels/delete.ts b/src/commands/edge/channels/delete.ts new file mode 100644 index 00000000..6b5c48b3 --- /dev/null +++ b/src/commands/edge/channels/delete.ts @@ -0,0 +1,42 @@ +import { type ArgumentsCamelCase, type Argv, type CommandModule } from 'yargs' + +import { resetManagedConfigKey } from '../../../lib/cli-config.js' +import { apiCommand, apiCommandBuilder, apiDocsURL, type APICommandFlags } from '../../../lib/command/api-command.js' +import { chooseChannel } from '../../../lib/command/util/edge/channels-choose.js' + + +export type CommandArgs = + & APICommandFlags + & { + id?: string + } + +const command = 'edge:channels:delete [id]' + +const describe = 'delete a channel' + +const builder = (yargs: Argv): Argv => + apiCommandBuilder(yargs) + .positional('id', { describe: 'channel id', type: 'string' }) + .example([ + ['$0 edge:channels:delete', 'choose the channel to delete from a list'], + [ + '$0 edge:channels:delete 636169e4-8b9f-4438-a941-953b0d617231', + 'delete the channel with the specified id', + ], + ]) + .epilog(apiDocsURL('deleteChannel')) + + +const handler = async (argv: ArgumentsCamelCase): Promise => { + const command = await apiCommand(argv) + + const id = await chooseChannel(command, 'Choose a channel to delete.', argv.id) + await command.client.channels.delete(id) + console.log(`command.cliConfig = ${JSON.stringify(command.cliConfig)}`) + await resetManagedConfigKey(command.cliConfig, 'defaultChannel', value => value === id) + console.log(`Channel ${id} deleted.`) +} + +const cmd: CommandModule = { command, describe, builder, handler } +export default cmd diff --git a/src/commands/index.ts b/src/commands/index.ts index f3c891b1..1d1b9baa 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -42,6 +42,8 @@ import devicesHistoryCommand from './devices/history.js' import devicesPreferencesCommand from './devices/preferences.js' import devicesUpdateCommand from './devices/update.js' import edgeChannelsCommand from './edge/channels.js' +import edgeChannelsCreateCommand from './edge/channels/create.js' +import edgeChannelsDeleteCommand from './edge/channels/delete.js' import edgeChannelsInvitesCommand from './edge/channels/invites.js' import edgeDriversCommand from './edge/drivers.js' import edgeDriversDefaultCommand from './edge/drivers/default.js' @@ -136,6 +138,8 @@ export const commands: CommandModule[] = [ devicesPreferencesCommand, devicesUpdateCommand, edgeChannelsCommand, + edgeChannelsCreateCommand, + edgeChannelsDeleteCommand, edgeChannelsInvitesCommand, edgeDriversCommand, edgeDriversDefaultCommand, diff --git a/src/commands/locations/modes/create.ts b/src/commands/locations/modes/create.ts index 3f3a3928..5e1e4e66 100644 --- a/src/commands/locations/modes/create.ts +++ b/src/commands/locations/modes/create.ts @@ -39,7 +39,6 @@ const builder = (yargs: Argv): Argv => ]) .epilog(apiDocsURL('createMode')) - const handler = async (argv: ArgumentsCamelCase): Promise => { const command = await apiCommand(argv)