Skip to content

Commit

Permalink
feat: upgrade next to 14 and remove tf dependency (#183)
Browse files Browse the repository at this point in the history
* feat: upgrade next to 14 and remove tf dependency

* feat: use node 20 for tests

* feat: updated yarn lock file

* feat: updated node version

* feat: use import instead of require

* feat: revert jest config

* feat: revert jest config

* feat: use test plugin

* fix: jest versions

* fix: upgrade to yarn 1.22.22

* fix: revert unnecessary changes

* fix: minor fixes

* fix: minor fixes
  • Loading branch information
NithinKuruba authored Aug 27, 2024
1 parent 755cc4e commit dd339c2
Show file tree
Hide file tree
Showing 22 changed files with 2,713 additions and 2,301 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
node-version: 20.17.0
- run: |
cd app
yarn
Expand Down
4 changes: 2 additions & 2 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
nodejs 18.18.2
yarn 1.22.4
nodejs 20.17.0
yarn 1.22.22
python 3.11.0
postgres 14.1
helm 3.10.2
3 changes: 3 additions & 0 deletions app/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"presets": ["next/babel"]
}
2 changes: 1 addition & 1 deletion app/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"extends": "next/core-web-vitals"
"extends": ["next/core-web-vitals"]
}
4 changes: 2 additions & 2 deletions app/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
FROM node:18.18.2-alpine as builder
FROM node:20.17.0-alpine as builder
WORKDIR /opt/app
COPY . .
RUN yarn install --frozen-lockfile
RUN yarn build

FROM node:18.18.2-alpine as runner
FROM node:20.17.0-alpine as runner
WORKDIR /opt/app
ENV NODE_ENV production

Expand Down
98 changes: 38 additions & 60 deletions app/__tests__/api/delete-realm.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@ import { createMocks } from 'node-mocks-http';
import deleteHandler from '../../pages/api/realms/[id]';
import githubResponseHandler from '../../pages/api/realms/pending';
import prisma from 'utils/prisma';
import { CustomRealmProfiles } from '../fixtures';
import { CustomRealmProfiles, MockHttpRequest } from '../fixtures';
import { getServerSession } from 'next-auth';
import { createCustomRealmPullRequest, mergePullRequest } from 'utils/github';
import { EventEnum, StatusEnum } from 'validators/create-realm';
import { removeUserAsRealmAdmin } from 'controllers/keycloak';
import { deleteCustomRealm, removeUserAsRealmAdmin } from 'controllers/keycloak';
import { sendDeletionCompleteEmail } from 'utils/mailer';

jest.mock('../../controllers/keycloak', () => {
return {
removeUserAsRealmAdmin: jest.fn(),
createCustomRealm: jest.fn(() => true),
disableCustomRealm: jest.fn(() => true),
deleteCustomRealm: jest.fn(() => true),
};
});

Expand Down Expand Up @@ -89,71 +90,24 @@ describe('Realm Delete Request', () => {
});

it('Only allows admins to delete realms', async () => {
const { req, res } = createMocks({
const { req, res }: MockHttpRequest = createMocks({
method: 'DELETE',
query: { id: 1 },
});
await deleteHandler(req, res);
expect(res.statusCode).toBe(401);
expect(createCustomRealmPullRequest).not.toHaveBeenCalled();
expect(mergePullRequest).not.toHaveBeenCalled();

mockAdminSession();
await deleteHandler(req, res);
expect(res.statusCode).toBe(200);
expect(createCustomRealmPullRequest).toHaveBeenCalled();
expect(mergePullRequest).toHaveBeenCalled();
});

it('Updates the status, archived, and prNumber when deleted successfully', async () => {
const { req, res } = createMocks({
method: 'DELETE',
query: { id: 1 },
});
mockAdminSession();
await deleteHandler(req, res);
expect(res.statusCode).toBe(200);

const rosterUpdateArgs = (prisma.roster.update as jest.Mock).mock.calls[0][0];
expect(rosterUpdateArgs.data.archived).toBe(true);
expect(rosterUpdateArgs.data.prNumber).toBe(1);
expect(rosterUpdateArgs.data.status).toBe(StatusEnum.PRSUCCESS);
});

it('Updates the status to failed if the pr fails or merge fails and logs an event', async () => {
// PR Creation failure
const failureEvent = {
data: {
realmId: 1,
eventCode: EventEnum.REQUEST_DELETE_FAILED,
idirUserId: 'test',
},
};
const { req, res } = createMocks({
method: 'DELETE',
query: { id: 1 },
});
(createCustomRealmPullRequest as jest.Mock).mockImplementationOnce(() => Promise.reject(new Error('Failed')));
mockAdminSession();
await deleteHandler(req, res);
expect(res.statusCode).toBe(500);

expect(deleteCustomRealm).toHaveBeenCalledTimes(3);
let rosterUpdateArgs = (prisma.roster.update as jest.Mock).mock.calls[0][0];
expect(rosterUpdateArgs.data.status).toBe(StatusEnum.PRFAILED);
expect(prisma.event.create).toHaveBeenCalledWith(failureEvent);

// PR merge failure
(mergePullRequest as jest.Mock).mockImplementationOnce(() => Promise.reject(new Error('Failed')));
await deleteHandler(req, res);

expect(res.statusCode).toBe(500);
rosterUpdateArgs = (prisma.roster.update as jest.Mock).mock.calls[0][0];
expect(rosterUpdateArgs.data.status).toBe(StatusEnum.PRFAILED);
expect(prisma.event.create).toHaveBeenCalledWith(failureEvent);
expect(rosterUpdateArgs.data.status).toBe('planned');
rosterUpdateArgs = (prisma.roster.update as jest.Mock).mock.calls[1][0];
expect(rosterUpdateArgs.data.archived).toBe(true);
});
});

