From 9678d1b61150c12bf09b8f470125a6a11caaefde Mon Sep 17 00:00:00 2001 From: John Undersander <19363370+john-u@users.noreply.github.com> Date: Wed, 15 Jun 2022 11:36:53 -0500 Subject: [PATCH] test(installedapps): add unit tests --- .changeset/slow-gorillas-care.md | 5 + .../__tests__/commands/installedapps.test.ts | 106 ++++++++++++++++++ .../commands/installedapps/delete.test.ts | 41 +++++++ .../commands/installedapps/rename.test.ts | 61 ++++++++++ packages/cli/src/commands/installedapps.ts | 16 +-- .../cli/src/commands/installedapps/rename.ts | 5 +- .../installedapps/installedapps-util.ts | 17 +++ packages/testlib/src/index.ts | 1 + 8 files changed, 235 insertions(+), 17 deletions(-) create mode 100644 .changeset/slow-gorillas-care.md create mode 100644 packages/cli/src/__tests__/commands/installedapps.test.ts create mode 100644 packages/cli/src/__tests__/commands/installedapps/delete.test.ts create mode 100644 packages/cli/src/__tests__/commands/installedapps/rename.test.ts create mode 100644 packages/cli/src/lib/commands/installedapps/installedapps-util.ts diff --git a/.changeset/slow-gorillas-care.md b/.changeset/slow-gorillas-care.md new file mode 100644 index 000000000..87ddcac87 --- /dev/null +++ b/.changeset/slow-gorillas-care.md @@ -0,0 +1,5 @@ +--- +"@smartthings/cli-testlib": patch +--- + +mock withLocations by default diff --git a/packages/cli/src/__tests__/commands/installedapps.test.ts b/packages/cli/src/__tests__/commands/installedapps.test.ts new file mode 100644 index 000000000..b38ae68e3 --- /dev/null +++ b/packages/cli/src/__tests__/commands/installedapps.test.ts @@ -0,0 +1,106 @@ +import { outputListing, withLocations, WithNamedLocation } from '@smartthings/cli-lib' +import { InstalledApp, InstalledAppsEndpoint, SmartThingsClient } from '@smartthings/core-sdk' +import InstalledAppsCommand from '../../commands/installedapps' +import { listTableFieldDefinitions, tableFieldDefinitions } from '../../lib/commands/installedapps/installedapps-util' + + +const MOCK_INSTALLED_APP = { installedAppId: 'installedAppId' } as InstalledApp +const MOCK_INSTALLED_APP_LIST = [MOCK_INSTALLED_APP] +const MOCK_INSTALLED_APP_WITH_LOCATION = { + installedAppId: 'installedAppId', + location: 'location', +} as InstalledApp & WithNamedLocation + +describe('InstalledAppsCommand', () => { + const getSpy = jest.spyOn(InstalledAppsEndpoint.prototype, 'get').mockImplementation() + const listSpy = jest.spyOn(InstalledAppsEndpoint.prototype, 'list').mockImplementation() + + const outputListingMock = jest.mocked(outputListing) + const withLocationsMock = jest.mocked(withLocations) + + it('calls outputListing with correct config', async () => { + await expect(InstalledAppsCommand.run(['installedAppId'])).resolves.not.toThrow() + + expect(outputListingMock).toBeCalledWith( + expect.any(InstalledAppsCommand), + expect.objectContaining({ + primaryKeyName: 'installedAppId', + sortKeyName: 'displayName', + listTableFieldDefinitions, + tableFieldDefinitions, + }), + 'installedAppId', + expect.any(Function), + expect.any(Function), + ) + }) + + it('calls correct get endpoint', async () => { + await expect(InstalledAppsCommand.run([])).resolves.not.toThrow() + + getSpy.mockResolvedValueOnce(MOCK_INSTALLED_APP) + + const getFunction = outputListingMock.mock.calls[0][4] + + await expect(getFunction('installedAppId')).resolves.toStrictEqual(MOCK_INSTALLED_APP) + expect(getSpy).toBeCalledWith('installedAppId') + }) + + it('calls correct list endpoint', async () => { + await expect(InstalledAppsCommand.run([])).resolves.not.toThrow() + + listSpy.mockResolvedValueOnce(MOCK_INSTALLED_APP_LIST) + + const listFunction = outputListingMock.mock.calls[0][3] + + await expect(listFunction()).resolves.toEqual(MOCK_INSTALLED_APP_LIST) + expect(listSpy).toBeCalledWith({ locationId: undefined }) + }) + + it('accepts location-id flag to filter list', async () => { + await expect(InstalledAppsCommand.run(['--location-id=locationId'])).resolves.not.toThrow() + + let listFunction = outputListingMock.mock.calls[0][3] + await listFunction() + + expect(listSpy).toBeCalledWith({ locationId: ['locationId'] }) + + outputListingMock.mockClear() + listSpy.mockClear() + + await expect(InstalledAppsCommand.run(['-l=locationId', '-l=anotherLocationId'])).resolves.not.toThrow() + + listFunction = outputListingMock.mock.calls[0][3] + await listFunction() + + expect(listSpy).toBeCalledWith({ locationId: ['locationId', 'anotherLocationId'] }) + }) + + it('includes location name when verbose flag is used', async () => { + await expect(InstalledAppsCommand.run(['--verbose'])).resolves.not.toThrow() + + expect(outputListingMock).toBeCalledWith( + expect.anything(), + expect.objectContaining({ + listTableFieldDefinitions: expect.arrayContaining(['location']), + }), + undefined, + expect.anything(), + expect.anything(), + ) + + const expectedList = [MOCK_INSTALLED_APP_WITH_LOCATION] + + listSpy.mockResolvedValueOnce(MOCK_INSTALLED_APP_LIST) + withLocationsMock.mockResolvedValueOnce(expectedList) + const listFunction = outputListingMock.mock.calls[0][3] + + await expect(listFunction()).resolves.toStrictEqual(expectedList) + + expect(listSpy).toBeCalledTimes(1) + expect(withLocationsMock).toBeCalledWith( + expect.any(SmartThingsClient), + MOCK_INSTALLED_APP_LIST, + ) + }) +}) diff --git a/packages/cli/src/__tests__/commands/installedapps/delete.test.ts b/packages/cli/src/__tests__/commands/installedapps/delete.test.ts new file mode 100644 index 000000000..ee4bdf8a8 --- /dev/null +++ b/packages/cli/src/__tests__/commands/installedapps/delete.test.ts @@ -0,0 +1,41 @@ +import { selectFromList } from '@smartthings/cli-lib' +import { InstalledAppsEndpoint } from '@smartthings/core-sdk' +import InstalledAppDeleteCommand from '../../../commands/installedapps/delete' + + +describe('InstalledAppDeleteCommand', () => { + const deleteSpy = jest.spyOn(InstalledAppsEndpoint.prototype, 'delete').mockImplementation() + const logSpy = jest.spyOn(InstalledAppDeleteCommand.prototype, 'log').mockImplementation() + + const selectFromListMock = jest.mocked(selectFromList).mockResolvedValue('installedAppId') + + it('prompts user to select app', async () => { + await expect(InstalledAppDeleteCommand.run(['installedAppId'])).resolves.not.toThrow() + + expect(selectFromListMock).toBeCalledWith( + expect.any(InstalledAppDeleteCommand), + expect.objectContaining({ + primaryKeyName: 'installedAppId', + sortKeyName: 'displayName', + listTableFieldDefinitions: ['displayName', 'installedAppType', 'installedAppStatus', 'installedAppId'], + }), + expect.objectContaining({ + preselectedId: 'installedAppId', + listItems: expect.any(Function), + promptMessage: 'Select an installed app to delete.', + }), + ) + }) + + it('calls correct delete endpoint', async () => { + await expect(InstalledAppDeleteCommand.run([])).resolves.not.toThrow() + + expect(deleteSpy).toBeCalledWith('installedAppId') + }) + + it('logs to user on successful delete', async () => { + await expect(InstalledAppDeleteCommand.run([])).resolves.not.toThrow() + + expect(logSpy).toBeCalledWith('Installed app installedAppId deleted.') + }) +}) diff --git a/packages/cli/src/__tests__/commands/installedapps/rename.test.ts b/packages/cli/src/__tests__/commands/installedapps/rename.test.ts new file mode 100644 index 000000000..d41864e17 --- /dev/null +++ b/packages/cli/src/__tests__/commands/installedapps/rename.test.ts @@ -0,0 +1,61 @@ +import { formatAndWriteItem, selectFromList } from '@smartthings/cli-lib' +import { InstalledApp, InstalledAppsEndpoint } from '@smartthings/core-sdk' +import InstalledAppRenameCommand from '../../../commands/installedapps/rename' +import { listTableFieldDefinitions, tableFieldDefinitions } from '../../../lib/commands/installedapps/installedapps-util' + + +jest.mock('inquirer') + +const MOCK_INSTALLED_APP = { installedAppId: 'installedAppId' } as InstalledApp + +describe('InstalledAppRenameCommand', () => { + const selectFromListMock = jest.mocked(selectFromList).mockResolvedValue('installedAppId') + const formatAndWriteItemMock = jest.mocked(formatAndWriteItem) + + const updateSpy = jest.spyOn(InstalledAppsEndpoint.prototype, 'update').mockImplementation() + jest.spyOn(InstalledAppsEndpoint.prototype, 'list').mockImplementation() + + it('prompts user to select an app', async () => { + await expect(InstalledAppRenameCommand.run(['installedAppId', 'installedAppName'])).resolves.not.toThrow() + + expect(selectFromListMock).toBeCalledWith( + expect.any(InstalledAppRenameCommand), + expect.objectContaining({ + itemName: 'installed app', + primaryKeyName: 'installedAppId', + sortKeyName: 'displayName', + tableFieldDefinitions, + listTableFieldDefinitions, + }), + expect.objectContaining({ + preselectedId: 'installedAppId', + listItems: expect.any(Function), + promptMessage: 'Select an app to rename.', + }), + ) + }) + + it('calls correct update endpoint', async () => { + await expect(InstalledAppRenameCommand.run(['installedAppId', 'installedAppName'])).resolves.not.toThrow() + + expect(updateSpy).toBeCalledWith('installedAppId', { displayName: 'installedAppName' }) + }) + + it('outputs updated app to user', async () => { + updateSpy.mockResolvedValueOnce(MOCK_INSTALLED_APP) + + await expect(InstalledAppRenameCommand.run(['installedAppId', 'installedAppName'])).resolves.not.toThrow() + + expect(formatAndWriteItemMock).toBeCalledWith( + expect.any(InstalledAppRenameCommand), + expect.objectContaining({ + itemName: 'installed app', + primaryKeyName: 'installedAppId', + sortKeyName: 'displayName', + tableFieldDefinitions, + listTableFieldDefinitions, + }), + MOCK_INSTALLED_APP, + ) + }) +}) diff --git a/packages/cli/src/commands/installedapps.ts b/packages/cli/src/commands/installedapps.ts index b612c30e7..13de5a1d5 100644 --- a/packages/cli/src/commands/installedapps.ts +++ b/packages/cli/src/commands/installedapps.ts @@ -2,22 +2,10 @@ import { Flags } from '@oclif/core' import { InstalledApp, InstalledAppListOptions } from '@smartthings/core-sdk' -import { APICommand, outputListing, TableFieldDefinition, withLocations } from '@smartthings/cli-lib' +import { APICommand, outputListing, withLocations } from '@smartthings/cli-lib' +import { InstalledAppWithLocation, listTableFieldDefinitions, tableFieldDefinitions } from '../lib/commands/installedapps/installedapps-util' -export type InstalledAppWithLocation = InstalledApp & { location?: string } - -export const listTableFieldDefinitions = ['displayName', 'installedAppType', 'installedAppStatus', 'installedAppId'] -export const tableFieldDefinitions: TableFieldDefinition[] = [ - 'displayName', 'installedAppId', 'installedAppType', 'installedAppStatus', - 'singleInstance', 'appId', 'locationId', 'singleInstance', - { - label: 'Classifications', - value: installedApp => installedApp.classifications?.join('\n') ?? '', - include: installedApp => !!installedApp.classifications, - }, -] - export default class InstalledAppsCommand extends APICommand { static description = 'get a specific app or a list of apps' diff --git a/packages/cli/src/commands/installedapps/rename.ts b/packages/cli/src/commands/installedapps/rename.ts index 7b92a6881..f394fa846 100644 --- a/packages/cli/src/commands/installedapps/rename.ts +++ b/packages/cli/src/commands/installedapps/rename.ts @@ -4,11 +4,10 @@ import inquirer from 'inquirer' import { InstalledAppListOptions } from '@smartthings/core-sdk' import { APICommand, formatAndWriteItem, selectFromList, withLocations } from '@smartthings/cli-lib' +import { listTableFieldDefinitions, tableFieldDefinitions } from '../../lib/commands/installedapps/installedapps-util' -import { listTableFieldDefinitions, tableFieldDefinitions } from '../installedapps' - -export default class DeviceComponentStatusCommand extends APICommand { +export default class InstalledAppRenameCommand extends APICommand { static description = 'renamed an installed app instance' static flags = { diff --git a/packages/cli/src/lib/commands/installedapps/installedapps-util.ts b/packages/cli/src/lib/commands/installedapps/installedapps-util.ts new file mode 100644 index 000000000..6d0e6276c --- /dev/null +++ b/packages/cli/src/lib/commands/installedapps/installedapps-util.ts @@ -0,0 +1,17 @@ +import { TableFieldDefinition } from '@smartthings/cli-lib' +import { InstalledApp } from '@smartthings/core-sdk' + + +export type InstalledAppWithLocation = InstalledApp & { location?: string } + +export const listTableFieldDefinitions = ['displayName', 'installedAppType', 'installedAppStatus', 'installedAppId'] + +export const tableFieldDefinitions: TableFieldDefinition[] = [ + 'displayName', 'installedAppId', 'installedAppType', 'installedAppStatus', + 'singleInstance', 'appId', 'locationId', 'singleInstance', + { + label: 'Classifications', + value: installedApp => installedApp.classifications?.join('\n') ?? '', + include: installedApp => !!installedApp.classifications, + }, +] diff --git a/packages/testlib/src/index.ts b/packages/testlib/src/index.ts index 5028fb35c..a49e81511 100644 --- a/packages/testlib/src/index.ts +++ b/packages/testlib/src/index.ts @@ -19,6 +19,7 @@ jest.mock('@smartthings/cli-lib', () => { outputItem: jest.fn(), resetManagedConfig: jest.fn(), formatAndWriteItem: jest.fn(), + withLocations: jest.fn(), withLocationsAndRooms: jest.fn(), yamlExists: jest.fn(), chooseDevice: jest.fn(),