-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* build(devdeps): add lodash types * fix(model): rectify db model definitions * refactor: add message param to ForbiddenError * feat: add CollaboratorsService * test: add tests for CollaboratorsService * feat: use CollaboratorService in authorization middleware * test: add tests for authorization middleware * feat: add CollaboratorsRouter * test: add tests for CollaboratorsRouter * feat(db-migration): change site_members role enum in the database * feat: modify authzMiddlewareService tests * fix: error in mock collaborators fixture
- Loading branch information
1 parent
04dbb39
commit f46d58a
Showing
22 changed files
with
1,502 additions
and
52 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
59 changes: 59 additions & 0 deletions
59
src/database/migrations/20220811070630-change-role-enum.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
module.exports = { | ||
async up(queryInterface, Sequelize) { | ||
// Change the role enum values in the site_members table | ||
await queryInterface.sequelize.transaction(async (transaction) => { | ||
// 1. Change column type to TEXT | ||
await queryInterface.changeColumn( | ||
"site_members", // name of Source model | ||
"role", // name of column we're modifying | ||
{ | ||
type: Sequelize.TEXT, | ||
}, | ||
{ transaction } | ||
) | ||
// 2. Discard enum type | ||
await queryInterface.sequelize.query( | ||
"drop type enum_site_members_role;", | ||
{ transaction } | ||
) | ||
// 3. Change column type to new enum type (fails if inconsistent with existing data) | ||
await queryInterface.changeColumn( | ||
"site_members", // name of Source model | ||
"role", // name of column we're modifying | ||
{ | ||
type: Sequelize.ENUM("ADMIN", "CONTRIBUTOR"), | ||
}, | ||
{ transaction } | ||
) | ||
}) | ||
}, | ||
|
||
async down(queryInterface, Sequelize) { | ||
// Change the role enum values in the site_members table | ||
await queryInterface.sequelize.transaction(async (transaction) => { | ||
// 1. Change column type to TEXT | ||
await queryInterface.changeColumn( | ||
"site_members", // name of Source model | ||
"role", // name of column we're modifying | ||
{ | ||
type: Sequelize.TEXT, | ||
}, | ||
{ transaction } | ||
) | ||
// 2. Discard enum type | ||
await queryInterface.sequelize.query( | ||
"drop type enum_site_members_role;", | ||
{ transaction } | ||
) | ||
// 3. Change column type to new enum type (fails if inconsistent with existing data) | ||
await queryInterface.changeColumn( | ||
"site_members", // name of Source model | ||
"role", // name of column we're modifying | ||
{ | ||
type: Sequelize.ENUM("ADMIN", "USER"), | ||
}, | ||
{ transaction } | ||
) | ||
}) | ||
}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,7 @@ | ||
import { Attributes } from "sequelize/types" | ||
|
||
import { User, SiteMember } from "@database/models" | ||
|
||
export const mockRecipient = "[email protected]" | ||
export const mockSubject = "mock subject" | ||
export const mockBody = "somebody" | ||
|
@@ -18,3 +22,119 @@ export const mockBearerTokenHeaders = { | |
Authorization: `Bearer ${process.env.POSTMAN_API_KEY}`, | ||
}, | ||
} | ||
|
||
const mockCollaboratorContributor1: Attributes<User> & { | ||
SiteMember: Attributes<SiteMember> | ||
} = { | ||
id: 1, | ||
email: "[email protected]", | ||
githubId: "test1", | ||
contactNumber: "12331231", | ||
lastLoggedIn: new Date("2022-07-30T07:41:09.661Z"), | ||
createdAt: new Date("2022-04-04T07:25:41.013Z"), | ||
updatedAt: new Date("2022-07-30T07:41:09.662Z"), | ||
deletedAt: undefined, | ||
SiteMember: { | ||
userId: 1, | ||
siteId: "16", | ||
role: "CONTRIBUTOR", | ||
createdAt: new Date("2022-07-29T03:50:49.145Z"), | ||
updatedAt: new Date("2022-07-29T03:50:49.145Z"), | ||
}, | ||
sites: [], | ||
} | ||
|
||
const mockCollaboratorAdmin1: Attributes<User> & { | ||
SiteMember: Attributes<SiteMember> | ||
} = { | ||
id: 2, | ||
email: "[email protected]", | ||
githubId: "test2", | ||
contactNumber: "12331232", | ||
lastLoggedIn: new Date("2022-07-30T07:41:09.661Z"), | ||
createdAt: new Date("2022-04-04T07:25:41.013Z"), | ||
updatedAt: new Date("2022-07-30T07:41:09.662Z"), | ||
deletedAt: undefined, | ||
SiteMember: { | ||
userId: 2, | ||
siteId: "16", | ||
role: "ADMIN", | ||
createdAt: new Date("2022-07-29T03:50:49.145Z"), | ||
updatedAt: new Date("2022-07-29T03:50:49.145Z"), | ||
}, | ||
sites: [], | ||
} | ||
const mockCollaboratorAdmin2: Attributes<User> & { | ||
SiteMember: Attributes<SiteMember> | ||
} = { | ||
id: 3, | ||
email: "[email protected]", | ||
githubId: "test3", | ||
contactNumber: "12331233", | ||
lastLoggedIn: new Date("2022-06-30T07:41:09.661Z"), | ||
createdAt: new Date("2022-04-04T07:25:41.013Z"), | ||
updatedAt: new Date("2022-07-30T07:41:09.662Z"), | ||
deletedAt: undefined, | ||
SiteMember: { | ||
userId: 3, | ||
siteId: "16", | ||
role: "ADMIN", | ||
createdAt: new Date("2022-07-29T03:50:49.145Z"), | ||
updatedAt: new Date("2022-07-29T03:50:49.145Z"), | ||
}, | ||
sites: [], | ||
} | ||
const mockCollaboratorContributor2: Attributes<User> & { | ||
SiteMember: Attributes<SiteMember> | ||
} = { | ||
id: 4, | ||
email: "[email protected]", | ||
githubId: "test4", | ||
contactNumber: "12331234", | ||
lastLoggedIn: new Date("2022-07-30T07:41:09.661Z"), | ||
createdAt: new Date("2022-04-04T07:25:41.013Z"), | ||
updatedAt: new Date("2022-07-30T07:41:09.662Z"), | ||
deletedAt: undefined, | ||
SiteMember: { | ||
userId: 4, | ||
siteId: "16", | ||
role: "CONTRIBUTOR", | ||
createdAt: new Date("2022-07-29T03:50:49.145Z"), | ||
updatedAt: new Date("2022-07-29T03:50:49.145Z"), | ||
}, | ||
sites: [], | ||
} | ||
|
||
export const unsortedMockCollaboratorsList = [ | ||
mockCollaboratorContributor1, | ||
mockCollaboratorAdmin1, | ||
mockCollaboratorAdmin2, | ||
mockCollaboratorContributor2, | ||
] | ||
|
||
export const expectedSortedMockCollaboratorsList = [ | ||
mockCollaboratorAdmin2, | ||
mockCollaboratorAdmin1, | ||
mockCollaboratorContributor1, | ||
mockCollaboratorContributor2, | ||
] | ||
|
||
export const mockSiteOrmResponseWithAllCollaborators = { | ||
id: 1, | ||
name: "", | ||
site_members: unsortedMockCollaboratorsList, | ||
} | ||
export const mockSiteOrmResponseWithOneAdminCollaborator = { | ||
id: 1, | ||
name: "", | ||
site_members: [mockCollaboratorAdmin1], | ||
} | ||
export const mockSiteOrmResponseWithOneContributorCollaborator = { | ||
id: 1, | ||
name: "", | ||
site_members: [mockCollaboratorContributor2], | ||
} | ||
export const mockSiteOrmResponseWithNoCollaborators = { | ||
id: 1, | ||
site_members: "", | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
import { NextFunction, Request, Response } from "express" | ||
|
||
import { ForbiddenError } from "@errors/ForbiddenError" | ||
|
||
import { AuthorizationMiddleware } from "@middleware/authorization" | ||
|
||
import UserWithSiteSessionData from "@root/classes/UserWithSiteSessionData" | ||
import AuthorizationMiddlewareService from "@root/services/middlewareServices/AuthorizationMiddlewareService" | ||
|
||
describe("Authorization middleware", () => { | ||
const TEST_SITE_NAME = "sitename" | ||
const TEST_ISOMER_USER_ID = "1" | ||
const mockAuthorizationMiddlewareService = { | ||
checkIsSiteAdmin: jest.fn(), | ||
checkIsSiteMember: jest.fn(), | ||
} | ||
const mockReq = ({ | ||
params: { siteName: TEST_SITE_NAME }, | ||
} as unknown) as Request< | ||
never, | ||
unknown, | ||
unknown, | ||
never, | ||
{ userWithSiteSessionData: UserWithSiteSessionData } | ||
> | ||
const mockRes = ({ | ||
locals: { | ||
sessionData: { getIsomerUserId: jest.fn(() => TEST_ISOMER_USER_ID) }, | ||
}, | ||
} as unknown) as Response< | ||
unknown, | ||
{ userWithSiteSessionData: UserWithSiteSessionData } | ||
> | ||
const mockNext = jest.fn() as NextFunction | ||
|
||
const authorizationMiddleware = new AuthorizationMiddleware({ | ||
authorizationMiddlewareService: (mockAuthorizationMiddlewareService as unknown) as AuthorizationMiddlewareService, | ||
}) | ||
|
||
beforeEach(() => { | ||
jest.clearAllMocks() | ||
}) | ||
|
||
describe("verifySiteAdmin", () => { | ||
it("correctly verifies that user is a site admin if no error is thrown in the authorization middleware service", async () => { | ||
// Arrange | ||
mockAuthorizationMiddlewareService.checkIsSiteAdmin.mockResolvedValue( | ||
undefined | ||
) | ||
|
||
// Act | ||
await authorizationMiddleware.verifySiteAdmin(mockReq, mockRes, mockNext) | ||
|
||
// Assert | ||
expect( | ||
mockAuthorizationMiddlewareService.checkIsSiteAdmin | ||
).toHaveBeenCalled() | ||
expect(mockNext).toHaveBeenCalledWith() | ||
}) | ||
|
||
it("correctly verifies that user is not site admin if an error is thrown in the authorization middleware service", async () => { | ||
// Arrange | ||
mockAuthorizationMiddlewareService.checkIsSiteAdmin.mockResolvedValue( | ||
new ForbiddenError() | ||
) | ||
|
||
// Act | ||
await authorizationMiddleware.verifySiteAdmin(mockReq, mockRes, mockNext) | ||
|
||
// Assert | ||
expect( | ||
mockAuthorizationMiddlewareService.checkIsSiteAdmin | ||
).toHaveBeenCalled() | ||
expect(mockNext).toHaveBeenCalledWith(new ForbiddenError()) | ||
}) | ||
}) | ||
|
||
describe("verifySiteMember", () => { | ||
it("correctly verifies that user is a site member if no error is thrown in the authorization middleware service", async () => { | ||
// Arrange | ||
mockAuthorizationMiddlewareService.checkIsSiteMember.mockResolvedValue( | ||
undefined | ||
) | ||
|
||
// Act | ||
await authorizationMiddleware.verifySiteMember(mockReq, mockRes, mockNext) | ||
|
||
// Assert | ||
expect( | ||
mockAuthorizationMiddlewareService.checkIsSiteMember | ||
).toHaveBeenCalled() | ||
expect(mockNext).toHaveBeenCalledWith() | ||
}) | ||
|
||
it("correctly verifies that user is not site member if an error is thrown in the authorization middleware service", async () => { | ||
// Arrange | ||
mockAuthorizationMiddlewareService.checkIsSiteMember.mockResolvedValue( | ||
new ForbiddenError() | ||
) | ||
|
||
// Act | ||
await authorizationMiddleware.verifySiteMember(mockReq, mockRes, mockNext) | ||
|
||
// Assert | ||
expect( | ||
mockAuthorizationMiddlewareService.checkIsSiteMember | ||
).toHaveBeenCalled() | ||
expect(mockNext).toHaveBeenCalledWith(new ForbiddenError()) | ||
}) | ||
}) | ||
}) |
Oops, something went wrong.