diff --git a/app/__tests__/api/update-realm.test.ts b/app/__tests__/api/update-realm.test.ts
index 64caa53..32b3501 100644
--- a/app/__tests__/api/update-realm.test.ts
+++ b/app/__tests__/api/update-realm.test.ts
@@ -143,4 +143,16 @@ describe('Profile Validations', () => {
const updatedFields = Object.keys(profileUpdate.mock.calls[0][0].data);
adminAllowedFields.forEach((field) => expect(updatedFields.includes(field)).toBeTruthy());
});
+
+ it('does not allow to update rejected realms', async () => {
+ (prisma.roster.findUnique as jest.Mock).mockImplementation(() => {
+ return Promise.resolve({ ...CustomRealmProfiles[0], approved: false });
+ });
+ const { req, res } = createMocks({
+ method: 'PUT',
+ query: { id: 1 },
+ });
+ await handler(req, res);
+ expect(res.statusCode).toBe(400);
+ });
});
diff --git a/app/__tests__/custom-realm-dashboard.test.tsx b/app/__tests__/custom-realm-dashboard.test.tsx
index ac0d2d2..bb296b5 100644
--- a/app/__tests__/custom-realm-dashboard.test.tsx
+++ b/app/__tests__/custom-realm-dashboard.test.tsx
@@ -7,12 +7,12 @@ import { getRealmEvents } from 'services/events';
import { CustomRealmFormData } from 'types/realm-profile';
import Router from 'next/router';
import { CustomRealms } from './fixtures';
-import { debug } from 'jest-preview';
jest.mock('services/realm', () => {
return {
deleteRealmRequest: jest.fn((realmInfo: CustomRealmFormData) => Promise.resolve([true, null])),
updateRealmProfile: jest.fn((id: number, status: string) => Promise.resolve([true, null])),
+ getRealmProfiles: jest.fn((excludeArchived: boolean) => Promise.resolve([CustomRealms, null])),
};
});
@@ -107,10 +107,10 @@ jest.mock('../pages/api/auth/[...nextauth]', () => {
});
describe('Table', () => {
- it('Loads in table data from serverside props', () => {
- render();
- expect(screen.getByText('realm 1'));
- expect(screen.getByText('realm 2'));
+ it('Loads in table data from serverside props', async () => {
+ render();
+ await waitFor(() => screen.getByText('realm 1'));
+ await waitFor(() => screen.getByText('realm 2'));
});
});
@@ -127,6 +127,7 @@ describe('Status update', () => {
router={Router as any}
/>,
);
+ await waitFor(() => screen.getByText('realm 1'));
fireEvent.click(screen.getByText('realm 1'));
screen.getByText('Access Request').click();
await waitFor(() => screen.getByText('Approve Custom Realm', { selector: 'button' }).click());
@@ -141,6 +142,7 @@ describe('Status update', () => {
router={Router as any}
/>,
);
+ await waitFor(() => screen.getByText('realm 1'));
fireEvent.click(screen.getByText('realm 1'));
screen.getByText('Access Request').click();
await waitFor(() => screen.getByText('Decline Custom Realm', { selector: 'button' }).click());
@@ -155,6 +157,7 @@ describe('Status update', () => {
router={Router as any}
/>,
);
+ await waitFor(() => screen.getByText('realm 1'));
fireEvent.click(screen.getByText('realm 1'));
screen.getByText('Access Request').click();
await waitFor(() => screen.getByText('Approve Custom Realm', { selector: 'button' }).click());
@@ -173,6 +176,7 @@ describe('Status update', () => {
router={Router as any}
/>,
);
+ await waitFor(() => screen.getByText('realm 1'));
fireEvent.click(screen.getByText('realm 1'));
screen.getByText('Access Request').click();
await waitFor(() => screen.getByText('Decline Custom Realm', { selector: 'button' }).click());
@@ -191,6 +195,7 @@ describe('Status update', () => {
router={Router as any}
/>,
);
+ await waitFor(() => screen.getByText('realm 1'));
(updateRealmProfile as jest.MockedFunction).mockImplementationOnce(() =>
Promise.resolve([null, { message: 'failure' }]),
);
@@ -227,6 +232,7 @@ describe('Status update', () => {
router={Router as any}
/>,
);
+ await waitFor(() => screen.getByText('realm 1'));
(updateRealmProfile as jest.MockedFunction).mockImplementationOnce(() =>
Promise.resolve([null, { message: 'failure' }]),
);
@@ -263,7 +269,8 @@ describe('Events table', () => {
});
it('fetches correct events when selected row changes', async () => {
- render();
+ render();
+ await waitFor(() => screen.getByText('realm 1'));
const row1 = screen.getByText('realm 1');
fireEvent.click(row1);
expect(getRealmEvents).toHaveBeenCalledTimes(1);
@@ -279,8 +286,8 @@ describe('Events table', () => {
});
it('displays events for the selected realm and updates when changing rows', async () => {
- render();
-
+ render();
+ await waitFor(() => screen.getByText('realm 1'));
const firstRealmRow = screen.getByText('realm 1');
fireEvent.click(firstRealmRow);
const eventTab = screen.getByText('Events');
@@ -306,6 +313,7 @@ describe('Events table', () => {
router={Router as any}
/>,
);
+ await waitFor(() => screen.getByText('realm 1'));
fireEvent.click(screen.getByText('realm 1'));
await waitFor(() => screen.getByText('Network error when fetching realm events.'));
});
diff --git a/app/__tests__/fixtures.ts b/app/__tests__/fixtures.ts
index 902b646..224e2bf 100644
--- a/app/__tests__/fixtures.ts
+++ b/app/__tests__/fixtures.ts
@@ -23,6 +23,8 @@ export const CustomRealms: CustomRealmFormData[] = [
rcChannelOwnedBy: '',
materialToSend: '',
status: 'pending',
+ idps: [],
+ protocol: [],
},
{
id: 2,
@@ -43,6 +45,30 @@ export const CustomRealms: CustomRealmFormData[] = [
division: 'division',
approved: null,
status: 'pending',
+ idps: [],
+ protocol: [],
+ },
+ {
+ id: 3,
+ realm: 'realm 3',
+ productName: 'name',
+ purpose: 'purpose',
+ primaryEndUsers: ['livingInBC', 'businessInBC', 'govEmployees', 'details'],
+ environments: ['dev', 'test', 'prod'],
+ preferredAdminLoginMethod: 'idir',
+ productOwnerEmail: 'a@b.com',
+ productOwnerIdirUserId: 'po',
+ technicalContactEmail: 'b@c.com',
+ technicalContactIdirUserId: 'd@e.com',
+ secondTechnicalContactIdirUserId: 'dmsd',
+ secondTechnicalContactEmail: 'a@b.com',
+ ministry: 'ministry',
+ branch: 'branch',
+ division: 'division',
+ approved: false,
+ status: 'pending',
+ idps: [],
+ protocol: [],
},
];
diff --git a/app/__tests__/my-dashboard.test.tsx b/app/__tests__/my-dashboard.test.tsx
new file mode 100644
index 0000000..9cd91e7
--- /dev/null
+++ b/app/__tests__/my-dashboard.test.tsx
@@ -0,0 +1,85 @@
+import React from 'react';
+import { render, screen, within, waitFor, fireEvent } from '@testing-library/react';
+import App from 'pages/_app';
+import CustomRealmDashboard from 'pages/custom-realm-dashboard';
+import { updateRealmProfile } from 'services/realm';
+import { getRealmEvents } from 'services/events';
+import { CustomRealmFormData } from 'types/realm-profile';
+import Router from 'next/router';
+import { CustomRealms } from './fixtures';
+import RealmLeftPanel from 'page-partials/my-dashboard/RealmLeftPanel';
+import noop from 'lodash.noop';
+import { debug } from 'jest-preview';
+
+const editFunction = jest.fn();
+
+jest.mock('next/router', () => ({
+ useRouter() {
+ return {
+ route: '/',
+ pathname: '',
+ query: '',
+ asPath: '',
+ push: jest.fn(() => Promise.resolve(true)),
+ events: {
+ on: jest.fn(),
+ off: jest.fn(),
+ },
+ beforePopState: jest.fn(() => null),
+ prefetch: jest.fn(() => null),
+ };
+ },
+}));
+
+// Mock authentication
+const mockSession = {
+ expires: new Date(Date.now() + 2 * 86400).toISOString(),
+ user: { username: 'admin' },
+};
+jest.mock('next-auth/react', () => {
+ const originalModule = jest.requireActual('next-auth/react');
+ return {
+ __esModule: true,
+ ...originalModule,
+ useSession: jest.fn(() => {
+ return { data: mockSession, status: 'authenticated' }; // return type is [] in v3 but changed to {} in v4
+ }),
+ };
+});
+
+jest.mock('next-auth/next', () => {
+ return {
+ __esModule: true,
+ getServerSession: jest.fn(() => {
+ return { data: mockSession, status: 'authenticated' };
+ }),
+ };
+});
+
+jest.mock('../pages/api/realms', () => {
+ return {
+ __esModule: true,
+ getAllRealms: jest.fn(() => Promise.resolve([CustomRealms, null])),
+ authOptions: {},
+ };
+});
+
+describe('realm table', () => {
+ it('loads all realms', () => {
+ render();
+ });
+
+ it('edit button disabled if realm is not approved', async () => {
+ render(
+ ,
+ );
+ const table = screen.getByRole('table');
+ const thirdRow = table.querySelector('tbody tr:nth-child(3)') as HTMLTableRowElement;
+ expect(thirdRow).toBeInTheDocument();
+ const actionCell = thirdRow.querySelector('td:nth-child(10)') as HTMLTableCellElement;
+ expect(actionCell).toBeInTheDocument();
+ const editButton = within(actionCell).getByRole('img', { name: 'Edit' });
+ fireEvent.click(editButton);
+ expect(editFunction).toHaveBeenCalledTimes(0);
+ });
+});
diff --git a/app/package.json b/app/package.json
index a86558c..6b6302a 100644
--- a/app/package.json
+++ b/app/package.json
@@ -68,12 +68,12 @@
},
"devDependencies": {
"@octokit/types": "^12.1.1",
- "@testing-library/jest-dom": "^6.1.4",
+ "@testing-library/jest-dom": "^6.4.8",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.5.1",
"@types/deep-diff": "^1.0.5",
"@types/easy-soap-request": "^4.1.1",
- "@types/jest": "^29.5.7",
+ "@types/jest": "^29.5.12",
"@types/jsonwebtoken": "^8.5.8",
"@types/jwk-to-pem": "^2.0.1",
"@types/jws": "^3.2.4",
diff --git a/app/page-partials/my-dashboard/RealmTable.tsx b/app/page-partials/my-dashboard/RealmTable.tsx
index 06f61f9..4c3fedc 100644
--- a/app/page-partials/my-dashboard/RealmTable.tsx
+++ b/app/page-partials/my-dashboard/RealmTable.tsx
@@ -94,20 +94,19 @@ function RealmTable({ realms, onEditClick, onViewClick }: Props) {
title="Edit"
icon={faEdit}
onClick={() => {
+ if (props.row.original.approved === false) return;
onEditClick(String(props.row.getValue('id')));
}}
+ disabled={props.row.original.approved === false}
/>
),
},
];
- const getStatus = (status?: string) => {
- switch (status) {
- case StatusEnum.APPLIED:
- return 'Ready';
- default:
- return 'In Progress';
- }
+ const getStatus = (status?: string, approved?: boolean) => {
+ if (status === StatusEnum.APPLIED) return 'Ready';
+ else if (approved === false) return 'Rejected';
+ else return 'In Progress';
};
return (
@@ -127,9 +126,10 @@ function RealmTable({ realms, onEditClick, onViewClick }: Props) {
secondTechnicalContact: r.secondTechnicalContactEmail
? `${r.secondTechnicalContactEmail} (${r.secondTechnicalContactIdirUserId})`
: '',
- status: getStatus(r.status),
+ status: getStatus(r.status, r.approved),
rcChannel: r.rcChannel,
rcChannelOwnedBy: r.rcChannelOwnedBy,
+ approved: r.approved,
};
})}
columns={columns}
diff --git a/app/pages/api/realms/[id].ts b/app/pages/api/realms/[id].ts
index d556511..4401e57 100644
--- a/app/pages/api/realms/[id].ts
+++ b/app/pages/api/realms/[id].ts
@@ -101,7 +101,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
},
});
- if (!currentRequest) {
+ if (!currentRequest || currentRequest.approved === false) {
return res.status(400).json({ success: false, error: 'Invalid request' });
}
diff --git a/app/pages/custom-realm-dashboard.tsx b/app/pages/custom-realm-dashboard.tsx
index 793a84d..d2225ba 100644
--- a/app/pages/custom-realm-dashboard.tsx
+++ b/app/pages/custom-realm-dashboard.tsx
@@ -57,12 +57,16 @@ interface SelectOption {
label: string;
}
-function CustomRealmDashboard({ defaultRealmRequests, alert }: Props) {
- const [realmRequests, setRealmRequests] = useState(defaultRealmRequests || []);
+function CustomRealmDashboard({ alert }: Props) {
+ const [realmRequests, setRealmRequests] = useState([]);
const [selectedRow, setSelectedRow] = useState();
const [lastUpdateTime, setLastUpdateTime] = useState(new Date());
const { setModalConfig } = useContext(ModalContext);
+ useEffect(() => {
+ fetchRealms();
+ }, []);
+
const handleDeleteRequest = (id: number) => {
const handleConfirm = async () => {
const [, err] = await deleteRealmRequest(id);
@@ -118,11 +122,11 @@ function CustomRealmDashboard({ defaultRealmRequests, alert }: Props) {
content: `Realm request for ${realm?.realm} ${approval}.`,
});
const updatedRealms = realmRequests.map((realm) => {
- if (realm.id === realmId) return { ...realm, approved: approving } as CustomRealmFormData;
+ if (realm.id === realmId) return { ...realm, approved: approving } as RealmProfile;
return realm;
});
setRealmRequests(updatedRealms);
- setSelectedRow({ ...selectedRow, approved: approving } as CustomRealmFormData);
+ setSelectedRow({ ...selectedRow, approved: approving } as RealmProfile);
};
const statusVerb = approval === 'approved' ? 'Approve' : 'Decline';
setModalConfig({
@@ -268,42 +272,3 @@ function CustomRealmDashboard({ defaultRealmRequests, alert }: Props) {
}
export default withBottomAlert(CustomRealmDashboard);
-
-interface ExtendedForm extends CustomRealmFormData {
- createdAt: object;
- updatedAt: object;
-}
-
-/**Fetch realm data with first page load */
-export const getServerSideProps = async (context: GetServerSidePropsContext) => {
- const session = await getServerSession(context.req, context.res, authOptions);
- if (!session)
- return {
- props: { defaultRealmRequests: [] },
- };
-
- const username = session?.user?.idir_username || '';
- const isAdmin = checkAdminRole(session?.user);
-
- try {
- const realms = await getAllRealms(username, isAdmin);
- // Strip non-serializable dates
- const formattedRealms = realms.map((realm: ExtendedForm) => {
- const { createdAt, updatedAt, ...rest } = realm;
- return rest;
- });
-
- return {
- props: {
- defaultRealmRequests: formattedRealms,
- },
- };
- } catch (err) {
- console.error(err);
- return {
- props: {
- defaltRealmRequests: [],
- },
- };
- }
-};
diff --git a/app/tsconfig.json b/app/tsconfig.json
index b9019a9..7893438 100644
--- a/app/tsconfig.json
+++ b/app/tsconfig.json
@@ -14,7 +14,8 @@
"isolatedModules": true,
"jsx": "preserve",
"baseUrl": ".",
- "incremental": true
+ "incremental": true,
+ "types": ["node", "jest", "@testing-library/jest-dom"]
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "pages/**/*", "src/**/*"],
"exclude": ["node_modules"]
diff --git a/app/yarn.lock b/app/yarn.lock
index 8ae4545..ef6ae4d 100644
--- a/app/yarn.lock
+++ b/app/yarn.lock
@@ -7,10 +7,10 @@
resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf"
integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==
-"@adobe/css-tools@^4.3.1":
- version "4.3.1"
- resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.3.1.tgz#abfccb8ca78075a2b6187345c26243c1a0842f28"
- integrity sha512-/62yikz7NLScCGAAST5SHdnjaDJQBDq0M2muyRTpf2VQhw6StBg2ALiu73zSJQ4fMVLA+0uBhBHAle7Wg+2kSg==
+"@adobe/css-tools@^4.4.0":
+ version "4.4.0"
+ resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.4.0.tgz#728c484f4e10df03d5a3acd0d8adcbbebff8ad63"
+ integrity sha512-Ff9+ksdQQB3rMncgqDK78uLznstjyfIf2Arnh22pW8kBpLs6rpKDwgnZT46hin5Hl1WzazzK64DOrhSwYpS7bQ==
"@ampproject/remapping@^2.2.0":
version "2.2.1"
@@ -1808,18 +1808,18 @@
lz-string "^1.5.0"
pretty-format "^27.0.2"
-"@testing-library/jest-dom@^6.1.4":
- version "6.1.4"
- resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.1.4.tgz#cf0835c33bc5ef00befb9e672b1e3e6a710e30e3"
- integrity sha512-wpoYrCYwSZ5/AxcrjLxJmCU6I5QAJXslEeSiMQqaWmP2Kzpd1LvF/qxmAIW2qposULGWq2gw30GgVNFLSc2Jnw==
+"@testing-library/jest-dom@^6.4.8":
+ version "6.4.8"
+ resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.4.8.tgz#9c435742b20c6183d4e7034f2b329d562c079daa"
+ integrity sha512-JD0G+Zc38f5MBHA4NgxQMR5XtO5Jx9g86jqturNTt2WUfRmLDIY7iKkWHDCCTiDuFMre6nxAD5wHw9W5kI4rGw==
dependencies:
- "@adobe/css-tools" "^4.3.1"
+ "@adobe/css-tools" "^4.4.0"
"@babel/runtime" "^7.9.2"
aria-query "^5.0.0"
chalk "^3.0.0"
css.escape "^1.5.1"
- dom-accessibility-api "^0.5.6"
- lodash "^4.17.15"
+ dom-accessibility-api "^0.6.3"
+ lodash "^4.17.21"
redent "^3.0.0"
"@testing-library/react@^14.0.0":
@@ -1947,10 +1947,10 @@
dependencies:
"@types/istanbul-lib-report" "*"
-"@types/jest@^29.5.7":
- version "29.5.7"
- resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.7.tgz#2c0dafe2715dd958a455bc10e2ec3e1ec47b5036"
- integrity sha512-HLyetab6KVPSiF+7pFcUyMeLsx25LDNDemw9mGsJBkai/oouwrjTycocSDYopMEwFhN2Y4s9oPyOCZNofgSt2g==
+"@types/jest@^29.5.12":
+ version "29.5.12"
+ resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.12.tgz#7f7dc6eb4cf246d2474ed78744b05d06ce025544"
+ integrity sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==
dependencies:
expect "^29.0.0"
pretty-format "^29.0.0"
@@ -3367,11 +3367,16 @@ doctrine@^3.0.0:
dependencies:
esutils "^2.0.2"
-dom-accessibility-api@^0.5.6, dom-accessibility-api@^0.5.9:
+dom-accessibility-api@^0.5.9:
version "0.5.16"
resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz#5a7429e6066eb3664d911e33fb0e45de8eb08453"
integrity sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==
+dom-accessibility-api@^0.6.3:
+ version "0.6.3"
+ resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz#993e925cc1d73f2c662e7d75dd5a5445259a8fd8"
+ integrity sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==
+
dom-align@^1.7.0:
version "1.12.4"
resolved "https://registry.yarnpkg.com/dom-align/-/dom-align-1.12.4.tgz#3503992eb2a7cfcb2ed3b2a6d21e0b9c00d54511"