diff --git a/.gitleaksignore b/.gitleaksignore
index cceb449a3..04b800c59 100644
--- a/.gitleaksignore
+++ b/.gitleaksignore
@@ -1,3 +1,4 @@
# SEE: https://github.com/gitleaks/gitleaks/blob/master/README.md#gitleaksignore
cd9c0efec38c5d63053dd865e5d4e207c0760d91:docs/guides/Perform_static_analysis.md:generic-api-key:37
+src/__tests__/components/molecules/LoginStatus.test.tsx:jwt:23
\ No newline at end of file
diff --git a/src/__tests__/app/auth/__snapshots__/page.test.tsx.snap b/src/__tests__/app/auth/__snapshots__/page.test.tsx.snap
new file mode 100644
index 000000000..b58c508b1
--- /dev/null
+++ b/src/__tests__/app/auth/__snapshots__/page.test.tsx.snap
@@ -0,0 +1,9 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`MockAuthPage 1`] = `
+
+
+ redirect
+
+
+`;
diff --git a/src/__tests__/app/auth/page.test.tsx b/src/__tests__/app/auth/page.test.tsx
new file mode 100644
index 000000000..5ed5d93b9
--- /dev/null
+++ b/src/__tests__/app/auth/page.test.tsx
@@ -0,0 +1,19 @@
+import { ReactNode, ComponentType } from 'react';
+import MockAuthPage from '@app/auth/page.dev';
+import { render } from '@testing-library/react';
+
+jest.mock('@aws-amplify/ui-react', () => ({
+ Authenticator: {
+ Provider: ({ children }: { children: ReactNode }) => children,
+ },
+ withAuthenticator: (Component: ComponentType) => Component,
+}));
+jest.mock('@molecules/Redirect/Redirect', () => ({
+ Redirect: () =>
redirect
,
+}));
+
+test('MockAuthPage', () => {
+ const container = render();
+
+ expect(container.asFragment()).toMatchSnapshot();
+});
diff --git a/src/__tests__/app/auth/signout/__snapshots__/page.test.tsx.snap b/src/__tests__/app/auth/signout/__snapshots__/page.test.tsx.snap
new file mode 100644
index 000000000..16df702b1
--- /dev/null
+++ b/src/__tests__/app/auth/signout/__snapshots__/page.test.tsx.snap
@@ -0,0 +1,9 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`MockSignoutPage 1`] = `
+
+
+ redirect
+
+
+`;
diff --git a/src/__tests__/app/auth/signout/page.test.tsx b/src/__tests__/app/auth/signout/page.test.tsx
new file mode 100644
index 000000000..34ec2ceb0
--- /dev/null
+++ b/src/__tests__/app/auth/signout/page.test.tsx
@@ -0,0 +1,29 @@
+import { ReactNode, ComponentType } from 'react';
+import MockSignoutPage from '@app/auth/signout/page.dev';
+import { render, screen } from '@testing-library/react';
+
+jest.mock('@aws-amplify/auth', () => ({
+ signOut: () => Promise.resolve(),
+}));
+
+jest.mock('@molecules/Redirect/Redirect', () => ({
+ Redirect: () => redirect
,
+}));
+
+jest.mock('@aws-amplify/ui-react', () => ({
+ Authenticator: {
+ Provider: ({ children }: { children: ReactNode }) => children,
+ },
+ withAuthenticator: (Component: ComponentType) => Component,
+}));
+jest.mock('@molecules/Redirect/Redirect', () => ({
+ Redirect: () => redirect
,
+}));
+
+test('MockSignoutPage', async () => {
+ const container = render();
+
+ await screen.findByText('redirect');
+
+ expect(container.asFragment()).toMatchSnapshot();
+});
diff --git a/src/__tests__/components/molecules/LoginStatus.test.tsx b/src/__tests__/components/molecules/LoginStatus.test.tsx
new file mode 100644
index 000000000..fd23ef4dc
--- /dev/null
+++ b/src/__tests__/components/molecules/LoginStatus.test.tsx
@@ -0,0 +1,28 @@
+/* eslint-disable unicorn/no-document-cookie */
+import { render } from '@testing-library/react';
+import { LoginStatus } from '@molecules/LoginStatus/LoginStatus';
+
+test('LoginStatus - no cookie', () => {
+ document.cookie = '';
+
+ const container = render();
+
+ expect(container.asFragment()).toMatchSnapshot();
+});
+
+test('LoginStatus - invalid cookie', () => {
+ document.cookie = 'CognitoIdentityServiceProvider.idToken=lemons';
+
+ const container = render();
+
+ expect(container.asFragment()).toMatchSnapshot();
+});
+
+test('LoginStatus - valid cookie', () => {
+ document.cookie =
+ 'CognitoIdentityServiceProvider.idToken=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImxvY2FsaG9zdEBuaHMubmV0In0.R0rk7pjJoU07efveI4p6W-xrTM-BnP8N-pU-RYczPBA';
+
+ const container = render();
+
+ expect(container.asFragment()).toMatchSnapshot();
+});
diff --git a/src/__tests__/components/molecules/Redirect.test.tsx b/src/__tests__/components/molecules/Redirect.test.tsx
new file mode 100644
index 000000000..d5de0b140
--- /dev/null
+++ b/src/__tests__/components/molecules/Redirect.test.tsx
@@ -0,0 +1,40 @@
+import { mockDeep } from 'jest-mock-extended';
+import { render } from '@testing-library/react';
+import { Redirect } from '@molecules/Redirect/Redirect';
+import {
+ useSearchParams,
+ ReadonlyURLSearchParams,
+ redirect,
+} from 'next/navigation';
+
+jest.mock('next/navigation', () => ({
+ ...jest.requireActual('next/navigation'),
+ redirect: jest.fn(),
+ useSearchParams: jest.fn(),
+}));
+
+test('Redirect - URL provided', () => {
+ const mockRedirect = jest.fn(mockDeep());
+ jest.mocked(redirect).mockImplementation(mockRedirect);
+
+ const mockSearchParams = new ReadonlyURLSearchParams({
+ redirect: 'redirect',
+ });
+ jest.mocked(useSearchParams).mockReturnValue(mockSearchParams);
+
+ render();
+
+ expect(mockRedirect).toHaveBeenCalledWith('redirect');
+});
+
+test('Redirect - URL not provided', () => {
+ const mockRedirect = jest.fn(mockDeep());
+ jest.mocked(redirect).mockImplementation(mockRedirect);
+
+ const mockSearchParams = new ReadonlyURLSearchParams({});
+ jest.mocked(useSearchParams).mockReturnValue(mockSearchParams);
+
+ render();
+
+ expect(mockRedirect).toHaveBeenCalledWith('/');
+});
diff --git a/src/__tests__/components/molecules/__snapshots__/LoginStatus.test.tsx.snap b/src/__tests__/components/molecules/__snapshots__/LoginStatus.test.tsx.snap
new file mode 100644
index 000000000..956bb051c
--- /dev/null
+++ b/src/__tests__/components/molecules/__snapshots__/LoginStatus.test.tsx.snap
@@ -0,0 +1,55 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`LoginStatus - invalid cookie 1`] = `
+
+
+
+`;
+
+exports[`LoginStatus - no cookie 1`] = `
+
+
+
+`;
+
+exports[`LoginStatus - valid cookie 1`] = `
+
+
+
+
+`;
diff --git a/src/app/auth/page.dev.tsx b/src/app/auth/page.dev.tsx
index 73e95c951..1a374a6f7 100644
--- a/src/app/auth/page.dev.tsx
+++ b/src/app/auth/page.dev.tsx
@@ -3,50 +3,24 @@
'use client';
import { Amplify } from 'aws-amplify';
-import { Suspense, useEffect } from 'react';
-import { useSearchParams } from 'next/navigation';
+import { Suspense } from 'react';
import { Authenticator, withAuthenticator } from '@aws-amplify/ui-react';
import '@aws-amplify/ui-react/styles.css';
+import { Redirect } from '@molecules/Redirect/Redirect';
Amplify.configure(require('@/amplify_outputs.json'), { ssr: true });
-const Redirect = () => {
- const searchParams = useSearchParams();
-
- const redirect = searchParams.get('redirect') ?? '/';
-
- useEffect(() => {
- location.href = redirect;
- }, [redirect]);
-
- if (redirect) {
- return (
-
- Redirecting to{' '}
-
- {redirect}
-
-
- );
- }
-};
-
-const WrappedRedirect = () => (
-
-
-
-);
-
-const MockAuthPage = () => {
- return withAuthenticator(WrappedRedirect, {
+const MockAuthPage = () =>
+ withAuthenticator(Redirect, {
variation: 'default',
hideSignUp: true,
})({});
-};
const WrappedAuthPage = () => (
-
+
+
+
);
diff --git a/src/app/auth/signout/page.dev.tsx b/src/app/auth/signout/page.dev.tsx
index c554fde7b..9d3b00119 100644
--- a/src/app/auth/signout/page.dev.tsx
+++ b/src/app/auth/signout/page.dev.tsx
@@ -4,34 +4,13 @@
import { Amplify } from 'aws-amplify';
import { Suspense, useState, useEffect } from 'react';
-import { useSearchParams } from 'next/navigation';
import { signOut } from '@aws-amplify/auth';
import { Authenticator } from '@aws-amplify/ui-react';
+import { Redirect } from '@molecules/Redirect/Redirect';
Amplify.configure(require('@/amplify_outputs.json'), { ssr: true });
-const Redirect = () => {
- const searchParams = useSearchParams();
-
- const redirect = searchParams.get('redirect') ?? '/';
-
- useEffect(() => {
- location.href = redirect;
- }, [redirect]);
-
- if (redirect) {
- return (
-
- Redirecting to{' '}
-
- {redirect}
-
-
- );
- }
-};
-
-const MockAuthPage = () => {
+const MockSignoutPage = () => {
const [signedOut, setSignedOut] = useState(false);
useEffect(() => {
@@ -42,20 +21,15 @@ const MockAuthPage = () => {
}
});
- if (signedOut) {
- return (
-
-
-
- );
- }
- return Signing out
;
+ return signedOut ? : Signing out
;
};
-const WrappedAuthPage = () => (
+const WrappedSignoutPage = () => (
-
+
+
+
);
-export default WrappedAuthPage;
+export default WrappedSignoutPage;
diff --git a/src/components/molecules/Header/Header.tsx b/src/components/molecules/Header/Header.tsx
index ca65578f6..25f658f4f 100644
--- a/src/components/molecules/Header/Header.tsx
+++ b/src/components/molecules/Header/Header.tsx
@@ -1,7 +1,7 @@
import Link from 'next/link';
import concatClassNames from '@utils/concat-class-names';
import content from '@content/content';
-import LoginStatus from '@molecules/LoginStatus/LoginStatus';
+import { LoginStatus } from '@molecules/LoginStatus/LoginStatus';
import styles from './Header.module.scss';
import { HeaderType } from './header.types';
diff --git a/src/components/molecules/LoginStatus/LoginStatus.tsx b/src/components/molecules/LoginStatus/LoginStatus.tsx
index 32d6ca2c9..586efc160 100644
--- a/src/components/molecules/LoginStatus/LoginStatus.tsx
+++ b/src/components/molecules/LoginStatus/LoginStatus.tsx
@@ -9,13 +9,16 @@ import cookie from 'cookie';
import { getAuthBasePath, getBasePath } from '@utils/get-base-path';
import { usePathname } from 'next/navigation';
-const getLoggedInUser = (cookieString: string) => {
- const cookies = cookie.parse(cookieString);
-
- if (!cookies) {
- return;
+const decodeCookie = (cookieValue: string) => {
+ try {
+ return jwtDecode(cookieValue);
+ } catch (error) {
+ console.error(error);
}
+};
+const getLoggedInUser = (cookieString: string) => {
+ const cookies = cookie.parse(cookieString);
const idTokenCookieName = Object.keys(cookies).find(
(cookieName) =>
cookieName.includes('CognitoIdentityServiceProvider') &&
@@ -27,14 +30,7 @@ const getLoggedInUser = (cookieString: string) => {
}
const idTokenCookieValue = cookies[idTokenCookieName];
-
- if (!idTokenCookieValue) {
- return;
- }
-
- const idTokenCookieDecoded = jwtDecode(
- idTokenCookieValue
- );
+ const idTokenCookieDecoded = decodeCookie(idTokenCookieValue);
if (!idTokenCookieDecoded) {
return;
@@ -43,7 +39,7 @@ const getLoggedInUser = (cookieString: string) => {
return idTokenCookieDecoded.email;
};
-export default function LoginStatus() {
+export const LoginStatus = () => {
const [browserCookie, setBrowserCookie] = useState('');
const pathname = usePathname();
@@ -76,4 +72,4 @@ export default function LoginStatus() {
Sign in
);
-}
+};
diff --git a/src/components/molecules/Redirect/Redirect.tsx b/src/components/molecules/Redirect/Redirect.tsx
new file mode 100644
index 000000000..2bc0d56af
--- /dev/null
+++ b/src/components/molecules/Redirect/Redirect.tsx
@@ -0,0 +1,12 @@
+'use client';
+
+import { getBasePath } from '@utils/get-base-path';
+import { useSearchParams, redirect } from 'next/navigation';
+
+export const Redirect = () => {
+ const searchParams = useSearchParams();
+
+ const redirectPath = searchParams.get('redirect') ?? '/';
+
+ redirect(redirectPath.replace(getBasePath(), ''));
+};