From 10a097b714a4d85e22ec013a9394c7701eb9e999 Mon Sep 17 00:00:00 2001 From: Ross Stenersen Date: Tue, 10 Dec 2024 16:41:25 -0600 Subject: [PATCH] refactor: convert apps:settings:update command to yargs --- .../cli/src/commands/apps/settings/update.ts | 37 ------ .../commands/apps/settings/update.test.ts | 115 ++++++++++++++++++ .../commands/locations/update.test.ts | 12 +- src/commands/apps/settings/update.ts | 53 ++++++++ src/commands/index.ts | 2 + 5 files changed, 176 insertions(+), 43 deletions(-) delete mode 100644 packages/cli/src/commands/apps/settings/update.ts create mode 100644 src/__tests__/commands/apps/settings/update.test.ts create mode 100644 src/commands/apps/settings/update.ts diff --git a/packages/cli/src/commands/apps/settings/update.ts b/packages/cli/src/commands/apps/settings/update.ts deleted file mode 100644 index 537123df..00000000 --- a/packages/cli/src/commands/apps/settings/update.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { AppSettingsRequest, AppSettingsResponse } from '@smartthings/core-sdk' -import { APICommand, inputAndOutputItem } from '@smartthings/cli-lib' -import { buildTableOutput, chooseApp } from '../../../lib/commands/apps-util.js' - - -export default class AppSettingsUpdateCommand extends APICommand { - static description = 'update the settings of an app' + - this.apiDocsURL('updateAppSettings') - - static flags = { - ...APICommand.flags, - ...inputAndOutputItem.flags, - } - - static args = [{ - name: 'id', - description: 'the app id', - }] - - static examples = [ - { - description: 'update the settings of the app with the given id using the data in "app-settings.json"', - command: 'smartthings apps:settings:update 392bcb11-e251-44f3-b58b-17f93015f3aa -i app-settings.json', - }, - { - description: 'ask for the ID of an app to update and then update it using the data in "app-settings.json"', - command: 'smartthings apps:settings:update -i app-settings.json', - }, - ] - - async run(): Promise { - const appId = await chooseApp(this, this.args.id) - await inputAndOutputItem(this, - { buildTableOutput: (data: AppSettingsResponse) => buildTableOutput(this.tableGenerator, data) }, - (_, data: AppSettingsRequest) => this.client.apps.updateSettings(appId, data)) - } -} diff --git a/src/__tests__/commands/apps/settings/update.test.ts b/src/__tests__/commands/apps/settings/update.test.ts new file mode 100644 index 00000000..384149b4 --- /dev/null +++ b/src/__tests__/commands/apps/settings/update.test.ts @@ -0,0 +1,115 @@ +import { jest } from '@jest/globals' + +import type { ArgumentsCamelCase, Argv } from 'yargs' + +import type { AppsEndpoint, AppSettingsResponse, SmartThingsClient } from '@smartthings/core-sdk' + +import type { APICommand, APICommandFlags } from '../../../../lib/command/api-command.js' +import type { + inputAndOutputItem, + inputAndOutputItemBuilder, +} from '../../../../lib/command/input-and-output-item.js' +import { type buildTableOutput, type chooseApp } from '../../../../lib/command/util/apps-util.js' +import type { CommandArgs } from '../../../../commands/locations/update.js' +import { apiCommandMocks } from '../../../test-lib/api-command-mock.js' +import { buildArgvMock, buildArgvMockStub } from '../../../test-lib/builder-mock.js' +import { CustomCommonOutputProducer } from '../../../../lib/command/format.js' +import { tableGeneratorMock } from '../../../test-lib/table-mock.js' + + +const { apiCommandMock, apiCommandBuilderMock, apiDocsURLMock } = apiCommandMocks('../../../..') + +const inputAndOutputItemMock = jest.fn() +const inputAndOutputItemBuilderMock = jest.fn() +jest.unstable_mockModule('../../../../lib/command/input-and-output-item.js', () => ({ + inputAndOutputItem: inputAndOutputItemMock, + inputAndOutputItemBuilder: inputAndOutputItemBuilderMock, +})) + +const buildTableOutputMock = jest.fn() +const chooseAppMock = jest.fn().mockResolvedValue('chosen-id') +jest.unstable_mockModule('../../../../lib/command/util/apps-util.js', () => ({ + buildTableOutput: buildTableOutputMock, + chooseApp: chooseAppMock, +})) + + +const { default: cmd } = await import('../../../../commands/apps/settings/update.js') + + +test('builder', () => { + const yargsMock = buildArgvMockStub() + const { + yargsMock: apiCommandBuilderArgvMock, + positionalMock, + exampleMock, + epilogMock, + argvMock, + } = buildArgvMock() + + apiCommandBuilderMock.mockReturnValueOnce(apiCommandBuilderArgvMock) + inputAndOutputItemBuilderMock.mockReturnValueOnce(argvMock) + + const builder = cmd.builder as (yargs: Argv) => Argv + expect(builder(yargsMock)).toBe(argvMock) + + expect(apiCommandBuilderMock).toHaveBeenCalledExactlyOnceWith(yargsMock) + expect(inputAndOutputItemBuilderMock).toHaveBeenCalledExactlyOnceWith(apiCommandBuilderArgvMock) + + expect(positionalMock).toHaveBeenCalledTimes(1) + expect(exampleMock).toHaveBeenCalledTimes(1) + expect(apiDocsURLMock).toHaveBeenCalledTimes(1) + expect(epilogMock).toHaveBeenCalledTimes(1) +}) + +describe('handler', () => { + const apiAppsUpdateSettingsMock = jest.fn() + const clientMock = { + apps: { + updateSettings: apiAppsUpdateSettingsMock, + }, + } as unknown as SmartThingsClient + const command = { + client: clientMock, + tableGenerator: tableGeneratorMock, + } as APICommand + apiCommandMock.mockResolvedValue(command) + + const inputArgv = { profile: 'default' } as ArgumentsCamelCase + + it('queries user for appId and process via inputAndOutputItem', async () => { + await expect(cmd.handler(inputArgv)).resolves.not.toThrow() + + expect(apiCommandMock).toHaveBeenCalledExactlyOnceWith(inputArgv) + expect(chooseAppMock).toHaveBeenCalledExactlyOnceWith(command, undefined) + expect(inputAndOutputItemMock).toHaveBeenCalledExactlyOnceWith( + command, + { buildTableOutput: expect.any(Function) }, + expect.any(Function), + ) + + const executeFunction = inputAndOutputItemMock.mock.calls[0][2] + + const settings = { settings: { balloonColor: 'gross color' } } as AppSettingsResponse + const updatedSettings = { settings: { balloonColor: 'orange' } } as AppSettingsResponse + apiAppsUpdateSettingsMock.mockResolvedValueOnce(updatedSettings) + + expect(await executeFunction(undefined, settings)).toBe(updatedSettings) + + expect(apiAppsUpdateSettingsMock).toHaveBeenCalledExactlyOnceWith('chosen-id', settings) + + const config = inputAndOutputItemMock.mock.calls[0][1] as + CustomCommonOutputProducer + buildTableOutputMock.mockReturnValueOnce('table output') + + expect(config.buildTableOutput(updatedSettings)).toBe('table output') + + expect(buildTableOutputMock).toHaveBeenCalledExactlyOnceWith(tableGeneratorMock, updatedSettings) + }) + + it('passes command line id on to chooseApp', async () => { + await expect(cmd.handler({ ...inputArgv, id: 'argv-id' })).resolves.not.toThrow() + + expect(chooseAppMock).toHaveBeenCalledExactlyOnceWith(command, 'argv-id') + }) +}) diff --git a/src/__tests__/commands/locations/update.test.ts b/src/__tests__/commands/locations/update.test.ts index 49c20795..957b3ff3 100644 --- a/src/__tests__/commands/locations/update.test.ts +++ b/src/__tests__/commands/locations/update.test.ts @@ -1,13 +1,13 @@ import { jest } from '@jest/globals' -import { ArgumentsCamelCase, Argv } from 'yargs' +import type { ArgumentsCamelCase, Argv } from 'yargs' -import { Location, LocationUpdate, LocationsEndpoint, SmartThingsClient } from '@smartthings/core-sdk' +import type { Location, LocationUpdate, LocationsEndpoint, SmartThingsClient } from '@smartthings/core-sdk' -import { chooseLocation, tableFieldDefinitions } from '../../../lib/command/util/locations-util.js' -import { APICommand, APICommandFlags } from '../../../lib/command/api-command.js' -import { inputAndOutputItem, inputAndOutputItemBuilder } from '../../../lib/command/input-and-output-item.js' -import { CommandArgs } from '../../../commands/locations/update.js' +import { type chooseLocation, tableFieldDefinitions } from '../../../lib/command/util/locations-util.js' +import type { APICommand, APICommandFlags } from '../../../lib/command/api-command.js' +import type { inputAndOutputItem, inputAndOutputItemBuilder } from '../../../lib/command/input-and-output-item.js' +import type { CommandArgs } from '../../../commands/locations/update.js' import { apiCommandMocks } from '../../test-lib/api-command-mock.js' import { buildArgvMock, buildArgvMockStub } from '../../test-lib/builder-mock.js' diff --git a/src/commands/apps/settings/update.ts b/src/commands/apps/settings/update.ts new file mode 100644 index 00000000..e33235c9 --- /dev/null +++ b/src/commands/apps/settings/update.ts @@ -0,0 +1,53 @@ +import { type ArgumentsCamelCase, type Argv, type CommandModule } from 'yargs' + +import { type AppSettingsRequest, type AppSettingsResponse } from '@smartthings/core-sdk' + +import { + apiCommand, + apiCommandBuilder, + apiDocsURL, + type APICommandFlags, +} from '../../../lib/command/api-command.js' +import { + inputAndOutputItem, + inputAndOutputItemBuilder, + type InputAndOutputItemFlags, +} from '../../../lib/command/input-and-output-item.js' +import { buildTableOutput, chooseApp } from '../../../lib/command/util/apps-util.js' + + +export type CommandArgs = APICommandFlags & InputAndOutputItemFlags & { + id?: string +} + +const command = 'apps:settings:update [id]' + +const describe = 'update the settings of an app' + +const builder = (yargs: Argv): Argv => + inputAndOutputItemBuilder(apiCommandBuilder(yargs)) + .positional('id', { describe: 'app id', type: 'string' }) + .example([ + [ + '$0 apps:settings:update 392bcb11-e251-44f3-b58b-17f93015f3aa -i app-settings.json', + 'update the settings of the app with the given id using the data in "app-settings.json"', + ], + [ + '$0 apps:settings:update -i app-settings.json', + 'ask for the ID of an app to update and then update it using the data in "app-settings.json"', + ], + ]) + .epilog(apiDocsURL('updateAppSettings')) + +const handler = async (argv: ArgumentsCamelCase): Promise => { + const command = await apiCommand(argv) + const appId = await chooseApp(command, argv.id) + await inputAndOutputItem( + command, + { buildTableOutput: (data: AppSettingsResponse) => buildTableOutput(command.tableGenerator, data) }, + (_, data: AppSettingsRequest) => command.client.apps.updateSettings(appId, data), + ) +} + +const cmd: CommandModule = { command, describe, builder, handler } +export default cmd diff --git a/src/commands/index.ts b/src/commands/index.ts index 5e5b2f65..e75c585e 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -5,6 +5,7 @@ import appsCreateCommand from './apps/create.js' import appsAuthorizeCommand from './apps/authorize.js' import appsOAuthGenerateCommand from './apps/oauth/generate.js' import appsOAuthUpdateCommand from './apps/oauth/update.js' +import appsSettingsUpdateCommand from './apps/settings/update.js' import capabilitiesCommand from './capabilities.js' import configCommand from './config.js' import devicepreferencesCommand from './devicepreferences.js' @@ -32,6 +33,7 @@ export const commands: CommandModule[] = [ appsAuthorizeCommand, appsOAuthGenerateCommand, appsOAuthUpdateCommand, + appsSettingsUpdateCommand, capabilitiesCommand, configCommand, devicepreferencesCommand,