Skip to content

Commit 05ea3c6

Browse files
committed
test(installedapps): add unit tests
1 parent 82652c9 commit 05ea3c6

File tree

8 files changed

+235
-17
lines changed

8 files changed

+235
-17
lines changed

.changeset/slow-gorillas-care.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@smartthings/cli-testlib": patch
3+
---
4+
5+
mock withLocations by default
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import { outputListing, withLocations, WithNamedLocation } from '@smartthings/cli-lib'
2+
import { InstalledApp, InstalledAppsEndpoint, SmartThingsClient } from '@smartthings/core-sdk'
3+
import InstalledAppsCommand from '../../commands/installedapps'
4+
import { listTableFieldDefinitions, tableFieldDefinitions } from '../../lib/commands/installedapps/installedapps-util'
5+
6+
7+
const MOCK_INSTALLED_APP = { installedAppId: 'installedAppId' } as InstalledApp
8+
const MOCK_INSTALLED_APP_LIST = [MOCK_INSTALLED_APP]
9+
const MOCK_INSTALLED_APP_WITH_LOCATION = {
10+
installedAppId: 'installedAppId',
11+
location: 'location',
12+
} as InstalledApp & WithNamedLocation
13+
14+
describe('InstalledAppsCommand', () => {
15+
const getSpy = jest.spyOn(InstalledAppsEndpoint.prototype, 'get').mockImplementation()
16+
const listSpy = jest.spyOn(InstalledAppsEndpoint.prototype, 'list').mockImplementation()
17+
18+
const outputListingMock = jest.mocked(outputListing)
19+
const withLocationsMock = jest.mocked(withLocations)
20+
21+
it('calls outputListing with correct config', async () => {
22+
await expect(InstalledAppsCommand.run(['installedAppId'])).resolves.not.toThrow()
23+
24+
expect(outputListingMock).toBeCalledWith(
25+
expect.any(InstalledAppsCommand),
26+
expect.objectContaining({
27+
primaryKeyName: 'installedAppId',
28+
sortKeyName: 'displayName',
29+
listTableFieldDefinitions,
30+
tableFieldDefinitions,
31+
}),
32+
'installedAppId',
33+
expect.any(Function),
34+
expect.any(Function),
35+
)
36+
})
37+
38+
it('calls correct get endpoint', async () => {
39+
await expect(InstalledAppsCommand.run([])).resolves.not.toThrow()
40+
41+
getSpy.mockResolvedValueOnce(MOCK_INSTALLED_APP)
42+
43+
const getFunction = outputListingMock.mock.calls[0][4]
44+
45+
await expect(getFunction('installedAppId')).resolves.toStrictEqual(MOCK_INSTALLED_APP)
46+
expect(getSpy).toBeCalledWith('installedAppId')
47+
})
48+
49+
it('calls correct list endpoint', async () => {
50+
await expect(InstalledAppsCommand.run([])).resolves.not.toThrow()
51+
52+
listSpy.mockResolvedValueOnce(MOCK_INSTALLED_APP_LIST)
53+
54+
const listFunction = outputListingMock.mock.calls[0][3]
55+
56+
await expect(listFunction()).resolves.toEqual(MOCK_INSTALLED_APP_LIST)
57+
expect(listSpy).toBeCalledWith({ locationId: undefined })
58+
})
59+
60+
it('accepts location-id flag to filter list', async () => {
61+
await expect(InstalledAppsCommand.run(['--location-id=locationId'])).resolves.not.toThrow()
62+
63+
let listFunction = outputListingMock.mock.calls[0][3]
64+
await listFunction()
65+
66+
expect(listSpy).toBeCalledWith({ locationId: ['locationId'] })
67+
68+
outputListingMock.mockClear()
69+
listSpy.mockClear()
70+
71+
await expect(InstalledAppsCommand.run(['-l=locationId', '-l=anotherLocationId'])).resolves.not.toThrow()
72+
73+
listFunction = outputListingMock.mock.calls[0][3]
74+
await listFunction()
75+
76+
expect(listSpy).toBeCalledWith({ locationId: ['locationId', 'anotherLocationId'] })
77+
})
78+
79+
it('includes location name when verbose flag is used', async () => {
80+
await expect(InstalledAppsCommand.run(['--verbose'])).resolves.not.toThrow()
81+
82+
expect(outputListingMock).toBeCalledWith(
83+
expect.anything(),
84+
expect.objectContaining({
85+
listTableFieldDefinitions: expect.arrayContaining(['location']),
86+
}),
87+
undefined,
88+
expect.anything(),
89+
expect.anything(),
90+
)
91+
92+
const expectedList = [MOCK_INSTALLED_APP_WITH_LOCATION]
93+
94+
listSpy.mockResolvedValueOnce(MOCK_INSTALLED_APP_LIST)
95+
withLocationsMock.mockResolvedValueOnce(expectedList)
96+
const listFunction = outputListingMock.mock.calls[0][3]
97+
98+
await expect(listFunction()).resolves.toStrictEqual(expectedList)
99+
100+
expect(listSpy).toBeCalledTimes(1)
101+
expect(withLocationsMock).toBeCalledWith(
102+
expect.any(SmartThingsClient),
103+
MOCK_INSTALLED_APP_LIST,
104+
)
105+
})
106+
})
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { selectFromList } from '@smartthings/cli-lib'
2+
import { InstalledAppsEndpoint } from '@smartthings/core-sdk'
3+
import InstalledAppDeleteCommand from '../../../commands/installedapps/delete'
4+
5+
6+
describe('InstalledAppDeleteCommand', () => {
7+
const deleteSpy = jest.spyOn(InstalledAppsEndpoint.prototype, 'delete').mockImplementation()
8+
const logSpy = jest.spyOn(InstalledAppDeleteCommand.prototype, 'log').mockImplementation()
9+
10+
const selectFromListMock = jest.mocked(selectFromList).mockResolvedValue('installedAppId')
11+
12+
it('prompts user to select app', async () => {
13+
await expect(InstalledAppDeleteCommand.run(['installedAppId'])).resolves.not.toThrow()
14+
15+
expect(selectFromListMock).toBeCalledWith(
16+
expect.any(InstalledAppDeleteCommand),
17+
expect.objectContaining({
18+
primaryKeyName: 'installedAppId',
19+
sortKeyName: 'displayName',
20+
listTableFieldDefinitions: ['displayName', 'installedAppType', 'installedAppStatus', 'installedAppId'],
21+
}),
22+
expect.objectContaining({
23+
preselectedId: 'installedAppId',
24+
listItems: expect.any(Function),
25+
promptMessage: 'Select an installed app to delete.',
26+
}),
27+
)
28+
})
29+
30+
it('calls correct delete endpoint', async () => {
31+
await expect(InstalledAppDeleteCommand.run([])).resolves.not.toThrow()
32+
33+
expect(deleteSpy).toBeCalledWith('installedAppId')
34+
})
35+
36+
it('logs to user on successful delete', async () => {
37+
await expect(InstalledAppDeleteCommand.run([])).resolves.not.toThrow()
38+
39+
expect(logSpy).toBeCalledWith('Installed app installedAppId deleted.')
40+
})
41+
})
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { formatAndWriteItem, selectFromList } from '@smartthings/cli-lib'
2+
import { InstalledApp, InstalledAppsEndpoint } from '@smartthings/core-sdk'
3+
import InstalledAppRenameCommand from '../../../commands/installedapps/rename'
4+
import { listTableFieldDefinitions, tableFieldDefinitions } from '../../../lib/commands/installedapps/installedapps-util'
5+
6+
7+
jest.mock('inquirer')
8+
9+
const MOCK_INSTALLED_APP = { installedAppId: 'installedAppId' } as InstalledApp
10+
11+
describe('InstalledAppRenameCommand', () => {
12+
const selectFromListMock = jest.mocked(selectFromList).mockResolvedValue('installedAppId')
13+
const formatAndWriteItemMock = jest.mocked(formatAndWriteItem)
14+
15+
const updateSpy = jest.spyOn(InstalledAppsEndpoint.prototype, 'update').mockImplementation()
16+
jest.spyOn(InstalledAppsEndpoint.prototype, 'list').mockImplementation()
17+
18+
it('prompts user to select an app', async () => {
19+
await expect(InstalledAppRenameCommand.run(['installedAppId', 'installedAppName'])).resolves.not.toThrow()
20+
21+
expect(selectFromListMock).toBeCalledWith(
22+
expect.any(InstalledAppRenameCommand),
23+
expect.objectContaining({
24+
itemName: 'installed app',
25+
primaryKeyName: 'installedAppId',
26+
sortKeyName: 'displayName',
27+
tableFieldDefinitions,
28+
listTableFieldDefinitions,
29+
}),
30+
expect.objectContaining({
31+
preselectedId: 'installedAppId',
32+
listItems: expect.any(Function),
33+
promptMessage: 'Select an app to rename.',
34+
}),
35+
)
36+
})
37+
38+
it('calls correct update endpoint', async () => {
39+
await expect(InstalledAppRenameCommand.run(['installedAppId', 'installedAppName'])).resolves.not.toThrow()
40+
41+
expect(updateSpy).toBeCalledWith('installedAppId', { displayName: 'installedAppName' })
42+
})
43+
44+
it('outputs updated app to user', async () => {
45+
updateSpy.mockResolvedValueOnce(MOCK_INSTALLED_APP)
46+
47+
await expect(InstalledAppRenameCommand.run(['installedAppId', 'installedAppName'])).resolves.not.toThrow()
48+
49+
expect(formatAndWriteItemMock).toBeCalledWith(
50+
expect.any(InstalledAppRenameCommand),
51+
expect.objectContaining({
52+
itemName: 'installed app',
53+
primaryKeyName: 'installedAppId',
54+
sortKeyName: 'displayName',
55+
tableFieldDefinitions,
56+
listTableFieldDefinitions,
57+
}),
58+
MOCK_INSTALLED_APP,
59+
)
60+
})
61+
})

