Skip to content

Commit

Permalink
refactor: convert installedapps:delete command to yargs
Browse files Browse the repository at this point in the history
  • Loading branch information
rossiam committed Feb 25, 2025
1 parent 62aa428 commit d7b8de6
Show file tree
Hide file tree
Showing 7 changed files with 150 additions and 101 deletions.
41 changes: 0 additions & 41 deletions packages/cli/src/__tests__/commands/installedapps/delete.test.ts

This file was deleted.

59 changes: 0 additions & 59 deletions packages/cli/src/commands/installedapps/delete.ts

This file was deleted.

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

import type { ArgumentsCamelCase, Argv } from 'yargs'

import { InstalledApp, type InstalledAppsEndpoint, type SmartThingsClient } from '@smartthings/core-sdk'

import type { CommandArgs } from '../../../commands/installedapps/delete.js'
import type { chooseInstalledAppFn } from '../../../lib/command/util/installedapps-util.js'
import type { ChooseFunction } from '../../../lib/command/util/util-util.js'
import { apiCommandMocks } from '../../test-lib/api-command-mock.js'
import { buildArgvMock } from '../../test-lib/builder-mock.js'
import { APICommand } from '../../../lib/command/api-command.js'


const { apiCommandMock, apiCommandBuilderMock, apiDocsURLMock } = apiCommandMocks('../../..')

const chooseInstalledAppMock = jest.fn<ChooseFunction<InstalledApp>>()
const chooseInstalledAppFnMock = jest.fn<typeof chooseInstalledAppFn>()
.mockReturnValue(chooseInstalledAppMock)
jest.unstable_mockModule('../../../lib/command/util/installedapps-util.js', () => ({
chooseInstalledAppFn: chooseInstalledAppFnMock,
}))

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


const { default: cmd } = await import('../../../commands/installedapps/delete.js')


test('builder', () => {
const {
yargsMock,
positionalMock,
exampleMock,
epilogMock,
argvMock,
} = buildArgvMock<object, CommandArgs>()

apiCommandBuilderMock.mockReturnValue(argvMock)

const builder = cmd.builder as (yargs: Argv<object>) => Argv<CommandArgs>
expect(builder(yargsMock)).toBe(argvMock)

expect(apiCommandBuilderMock).toHaveBeenCalledTimes(1)
expect(apiCommandBuilderMock).toHaveBeenCalledWith(yargsMock)

expect(positionalMock).toHaveBeenCalledTimes(1)
expect(exampleMock).toHaveBeenCalledTimes(1)
expect(apiDocsURLMock).toHaveBeenCalledTimes(1)
expect(epilogMock).toHaveBeenCalledTimes(1)
})

test('handler', async () => {
const apiInstalledAppsDeleteMock = jest.fn<typeof InstalledAppsEndpoint.prototype.delete>()
chooseInstalledAppMock.mockResolvedValueOnce('chosen-installed-app-id')
const clientMock = {
installedApps: {
delete: apiInstalledAppsDeleteMock,
},
} as unknown as SmartThingsClient
const command = {
client: clientMock,
} as APICommand<CommandArgs>
apiCommandMock.mockResolvedValue(command)
const inputArgv = {
profile: 'default',
id: 'command-line-id',
verbose: true,
location: ['location-id-1'],
} as ArgumentsCamelCase<CommandArgs>

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

expect(apiCommandMock).toHaveBeenCalledExactlyOnceWith(inputArgv)
expect(chooseInstalledAppFnMock).toHaveBeenCalledExactlyOnceWith({
listOptions: {
locationId: ['location-id-1'],
},
verbose: true,
})
expect(chooseInstalledAppMock).toHaveBeenCalledExactlyOnceWith(
command,
'command-line-id',
{ promptMessage: 'Select an installed app to delete.' },
)
expect(apiInstalledAppsDeleteMock).toHaveBeenCalledExactlyOnceWith('chosen-installed-app-id')

expect(consoleLogSpy).toHaveBeenLastCalledWith('Installed app chosen-installed-app-id deleted.')
})
2 changes: 1 addition & 1 deletion src/commands/apps/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export type CommandArgs = APICommandFlags & {
id?: string
}

export const command = 'apps:delete [id]'
const command = 'apps:delete [id]'

const describe = 'delete an app'

Expand Down
2 changes: 2 additions & 0 deletions src/commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import devicesCapabilityStatusCommand from './devices/capability-status.js'
import devicesPreferencesCommand from './devices/preferences.js'
import edgeDriversCommand from './edge/drivers.js'
import installedappsCommand from './installedapps.js'
import installedappsDeleteCommand from './installedapps/delete.js'
import installedappsRenameCommand from './installedapps/rename.js'
import locationsCommand from './locations.js'
import logoutCommand from './logout.js'
Expand Down Expand Up @@ -82,6 +83,7 @@ export const commands: CommandModule<object, any>[] = [
devicesPreferencesCommand,
edgeDriversCommand,
installedappsCommand,
installedappsDeleteCommand,
installedappsRenameCommand,
locationsCommand,
logoutCommand,
Expand Down
56 changes: 56 additions & 0 deletions src/commands/installedapps/delete.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { type ArgumentsCamelCase, type Argv, type CommandModule } from 'yargs'

import { type InstalledAppListOptions } from '@smartthings/core-sdk'

import { apiCommand, apiCommandBuilder, type APICommandFlags, apiDocsURL } from '../../lib/command/api-command.js'
import { chooseInstalledAppFn } from '../../lib/command/util/installedapps-util.js'


export type CommandArgs =
& APICommandFlags
& {
location?: string[]
verbose: boolean
id?: string
}

const command = 'installedapps:delete [id]'

const describe = 'delete an installed app'

const builder = (yargs: Argv): Argv<CommandArgs> =>
apiCommandBuilder(yargs)
.option('location', {
alias: 'l',
describe: 'if prompting for an installed app, include only installed apps in the specified location(s)',
type: 'string',
array: true,
})
.option('verbose',
{ alias: 'v', describe: 'include location name in output', type: 'boolean', default: false })
.positional('id', { describe: 'installed app id', type: 'string' })
.example([
['$0 installedapps:delete', 'choose the installed app to delete from a list'],
[
'$0 installedapps:delete 5dfd6626-ab1d-42da-bb76-90def3153998',
'delete the installed app with the specified id',
],
])
.epilog(apiDocsURL('deleteInstallation'))


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

const listOptions: InstalledAppListOptions = {
locationId: argv.location,
}
const chooseFunction = chooseInstalledAppFn({ listOptions, verbose: argv.verbose })
const id = await chooseFunction(command, argv.id, { promptMessage: 'Select an installed app to delete.' })

await command.client.installedApps.delete(id)
console.log(`Installed app ${id} deleted.`)
}

const cmd: CommandModule<object, CommandArgs> = { command, describe, builder, handler }
export default cmd
2 changes: 2 additions & 0 deletions src/lib/command/util/util-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export type ChooseOptions<T extends object> = {
listItems?: ListDataFunction<T>
autoChoose?: boolean
listFilter?: ListItemPredicate<T>
promptMessage?: string
}

export const chooseOptionsDefaults = <T extends object>(): ChooseOptions<T> => ({
Expand Down Expand Up @@ -62,5 +63,6 @@ export const createChooseFn = <T extends object>(
preselectedId,
autoChoose: opts.autoChoose,
listItems: listItemsWrapper,
promptMessage: opts.promptMessage,
})
}

0 comments on commit d7b8de6

Please sign in to comment.