Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/eight-apricots-lie.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@smartthings/cli-testlib": patch
"@smartthings/cli": patch
---

feat: added device and location history commands
10 changes: 2 additions & 8 deletions packages/cli/src/__tests__/commands/devices.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import { buildTableOutput } from '../../lib/commands/devices-util'
jest.mock('../../lib/commands/devices-util')

describe('DevicesCommand', () => {
const deviceId = 'device-id'
const getSpy = jest.spyOn(DevicesEndpoint.prototype, 'get').mockImplementation()
const outputListingMock = jest.mocked(outputListing)

it('passes undefined for location id when not specified', async () => {
Expand Down Expand Up @@ -218,9 +220,6 @@ describe('DevicesCommand', () => {
})

it('uses UUID from the command line', async () => {
const deviceId = 'device-id'
const getSpy = jest.spyOn(DevicesEndpoint.prototype, 'get').mockImplementation()

outputListingMock.mockImplementationOnce(async (_command, _config, _id, _listFunction, getFunction) => {
await getFunction(deviceId)
})
Expand All @@ -237,9 +236,6 @@ describe('DevicesCommand', () => {
})

it('includes attribute values when status flag is set', async () => {
const deviceId = 'device-id'
const getSpy = jest.spyOn(DevicesEndpoint.prototype, 'get').mockImplementation()

outputListingMock.mockImplementationOnce(async (_command, _config, _id, _listFunction, getFunction) => {
await getFunction(deviceId)
})
Expand All @@ -256,8 +252,6 @@ describe('DevicesCommand', () => {
})

it('includes health status when health flag is set', async () => {
const deviceId = 'device-id'
const getSpy = jest.spyOn(DevicesEndpoint.prototype, 'get').mockImplementation()
const getHealthSpy = jest.spyOn(DevicesEndpoint.prototype, 'getHealth').mockImplementation()

outputListingMock.mockImplementationOnce(async (_command, _config, _id, _listFunction, getFunction) => {
Expand Down
82 changes: 82 additions & 0 deletions packages/cli/src/__tests__/commands/devices/history.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import {
buildOutputFormatter,
calculateOutputFormat,
chooseDevice,
IOFormat,
jsonFormatter,
writeOutput,
} from '@smartthings/cli-lib'
import { Device, DeviceActivity, DevicesEndpoint, HistoryEndpoint, PaginatedList } from '@smartthings/core-sdk'
import DeviceHistoryCommand from '../../../commands/devices/history'
import { writeDeviceEventsTable } from '../../../lib/commands/history-util'


jest.mock('../../../lib/commands/history-util')

describe('DeviceHistoryCommand', () => {
const getDeviceSpy = jest.spyOn(DevicesEndpoint.prototype, 'get').mockImplementation()
const historySpy = jest.spyOn(HistoryEndpoint.prototype, 'devices').mockImplementation()
const deviceSelectionMock = jest.mocked(chooseDevice).mockResolvedValue('deviceId')
const calculateOutputFormatMock = jest.mocked(calculateOutputFormat).mockReturnValue(IOFormat.COMMON)
const writeDeviceEventsTableMock = jest.mocked(writeDeviceEventsTable)

it('prompts user to select device', async () => {
getDeviceSpy.mockResolvedValue({ locationId: 'locationId' } as Device)
historySpy.mockResolvedValueOnce({
items: [],
hasNext: (): boolean => false,
} as unknown as PaginatedList<DeviceActivity>)
await expect(DeviceHistoryCommand.run(['deviceId'])).resolves.not.toThrow()

expect(deviceSelectionMock).toBeCalledWith(
expect.any(DeviceHistoryCommand),
'deviceId',
{ allowIndex: true },
)
})

it('queries history and writes event table interactively', async () => {
getDeviceSpy.mockResolvedValue({ locationId: 'locationId' } as Device)
historySpy.mockResolvedValueOnce({
items: [],
hasNext: (): boolean => false,
} as unknown as PaginatedList<DeviceActivity>)

await expect(DeviceHistoryCommand.run(['deviceId'])).resolves.not.toThrow()

expect(getDeviceSpy).toBeCalledTimes(1)
expect(getDeviceSpy).toBeCalledWith('deviceId')
expect(historySpy).toBeCalledTimes(1)
expect(historySpy).toBeCalledWith({
deviceId: 'deviceId',
locationId: 'locationId',
})
expect(writeDeviceEventsTableMock).toBeCalledTimes(1)
})

it('queries history and write event table directly', async () => {
const buildOutputFormatterMock = jest.mocked(buildOutputFormatter)
const writeOutputMock = jest.mocked(writeOutput)

getDeviceSpy.mockResolvedValue({ locationId: 'locationId' } as Device)
historySpy.mockResolvedValueOnce({
items: [],
hasNext: (): boolean => false,
} as unknown as PaginatedList<DeviceActivity>)
calculateOutputFormatMock.mockReturnValue(IOFormat.JSON)
buildOutputFormatterMock.mockReturnValue(jsonFormatter(4))

await expect(DeviceHistoryCommand.run(['deviceId'])).resolves.not.toThrow()

expect(getDeviceSpy).toBeCalledTimes(1)
expect(getDeviceSpy).toBeCalledWith('deviceId')
expect(historySpy).toBeCalledTimes(1)
expect(historySpy).toBeCalledWith({
deviceId: 'deviceId',
locationId: 'locationId',
})
expect(writeDeviceEventsTableMock).toBeCalledTimes(0)
expect(buildOutputFormatterMock).toBeCalledTimes(1)
expect(writeOutputMock).toBeCalledTimes(1)
})
})
61 changes: 61 additions & 0 deletions packages/cli/src/__tests__/commands/locations/history.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import {
buildOutputFormatter,
calculateOutputFormat,
IOFormat,
jsonFormatter,
writeOutput,
} from '@smartthings/cli-lib'
import { DeviceActivity, HistoryEndpoint, PaginatedList } from '@smartthings/core-sdk'
import LocationHistoryCommand from '../../../commands/locations/history'
import { writeDeviceEventsTable } from '../../../lib/commands/history-util'
import { chooseLocation } from '../../../commands/locations'


jest.mock('../../../lib/commands/history-util')
jest.mock('../../../commands/locations')

describe('LocationHistoryCommand', () => {
const mockChooseLocation = jest.mocked(chooseLocation).mockResolvedValue('locationId')
const historySpy = jest.spyOn(HistoryEndpoint.prototype, 'devices').mockImplementation()
const calculateOutputFormatMock = jest.mocked(calculateOutputFormat).mockReturnValue(IOFormat.COMMON)
const writeDeviceEventsTableMock = jest.mocked(writeDeviceEventsTable)

it('queries history and writes event table interactively', async () => {
historySpy.mockResolvedValueOnce({
items: [],
hasNext: (): boolean => false,
} as unknown as PaginatedList<DeviceActivity>)

await expect(LocationHistoryCommand.run(['locationId'])).resolves.not.toThrow()

expect(mockChooseLocation).toBeCalledTimes(1)
expect(historySpy).toBeCalledTimes(1)
expect(historySpy).toBeCalledWith({
locationId: 'locationId',
})
expect(writeDeviceEventsTableMock).toBeCalledTimes(1)
})

it('queries history and write event table directly', async () => {
const buildOutputFormatterMock = jest.mocked(buildOutputFormatter)
const writeOutputMock = jest.mocked(writeOutput)

historySpy.mockResolvedValueOnce({
items: [],
hasNext: (): boolean => false,
} as unknown as PaginatedList<DeviceActivity>)
calculateOutputFormatMock.mockReturnValue(IOFormat.JSON)
buildOutputFormatterMock.mockReturnValue(jsonFormatter(4))

await expect(LocationHistoryCommand.run(['locationId'])).resolves.not.toThrow()

expect(mockChooseLocation).toBeCalledTimes(1)
expect(historySpy).toBeCalledTimes(1)
expect(historySpy).toBeCalledWith({
locationId: 'locationId',
})
expect(writeDeviceEventsTableMock).toBeCalledTimes(0)
expect(buildOutputFormatterMock).toBeCalledTimes(1)
expect(writeOutputMock).toBeCalledTimes(1)
})
})
58 changes: 16 additions & 42 deletions packages/cli/src/__tests__/lib/commands/devices-util.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,21 @@ import {


describe('devices-util', () => {
const tablePushMock: jest.Mock<number, [(string | undefined)[]]> = jest.fn()
const tableToStringMock = jest.fn()
const tableMock = {
push: tablePushMock,
toString: tableToStringMock,
} as unknown as Table
const newOutputTableMock = jest.fn().mockReturnValue(tableMock)
const buildTableFromItemMock = jest.fn()
const buildTableFromListMock = jest.fn()

const tableGeneratorMock: TableGenerator = {
newOutputTable: newOutputTableMock,
buildTableFromItem: buildTableFromItemMock,
buildTableFromList: buildTableFromListMock,
}

describe('prettyPrintAttribute', () => {
it ('handles integer value', () => {
Expand Down Expand Up @@ -43,19 +58,6 @@ describe('devices-util', () => {
})

describe('buildStatusTableOutput', () => {
const tablePushMock: jest.Mock<number, [(string | undefined)[]]> = jest.fn()
const tableToStringMock = jest.fn()
const tableMock = {
push: tablePushMock,
toString: tableToStringMock,
} as unknown as Table
const newOutputTableMock = jest.fn().mockReturnValue(tableMock)

const tableGeneratorMock: TableGenerator = {
newOutputTable: newOutputTableMock,
buildTableFromItem: jest.fn(),
buildTableFromList: jest.fn(),
} as TableGenerator

it('handles a single component', () => {
const deviceStatus: DeviceStatus = {
Expand Down Expand Up @@ -112,19 +114,6 @@ describe('devices-util', () => {
})

describe('buildEmbeddedStatusTableOutput', () => {
const tablePushMock: jest.Mock<number, [(string | undefined)[]]> = jest.fn()
const tableToStringMock = jest.fn()
const tableMock = {
push: tablePushMock,
toString: tableToStringMock,
} as unknown as Table
const newOutputTableMock = jest.fn().mockReturnValue(tableMock)

const tableGeneratorMock: TableGenerator = {
newOutputTable: newOutputTableMock,
buildTableFromItem: jest.fn(),
buildTableFromList: jest.fn(),
} as TableGenerator

it('handles a single component', () => {
const device = {
Expand Down Expand Up @@ -199,21 +188,6 @@ describe('devices-util', () => {
})

describe('buildTableOutput', () => {
const tablePushMock: jest.Mock<number, [(string | undefined)[]]> = jest.fn()
const tableToStringMock = jest.fn()
const tableMock = {
push: tablePushMock,
toString: tableToStringMock,
} as unknown as Table
const newOutputTableMock = jest.fn().mockReturnValue(tableMock)
const buildTableFromItemMock = jest.fn()
const buildTableFromListMock = jest.fn()

const tableGeneratorMock: TableGenerator = {
newOutputTable: newOutputTableMock,
buildTableFromItem: buildTableFromItemMock,
buildTableFromList: buildTableFromListMock,
}

it('includes all main fields', () => {
const device = {
Expand Down Expand Up @@ -494,7 +468,7 @@ describe('devices-util', () => {
expect(buildTableOutput(tableGeneratorMock, device))
.toEqual('Main Info\nmain table\n\nDevice Integration Info (from virtual)\nvirtual device info\n\n' + summarizedText)

expect(tablePushMock).toHaveBeenCalledTimes(9)
expect(tablePushMock).toHaveBeenCalledTimes(8)
expect(buildTableFromItemMock).toHaveBeenCalledTimes(1)
expect(buildTableFromItemMock).toHaveBeenCalledWith(virtual,
['name', { prop: 'hubId', skipEmpty: true }, { prop: 'driverId', skipEmpty: true }])
Expand Down
Loading