packages/cli/src/commands/installedapps.ts

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,10 @@ import { Flags } from '@oclif/core'
22

33
import { InstalledApp, InstalledAppListOptions } from '@smartthings/core-sdk'
44

5-
import { APICommand, outputListing, TableFieldDefinition, withLocations } from '@smartthings/cli-lib'
5+
import { APICommand, outputListing, withLocations } from '@smartthings/cli-lib'
6+
import { InstalledAppWithLocation, listTableFieldDefinitions, tableFieldDefinitions } from '../lib/commands/installedapps/installedapps-util'
67

78

8-
export type InstalledAppWithLocation = InstalledApp & { location?: string }
9-
10-
export const listTableFieldDefinitions = ['displayName', 'installedAppType', 'installedAppStatus', 'installedAppId']
11-
export const tableFieldDefinitions: TableFieldDefinition<InstalledApp>[] = [
12-
'displayName', 'installedAppId', 'installedAppType', 'installedAppStatus',
13-
'singleInstance', 'appId', 'locationId', 'singleInstance',
14-
{
15-
label: 'Classifications',
16-
value: installedApp => installedApp.classifications?.join('\n') ?? '',
17-
include: installedApp => !!installedApp.classifications,
18-
},
19-
]
20-
219
export default class InstalledAppsCommand extends APICommand<typeof InstalledAppsCommand.flags> {
2210
static description = 'get a specific app or a list of apps'
2311

packages/cli/src/commands/installedapps/rename.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,10 @@ import inquirer from 'inquirer'
44
import { InstalledAppListOptions } from '@smartthings/core-sdk'
55

66
import { APICommand, formatAndWriteItem, selectFromList, withLocations } from '@smartthings/cli-lib'
7+
import { listTableFieldDefinitions, tableFieldDefinitions } from '../../lib/commands/installedapps/installedapps-util'
78

8-
import { listTableFieldDefinitions, tableFieldDefinitions } from '../installedapps'
99

10-
11-
export default class DeviceComponentStatusCommand extends APICommand<typeof DeviceComponentStatusCommand.flags> {
10+
export default class InstalledAppRenameCommand extends APICommand<typeof InstalledAppRenameCommand.flags> {
1211
static description = 'renamed an installed app instance'
1312

1413
static flags = {
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { TableFieldDefinition } from '@smartthings/cli-lib'
2+
import { InstalledApp } from '@smartthings/core-sdk'
3+
4+
5+
export type InstalledAppWithLocation = InstalledApp & { location?: string }
6+
7+
export const listTableFieldDefinitions = ['displayName', 'installedAppType', 'installedAppStatus', 'installedAppId']
8+
9+
export const tableFieldDefinitions: TableFieldDefinition<InstalledApp>[] = [
10+
'displayName', 'installedAppId', 'installedAppType', 'installedAppStatus',
11+
'singleInstance', 'appId', 'locationId', 'singleInstance',
12+
{
13+
label: 'Classifications',
14+
value: installedApp => installedApp.classifications?.join('\n') ?? '',
15+
include: installedApp => !!installedApp.classifications,
16+
},
17+
]

packages/testlib/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ jest.mock('@smartthings/cli-lib', () => {
1919
outputItem: jest.fn(),
2020
resetManagedConfig: jest.fn(),
2121
formatAndWriteItem: jest.fn(),
22+
withLocations: jest.fn(),
2223
withLocationsAndRooms: jest.fn(),
2324
yamlExists: jest.fn(),
2425
chooseDevice: jest.fn(),

0 commit comments

Comments
 (0)