diff --git a/src/config/index.ts b/src/config/index.ts index 06c1248..82606ec 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -23,6 +23,7 @@ export const ADD_TEAM = 'add-team'; export const TEAM_REQUEST = 'team-request'; export const ADD_TEAM_MEMBER = 'add-team-member'; export const ADD_REPO = 'add-repo'; +export const REPO_REQUEST = 'repo-request'; // Routing paths @@ -36,6 +37,7 @@ export const ADD_TEAM_URL = '/add-team'; export const TEAM_REQUEST_URL = '/team-request'; export const ADD_TEAM_MEMBER_URL = '/add-team-member'; export const ADD_REPO_URL = '/add-repo'; +export const REPO_REQUEST_URL = '/repo-request'; export const SERVICE_URL = `${BASE_URL}${CONFIRMATION_URL}`; diff --git a/src/controller/repo-request.controller.ts b/src/controller/repo-request.controller.ts new file mode 100644 index 0000000..9703880 --- /dev/null +++ b/src/controller/repo-request.controller.ts @@ -0,0 +1,18 @@ +import { Request, Response } from 'express'; +import { log } from '../utils/logger'; +import * as config from '../config'; + +export const get = (_req: Request, res: Response) => { + return res.render(config.REPO_REQUEST); +}; + +export const post = (req: Request, res: Response) => { + + const repoName = req.body.repo_name; + + // validation middleware and data assignment to be implemented + + log.info(`Repository Name: ${repoName}`); + + return res.redirect(config.LANDING); +}; diff --git a/src/routes/index.ts b/src/routes/index.ts index 8cccbff..6117779 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -12,6 +12,7 @@ import removeMemberRouter from './remove-member'; import teamRequestRouter from './team-request'; import addTeamMemberRouter from './add-team-member'; import memberRequestRouter from './member-request'; +import repoRequestRouter from './repo-request'; const router = Router(); @@ -29,5 +30,6 @@ router.use(removeMemberRouter); router.use(teamRequestRouter); router.use(addTeamMemberRouter); router.use(memberRequestRouter); +router.use(repoRequestRouter); export default router; diff --git a/src/routes/repo-request.ts b/src/routes/repo-request.ts new file mode 100644 index 0000000..22ad656 --- /dev/null +++ b/src/routes/repo-request.ts @@ -0,0 +1,13 @@ +import { Router } from 'express'; + +import { authentication } from '../middleware/authentication.middleware'; + +import { get, post } from '../controller/repo-request.controller'; +import * as config from '../config'; + +const addRepoRouter = Router(); + +addRepoRouter.get(config.REPO_REQUEST_URL, authentication, get); +addRepoRouter.post(config.REPO_REQUEST_URL, authentication, post); + +export default addRepoRouter; diff --git a/src/views/landing-page.html b/src/views/landing-page.html index dc9aed8..595e0f7 100644 --- a/src/views/landing-page.html +++ b/src/views/landing-page.html @@ -45,7 +45,7 @@

Repo requests