describe('Github Actions Delete', () => {
describe('delete realms', () => {
const mockToken = 'secret';
beforeEach(() => {
jest.clearAllMocks();
Expand All @@ -174,7 +128,7 @@ describe('Github Actions Delete', () => {
};

it('requires api token', async () => {
let { req, res } = createMocks(requestData);
let { req, res }: MockHttpRequest = createMocks(requestData);
await githubResponseHandler(req, res);
expect(res.statusCode).toBe(200);

Expand All @@ -185,7 +139,7 @@ describe('Github Actions Delete', () => {
});

it('Removes technical contact and product owner from all envirionments', async () => {
const { req, res } = createMocks(requestData);
const { req, res }: MockHttpRequest = createMocks(requestData);
await githubResponseHandler(req, res);
expect(res.statusCode).toBe(200);

Expand All @@ -201,11 +155,35 @@ describe('Github Actions Delete', () => {
});

it('Only sends deletion complete email if all users removed successfully', async () => {
const { req, res } = createMocks(requestData);
const { req, res }: MockHttpRequest = createMocks(requestData);
(removeUserAsRealmAdmin as jest.Mock).mockImplementationOnce(() => Promise.reject(new Error('failure')));
await githubResponseHandler(req, res);

expect(res.statusCode).toBe(500);
expect(sendDeletionCompleteEmail).not.toHaveBeenCalled();
});

it('calls kc admin api to disable realm in all environments', async () => {
(prisma.roster.findUnique as jest.Mock).mockImplementation(() => {
return Promise.resolve(CustomRealmProfiles[0]);
});
(getServerSession as jest.Mock).mockImplementation(() => {
return {
expires: new Date(Date.now() + 2 * 86400).toISOString(),
user: {
username: 'test',
client_roles: ['sso-admin'],
},
status: 'authenticated',
};
});
const { req, res }: MockHttpRequest = createMocks({
method: 'DELETE',
query: { id: 1 },
});
await deleteHandler(req, res);

expect(res.statusCode).toBe(200);
expect(deleteCustomRealm).toHaveBeenCalledTimes(3);
});
});
58 changes: 49 additions & 9 deletions app/__tests__/api/update-realm.test.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,31 @@
import { createMocks } from 'node-mocks-http';
import handler from '../../pages/api/realms/[id]';
import prisma from 'utils/prisma';
import { CustomRealmProfiles } from '../fixtures';
import { CustomRealmProfiles, MockHttpRequest } from '../fixtures';
import { getServerSession } from 'next-auth';
import { createCustomRealm } from 'controllers/keycloak';

jest.mock('../../utils/mailer', () => {
return {
sendUpdateEmail: jest.fn(),
sendDeleteEmail: jest.fn(),
sendReadyToUseEmail: jest.fn(),
};
});

jest.mock('../../utils/github', () => {
jest.mock('../../utils/idir', () => {
return {
createCustomRealmPullRequest: jest.fn(),
mergePullRequest: jest.fn(),
deleteBranch: jest.fn(),
generateXML: jest.fn(),
makeSoapRequest: jest.fn(() => Promise.resolve({ response: null })),
getBceidAccounts: jest.fn(() => Promise.resolve([])),
};
});

jest.mock('../../controllers/keycloak.ts', () => {
return {
createCustomRealm: jest.fn(() => true),
disableCustomRealm: jest.fn(() => true),
addUserAsRealmAdmin: jest.fn(() => true),
};
});

Expand Down Expand Up @@ -79,7 +90,7 @@ describe('Profile Validations', () => {
(prisma.roster.findUnique as jest.Mock).mockImplementation(() => {
return Promise.resolve(CustomRealmProfiles[0]);
});
const { req, res } = createMocks({
const { req, res }: MockHttpRequest = createMocks({
method: 'PUT',
body: CustomRealmProfiles[0],
});
Expand All @@ -105,7 +116,7 @@ describe('Profile Validations', () => {
status: 'authenticated',
};
});
const { req, res } = createMocks({
const { req, res }: MockHttpRequest = createMocks({
method: 'PUT',
body: CustomRealmProfiles[0],
});
Expand All @@ -132,7 +143,7 @@ describe('Profile Validations', () => {
status: 'authenticated',
};
});
const { req, res } = createMocks({
const { req, res }: MockHttpRequest = createMocks({
method: 'PUT',
body: CustomRealmProfiles[0],
});
Expand All @@ -148,11 +159,40 @@ describe('Profile Validations', () => {
(prisma.roster.findUnique as jest.Mock).mockImplementation(() => {
return Promise.resolve({ ...CustomRealmProfiles[0], approved: false });
});
const { req, res } = createMocks({
const { req, res }: MockHttpRequest = createMocks({
method: 'PUT',
query: { id: 1 },
});
await handler(req, res);
expect(res.statusCode).toBe(400);
});

it('calls kc admin api to create realm in all environments', async () => {
(prisma.roster.findUnique as jest.Mock).mockImplementation(() => {
return Promise.resolve(CustomRealmProfiles[0]);
});

(prisma.roster.update as jest.Mock).mockImplementation(() => {
return Promise.resolve({ ...CustomRealmProfiles[0], approved: true });
});
(getServerSession as jest.Mock).mockImplementation(() => {
return {
expires: new Date(Date.now() + 2 * 86400).toISOString(),
user: {
username: 'test',
client_roles: ['sso-admin'],
},
status: 'authenticated',
};
});
const { req, res }: MockHttpRequest = createMocks({
method: 'PUT',
body: { ...CustomRealmProfiles[0], approved: true },
query: { id: 1 },
});
await handler(req, res);

expect(res.statusCode).toBe(200);
expect(createCustomRealm).toHaveBeenCalledTimes(3);
});
});
12 changes: 7 additions & 5 deletions app/__tests__/custom-realm-dashboard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ 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 { getRealmProfiles, 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 { debug } from 'jest-preview';

jest.mock('services/realm', () => {
return {
Expand Down Expand Up @@ -221,7 +222,7 @@ describe('Status update', () => {
await waitFor(() => screen.getByText('Approve Custom Realm', { selector: 'button' }).click());
await waitFor(() => screen.getByText('Are you sure you want to approve request 1?'));
screen.getByText('Confirm', { selector: 'button' }).click();
await waitFor(() => within(firstRow).getByText('Approved'));
await waitFor(() => screen.getByText('realm 1'));
});

it('Updates status in table only when successfully declined', async () => {
Expand Down Expand Up @@ -251,15 +252,16 @@ describe('Status update', () => {
expect(screen.queryByTestId('grid-svg')).not.toBeInTheDocument();
});

firstRow = tbody.querySelector('tr') as HTMLTableRowElement;
within(firstRow).queryByText('Pending');
firstRow = tbody.querySelectorAll('tr') as any;

within(firstRow[2]).queryByText('Pending');

// Successful request
(updateRealmProfile as jest.MockedFunction<any>).mockImplementationOnce(() => Promise.resolve([true, null]));
await waitFor(() => screen.getByText('Decline Custom Realm', { selector: 'button' }).click());
await waitFor(() => screen.getByText('Are you sure you want to decline request 1?'));
screen.getByText('Confirm', { selector: 'button' }).click();
await waitFor(() => within(firstRow).queryByText('Declined'));
await waitFor(() => within(firstRow[2]).queryByText('Declined'));
});
});

Expand Down
6 changes: 6 additions & 0 deletions app/__tests__/fixtures.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { NextApiRequest, NextApiResponse } from 'next/types';
import { CustomRealmFormData, RealmProfile } from 'types/realm-profile';

export const CustomRealms: CustomRealmFormData[] = [
Expand Down Expand Up @@ -86,3 +87,8 @@ export const CustomRealmProfiles: RealmProfile[] = CustomRealms.map((realm) => (
updatedAt: '',
status: 'pending',
}));

export interface MockHttpRequest {
req: NextApiRequest;
res: NextApiResponse;
}
Loading

0 comments on commit dd339c2

Please sign in to comment.