diff --git a/packages/manager/apps/procedures/package.json b/packages/manager/apps/procedures/package.json
index dfa68d879aae..9332d9df586c 100644
--- a/packages/manager/apps/procedures/package.json
+++ b/packages/manager/apps/procedures/package.json
@@ -45,6 +45,7 @@
"@tanstack/react-query-devtools": "^5.51.21",
"@testing-library/jest-dom": "^6.4.6",
"@testing-library/react": "^14.1.2",
+ "@testing-library/react-hooks": "^8.0.1",
"@vitejs/plugin-react": "^4.2.1",
"@vitest/coverage-v8": "^1.2.0",
"element-internals-polyfill": "^1.3.10",
diff --git a/packages/manager/apps/procedures/src/components/Loading/Loading.test.tsx b/packages/manager/apps/procedures/src/components/Loading/Loading.test.tsx
new file mode 100644
index 000000000000..b7e45d524463
--- /dev/null
+++ b/packages/manager/apps/procedures/src/components/Loading/Loading.test.tsx
@@ -0,0 +1,15 @@
+import React from 'react';
+import { render } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import Loading from './Loading';
+
+describe('Loading', () => {
+ it('should render with the correct layout', () => {
+ const { container } = render();
+
+ expect(
+ container.querySelector('.flex.justify-center.items-center'),
+ ).toBeInTheDocument();
+ expect(container.querySelector('.w-28')).toBeInTheDocument();
+ });
+});
diff --git a/packages/manager/apps/procedures/src/components/Loading/SkeletonLoading.test.tsx b/packages/manager/apps/procedures/src/components/Loading/SkeletonLoading.test.tsx
new file mode 100644
index 000000000000..5f189d99f748
--- /dev/null
+++ b/packages/manager/apps/procedures/src/components/Loading/SkeletonLoading.test.tsx
@@ -0,0 +1,19 @@
+import React from 'react';
+import { render } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import { SkeletonLoading } from './SkeletonLoading';
+
+describe('SkeletonLoading Component', () => {
+ it('should render the correct number of skeleton loaders', () => {
+ const { container } = render();
+
+ const skeletons = container.querySelectorAll('osds-skeleton');
+ expect(skeletons).toHaveLength(7);
+ });
+
+ it('should have the correct wrapper classes', () => {
+ const { container } = render();
+ const wrapper = container.firstChild;
+ expect(wrapper).toHaveClass('w-[100%]', 'p-10');
+ });
+});
diff --git a/packages/manager/apps/procedures/src/context/User/modals/ExpiredSessionModal.test.tsx b/packages/manager/apps/procedures/src/context/User/modals/ExpiredSessionModal.test.tsx
new file mode 100644
index 000000000000..0a6e622be107
--- /dev/null
+++ b/packages/manager/apps/procedures/src/context/User/modals/ExpiredSessionModal.test.tsx
@@ -0,0 +1,36 @@
+import React from 'react';
+import { render, screen, fireEvent } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import { vi } from 'vitest';
+import { ExpiredSessionModal } from './ExpiredSessionModal';
+
+const onCloseMock = vi.fn();
+
+describe('ExpiredSessionModal', () => {
+ it('should render the modal with the correct content', () => {
+ render();
+
+ expect(
+ screen.getByText('account-disable-2fa-session-modal-expired-title'),
+ ).toBeInTheDocument();
+
+ expect(
+ screen.getByText('account-disable-2fa-session-modal-expired-message'),
+ ).toBeInTheDocument();
+
+ expect(
+ screen.getByText('account-disable-2fa-session-modal-expired-auth-button'),
+ ).toBeInTheDocument();
+ });
+
+ it('should call onClose when the button is clicked', () => {
+ render();
+
+ const button = screen.getByText(
+ 'account-disable-2fa-session-modal-expired-auth-button',
+ );
+ fireEvent.click(button);
+
+ expect(onCloseMock).toHaveBeenCalledTimes(1);
+ });
+});
diff --git a/packages/manager/apps/procedures/src/context/User/modals/SessionModals.test.tsx b/packages/manager/apps/procedures/src/context/User/modals/SessionModals.test.tsx
new file mode 100644
index 000000000000..f5abce322bdf
--- /dev/null
+++ b/packages/manager/apps/procedures/src/context/User/modals/SessionModals.test.tsx
@@ -0,0 +1,151 @@
+import React from 'react';
+import { render, screen, fireEvent } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import { vi } from 'vitest';
+import { useSessionModal } from '../useSessionModal';
+import userContext, { User } from '../context';
+import { getRedirectLoginUrl } from '@/utils/url-builder';
+import { SessionModals } from './SessionModals';
+
+vi.mock('../useSessionModal', () => ({
+ useSessionModal: vi.fn(),
+}));
+
+vi.mock('@/utils/url-builder', () => ({
+ getRedirectLoginUrl: vi.fn(),
+}));
+
+vi.mock('./ExpiredSessionModal', () => ({
+ ExpiredSessionModal: ({ onClose }: { onClose: () => void }) => (
+
+
+
+ ),
+}));
+
+vi.mock('./WarningSessionModal', () => ({
+ WarningSessionModal: ({ onClose }: { onClose: () => void }) => (
+
+
+
+ ),
+}));
+
+const mockUser: Partial = {
+ legalForm: 'administration',
+ email: 'test@example.com',
+ language: 'en',
+ subsidiary: 'FR',
+};
+
+describe('SessionModals', () => {
+ const mockSetShowExpiredModal = vi.fn();
+ const mockSetShowWarningModal = vi.fn();
+
+ beforeEach(() => {
+ (useSessionModal as any).mockReturnValue({
+ setShowExpiredModal: mockSetShowExpiredModal,
+ setShowWarningModal: mockSetShowWarningModal,
+ showExpiredModal: false,
+ showWarningModal: false,
+ });
+
+ (getRedirectLoginUrl as any).mockReturnValue('https://login.url');
+ });
+
+ afterEach(() => {
+ vi.clearAllMocks();
+ });
+
+ it('should not display any modals by default', () => {
+ render(
+
+
+ ,
+ );
+
+ expect(
+ screen.queryByTestId('expired-session-modal'),
+ ).not.toBeInTheDocument();
+ expect(
+ screen.queryByTestId('warning-session-modal'),
+ ).not.toBeInTheDocument();
+ });
+
+ it('should display the ExpiredSessionModal when showExpiredModal is true', () => {
+ (useSessionModal as any).mockReturnValue({
+ setShowExpiredModal: mockSetShowExpiredModal,
+ setShowWarningModal: mockSetShowWarningModal,
+ showExpiredModal: true,
+ showWarningModal: false,
+ });
+
+ render(
+
+
+ ,
+ );
+
+ expect(screen.getByTestId('expired-session-modal')).toBeInTheDocument();
+ });
+
+ it('should close the ExpiredSessionModal and redirect when the close button is clicked', async () => {
+ (useSessionModal as any).mockReturnValue({
+ setShowExpiredModal: mockSetShowExpiredModal,
+ setShowWarningModal: mockSetShowWarningModal,
+ showExpiredModal: true,
+ showWarningModal: false,
+ });
+
+ render(
+
+
+ ,
+ );
+
+ const closeButton = screen.getByText('Close Expired Modal');
+ fireEvent.click(closeButton);
+
+ await vi.waitFor(() => {
+ expect(mockSetShowExpiredModal).toHaveBeenCalledWith(false);
+ expect(getRedirectLoginUrl).toHaveBeenCalledWith(mockUser);
+ });
+ });
+
+ it('should display the WarningSessionModal when showWarningModal is true', () => {
+ (useSessionModal as any).mockReturnValue({
+ setShowExpiredModal: mockSetShowExpiredModal,
+ setShowWarningModal: mockSetShowWarningModal,
+ showExpiredModal: false,
+ showWarningModal: true,
+ });
+
+ render(
+
+
+ ,
+ );
+
+ expect(screen.getByTestId('warning-session-modal')).toBeInTheDocument();
+ });
+
+ it('should close the WarningSessionModal when the close button is clicked', () => {
+ (useSessionModal as any).mockReturnValue({
+ setShowExpiredModal: mockSetShowExpiredModal,
+ setShowWarningModal: mockSetShowWarningModal,
+ showExpiredModal: false,
+ showWarningModal: true,
+ });
+
+ render(
+
+
+ ,
+ );
+
+ const closeButton = screen.getByText('Close Warning Modal');
+ fireEvent.click(closeButton);
+
+ expect(mockSetShowWarningModal).toHaveBeenCalledWith(false);
+ });
+});
diff --git a/packages/manager/apps/procedures/src/context/User/modals/SessionModals.tsx b/packages/manager/apps/procedures/src/context/User/modals/SessionModals.tsx
index c416ccb96f80..95c079d0f70d 100644
--- a/packages/manager/apps/procedures/src/context/User/modals/SessionModals.tsx
+++ b/packages/manager/apps/procedures/src/context/User/modals/SessionModals.tsx
@@ -6,7 +6,9 @@ import { WarningSessionModal } from './WarningSessionModal';
import userContext from '../context';
export const SessionModals: FunctionComponent = () => {
- const { user } = useContext(userContext);
+ const context = useContext(userContext);
+
+ const { user } = context;
const {
setShowExpiredModal,
setShowWarningModal,
diff --git a/packages/manager/apps/procedures/src/context/User/modals/WarningSessionModal.test.tsx b/packages/manager/apps/procedures/src/context/User/modals/WarningSessionModal.test.tsx
new file mode 100644
index 000000000000..5a746c785dee
--- /dev/null
+++ b/packages/manager/apps/procedures/src/context/User/modals/WarningSessionModal.test.tsx
@@ -0,0 +1,32 @@
+import React from 'react';
+import { render, screen, fireEvent } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import { vi } from 'vitest';
+import { WarningSessionModal } from './WarningSessionModal';
+
+const onCloseMock = vi.fn();
+
+describe('WarningSessionModal', () => {
+ it('should render the modal with the correct content', () => {
+ render();
+
+ expect(
+ screen.getByText('account-disable-2fa-session-modal-warning-message'),
+ ).toBeInTheDocument();
+
+ expect(
+ screen.getByText('account-disable-2fa-session-modal-warning-ok-button'),
+ ).toBeInTheDocument();
+ });
+
+ it('should call onClose when the button is clicked', () => {
+ render();
+
+ const button = screen.getByText(
+ 'account-disable-2fa-session-modal-warning-ok-button',
+ );
+ fireEvent.click(button);
+
+ expect(onCloseMock).toHaveBeenCalledTimes(1);
+ });
+});
diff --git a/packages/manager/apps/procedures/src/context/User/useSessionModal.test.tsx b/packages/manager/apps/procedures/src/context/User/useSessionModal.test.tsx
new file mode 100644
index 000000000000..fce6f89fc8fe
--- /dev/null
+++ b/packages/manager/apps/procedures/src/context/User/useSessionModal.test.tsx
@@ -0,0 +1,137 @@
+import { vi } from 'vitest';
+import { renderHook } from '@testing-library/react-hooks';
+import { useSessionModal } from '@/context/User/useSessionModal';
+import { useFetchServerTime } from '@/data/hooks/useUtils';
+import { User } from '@/context/User/context';
+
+vi.mock('@/data/hooks/useUtils', () => ({
+ useFetchServerTime: vi.fn(),
+}));
+
+const fakeUrl = 'http://example.com/login';
+
+vi.mock('@/utils/url-builder', () => ({
+ getRedirectLoginUrl: vi.fn(() => fakeUrl),
+}));
+
+const assignMock = vi.fn();
+
+describe('useSessionModal', () => {
+ const mockUser: Partial = {
+ exp: 0.002,
+ iat: 0,
+ };
+
+ beforeEach(() => {
+ vi.clearAllMocks();
+ vi.useFakeTimers();
+ vi.setSystemTime(0);
+ Object.defineProperty(window, 'location', {
+ value: {
+ ...window.location,
+ assign: assignMock,
+ },
+ writable: true,
+ });
+ });
+
+ afterEach(() => {
+ vi.useRealTimers();
+ });
+
+ it('should initialize modal states as false', () => {
+ (useFetchServerTime as jest.Mock).mockReturnValue({
+ data: 0,
+ isError: false,
+ isFetched: false,
+ });
+
+ const { result } = renderHook(() => useSessionModal(mockUser as User, 0.5));
+
+ expect(result.current.showWarningModal).toBe(false);
+ expect(result.current.showExpiredModal).toBe(false);
+ });
+
+ it('should redirect in expected url when the user is undefined', async () => {
+ (useFetchServerTime as jest.Mock).mockReturnValue({
+ data: undefined,
+ isError: true,
+ isFetched: true,
+ });
+
+ renderHook(() => useSessionModal(undefined, 0.5));
+
+ await vi.waitFor(() => {
+ expect(assignMock).toHaveBeenCalledWith(fakeUrl);
+ });
+ });
+
+ it('should redirect in expected url when remaining Expire Time is reached', async () => {
+ const serverTime = 0;
+ mockUser.exp = -1;
+ (useFetchServerTime as jest.Mock).mockReturnValue({
+ data: serverTime,
+ isError: false,
+ isFetched: true,
+ });
+
+ renderHook(() => useSessionModal(mockUser as User, 0.5));
+
+ await vi.waitFor(() => {
+ expect(assignMock).toHaveBeenCalledWith(fakeUrl);
+ });
+ });
+
+ it('should show the warning modal when warning time is reached', async () => {
+ mockUser.exp = 0.1;
+ const serverTime = 0;
+
+ (useFetchServerTime as jest.Mock).mockReturnValue({
+ data: serverTime,
+ isError: false,
+ isFetched: true,
+ });
+
+ const { result } = renderHook(() => useSessionModal(mockUser as User, 0.1));
+
+ await vi.waitFor(() => {
+ expect(result.current.showWarningModal).toBe(true);
+ expect(result.current.showExpiredModal).toBe(false);
+ });
+ });
+
+ it('should show the expired modal when expired time is reached', async () => {
+ mockUser.exp = 0.1;
+ const serverTime = 0;
+
+ (useFetchServerTime as jest.Mock).mockReturnValue({
+ data: serverTime,
+ isError: false,
+ isFetched: true,
+ });
+
+ const { result } = renderHook(() => useSessionModal(mockUser as User, 0.5));
+
+ await vi.waitFor(() => {
+ expect(result.current.showExpiredModal).toBe(true);
+ });
+ });
+
+ it('should show the expired modal and warning modal when times are reached', async () => {
+ mockUser.exp = 1;
+ const serverTime = 0.5;
+
+ (useFetchServerTime as jest.Mock).mockReturnValue({
+ data: serverTime,
+ isError: false,
+ isFetched: true,
+ });
+
+ const { result } = renderHook(() => useSessionModal(mockUser as User, 0.5));
+
+ await vi.waitFor(() => {
+ expect(result.current.showWarningModal).toBe(true);
+ expect(result.current.showExpiredModal).toBe(true);
+ });
+ });
+});
diff --git a/packages/manager/apps/procedures/src/context/User/useUser.test.tsx b/packages/manager/apps/procedures/src/context/User/useUser.test.tsx
new file mode 100644
index 000000000000..7b2e4afe3611
--- /dev/null
+++ b/packages/manager/apps/procedures/src/context/User/useUser.test.tsx
@@ -0,0 +1,34 @@
+import React, { FunctionComponent } from 'react';
+import { renderHook } from '@testing-library/react-hooks';
+import userContext, { UserContext } from '@/context/User/context';
+import useUserContext from './useUser';
+
+const mockContextValue: UserContext = {
+ user: {
+ legalForm: 'administration',
+ email: 'johndoe@example.com',
+ language: 'en',
+ subsidiary: 'FR',
+ iss: 'issuer.example.com',
+ sub: '12345',
+ exp: 1700000000,
+ nbf: 1699990000,
+ iat: 1699980000,
+ },
+};
+
+const { Provider } = userContext;
+
+const wrapper: FunctionComponent = ({ children }: any) => (
+ {children}
+);
+
+describe('useUserContext', () => {
+ it('should return the user context', () => {
+ const { result } = renderHook(() => useUserContext(), {
+ wrapper,
+ });
+
+ expect(result.current.user).toEqual(mockContextValue.user);
+ });
+});
diff --git a/packages/manager/apps/procedures/src/pages/disableMFA/create/confirm/Confirm.page.tsx b/packages/manager/apps/procedures/src/pages/disableMFA/create/confirm/Confirm.page.tsx
deleted file mode 100644
index 2ff7c60e2583..000000000000
--- a/packages/manager/apps/procedures/src/pages/disableMFA/create/confirm/Confirm.page.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import React from 'react';
-
-export default function ConfirmCreateRequest() {
- return Confirm request creation
;
-}
diff --git a/packages/manager/apps/procedures/src/pages/disableMFA/error/Error.page.test.tsx b/packages/manager/apps/procedures/src/pages/disableMFA/error/Error.page.test.tsx
new file mode 100644
index 000000000000..c2a97fe76c47
--- /dev/null
+++ b/packages/manager/apps/procedures/src/pages/disableMFA/error/Error.page.test.tsx
@@ -0,0 +1,19 @@
+import { render, screen } from '@testing-library/react';
+import React from 'react';
+import ErrorPage from './Error.page';
+
+describe('ErrorPage Component', () => {
+ it('should render all text elements with the correct translation keys', () => {
+ render();
+
+ expect(
+ screen.getByText('account-disable-2fa-error-validation'),
+ ).toBeInTheDocument();
+ expect(
+ screen.getByText('account-disable-2fa-error-verification'),
+ ).toBeInTheDocument();
+ expect(
+ screen.getByText('account-disable-2fa-error-suggestion'),
+ ).toBeInTheDocument();
+ });
+});
diff --git a/packages/manager/apps/procedures/src/pages/disableMFA/error/Error.page.tsx b/packages/manager/apps/procedures/src/pages/disableMFA/error/Error.page.tsx
index f58975067611..44e1e0601d82 100644
--- a/packages/manager/apps/procedures/src/pages/disableMFA/error/Error.page.tsx
+++ b/packages/manager/apps/procedures/src/pages/disableMFA/error/Error.page.tsx
@@ -7,7 +7,7 @@ import {
} from '@ovhcloud/ods-common-theming';
import { ODS_TEXT_SIZE } from '@ovhcloud/ods-components';
-export default function SeeRequest() {
+export default function ErrorPage() {
const { t } = useTranslation('account-disable-2fa/error');
return (
diff --git a/packages/manager/apps/procedures/src/pages/disableMFA/see/See.page.test.tsx b/packages/manager/apps/procedures/src/pages/disableMFA/see/See.page.test.tsx
new file mode 100644
index 000000000000..a04451705c33
--- /dev/null
+++ b/packages/manager/apps/procedures/src/pages/disableMFA/see/See.page.test.tsx
@@ -0,0 +1,20 @@
+import { render, screen } from '@testing-library/react';
+import React from 'react';
+import SeeRequest from './See.page';
+
+describe('SeeRequest Component', () => {
+ it('should render all text elements with the correct translation keys', () => {
+ render();
+
+ expect(
+ screen.getByText('account-disable-2fa-see-title'),
+ ).toBeInTheDocument();
+
+ expect(
+ screen.getByText('account-disable-2fa-see-description1'),
+ ).toBeInTheDocument();
+ expect(
+ screen.getByText('account-disable-2fa-see-description2'),
+ ).toBeInTheDocument();
+ });
+});
diff --git a/packages/manager/apps/procedures/src/routes/routes.tsx b/packages/manager/apps/procedures/src/routes/routes.tsx
index a81c18f1c4c0..7c1ae3635e30 100644
--- a/packages/manager/apps/procedures/src/routes/routes.tsx
+++ b/packages/manager/apps/procedures/src/routes/routes.tsx
@@ -33,12 +33,6 @@ export const Routes: any = [
import('@/pages/disableMFA/create/Create.page'),
),
children: [
- {
- path: 'confirm',
- ...lazyRouteConfig(() =>
- import('@/pages/disableMFA/create/confirm/Confirm.page'),
- ),
- },
{
path: '',
...lazyRouteConfig(() =>