Add Repo
  • - Additional Repo Requests (e.g permission changes) + Additional Repo Requests (e.g permission changes)
  • diff --git a/src/views/repo-request.html b/src/views/repo-request.html new file mode 100644 index 0000000..1b3e183 --- /dev/null +++ b/src/views/repo-request.html @@ -0,0 +1,43 @@ +{% extends "layout.html" %} + +{% block beforeContent %} + {% include "include/back-link.html" %} +{% endblock %} + +{% block content %} +
    +
    +

    Additional Repository Requests

    + +

    + To request a more specific repository request that is not covered by the other options, such as permission changes. +

    + + {{ govukInput({ + label: { + text: "Repository name", + classes: "govuk-label--m" + }, + classes: "govuk-input--width-10", + id: "repo-name", + name: "repo_name" + }) }} + {{ govukTextarea({ + name: "description", + id: "description", + label: { + text: "Description - (e.g. Permission changes) ", + classes: "govuk-label--m", + isPageHeading: true + }, + hint: { + text: "Include a description of the request you would like to have completed." + } + }) }} + + {% include "include/save-button.html" %} + +
    +
    +
    +{% endblock %} \ No newline at end of file diff --git a/test/integration/routes/repo-request.spec.ts b/test/integration/routes/repo-request.spec.ts new file mode 100644 index 0000000..67d38ec --- /dev/null +++ b/test/integration/routes/repo-request.spec.ts @@ -0,0 +1,58 @@ +jest.mock('../../../src/middleware/logger.middleware'); +jest.mock('../../../src/middleware/authentication.middleware'); +jest.mock('../../../src/utils/logger'); + +import { jest, beforeEach, describe, expect, test } from '@jest/globals'; +import { Request, Response, NextFunction } from 'express'; +import request from 'supertest'; + +import app from '../../../src/app'; +import * as config from '../../../src/config'; +import { logger } from '../../../src/middleware/logger.middleware'; +import { log } from '../../../src/utils/logger'; +import { authentication } from '../../../src/middleware/authentication.middleware'; + +import { MOCK_REDIRECT_MESSAGE, MOCK_GET_REPO_REQUEST_RESPONSE, MOCK_POST_REPO_REQUEST_RESPONSE } from '../../mock/text.mock'; +import { MOCK_POST_REPO_REQUEST } from '../../mock/data'; + +const mockedLogger = logger as jest.Mock; +mockedLogger.mockImplementation((_req: Request, _res: Response, next: NextFunction) => next()); +const mockedAuth = authentication as jest.Mock; +mockedAuth.mockImplementation((_req: Request, _res: Response, next: NextFunction) => next()); + +describe('repo-request endpoint integration tests', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('GET tests', () => { + test('renders the repo-request page', async () => { + const res = await request(app).get(config.REPO_REQUEST_URL); + + expect(res.status).toEqual(200); + expect(res.text).toContain(MOCK_GET_REPO_REQUEST_RESPONSE); + expect(mockedLogger).toHaveBeenCalledTimes(1); + expect(mockedAuth).toHaveBeenCalledTimes(1); + }); + }); + describe('POST tests', () => { + test('Should redirect to landing page after POST request', async () => { + const res = await request(app).post(config.REPO_REQUEST_URL).send(MOCK_POST_REPO_REQUEST); + + expect(res.status).toEqual(302); + expect(res.text).toContain(MOCK_REDIRECT_MESSAGE); + expect(mockedLogger).toHaveBeenCalledTimes(1); + expect(mockedAuth).toHaveBeenCalledTimes(1); + }); + test('Should log the repository name and description on POST request', async () => { + const res = await request(app).post(config.REPO_REQUEST_URL).send(MOCK_POST_REPO_REQUEST); + + const mockLog = log.info as jest.Mock; + + expect(mockLog).toBeCalledWith(MOCK_POST_REPO_REQUEST_RESPONSE); + expect(res.text).toContain(MOCK_REDIRECT_MESSAGE); + expect(mockedLogger).toHaveBeenCalledTimes(1); + expect(mockedAuth).toHaveBeenCalledTimes(1); + }); + }); +}); diff --git a/test/mock/data.ts b/test/mock/data.ts index 383d015..6b279fd 100644 --- a/test/mock/data.ts +++ b/test/mock/data.ts @@ -10,6 +10,7 @@ export const MOCK_POST_TEAM_REQUEST = { team_name: 'team1' }; export const MOCK_POST_MEMBER_REQUEST = { github_handle: 'example' }; export const MOCK_POST_ADD_TEAM_MEMBER = { team_name: 'team1', team_member_github_handle: 'joe' }; +export const MOCK_POST_REPO_REQUEST = { repo_name: 'repo1' }; export const MOCK_CORS_VALUE = { origin: [config.CDN_HOST, config.BASE_URL], diff --git a/test/mock/text.mock.ts b/test/mock/text.mock.ts index 43f0d9e..dd2b313 100644 --- a/test/mock/text.mock.ts +++ b/test/mock/text.mock.ts @@ -23,6 +23,9 @@ export const MOCK_POST_MEMBER_REQUEST_RESPONSE = 'GitHub Handle: example'; export const MOCK_GET_ADD_TEAM_MEMBER_RESPONSE = 'Add a GitHub member to a team'; export const MOCK_POST_ADD_TEAM_MEMBER_RESPONSE = 'Team Name: team1, Team Member GitHub Handle: joe'; +export const MOCK_GET_REPO_REQUEST_RESPONSE = 'Additional Repository Requests'; +export const MOCK_POST_REPO_REQUEST_RESPONSE = 'Repository Name: repo1'; + export const MOCK_NOT_FOUND_RESPONSE = 'Page not found'; export const MOCK_SERVICE_UNAVAILABLE = 'Sorry, there is a problem with the service'; export const MOCK_SERVER_ERROR = 'Pipe 3000 requires elevated privileges'; diff --git a/test/unit/controller/repo-request.controller.spec.ts b/test/unit/controller/repo-request.controller.spec.ts new file mode 100644 index 0000000..b39320d --- /dev/null +++ b/test/unit/controller/repo-request.controller.spec.ts @@ -0,0 +1,58 @@ +jest.mock('../../../src/utils/logger'); +import { describe, expect, afterEach, test, jest } from '@jest/globals'; +import { Request, Response } from 'express'; + +import { get, post } from '../../../src/controller/repo-request.controller'; +import * as config from '../../../src/config'; +import { log } from '../../../src/utils/logger'; + +import { MOCK_POST_REPO_REQUEST } from '../../mock/data'; +import { MOCK_POST_REPO_REQUEST_RESPONSE } from '../../mock/text.mock'; + +const req = { + body: MOCK_POST_REPO_REQUEST +} as Request; + +const mockResponse = () => { + const res = {} as Response; + res.render = jest.fn().mockReturnValue(res) as any; + res.redirect = jest.fn().mockReturnValue(res) as any; + return res; +}; + +describe('repo-request controller test suites', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + + describe('repo-request GET tests', () => { + + test('should render repo request page', () => { + const res = mockResponse(); + + get(req, res); + + expect(res.render).toHaveBeenCalledWith(config.REPO_REQUEST); + }); + }); + + describe('repo-request POST tests', () => { + + test('should redirect to landing-page on POST request', () => { + const res = mockResponse(); + + post(req, res); + + expect(res.redirect).toBeCalledWith(config.LANDING); + }); + test('should log Repository Name on POST request', () => { + const res = mockResponse(); + + const mockLogInfo = log.info as jest.Mock; + + post(req, res); + + expect(mockLogInfo).toHaveBeenCalledWith(MOCK_POST_REPO_REQUEST_RESPONSE); + }); + }); +});