Skip to content
Closed
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
18 changes: 18 additions & 0 deletions redisinsight/api/src/modules/rdi/client/api.rdi.client.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,24 @@ describe('ApiRdiClient', () => {
);
});

it('should set dummy authorization headers in dev mode when login is disabled', async () => {
mockedAxios.post.mockRejectedValueOnce({
status: 404,
});

await client.connect();

expect(mockedAxios.post).toHaveBeenCalledWith(RdiUrl.Login, {
username: mockRdi.username,
password: mockRdi.password,
});

expect(client['auth']['jwt']).toEqual(expect.any(String));
expect(mockedAxios.defaults.headers.common['Authorization']).toEqual(
`Bearer ${client['auth']['jwt']}`,
);
});

it('should throw an error if login fails', async () => {
mockedAxios.post.mockRejectedValueOnce(mockRdiUnauthorizedError);

Expand Down
39 changes: 31 additions & 8 deletions redisinsight/api/src/modules/rdi/client/api.rdi.client.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import axios, { AxiosInstance } from 'axios';
import { sign } from 'jsonwebtoken';
import axios, { AxiosError, AxiosInstance } from 'axios';
import { plainToInstance } from 'class-transformer';
import { Logger } from '@nestjs/common';
import { HttpStatus, Logger } from '@nestjs/common';

import { RdiClient } from 'src/modules/rdi/client/rdi.client';
import {
Expand Down Expand Up @@ -68,6 +69,28 @@ export class ApiRdiClient extends RdiClient {
});
}

private async loginDev(): Promise<string> {
return sign({}, 'dev', { expiresIn: '1h' });
}

private async login(): Promise<string> {
try {
const response = await this.client.post(RdiUrl.Login, {
username: this.rdi.username,
password: this.rdi.password,
});

return response.data.access_token;
} catch (e) {
// If /login endpoint is not found we assume that RDI is in dev mode
if (e.status === HttpStatus.NOT_FOUND) {
return this.loginDev();
}

throw e;
}
}

async getSchema(): Promise<object> {
try {
const [config, jobs] = await Promise.all([
Expand Down Expand Up @@ -261,16 +284,16 @@ export class ApiRdiClient extends RdiClient {

async connect(): Promise<void> {
try {
const response = await this.client.post(RdiUrl.Login, {
username: this.rdi.username,
password: this.rdi.password,
});
const accessToken = response.data.access_token;
const accessToken = await this.login();

const { exp } = JSON.parse(
Buffer.from(accessToken.split('.')[1], 'base64').toString(),
);

this.auth = { jwt: accessToken, exp };
this.auth = {
jwt: accessToken,
exp,
};
this.client.defaults.headers.common['Authorization'] =
`Bearer ${accessToken}`;
} catch (e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import React from 'react'
import { cloneDeep } from 'lodash'
import {
act,
cleanup,
fireEvent,
mockedStore,
render,
screen,
} from 'uiSrc/utils/test-utils'

import { instancesSelector } from 'uiSrc/slices/rdi/instances'
import { RdiInstance } from 'uiSrc/slices/interfaces'
import RdiInstancesListWrapper from './RdiInstancesListWrapper'

jest.mock('uiSrc/slices/rdi/instances', () => ({
...jest.requireActual('uiSrc/slices/rdi/instances'),
instancesSelector: jest.fn(),
}))

const mockInstances: RdiInstance[] = [
{
id: '1',
name: 'My first integration',
url: 'redis-12345.c253.us-central1-1.gce.cloud.redislabs.com:12345',
lastConnection: new Date(),
version: '1.2',
error: '',
loading: false,
},
{
id: '2',
name: 'My second integration',
url: 'redis-67890.c253.us-central1-1.gce.cloud.redislabs.com:67890',
lastConnection: new Date(),
version: '1.3',
error: '',
loading: false,
},
]

const mockSelectorData = {
loading: false,
error: '',
data: mockInstances,
connectedInstance: {
id: '',
name: '',
url: '',
lastConnection: null,
version: '',
error: '',
loading: false,
},
loadingChanging: false,
errorChanging: '',
changedSuccessfully: false,
isPipelineLoaded: false,
}

let store: typeof mockedStore

describe('RdiInstancesListWrapper', () => {
beforeEach(() => {
cleanup()
store = cloneDeep(mockedStore)
store.clearActions()
;(instancesSelector as jest.Mock).mockReturnValue({
...mockSelectorData,
})
})

afterEach(() => {
jest.clearAllMocks()
})

it('should show confirmation popover when click on delete', async () => {
const mockProps = {
width: 1200,
editedInstance: null,
onEditInstance: jest.fn(),
onDeleteInstances: jest.fn(),
}

render(<RdiInstancesListWrapper {...mockProps} />, { store })

// Find and click the delete button for the first instance
const deleteButton = screen.getByTestId('delete-instance-1-icon')

await act(async () => {
fireEvent.click(deleteButton)
})

// Check that the popover is visible with the confirmation content
expect(screen.getByText('will be removed from RedisInsight.')).toBeInTheDocument()
// Check that the instance name appears in the popover (there should be multiple instances, but we just need one)
expect(screen.getAllByText('My first integration').length).toBeGreaterThan(0)
expect(screen.getByTestId('delete-instance-1')).toBeInTheDocument()
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -47,18 +47,15 @@ const RdiInstancesListWrapper = ({
const { search } = useLocation()

const instances = useSelector(instancesSelector)
const [, forceRerender] = useState({})
const deleting = { id: '' }
const [deletingId, setDeletingId] = useState('')
const isLoadingRef = useRef(false)

const closePopover = () => {
deleting.id = ''
forceRerender({})
setDeletingId('')
}

const showPopover = (id: string) => {
deleting.id = `${id + suffix}`
forceRerender({})
setDeletingId(`${id + suffix}`)
}

useEffect(() => {
Expand All @@ -76,7 +73,6 @@ const RdiInstancesListWrapper = ({
}

isLoadingRef.current = instances.loading
forceRerender({})
}, [instances.loading, search])

useEffect(() => {
Expand Down Expand Up @@ -232,7 +228,7 @@ const RdiInstancesListWrapper = ({
text="will be removed from RedisInsight."
item={instance.id}
suffix={suffix}
deleting={deleting.id}
deleting={deletingId}
closePopover={closePopover}
updateLoading={false}
showPopover={showPopover}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
} from 'uiSrc/slices/app/context'
import { PageNames, Pages } from 'uiSrc/constants'
import { MOCK_RDI_PIPELINE_DATA } from 'uiSrc/mocks/data/rdi'
import { getPipeline } from 'uiSrc/slices/rdi/pipeline'
import PipelineManagementPage, { Props } from './PipelineManagementPage'

const mockedProps = mock<Props>()
Expand Down Expand Up @@ -99,6 +100,7 @@ describe('PipelineManagementPage', () => {

unmount()
const expectedActions = [
getPipeline(),
setLastPageContext(PageNames.rdiPipelineManagement),
setLastPipelineManagementPage(Pages.rdiPipelineConfig('rdiInstanceId')),
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useDispatch, useSelector } from 'react-redux'
import { IRoute, PageNames, Pages } from 'uiSrc/constants'
import { connectedInstanceSelector } from 'uiSrc/slices/rdi/instances'
import {
fetchRdiPipeline,
fetchRdiPipelineJobFunctions,
fetchRdiPipelineSchema,
} from 'uiSrc/slices/rdi/pipeline'
Expand Down Expand Up @@ -43,6 +44,7 @@ const PipelineManagementPage = ({ routes = [] }: Props) => {
setTitle(`${rdiInstanceName} - Pipeline Management`)

useEffect(() => {
dispatch(fetchRdiPipeline(rdiInstanceId))
dispatch(fetchRdiPipelineSchema(rdiInstanceId))
dispatch(fetchRdiPipelineJobFunctions(rdiInstanceId))
}, [])
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
import React from 'react'
import reactRouterDom from 'react-router-dom'
import { render, screen, fireEvent } from 'uiSrc/utils/test-utils'
import { cloneDeep } from 'lodash'
import {
render,
screen,
fireEvent,
mockedStore,
cleanup,
initialStateDefault,
} from 'uiSrc/utils/test-utils'

import { rdiPipelineSelector } from 'uiSrc/slices/rdi/pipeline'
import { RdiPipelineTabs } from 'uiSrc/slices/interfaces'
import Navigation from './Navigation'

jest.mock('uiSrc/telemetry', () => ({
Expand Down Expand Up @@ -29,12 +39,53 @@ jest.mock('formik', () => ({
}),
}))

jest.mock('uiSrc/slices/rdi/pipeline', () => ({
...jest.requireActual('uiSrc/slices/rdi/pipeline'),
rdiPipelineSelector: jest.fn(),
}))

let store: typeof mockedStore
beforeEach(() => {
cleanup()
store = cloneDeep(mockedStore)
store.clearActions()
;(rdiPipelineSelector as jest.Mock).mockReturnValue(
initialStateDefault.rdi.pipeline,
)
})

describe('Navigation', () => {
it('should render', () => {
expect(render(<Navigation />)).toBeTruthy()
})

it('should not show nav when pipeline is loading', () => {
render(<Navigation />)

expect(
screen.queryByTestId(`rdi-nav-btn-${RdiPipelineTabs.Config}`),
).not.toBeInTheDocument()
})

it('should show nav when pipeline is not loading', () => {
;(rdiPipelineSelector as jest.Mock).mockReturnValue({
...initialStateDefault.rdi.pipeline,
loading: false,
})

render(<Navigation />)

expect(
screen.queryByTestId(`rdi-nav-btn-${RdiPipelineTabs.Config}`),
).toBeInTheDocument()
})

it('should call proper history push after click on tabs', () => {
;(rdiPipelineSelector as jest.Mock).mockReturnValue({
...initialStateDefault.rdi.pipeline,
loading: false,
})

const pushMock = jest.fn()
reactRouterDom.useHistory = jest.fn().mockReturnValue({ push: pushMock })

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ const Navigation = () => {
<EuiTextColor component="div">Pipeline Management</EuiTextColor>
</div>
<div className={styles.tabs} data-testid="rdi-pipeline-tabs">
{renderTabs()}
{!loading && renderTabs()}
</div>
</div>
)
Expand Down
Loading
Loading