Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: release #188

Merged
merged 23 commits into from
Oct 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
896822f
chore: reduce sandbox resources (#168)
thegentlemanphysicist Aug 13, 2024
03057f0
chore: add alt text to realm reg home page image (#169)
thegentlemanphysicist Aug 14, 2024
de2e226
chore: remove beta tag from realm reg (#171)
thegentlemanphysicist Aug 19, 2024
b50b382
feat: use common table component from sso components (#172)
NithinKuruba Aug 20, 2024
069f9f9
feat: add pagination for tables
NithinKuruba Aug 20, 2024
9fb231b
chore: ui enhancements (#170)
jlangy Aug 20, 2024
68b1a26
feat: add sso team email for all update notifications (#173)
NithinKuruba Aug 20, 2024
655ab90
fix: typescript (#174)
jlangy Aug 20, 2024
9e002cb
fix: admin edit rights (#175)
jlangy Aug 20, 2024
23d7597
chore: change form text (#177)
thegentlemanphysicist Aug 21, 2024
ad6f829
feat: disable edit button if rejected (#176)
NithinKuruba Aug 21, 2024
ac7c844
fix: type errors
NithinKuruba Aug 21, 2024
dc319f3
feat: enable loading grid for custom realm dashboard
NithinKuruba Aug 21, 2024
5813bcc
feat: restore realm (#178)
jlangy Aug 22, 2024
32144ef
chore: typography styles (#179)
jlangy Aug 26, 2024
d3471a6
chore: fix text colours (#180)
thegentlemanphysicist Aug 26, 2024
755cc4e
fix: typo (#181)
jlangy Aug 26, 2024
dd339c2
feat: upgrade next to 14 and remove tf dependency (#183)
NithinKuruba Aug 27, 2024
b345527
fix: build issues with nextjs 14 (#184)
NithinKuruba Aug 28, 2024
4866c04
fix: refactor restore func to use kc admin api (#185)
NithinKuruba Aug 29, 2024
c6dc14b
fix: restore users during realm restoration (#186)
NithinKuruba Aug 30, 2024
21ac205
chore: remove metabase references (#187)
thegentlemanphysicist Sep 3, 2024
1b4c5c7
feat: text improvements and url cleanup (#189)
NithinKuruba Oct 24, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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
198 changes: 64 additions & 134 deletions app/__tests__/api/delete-realm.test.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,29 @@
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 { getServerSession } from 'next-auth';
import { createCustomRealmPullRequest, mergePullRequest } from 'utils/github';
import { EventEnum, StatusEnum } from 'validators/create-realm';
import { removeUserAsRealmAdmin } from 'controllers/keycloak';
import { sendDeletionCompleteEmail } from 'utils/mailer';

jest.mock('../../controllers/keycloak', () => {
import { CustomRealmProfiles, MockHttpRequest } from '../fixtures';
import { manageCustomRealm, removeUserAsRealmAdmin } from 'controllers/keycloak';
import { ssoTeamEmail } from 'utils/mailer';
import { createMockSendEmail, mockAdminSession, mockSession } from './utils/mocks';
import { createEvent } from 'utils/helpers';
import { EventEnum } from 'validators/create-realm';

jest.mock('../../utils/helpers', () => {
return {
removeUserAsRealmAdmin: jest.fn(),
...jest.requireActual('../../utils/helpers'),
createEvent: jest.fn(),
};
});

jest.mock('../../utils/mailer', () => {
return {
sendUpdateEmail: jest.fn(),
sendDeleteEmail: jest.fn(),
sendDeletionCompleteEmail: jest.fn(() => Promise.resolve(true)),
};
});
jest.mock('utils/ches');

jest.mock('../../utils/github', () => {
jest.mock('../../controllers/keycloak', () => {
return {
createCustomRealmPullRequest: jest.fn(() => Promise.resolve({ data: { number: 1 } })),
mergePullRequest: jest.fn(() => Promise.resolve({ data: { merged: true } })),
deleteBranch: jest.fn(),
removeUserAsRealmAdmin: jest.fn(() => true),
createCustomRealm: jest.fn(() => true),
disableCustomRealm: jest.fn(() => true),
deleteCustomRealm: jest.fn(() => true),
manageCustomRealm: jest.fn(() => true),
};
});

Expand All @@ -37,16 +33,6 @@ jest.mock('next/config', () => () => ({
},
}));

const mockSession = {
expires: new Date(Date.now() + 2 * 86400).toISOString(),
user: {
username: 'test',
family_name: 'test',
idir_username: 'test',
},
status: 'authenticated',
};

jest.mock('next-auth/next', () => {
return {
__esModule: true,
Expand All @@ -61,19 +47,7 @@ jest.mock('../../pages/api/auth/[...nextauth]', () => {
};
});

const mockAdminSession = () => {
(getServerSession as jest.Mock).mockImplementation(() => {
return {
...mockSession,
user: {
...mockSession.user,
client_roles: ['sso-admin'],
},
};
});
};

describe('Realm Delete Request', () => {
describe('Delete Realm Permissions', () => {
beforeEach(() => {
jest.clearAllMocks();
// Mock prisma find/update functions
Expand All @@ -89,123 +63,79 @@ 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(manageCustomRealm).toHaveBeenCalledTimes(1);
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.archived).toBe(true);
expect(rosterUpdateArgs.data.status).toBe('applied');
});
});

describe('Github Actions Delete', () => {
const mockToken = 'secret';
describe('Delete Realms', () => {
beforeEach(() => {
jest.clearAllMocks();
(prisma.roster.findUnique as jest.Mock).mockImplementation(() => {
return Promise.resolve({ ...CustomRealmProfiles[0], id: 1, archived: true });
});
});
const requestData = {
method: 'PUT' as 'PUT',
body: {
ids: [1],
action: 'tf_apply',
success: 'true',
},
headers: {
Authorization: mockToken,
},
};

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

// Remove auth header
({ req, res } = createMocks({ ...requestData, headers: { Authorization: 'empty' } }));
await githubResponseHandler(req, res);
expect(res.statusCode).toBe(401);
});
it('successfully deletes realm in all environments and sends email', async () => {
mockAdminSession();
const { req, res }: MockHttpRequest = createMocks({
method: 'DELETE',
query: { id: 1 },
});

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

// Email only sent once
expect(sendDeletionCompleteEmail).toHaveBeenCalledTimes(1);
await deleteHandler(req, res);

expect(res.statusCode).toBe(200);
expect(manageCustomRealm).toHaveBeenCalledTimes(1);
// PO email and technical contact email removed in each realm
['dev', 'test', 'prod'].forEach((env) => {
expect(removeUserAsRealmAdmin).toHaveBeenCalledWith(['[email protected]', '[email protected]'], env, 'realm 1');
CustomRealmProfiles[0].environments.forEach((env) => {
expect(removeUserAsRealmAdmin).toHaveBeenCalledWith(
[CustomRealmProfiles[0].productOwnerEmail, CustomRealmProfiles[0].technicalContactEmail],
env,
CustomRealmProfiles[0].realm,
);
});
// No extra calls
expect(removeUserAsRealmAdmin).toHaveBeenCalledTimes(3);
expect(emailList.length).toBe(1);
expect(emailList[0].subject).toContain(
`Notification: Custom Realm ${CustomRealmProfiles[0].realm} has now been Deleted.`,
);
expect(emailList[0].to).toEqual(
expect.arrayContaining([CustomRealmProfiles[0].productOwnerEmail, CustomRealmProfiles[0].technicalContactEmail]),
);
expect(emailList[0].cc).toEqual(expect.arrayContaining([ssoTeamEmail]));
});

it('Only sends deletion complete email if all users removed successfully', async () => {
const { req, res } = createMocks(requestData);
(removeUserAsRealmAdmin as jest.Mock).mockImplementationOnce(() => Promise.reject(new Error('failure')));
await githubResponseHandler(req, res);
it('does not send email if deleting realm in all environments fails', async () => {
mockAdminSession();

(manageCustomRealm as jest.Mock).mockImplementationOnce(() => Promise.reject('some error'));

expect(res.statusCode).toBe(500);
expect(sendDeletionCompleteEmail).not.toHaveBeenCalled();
const { req, res }: MockHttpRequest = createMocks({
method: 'DELETE',
query: { id: 1 },
});

const emailList = createMockSendEmail();

await deleteHandler(req, res);
const createEventArgs = (createEvent as jest.Mock).mock.calls[0][0];
expect(createEventArgs.eventCode).toBe(EventEnum.REQUEST_DELETE_FAILED);
expect(res.statusCode).toBe(422);
expect(manageCustomRealm).toHaveBeenCalledTimes(1);
expect(emailList.length).toBe(0);
});
});
Loading