Skip to content

Commit

Permalink
test: add unit tests for getSchemaAppEnsuringOrganization
Browse files Browse the repository at this point in the history
  • Loading branch information
rossiam committed Nov 18, 2024
1 parent da64393 commit 7ebda67
Show file tree
Hide file tree
Showing 2 changed files with 153 additions and 13 deletions.
162 changes: 151 additions & 11 deletions src/__tests__/lib/command/util/schema-util.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { jest } from '@jest/globals'

import type {
import {
OrganizationResponse,
OrganizationsEndpoint,
SchemaApp,
Expand All @@ -11,9 +11,10 @@ import type {
} from '@smartthings/core-sdk'

import type { Profile } from '../../../../lib/cli-config.js'
import type { stdinIsTTY, stdoutIsTTY } from '../../../../lib/io-util.js'
import type { clipToMaximum, fatalError } from '../../../../lib/util.js'
import type { APICommand } from '../../../../lib/command/api-command.js'
import type { chooseOrganization, organizationDef } from '../../../../lib/command/util/organizations-util.js'
import { chooseOrganization, organizationDef } from '../../../../lib/command/util/organizations-util.js'
import type { InputData } from '../../../../lib/command/util/schema-util.js'
import type {
booleanDef,
Expand All @@ -38,8 +39,17 @@ import {
} from '../../../../lib/command/util/util-util.js'


const stdinIsTTYMock = jest.fn<typeof stdinIsTTY>().mockReturnValue(true)
const stdoutIsTTYMock = jest.fn<typeof stdoutIsTTY>().mockReturnValue(true)
jest.unstable_mockModule('../../../../lib/io-util.js', () => ({
stdinIsTTY: stdinIsTTYMock,
stdoutIsTTY: stdoutIsTTYMock,
}))

const clipToMaximumMock = jest.fn<typeof clipToMaximum>().mockReturnValue('clipped result')
const fatalErrorMock = jest.fn<typeof fatalError>()
// simulate never returning with an error
.mockImplementation(() => { throw Error('fatal error' ) })
jest.unstable_mockModule('../../../../lib/util.js', () => ({
clipToMaximum: clipToMaximumMock,
fatalError: fatalErrorMock,
Expand Down Expand Up @@ -86,32 +96,59 @@ jest.unstable_mockModule('../../../../lib/command/util/util-util.js', () => ({
createChooseFn: createChooseFnMock,
}))

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


const {
appLinksDefSummarize,
buildInputDefinition,
chooseSchemaAppFn,
getSchemaAppCreateFromUser,
getSchemaAppEnsuringOrganization,
getSchemaAppUpdateFromUser,
validateFinal,
} = await import('../../../../lib/command/util/schema-util.js')


const organizations = [
{ name: 'Organization 1', organizationId: 'organization-id-1' },
{ name: 'Organization 2', organizationId: 'organization-id-2' },
] as OrganizationResponse[]
const schemaAppWithOrganization = {
endpointAppId: 'has-organization',
organizationId: 'my-organization',
appName: 'Has Organization',
} as SchemaApp
const schemaAppWithoutOrganization = {
endpointAppId: 'sans-organization',
appName: 'Sans Organization',
} as SchemaApp
const schemaAppNotInList = {
endpointAppId: 'not-in-list',
organizationId: 'their-organization',
appName: 'Not Included in List',
} as SchemaApp
const schemaList = [schemaAppWithOrganization, schemaAppWithoutOrganization]
const apiOrganizationsGetMock = jest.fn<typeof OrganizationsEndpoint.prototype.get>()
const organization1 = { name: 'Organization 1', organizationId: 'organization-id-1' } as OrganizationResponse
const organization2 = { name: 'Organization 2', organizationId: 'organization-id-2' } as OrganizationResponse
const organizations = [organization1, organization2]
const apiOrganizationListMock = jest.fn<typeof OrganizationsEndpoint.prototype.list>()
.mockResolvedValue(organizations)
const apiSchemaListMock = jest.fn<typeof SchemaEndpoint.prototype.list>()
.mockResolvedValue(schemaList)
const apiSchemaGetMock = jest.fn<typeof SchemaEndpoint.prototype.get>()
const cloneMock = jest.fn<SmartThingsClient['clone']>()
const clientMock = {
organizations: {
organizations: {
get: apiOrganizationsGetMock,
list: apiOrganizationListMock,
},
schema: {
list: apiSchemaListMock,
get: apiSchemaGetMock,
},
clone: cloneMock,
} as unknown as SmartThingsClient
const commandMock = {
profile: {},
client: clientMock,
} as APICommand
const clonedClient = { ...clientMock } as SmartThingsClient
cloneMock.mockReturnValue(clonedClient)
const commandMock = { profile: {}, client: clientMock } as APICommand

const appLinksDefMock = { name: 'Generated App Links Def' } as InputDefinition<ViperAppLinks>
const generatedDef = { name: 'Final Generated Def' } as InputDefinition<InputData>
Expand Down Expand Up @@ -358,3 +395,106 @@ test('chooseSchemaAppFn uses correct endpoint to list schema apps', async () =>

expect(apiSchemaListMock).toHaveBeenCalledExactlyOnceWith()
})

describe('getSchemaAppEnsuringOrganization', () => {
const defaultFlags = { json: false, yaml: false }

it('uses get when the app already has an organization', async () => {
apiSchemaGetMock.mockResolvedValueOnce(schemaAppWithOrganization)

const { schemaApp, organizationWasUpdated } =
await getSchemaAppEnsuringOrganization(commandMock, 'has-organization', defaultFlags)

expect(schemaApp).toBe(schemaAppWithOrganization)
expect(organizationWasUpdated).toBe(false)

expect(apiSchemaListMock).toHaveBeenCalledExactlyOnceWith()
expect(apiSchemaGetMock).toHaveBeenCalledExactlyOnceWith('has-organization')

expect(fatalErrorMock).not.toHaveBeenCalled()
expect(chooseOrganizationMock).not.toHaveBeenCalled()
expect(apiOrganizationsGetMock).not.toHaveBeenCalled()
expect(cloneMock).not.toHaveBeenCalled()
})

it('uses get when no app found in list', async () => {
apiSchemaGetMock.mockResolvedValueOnce(schemaAppNotInList)

const { schemaApp, organizationWasUpdated } =
await getSchemaAppEnsuringOrganization(commandMock, 'not-in-list', defaultFlags)

expect(schemaApp).toBe(schemaAppNotInList)
expect(organizationWasUpdated).toBe(false)

expect(apiSchemaListMock).toHaveBeenCalledExactlyOnceWith()
expect(apiSchemaGetMock).toHaveBeenCalledExactlyOnceWith('not-in-list')

expect(fatalErrorMock).not.toHaveBeenCalled()
expect(chooseOrganizationMock).not.toHaveBeenCalled()
expect(apiOrganizationsGetMock).not.toHaveBeenCalled()
expect(cloneMock).not.toHaveBeenCalled()
})

it('sets and organization when app found without an organization', async () => {
chooseOrganizationMock.mockResolvedValueOnce('organization-id-1')
apiOrganizationsGetMock.mockResolvedValueOnce(organization1)
// TODO: maybe use an "updated organization" here and in corresponding expect below
apiSchemaGetMock.mockResolvedValueOnce(schemaAppWithoutOrganization)

const { schemaApp, organizationWasUpdated } =
await getSchemaAppEnsuringOrganization(commandMock, 'sans-organization', defaultFlags)

expect(schemaApp).toBe(schemaAppWithoutOrganization)
expect(organizationWasUpdated).toBe(true)

expect(apiSchemaListMock).toHaveBeenCalledExactlyOnceWith()
expect(consoleLogSpy).toHaveBeenCalledWith(
'The schema "Sans Organization" (sans-organization) does not have an organization\n' +
'You must choose one now.',
)
expect(chooseOrganizationMock).toHaveBeenCalledExactlyOnceWith(commandMock)
expect(apiOrganizationsGetMock).toHaveBeenCalledExactlyOnceWith('organization-id-1')
expect(cloneMock).toHaveBeenCalledExactlyOnceWith(
// eslint-disable-next-line @typescript-eslint/naming-convention
{ 'X-ST-Organization': 'organization-id-1' },
)
expect(apiSchemaGetMock).toHaveBeenCalledExactlyOnceWith('sans-organization')

expect(fatalErrorMock).not.toHaveBeenCalled()
})

it.each([
{ flags: { ...defaultFlags, json: true }, stdinIsTTYReturn: true, stdoutIsTTYReturn: true },
{ flags: { ...defaultFlags, yaml: true }, stdinIsTTYReturn: true, stdoutIsTTYReturn: true },
{ flags: { ...defaultFlags, input: 'input-file.json' }, stdinIsTTYReturn: true, stdoutIsTTYReturn: true },
{ flags: { ...defaultFlags, output: 'output-file.yaml' }, stdinIsTTYReturn: true, stdoutIsTTYReturn: true },
{ flags: defaultFlags, stdinIsTTYReturn: false, stdoutIsTTYReturn: true },
{ flags: defaultFlags, stdinIsTTYReturn: true, stdoutIsTTYReturn: false },
])(
'errors with $flags flags, stdin is TTY $stdinIsTTYReturn, stdout is TTY $stdoutIsTTYReturn',
async ({ flags, stdinIsTTYReturn, stdoutIsTTYReturn }) => {
apiSchemaGetMock.mockResolvedValueOnce(schemaAppWithoutOrganization)
if (!stdinIsTTYReturn) {
stdinIsTTYMock.mockReturnValueOnce(stdinIsTTYReturn)
}
if (!stdoutIsTTYReturn) {
stdoutIsTTYMock.mockReturnValueOnce(stdoutIsTTYReturn)
}

await expect(getSchemaAppEnsuringOrganization(commandMock, 'sans-organization', flags))
.rejects.toThrow('fatal error')

expect(apiSchemaListMock).toHaveBeenCalledExactlyOnceWith()
expect(fatalErrorMock).toHaveBeenCalledExactlyOnceWith(
'Schema app does not have an organization associated with it.\n' +
'Please run "smartthings schema sans-organization" and choose an organization ' +
'when prompted.',
)

expect(chooseOrganizationMock).not.toHaveBeenCalled()
expect(apiOrganizationsGetMock).not.toHaveBeenCalled()
expect(cloneMock).not.toHaveBeenCalled()
expect(apiSchemaGetMock).not.toHaveBeenCalled()
},
)
})
4 changes: 2 additions & 2 deletions src/lib/command/util/schema-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
} from '../../item-input/index.js'
import { clipToMaximum, fatalError } from '../../util.js'
import { emailValidate, httpsURLValidate } from '../../validate-util.js'
import { APICommandFlags, type APICommand } from '../api-command.js'
import { type APICommand } from '../api-command.js'
import { chooseOrganization, organizationDef } from './organizations-util.js'
import { arnDef, webHookUrlDef } from './schema-util-input-primitives.js'
import { type ChooseFunction, createChooseFn } from './util-util.js'
Expand Down Expand Up @@ -151,7 +151,7 @@ export const chooseSchemaApp = chooseSchemaAppFn()
// set. So, here we check to see if the app has an organization before we query it and
// prompt the user for the correct organization.
export const getSchemaAppEnsuringOrganization = async (
command: APICommand<APICommandFlags>,
command: APICommand,
schemaAppId: string,
flags: {
json: boolean
Expand Down

0 comments on commit 7ebda67

Please sign in to comment.