diff --git a/src/react/Routes.test.tsx b/src/react/Routes.test.tsx
new file mode 100644
index 000000000..90e18acab
--- /dev/null
+++ b/src/react/Routes.test.tsx
@@ -0,0 +1,200 @@
+import { screen, waitFor } from '@testing-library/react';
+import { useDispatch, useSelector } from 'react-redux';
+import { useIsVeeamVBROnly } from './ISV/hooks/useIsVeeamVBROnly';
+import InternalRoutes, { PrivateRoutes } from './Routes';
+import { renderWithRouterMatch } from './utils/testUtil';
+
+// Mock useIsVeeamVBROnly as it comes from ShellHooks
+jest.mock('./ISV/hooks/useIsVeeamVBROnly');
+jest.mock('react-redux', () => ({
+ ...jest.requireActual('react-redux'),
+ useSelector: jest.fn(),
+ useDispatch: jest.fn(),
+}));
+
+describe('Routes component', () => {
+ const mockUseIsVeeamVBROnly = useIsVeeamVBROnly as jest.Mock;
+ const mockUseSelector = useSelector as jest.Mock;
+ const mockUseDispatch = useDispatch as jest.Mock;
+ const selectors = {
+ loadingAccounts: () => screen.queryByText(/Loading accounts/i),
+ loadingDataServices: () => screen.queryByText(/Loading Data Services/i),
+ createDataService: () => screen.queryByText(/Create new Data Service/i),
+ loadingClients: () => screen.queryByText(/Loading clients/i),
+ dataServicesLink: () => screen.queryByText(/Data Services/i),
+ };
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+
+ // Default mock implementations
+ mockUseDispatch.mockReturnValue(jest.fn());
+
+ // Setup the default state for useSelector
+ mockUseSelector.mockImplementation((selector) => {
+ // Create a mock state that has the necessary fields
+ const mockState = {
+ auth: {
+ isClientsLoaded: true,
+ config: {
+ managementEndpoint: 'http://test-endpoint.com',
+ },
+ oidcLogout: jest.fn(),
+ },
+ oidc: {
+ user: {
+ access_token: 'mock-token',
+ expired: false,
+ expires_at: Date.now() / 1000 + 3600, // 1 hour from now
+ },
+ },
+ configuration: {
+ latest: {
+ version: 1,
+ },
+ },
+ };
+
+ // Pass the mock state to the selector function
+ return selector(mockState);
+ });
+ });
+
+ it('should show loading state when isClientsLoaded is false', async () => {
+ // Override the default mock to set isClientsLoaded to false
+ mockUseSelector.mockImplementation((selector) => {
+ const mockState = {
+ auth: {
+ isClientsLoaded: false,
+ config: {
+ managementEndpoint: 'http://test-endpoint.com',
+ },
+ },
+ oidc: {
+ user: {
+ access_token: 'mock-token',
+ expired: false,
+ },
+ },
+ };
+ return selector(mockState);
+ });
+
+ // Render the PrivateRoutes component
+ renderWithRouterMatch(, {
+ path: '/*',
+ route: '/accounts',
+ });
+
+ // Verify that loading state is shown
+ await waitFor(() => {
+ expect(selectors.loadingClients()).toBeInTheDocument();
+ });
+ });
+
+ it('should render the Create Data Service page in standard configuration', async () => {
+ // Mock the hook to return false
+ mockUseIsVeeamVBROnly.mockReturnValue(false);
+
+ // Render with the create-dataservice route
+ renderWithRouterMatch(, {
+ path: '/*',
+ route: '/create-dataservice',
+ });
+
+ await waitFor(() => {
+ expect(selectors.createDataService()).toBeInTheDocument();
+ });
+ });
+
+ it('should not render the Create Data Service page in ARTESCA+VEEAM configuration', async () => {
+ // Mock the hook to return true
+ mockUseIsVeeamVBROnly.mockReturnValue(true);
+
+ // Render with the create-dataservice route
+ renderWithRouterMatch(, {
+ path: '/*',
+ route: '/create-dataservice',
+ });
+
+ // Verify that it redirects to accounts
+ await waitFor(() => {
+ expect(selectors.createDataService()).not.toBeInTheDocument();
+ expect(selectors.loadingAccounts()).toBeInTheDocument();
+ });
+ });
+
+ it('should render the Data Services page in standard configuration', async () => {
+ // Mock the hook to return false
+ mockUseIsVeeamVBROnly.mockReturnValue(false);
+
+ // Render with the dataservices route
+ renderWithRouterMatch(, {
+ path: '/*',
+ route: '/dataservices',
+ });
+
+ await waitFor(() => {
+ expect(selectors.loadingDataServices()).toBeInTheDocument();
+ });
+ });
+
+ it('should not render the Data Services page in ARTESCA+VEEAM configuration', async () => {
+ // Mock the hook to return true
+ mockUseIsVeeamVBROnly.mockReturnValue(true);
+
+ // Render with the dataservices route
+ renderWithRouterMatch(, {
+ path: '/*',
+ route: '/dataservices',
+ });
+
+ await waitFor(() => {
+ expect(selectors.loadingDataServices()).not.toBeInTheDocument();
+ });
+ });
+ it('should redirect incorrect routes to Accounts page', async () => {
+ renderWithRouterMatch(, {
+ path: '/*',
+ route: '/incorrect-route',
+ });
+
+ await waitFor(() => {
+ expect(selectors.loadingAccounts()).toBeInTheDocument();
+ });
+ });
+
+ describe('sidebar entries', () => {
+ it('should hide Data Services from sidebar in ARTESCA+VEEAM configuration', async () => {
+ // Mock the hook to return true
+ mockUseIsVeeamVBROnly.mockReturnValue(true);
+
+ // Render InternalRoutes with any route
+ renderWithRouterMatch(, {
+ path: '/*',
+ route: '/accounts',
+ });
+
+ // Check that Data Services link is not in the sidebar
+ await waitFor(() => {
+ expect(selectors.dataServicesLink()).not.toBeInTheDocument();
+ });
+ });
+
+ it('should show Data Services in sidebar in standard configuration', async () => {
+ // Mock the hook to return false
+ mockUseIsVeeamVBROnly.mockReturnValue(false);
+
+ // Render InternalRoutes with any route
+ renderWithRouterMatch(, {
+ path: '/*',
+ route: '/accounts',
+ });
+
+ // Check that Data Services link is in the sidebar
+ await waitFor(() => {
+ expect(selectors.dataServicesLink()).toBeInTheDocument();
+ });
+ });
+ });
+});
diff --git a/src/react/Routes.tsx b/src/react/Routes.tsx
index 6b66e582d..199401fd9 100644
--- a/src/react/Routes.tsx
+++ b/src/react/Routes.tsx
@@ -46,6 +46,7 @@ import AccountUserAccessKeys from './account/AccountUserAccessKeys';
import AccountCreateUser from './account/AccountCreateUser';
import CreateAccountPolicy from './account/CreateAccountPolicy';
import { ISVSteps } from './ISV/components/ISVSteps';
+import { useIsVeeamVBROnly } from './ISV/hooks/useIsVeeamVBROnly';
export const RemoveTrailingSlash = ({ ...rest }) => {
const location = useLocation();
@@ -97,13 +98,15 @@ const RedirectToAccount = () => {
}
};
-function PrivateRoutes() {
+export function PrivateRoutes() {
const dispatch = useDispatch();
const isClientsLoaded = useSelector(
(state: AppState) => state.auth.isClientsLoaded,
);
const user = useSelector((state: AppState) => state.oidc.user);
const config = useConfig();
+ const isArtescaPlusVeeamEnabled = useIsVeeamVBROnly();
+
const managementEndpoint = useSelector(
(state: AppState) => state.auth?.config?.managementEndpoint,
);
@@ -208,22 +211,26 @@ function PrivateRoutes() {
}
/>
-
-
-
- }
- />
-
-
-
- }
- />
+ {!isArtescaPlusVeeamEnabled && (
+ <>
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+ >
+ )}
{
@@ -475,14 +483,18 @@ function InternalRoutes() {
},
active: doesRouteMatch('/locations'),
},
- {
- label: 'Data Services',
- icon: ,
- onClick: () => {
- navigate('/dataservices');
- },
- active: doesRouteMatch('/dataservices'),
- },
+ ...(isArtescaPlusVeeamEnabled
+ ? []
+ : [
+ {
+ label: 'Data Services',
+ icon: ,
+ onClick: () => {
+ navigate('/dataservices');
+ },
+ active: doesRouteMatch('/dataservices'),
+ },
+ ]),
]
: []),
],