Skip to content

Commit ab87695

Browse files
#RI-5856 - add tests
1 parent 00355ff commit ab87695

File tree

7 files changed

+139
-15
lines changed

7 files changed

+139
-15
lines changed

redisinsight/ui/src/pages/rdi/instance/InstancePage.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { useDispatch, useSelector } from 'react-redux'
33
import { useHistory, useLocation, useParams } from 'react-router-dom'
44
import { Formik, FormikProps } from 'formik'
55
import { EuiText } from '@elastic/eui'
6-
import { capitalize } from 'lodash'
6+
import { upperFirst } from 'lodash'
77

88
import {
99
appContextSelector,
@@ -95,7 +95,7 @@ const RdiInstancePage = ({ routes = [] }: Props) => {
9595
dispatch(addErrorNotification(createAxiosError({
9696
message: (
9797
<>
98-
<EuiText>{`${capitalize(errors[0].filename)} has an invalid structure.`}</EuiText>
98+
<EuiText>{`${upperFirst(errors[0].filename)} has an invalid structure.`}</EuiText>
9999
<EuiText>{errors[0].msg}</EuiText>
100100
</>
101101
)

redisinsight/ui/src/pages/rdi/pipeline-management/pages/config/Config.spec.tsx

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
import React from 'react'
22
import { useFormikContext } from 'formik'
33
import { cloneDeep } from 'lodash'
4+
import { EuiText } from '@elastic/eui'
5+
import { AxiosError } from 'axios'
46
import { rdiPipelineSelector, setChangedFile, deleteChangedFile } from 'uiSrc/slices/rdi/pipeline'
57
import { rdiTestConnectionsSelector } from 'uiSrc/slices/rdi/testConnections'
68
import { act, cleanup, fireEvent, mockedStore, render, screen } from 'uiSrc/utils/test-utils'
79

810
import { sendPageViewTelemetry, TelemetryPageView } from 'uiSrc/telemetry'
911
import { MOCK_RDI_PIPELINE_DATA } from 'uiSrc/mocks/data/rdi'
1012
import { FileChangeType } from 'uiSrc/slices/interfaces'
13+
import { addErrorNotification } from 'uiSrc/slices/app/notifications'
1114
import Config from './Config'
1215

1316
jest.mock('uiSrc/telemetry', () => ({
@@ -149,7 +152,7 @@ describe('Config', () => {
149152
expect(queryByTestId('test-connection-panel')).toBeInTheDocument()
150153
})
151154

152-
it('should open right panel', async () => {
155+
it('should close right panel', async () => {
153156
const { queryByTestId } = render(<Config />)
154157

155158
expect(queryByTestId('test-connection-panel')).not.toBeInTheDocument()
@@ -167,6 +170,40 @@ describe('Config', () => {
167170
expect(queryByTestId('test-connection-panel')).not.toBeInTheDocument()
168171
})
169172

173+
it('should render error notification', async () => {
174+
const mockUseFormikContext = {
175+
setFieldValue: mockSetFieldValue,
176+
values: { config: 'sources:incorrect\n target:' },
177+
};
178+
(useFormikContext as jest.Mock).mockReturnValue(mockUseFormikContext)
179+
180+
const { queryByTestId } = render(<Config />)
181+
182+
expect(queryByTestId('test-connection-panel')).not.toBeInTheDocument()
183+
184+
await act(async () => {
185+
fireEvent.click(screen.getByTestId('rdi-test-connection-btn'))
186+
})
187+
188+
const expectedActions = [
189+
addErrorNotification({
190+
response: {
191+
data: {
192+
message: (
193+
<>
194+
<EuiText>Config has an invalid structure.</EuiText>
195+
<EuiText>end of the stream or a document separator is expected</EuiText>
196+
</>
197+
)
198+
}
199+
}
200+
} as AxiosError)
201+
]
202+
203+
expect(store.getActions().slice(0, expectedActions.length)).toEqual(expectedActions)
204+
expect(queryByTestId('test-connection-panel')).not.toBeInTheDocument()
205+
})
206+
170207
it('should render loading spinner', () => {
171208
const rdiPipelineSelectorMock = jest.fn().mockReturnValue({
172209
loading: true,

redisinsight/ui/src/pages/rdi/pipeline-management/pages/job/Job.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React, { useState, useEffect, useRef, useCallback } from 'react'
22
import { useDispatch, useSelector } from 'react-redux'
33
import { EuiText, EuiLink, EuiButton, EuiLoadingSpinner, EuiToolTip } from '@elastic/eui'
44
import { useFormikContext } from 'formik'
5-
import { capitalize, get, throttle } from 'lodash'
5+
import { upperFirst, get, throttle } from 'lodash'
66
import cx from 'classnames'
77
import { monaco as monacoEditor } from 'react-monaco-editor'
88

@@ -72,7 +72,7 @@ const Job = (props: Props) => {
7272
dispatch(addErrorNotification(createAxiosError({
7373
message: (
7474
<>
75-
{`${capitalize(name)} has an invalid structure.`}
75+
<EuiText>{`${upperFirst(name)} has an invalid structure.`}</EuiText>
7676
<EuiText>{msg}</EuiText>
7777
</>
7878
)

redisinsight/ui/src/pages/rdi/pipeline-management/pages/job/JobsWrapper.spec.tsx

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ import { sendPageViewTelemetry, TelemetryPageView, sendEventTelemetry, Telemetry
99
import { MOCK_RDI_PIPELINE_CONFIG, MOCK_RDI_PIPELINE_DATA, MOCK_RDI_PIPELINE_JOB2 } from 'uiSrc/mocks/data/rdi'
1010
import { FileChangeType } from 'uiSrc/slices/interfaces'
1111
import JobWrapper from './JobWrapper'
12+
import {addErrorNotification} from "uiSrc/slices/app/notifications";
13+
import {EuiText} from "@elastic/eui";
14+
import {AxiosError} from "axios";
1215

1316
jest.mock('uiSrc/telemetry', () => ({
1417
...jest.requireActual('uiSrc/telemetry'),
@@ -179,4 +182,42 @@ describe('JobWrapper', () => {
179182

180183
expect(store.getActions()).toEqual(expectedActions)
181184
})
185+
186+
it('should render error notification', () => {
187+
const rdiPipelineSelectorMock = jest.fn().mockReturnValue({
188+
loading: false,
189+
schema: { jobs: { test: {} } },
190+
data: { jobs: [{ name: 'jobName', value: 'sources:incorrect\n target:' }] }
191+
});
192+
(rdiPipelineSelector as jest.Mock).mockImplementation(rdiPipelineSelectorMock)
193+
194+
const mockUseFormikContext = {
195+
setFieldValue: jest.fn,
196+
values: { config: MOCK_RDI_PIPELINE_CONFIG, jobs: [{ name: 'jobName', value: 'sources:incorrect\n target:' }] },
197+
};
198+
(useFormikContext as jest.Mock).mockReturnValue(mockUseFormikContext)
199+
200+
const { queryByTestId } = render(<JobWrapper />)
201+
202+
fireEvent.click(screen.getByTestId('rdi-job-dry-run'))
203+
204+
const expectedActions = [
205+
addErrorNotification({
206+
response: {
207+
data: {
208+
message: (
209+
<>
210+
<EuiText>JobName has an invalid structure.</EuiText>
211+
<EuiText>end of the stream or a document separator is expected</EuiText>
212+
</>
213+
)
214+
}
215+
}
216+
} as AxiosError)
217+
]
218+
219+
expect(store.getActions().slice(0 - expectedActions.length)).toEqual(expectedActions)
220+
221+
expect(queryByTestId('dry-run-panel')).not.toBeInTheDocument()
222+
})
182223
})

redisinsight/ui/src/utils/apiResponse.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { AxiosError } from 'axios'
2-
import { first, isArray, get } from 'lodash'
1+
import {AxiosError, AxiosResponse} from 'axios'
2+
import {first, isArray, get, set} from 'lodash'
33
import { AddRedisDatabaseStatus, EnhancedAxiosError, ErrorOptions, IBulkOperationResult } from 'uiSrc/slices/interfaces'
44
import { parseCustomError } from 'uiSrc/utils'
55

@@ -14,9 +14,9 @@ export const getAxiosError = (error: EnhancedAxiosError): AxiosError => {
1414

1515
export const createAxiosError = (options: ErrorOptions): AxiosError => ({
1616
response: {
17-
data: options
18-
}
19-
} as AxiosError)
17+
data: options,
18+
},
19+
}) as AxiosError
2020

2121
export const getApiErrorCode = (error: AxiosError) => error?.response?.status
2222

redisinsight/ui/src/utils/tests/transformers/transformRdiPipeline.spec.ts

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,15 +51,61 @@ const pipelineToJsonTests: any[] = [
5151
]
5252
}
5353
},
54-
}
55-
]
54+
},
55+
0,
56+
{}
57+
],
58+
[
59+
{
60+
config: 'connections:incorrect\n # Redis data DB connection details\n # This section is for configuring the Redis database to which Redis Data Integration will connect to\n target:\n # Target type - Redis is the only supported type (default: redis)\n type: redis\n # Host of the Redis database to which Redis Data Integration will write the processed data\n host: redis-18262.c232.us-east-1-2.ec2.cloud.redislabs.com\n # Port for the Redis database to which Redis Data Integration will write the processed data\n port: 18262\n # User of the Redis database to which Redis Data Integration will write the processed data\n user: default',
61+
jobs: [
62+
{ name: 'job1', value: 'source:\n row_format: full\n server_name: chinook\n schema: dbo\n table: Employee\ntransform:\n - uses: filter\n with:\n language: jmespath\n expression: in(after.LastName,[\'Smith\']) && opcode == \'c\'\noutput:\n - uses: redis.write\n with:\n data_type: json\n mapping:\n - EmployeeId\n - FirstName\n - LastName' }
63+
]
64+
},
65+
undefined,
66+
1,
67+
[{ filename: 'config', msg: 'end of the stream or a document separator is expected' }]
68+
],
69+
[
70+
{
71+
config: 'connections:\n # Redis data DB connection details\n # This section is for configuring the Redis database to which Redis Data Integration will connect to\n target:\n # Target type - Redis is the only supported type (default: redis)\n type: redis\n # Host of the Redis database to which Redis Data Integration will write the processed data\n host: redis-18262.c232.us-east-1-2.ec2.cloud.redislabs.com\n # Port for the Redis database to which Redis Data Integration will write the processed data\n port: 18262\n # User of the Redis database to which Redis Data Integration will write the processed data\n user: default',
72+
jobs: [
73+
{ name: 'job1', value: 'source:incorrect\n row_format: full\n server_name: chinook\n schema: dbo\n table: Employee\ntransform:\n - uses: filter\n with:\n language: jmespath\n expression: in(after.LastName,[\'Smith\']) && opcode == \'c\'\noutput:\n - uses: redis.write\n with:\n data_type: json\n mapping:\n - EmployeeId\n - FirstName\n - LastName' }
74+
]
75+
},
76+
undefined,
77+
1,
78+
[{ filename: 'job1', msg: 'end of the stream or a document separator is expected' }]
79+
],
80+
[
81+
{
82+
config: 'connections:incorrect\n # Redis data DB connection details\n # This section is for configuring the Redis database to which Redis Data Integration will connect to\n target:\n # Target type - Redis is the only supported type (default: redis)\n type: redis\n # Host of the Redis database to which Redis Data Integration will write the processed data\n host: redis-18262.c232.us-east-1-2.ec2.cloud.redislabs.com\n # Port for the Redis database to which Redis Data Integration will write the processed data\n port: 18262\n # User of the Redis database to which Redis Data Integration will write the processed data\n user: default',
83+
jobs: [
84+
{ name: 'job1', value: 'source:incorrect\n row_format: full\n server_name: chinook\n schema: dbo\n table: Employee\ntransform:\n - uses: filter\n with:\n language: jmespath\n expression: in(after.LastName,[\'Smith\']) && opcode == \'c\'\noutput:\n - uses: redis.write\n with:\n data_type: json\n mapping:\n - EmployeeId\n - FirstName\n - LastName' },
85+
{ name: 'job2', value: 'source:\n row_format: full\n server_name: chinook\n schema: dbo\n table: Employee\ntransform:\n - uses: filter\n with:\n language: jmespath\n expression: in(after.LastName,[\'Smith\']) && opcode == \'c\'\noutput:\n - uses: redis.write\n with:\n data_type: json\n mapping:\n - EmployeeId\n - FirstName\n - LastName' },
86+
{ name: 'job3', value: 'source:incorrect\n row_format: full\n server_name: chinook\n schema: dbo\n table: Employee\ntransform:\n - uses: filter\n with:\n language: jmespath\n expression: in(after.LastName,[\'Smith\']) && opcode == \'c\'\noutput:\n - uses: redis.write\n with:\n data_type: json\n mapping:\n - EmployeeId\n - FirstName\n - LastName' }
87+
]
88+
},
89+
undefined,
90+
1,
91+
[
92+
{ filename: 'config', msg: 'end of the stream or a document separator is expected' },
93+
{ filename: 'job1', msg: 'end of the stream or a document separator is expected' },
94+
{ filename: 'job3', msg: 'end of the stream or a document separator is expected' },
95+
]
96+
],
5697
]
5798

5899
describe('pipelineToJson', () => {
59100
it.each(pipelineToJsonTests)('for input: %s (input), should be output: %s',
60-
(input, expected) => {
61-
const result = pipelineToJson(input)
101+
(input, expected, callback, errors) => {
102+
const mockOnError = jest.fn()
103+
const result = pipelineToJson(input, mockOnError)
62104
expect(result).toEqual(expected)
105+
expect(mockOnError).toBeCalledTimes(callback)
106+
if (callback) {
107+
expect(mockOnError).toBeCalledWith(errors)
108+
}
63109
})
64110
})
65111

redisinsight/ui/src/utils/transformers/transformRdiPipeline.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export const pipelineToJson = ({ config, jobs }: IPipeline, onError: (errors: IY
4343
}
4444
result.jobs = jobs.reduce<{ [key: string]: unknown }>((acc, job) => {
4545
try {
46-
acc[job.name] = yaml.load(job.value)
46+
acc[job.name] = yaml.load(job.value) || {}
4747
} catch (e) {
4848
if (e instanceof YAMLException) {
4949
errors.push({ filename: job.name, msg: e.reason })

0 commit comments

Comments
 (0)