Skip to content

Commit

Permalink
Merge pull request #135 from hivemq/feat/123/start-stop-devices
Browse files Browse the repository at this point in the history
feat(123): Start, stop or restart bridges and adapters
  • Loading branch information
simon622 authored Sep 29, 2023
2 parents 5a4b526 + a94552e commit ae402cd
Show file tree
Hide file tree
Showing 33 changed files with 701 additions and 84 deletions.
2 changes: 1 addition & 1 deletion hivemq-edge/src/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"lint:prettier:write": "prettier --write .",
"lint:stylelint": "stylelint '{src}/**/*.{css}'",
"lint:all": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0 && prettier --check .",
"dev:openAPI": "openapi --input '../../../../hivemq-edge/ext/hivemq-edge-openapi-2023.6.yaml' -o ./src/api/__generated__ -c axios --name HiveMqClient --exportSchemas true",
"dev:openAPI": "openapi --input '../../../../hivemq-edge/ext/hivemq-edge-openapi-2023.7.yaml' -o ./src/api/__generated__ -c axios --name HiveMqClient --exportSchemas true",
"cypress:open": "cypress open",
"cypress:open:component": "cypress open --component --browser chrome",
"cypress:open:e2e": "cypress open --e2e --browser chrome",
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions hivemq-edge/src/frontend/src/api/__generated__/index.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { ApiError, StatusTransitionCommand } from '../../__generated__'
import { ApiError, StatusTransitionCommand, StatusTransitionResult } from '../../__generated__'

import { useHttpClient } from '@/api/hooks/useHttpClient/useHttpClient.ts'
import { QUERY_KEYS } from '@/api/utils.ts'
Expand All @@ -17,7 +17,7 @@ export const useSetConnectionStatus = () => {
return appClient.bridges.changeStatus(name, requestBody)
}

return useMutation<string, ApiError, SetConnectionStatusProps>(setConnectionStatus, {
return useMutation<StatusTransitionResult, ApiError, SetConnectionStatusProps>(setConnectionStatus, {
onSuccess: () => {
// queryClient.invalidateQueries(['bridges', variables.name, QUERY_KEYS.CONNECTION_STATUS])
queryClient.invalidateQueries([QUERY_KEYS.BRIDGES])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,14 @@ export const handlers = [
return res(ctx.json<any>({}), ctx.status(200))
}),

rest.put('*/protocol-adapters/adapters/:adapterId/status', (_, res, ctx) => {
// @ts-ignore
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { adapterType } = req.params
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return res(ctx.json<any>({}), ctx.status(200))
}),

rest.put('*/protocol-adapters/adapters/:adapterType', (_, res, ctx) => {
// @ts-ignore
// eslint-disable-next-line @typescript-eslint/no-unused-vars
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { ApiError, StatusTransitionCommand, StatusTransitionResult } from '../../__generated__'

import { useHttpClient } from '@/api/hooks/useHttpClient/useHttpClient.ts'
import { QUERY_KEYS } from '@/api/utils.ts'

interface SetConnectionStatusProps {
adapterId: string
requestBody: StatusTransitionCommand
}

export const useSetConnectionStatus = () => {
const appClient = useHttpClient()
const queryClient = useQueryClient()

const changeStatus = ({ adapterId, requestBody }: SetConnectionStatusProps) => {
return appClient.protocolAdapters.changeStatus1(adapterId, requestBody)
}

return useMutation<StatusTransitionResult, ApiError, SetConnectionStatusProps>(changeStatus, {
onSuccess: () => {
queryClient.invalidateQueries([QUERY_KEYS.ADAPTERS])
},
})
}
4 changes: 4 additions & 0 deletions hivemq-edge/src/frontend/src/api/types/api-devices.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export enum DeviceTypes {
BRIDGE = 'BRIDGE',
ADAPTER = 'ADAPTER',
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { describe, expect, vi } from 'vitest'
import { render, screen, waitFor } from '@testing-library/react'
import { QueryClientProvider } from '@tanstack/react-query'

import '@/config/i18n.config.ts'

import { StatusTransitionResult } from '@/api/__generated__'
import { DeviceTypes } from '@/api/types/api-devices.ts'
import queryClient from '@/api/queryClient.ts'

import { AuthProvider } from '@/modules/Auth/AuthProvider.tsx'

import ConnectionController from './ConnectionController.tsx'

const { mutateAdapterAsync, mutateBridgeAsync, successToast, errorToast } = vi.hoisted(() => {
return {
mutateAdapterAsync: vi.fn<never, StatusTransitionResult>().mockResolvedValue({}),
mutateBridgeAsync: vi.fn<never, StatusTransitionResult>().mockResolvedValue({}),
successToast: vi.fn(),
errorToast: vi.fn(),
}
})

vi.mock('@/api/hooks/useProtocolAdapters/useSetConnectionStatus.tsx', () => {
return { useSetConnectionStatus: () => ({ mutateAsync: mutateAdapterAsync }) }
})

vi.mock('@/api/hooks/useGetBridges/useSetConnectionStatus.tsx', () => {
return { useSetConnectionStatus: () => ({ mutateAsync: mutateBridgeAsync }) }
})

vi.mock('@/hooks/useEdgeToast/useEdgeToast.tsx', () => {
return { useEdgeToast: () => ({ successToast: successToast, errorToast: errorToast }) }
})

const wrapper: React.JSXElementConstructor<{ children: React.ReactElement }> = ({ children }) => (
<QueryClientProvider client={queryClient}>
<AuthProvider>{children}</AuthProvider>
</QueryClientProvider>
)

const mockID = 'my-id'
const mockStatusTransitionResult: StatusTransitionResult = {
type: 'adapter',
identifier: mockID,
status: StatusTransitionResult.status.PENDING,
callbackTimeoutMillis: 2000,
}

describe('ConnectionController', () => {
it('should trigger a correct mutation on a state change', async () => {
mutateAdapterAsync.mockResolvedValue(mockStatusTransitionResult)
render(<ConnectionController type={DeviceTypes.ADAPTER} id={mockID} />, { wrapper })

expect(screen.getByTestId('device-action-start')).toBeDefined()

await waitFor(() => {
screen.getByTestId('device-action-start').click()

expect(mutateAdapterAsync).toHaveBeenCalledWith({
adapterId: mockID,
requestBody: {
command: 'START',
},
})

expect(successToast).toHaveBeenCalledWith({
description: "We've successfully started the adapter. It might take up to 2 seconds to update the status.",
title: 'Connection updating',
})
})
})

it('should trigger an error when mutation not resolved', async () => {
const err = [
{
fieldName: 'command',
title: 'Invalid user supplied data',
},
]

mutateBridgeAsync.mockRejectedValue({
errors: err,
})
render(<ConnectionController type={DeviceTypes.BRIDGE} id={mockID} />, { wrapper })

screen.getByTestId('device-action-start').click()
await waitFor(() => {
expect(mutateBridgeAsync).toHaveBeenCalledWith({
name: mockID,
requestBody: {
command: 'START',
},
})

expect(errorToast).toHaveBeenCalledWith(
{
title: 'Connection updating',
description: 'There was a problem trying to reconnect the bridge',
},
{
errors: err,
}
)
})
})
})
Loading

0 comments on commit ae402cd

Please sign in to comment.