Ref(backend): OccupationGroup & SkillGroup module index.test.ts refactor and coverage tests for other uncovered logics#497
Merged
irumvanselme merged 2 commits intoMay 21, 2026
Conversation
…age tests for other uncovered logics
There was a problem hiding this comment.
Pull request overview
Refactors the occupationGroup module test suite. The previously monolithic backend/src/esco/occupationGroup/index.test.ts (~2042 lines covering POST, GET list, GET by ID, GET parent, GET children behaviors via the top-level router) is replaced with a thin router-level test that mocks each sub-handler, while the actual controller behavior is split into per-endpoint test files. Additional coverage tests are also added to the per-endpoint files, and an unused helper is deleted from _shared/testDataHelper.ts.
Changes:
- Replace
occupationGroup/index.test.tswith mock-based router routing tests. - Migrate POST/GET/GET-by-id/parent/children controller behavior tests to their respective
index.test.tsfiles and add new edge-case coverage (cursor decode failure, repo failures, schema-invalid path, missing modelId, etc.). - Remove unused
getIOccupationGroupMockDataWithOccupationGroupChildrenhelper.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 16 comments.
Show a summary per file
| File | Description |
|---|---|
| backend/src/esco/occupationGroup/POST/index.ts | Removes a blank line between schema validation and modelId extraction. |
| backend/src/esco/occupationGroup/POST/index.test.ts | Adds many POST coverage tests migrated from root index.test.ts and new ones; introduces handler wrappers and a global ParseValidationError mock. |
| backend/src/esco/occupationGroup/index.test.ts | Replaces controller tests with mocked sub-handler routing tests. |
| backend/src/esco/occupationGroup/GET/index.test.ts | Adds cursor/next-cursor, model-validation, repo-failure coverage tests. |
| backend/src/esco/occupationGroup/GET/query.test.ts | Adds path-parameter parsing and cursor round-trip tests. |
| backend/src/esco/occupationGroup/[id]/GET/index.test.ts | Adds INTERNAL_SERVER_ERROR on fetch failure and BAD_REQUEST on path-validation failure tests. |
| backend/src/esco/occupationGroup/[id]/parent/GET/index.test.ts | Adds NOT_FOUND for missing model and INTERNAL_SERVER_ERROR for repo failure tests. |
| backend/src/esco/occupationGroup/[id]/children/GET/index.test.ts | Adds INTERNAL_SERVER_ERROR for repo failure test. |
| backend/src/esco/occupationGroup/_shared/testDataHelper.ts | Removes unused getIOccupationGroupMockDataWithOccupationGroupChildren helper. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| } as never); | ||
|
|
||
| const mockServiceRegistry = mockGetServiceRegistry(); | ||
| mockServiceRegistry.occupationGroup.findById = jest.fn().mockResolvedValue(Promise.reject(new Error("DB error"))); |
| mockGetOccupationGroupParentPathParameters.mockReturnValue({ modelId: "model-1", id: "group-1" } as never); | ||
|
|
||
| const mockServiceRegistry = mockGetServiceRegistry(); | ||
| mockServiceRegistry.occupationGroup.findParent = jest.fn().mockResolvedValue(Promise.reject(new Error("DB error"))); |
| const mockServiceRegistry = mockGetServiceRegistry(); | ||
| mockServiceRegistry.occupationGroup.findChildren = jest | ||
| .fn() | ||
| .mockResolvedValue(Promise.reject(new Error("DB error"))); |
| import { handler as getByIdHandler } from "./[id]/GET/index"; | ||
| import { handler as getParentHandler } from "./[id]/parent/GET/index"; | ||
| import { handler as getChildrenHandler } from "./[id]/children/GET/index"; | ||
| describe("Occupations Router", () => { |
Comment on lines
+50
to
+56
| const postOccupationGroupHandler = async (event: APIGatewayProxyEvent) => { | ||
| return new OccupationGroupCreateController().postOccupationGroup(event); | ||
| }; | ||
|
|
||
| const postOccupationGroupSchemaInvalidHandler = async (event: APIGatewayProxyEvent) => { | ||
| getMockGetSchema().mockReturnValue(jest.fn().mockReturnValue(false) as never); | ||
| return new OccupationGroupCreateController().postOccupationGroup(event); |
Comment on lines
+50
to
+56
| const postOccupationGroupHandler = async (event: APIGatewayProxyEvent) => { | ||
| return new OccupationGroupCreateController().postOccupationGroup(event); | ||
| }; | ||
|
|
||
| const postOccupationGroupSchemaInvalidHandler = async (event: APIGatewayProxyEvent) => { | ||
| getMockGetSchema().mockReturnValue(jest.fn().mockReturnValue(false) as never); | ||
| return new OccupationGroupCreateController().postOccupationGroup(event); |
Comment on lines
122
to
123
| } | ||
|
|
||
| const { modelId: resolvedModelId } = parsePath<{ modelId?: string }>(Routes.OCCUPATION_GROUPS_ROUTE, event.path); |
Comment on lines
+27
to
+56
| test("should route POST to postHandler", async () => { | ||
| const event = { httpMethod: HTTP_VERBS.POST } as APIGatewayProxyEvent; | ||
| const response = await handler(event); | ||
| expect(postHandler).toHaveBeenCalledWith(event); | ||
| expect(response.body).toBe("POST"); | ||
| }); | ||
|
|
||
| describe("GET", () => { | ||
| // GIVEN a valid GET request (method & header) | ||
| const givenModelId = getMockStringId(1); | ||
| const givenEvent = { | ||
| httpMethod: HTTP_VERBS.GET, | ||
| headers: {}, | ||
| pathParameters: { modelId: givenModelId.toString() }, | ||
| }; | ||
|
|
||
| // AND a configured base path for resource | ||
| const givenResourcesBaseUrl = "https://some/path/to/api/resources"; | ||
| jest.spyOn(config, "getResourcesBaseUrl").mockReturnValue(givenResourcesBaseUrl); | ||
|
|
||
| test("GET should return only the occupationGroups for the given modelId", async () => { | ||
| // AND GIVEN a repository that will successfully get an arbitrary number (N) of models | ||
| const givenOccupationGroups: Array<IOccupationGroup> = [ | ||
| { | ||
| ...getIOccupationGroupMockData(1, givenModelId), | ||
| UUID: "foo", | ||
| UUIDHistory: ["foo"], | ||
| importId: randomUUID(), | ||
| }, | ||
| { | ||
| ...getIOccupationGroupMockData(2, givenModelId), | ||
| UUID: "bar", | ||
| UUIDHistory: ["bar"], | ||
| importId: randomUUID(), | ||
| }, | ||
| { | ||
| ...getIOccupationGroupMockData(3, givenModelId), | ||
| UUID: "baz", | ||
| UUIDHistory: ["baz"], | ||
| importId: randomUUID(), | ||
| }, | ||
| ]; | ||
|
|
||
| const firstPageOccupationGroups = givenOccupationGroups.slice(-2); | ||
|
|
||
| const limit = 2; | ||
| const firstPageCursor = Buffer.from( | ||
| JSON.stringify({ id: givenOccupationGroups[2].id, createdAt: givenOccupationGroups[2].createdAt }) | ||
| ).toString("base64"); | ||
|
|
||
| const expectedNextCursor = null; | ||
|
|
||
| // AND the user is not model manager | ||
| checkRole.mockResolvedValueOnce(true); | ||
|
|
||
| const givenOccupationGroupServiceMock = { | ||
| create: jest.fn(), | ||
| findById: jest.fn().mockResolvedValue(null), | ||
| findParent: jest.fn().mockResolvedValue(null), | ||
| findPaginated: jest.fn().mockResolvedValue({ items: firstPageOccupationGroups, nextCursor: null }), | ||
| findChildren: jest.fn().mockResolvedValue([]), | ||
| validateModelForOccupationGroup: jest.fn().mockResolvedValue(null), | ||
| } as IOccupationGroupService; | ||
| const mockServiceRegistry = mockGetServiceRegistry(); | ||
| mockServiceRegistry.occupationGroup = givenOccupationGroupServiceMock; | ||
|
|
||
| // WHEN the occupationGroup handler is invoked with the given event and the modelId as path parameter | ||
| const actualResponse = await occupationGroupHandler({ | ||
| ...givenEvent, | ||
| queryStringParameters: { limit: limit.toString(), cursor: firstPageCursor }, | ||
| path: `/models/${givenModelId}/occupationGroups`, | ||
| } as never); | ||
|
|
||
| const expectedFirstPageOccupationGroups = { | ||
| data: firstPageOccupationGroups, | ||
| limit: limit, | ||
| nextCursor: expectedNextCursor, | ||
| }; | ||
|
|
||
| // THEN expect the handler to return the OK status | ||
| expect(actualResponse.statusCode).toEqual(StatusCodes.OK); | ||
| expect(actualResponse.headers).toMatchObject({ | ||
| "Content-Type": "application/json", | ||
| }); | ||
|
|
||
| // AND the response body contains only the first page OccupationGroups | ||
| expect(JSON.parse(actualResponse.body)).toMatchObject({ | ||
| ...expectedFirstPageOccupationGroups, | ||
| data: expect.arrayContaining( | ||
| expectedFirstPageOccupationGroups.data.map((og) => | ||
| expect.objectContaining({ | ||
| UUID: og.UUID, | ||
| UUIDHistory: og.UUIDHistory, | ||
| code: og.code, | ||
| originUri: og.originUri, | ||
| preferredLabel: og.preferredLabel, | ||
| altLabels: og.altLabels, | ||
| groupType: og.groupType, | ||
| description: og.description, | ||
| id: og.id, | ||
| modelId: og.modelId, | ||
| createdAt: og.createdAt.toISOString(), | ||
| updatedAt: og.updatedAt.toISOString(), | ||
| path: `${givenResourcesBaseUrl}/models/${og.modelId}/occupationGroups/${og.id}`, | ||
| tabiyaPath: `${givenResourcesBaseUrl}/models/${og.modelId}/occupationGroups/${og.UUID}`, | ||
| }) | ||
| ) | ||
| ), | ||
| }); | ||
| }); | ||
|
|
||
| test("GET should return nextCursor when nextCursor is present in the paginated occupation group result", async () => { | ||
| // GIVEN role check passes for anonymous access | ||
| checkRole.mockResolvedValueOnce(true); | ||
|
|
||
| const limit = 1; | ||
| const givenOccupationGroups: Array<IOccupationGroup> = [ | ||
| { | ||
| ...getIOccupationGroupMockData(1, givenModelId), | ||
| UUID: "foo", | ||
| UUIDHistory: ["foo"], | ||
| importId: randomUUID(), | ||
| }, | ||
| { | ||
| ...getIOccupationGroupMockData(2, givenModelId), | ||
| UUID: "bar", | ||
| UUIDHistory: ["bar"], | ||
| importId: randomUUID(), | ||
| }, | ||
| ]; | ||
|
|
||
| // AND a service that will successfully get the occupation groups (returns 2 items for limit 1) | ||
| const givenOccupationGroupServiceMock = { | ||
| create: jest.fn(), | ||
| findById: jest.fn().mockResolvedValue(null), | ||
| findParent: jest.fn().mockResolvedValue(null), | ||
| findPaginated: jest.fn().mockResolvedValue({ | ||
| items: [givenOccupationGroups[0]], | ||
| nextCursor: { _id: givenOccupationGroups[1].id, createdAt: givenOccupationGroups[0].createdAt }, | ||
| }), | ||
| validateModelForOccupationGroup: jest.fn().mockResolvedValue(null), | ||
| findChildren: jest.fn().mockResolvedValue([]), | ||
| } as IOccupationGroupService; | ||
| const mockServiceRegistry = mockGetServiceRegistry(); | ||
| mockServiceRegistry.occupationGroup = givenOccupationGroupServiceMock; | ||
|
|
||
| // WHEN the occupationGroup handler is invoked with the given event and limit 1 | ||
| const actualResponse = await occupationGroupHandler({ | ||
| ...givenEvent, | ||
| queryStringParameters: { limit: limit.toString() }, | ||
| path: `/models/${givenModelId}/occupationGroups`, | ||
| } as never); | ||
|
|
||
| // THEN expect the handler to return the OK status | ||
| expect(actualResponse.statusCode).toEqual(StatusCodes.OK); | ||
| expect(actualResponse.headers).toMatchObject({ | ||
| "Content-Type": "application/json", | ||
| }); | ||
|
|
||
| // verify the service was called correctly | ||
| expect(getServiceRegistry().occupationGroup.findPaginated).toHaveBeenCalledWith(givenModelId, undefined, limit); | ||
| // AND the response body contains a nextCursor (base64 encoded) | ||
| const responseBody = JSON.parse(actualResponse.body); | ||
| expect(responseBody.nextCursor).toBeDefined(); | ||
| expect(typeof responseBody.nextCursor).toBe("string"); | ||
|
|
||
| // Verify it's a valid base64 string by decoding it | ||
| const decodedCursor = Buffer.from(responseBody.nextCursor, "base64").toString("utf-8"); | ||
| expect(JSON.parse(decodedCursor)).toHaveProperty("id"); | ||
| expect(JSON.parse(decodedCursor)).toHaveProperty("createdAt"); | ||
| }); | ||
|
|
||
| test("GET should respond with the BAD_REQUEST status code if the modelId is not passed as a path parameter", async () => { | ||
| // AND GIVEN the repository fails to get the occupationGroups | ||
| const firstPageCursorObject = { | ||
| id: getMockStringId(1), | ||
| createdAt: new Date(), | ||
| }; | ||
| const firstPageCursor = Buffer.from(JSON.stringify(firstPageCursorObject)).toString("base64"); | ||
|
|
||
| const limit = 2; | ||
|
|
||
| const givenBadEvent = { | ||
| httpMethod: HTTP_VERBS.GET, | ||
| headers: {}, | ||
| queryStringParameters: {}, | ||
| }; | ||
| // AND the user is not model manager | ||
| checkRole.mockResolvedValueOnce(true); | ||
|
|
||
| // WHEN the occupationGroup handler is invoked with the given event | ||
| const actualResponse = await occupationGroupHandler({ | ||
| ...givenBadEvent, | ||
| queryStringParameters: { limit: limit.toString(), cursor: firstPageCursor }, | ||
| path: `/models//occupationGroups`, | ||
| } as never); | ||
|
|
||
| // THEN expect the handler to return the BAD_REQUEST status | ||
| expect(actualResponse.statusCode).toEqual(StatusCodes.BAD_REQUEST); | ||
| // AND the response body contains the error information | ||
| const parsedMissing = JSON.parse(actualResponse.body); | ||
| expect(parsedMissing).toMatchObject({ | ||
| errorCode: | ||
| OccupationGroupAPISpecs.GET.Enums.Response.Status500.ErrorCodes.DB_FAILED_TO_RETRIEVE_OCCUPATION_GROUPS, | ||
| message: "modelId is missing in the path", | ||
| }); | ||
| expect(typeof parsedMissing.details).toBe("string"); | ||
| }); | ||
| test("GET should respond with the BAD_REQUEST status code if the modelId is not correct model id", async () => { | ||
| // AND GIVEN the repository fails to get the occupationGroups | ||
| const firstPageCursorObject = { | ||
| id: getMockStringId(1), | ||
| createdAt: new Date(), | ||
| }; | ||
| const firstPageCursor = Buffer.from(JSON.stringify(firstPageCursorObject)).toString("base64"); | ||
|
|
||
| const limit = 2; | ||
|
|
||
| const givenBadEvent = { | ||
| httpMethod: HTTP_VERBS.GET, | ||
| headers: {}, | ||
| pathParameters: { modelId: "foo" }, | ||
| queryStringParameters: {}, | ||
| path: `/models/foo/occupationGroups`, | ||
| }; | ||
|
|
||
| // AND the user is not model manager | ||
| checkRole.mockResolvedValueOnce(true); | ||
|
|
||
| // WHEN the occupationGroup handler is invoked with the given event | ||
| const actualResponse = await occupationGroupHandler({ | ||
| ...givenBadEvent, | ||
| queryStringParameters: { limit: limit.toString(), cursor: firstPageCursor }, | ||
| } as never); | ||
|
|
||
| // THEN expect the handler to return the BAD_REQUEST status | ||
| expect(actualResponse.statusCode).toEqual(StatusCodes.BAD_REQUEST); | ||
| // AND the response body contains the error information | ||
| const parsedInvalidModel = JSON.parse(actualResponse.body); | ||
| expect(parsedInvalidModel).toMatchObject({ | ||
| errorCode: ErrorAPISpecs.Constants.ErrorCodes.INVALID_JSON_SCHEMA, | ||
| message: ErrorAPISpecs.Constants.ReasonPhrases.INVALID_JSON_SCHEMA, | ||
| }); | ||
| expect(typeof parsedInvalidModel.details).toBe("string"); | ||
| }); | ||
| test("GET should respond with the BAD_REQUEST status code if the query parameter is not valid query parameter", async () => { | ||
| // GIVEN the repository fails to get the occupationGroups | ||
| const firstPageCursorObject = { | ||
| id: getMockStringId(1), | ||
| createdAt: new Date(), | ||
| }; | ||
| const firstPageCursor = Buffer.from(JSON.stringify(firstPageCursorObject)).toString("base64"); | ||
|
|
||
| // AND the user is not model manager | ||
| checkRole.mockResolvedValueOnce(true); | ||
|
|
||
| // WHEN the occupationGroup handler is invoked with the given event | ||
| const actualResponse = await occupationGroupHandler({ | ||
| ...givenEvent, | ||
| queryStringParameters: { limit: "foo", cursor: firstPageCursor }, | ||
| path: `/models/${givenModelId}/occupationGroups`, | ||
| } as never); | ||
|
|
||
| // THEN expect the handler to return the BAD_REQUEST status | ||
| expect(actualResponse.statusCode).toEqual(StatusCodes.BAD_REQUEST); | ||
| // AND the response body contains the error information | ||
| const parsedInvalidQuery = JSON.parse(actualResponse.body); | ||
| expect(parsedInvalidQuery).toMatchObject({ | ||
| errorCode: ErrorAPISpecs.Constants.GET.ErrorCodes.INVALID_QUERY_PARAMETER, | ||
| message: ErrorAPISpecs.Constants.ReasonPhrases.INVALID_JSON_SCHEMA, | ||
| }); | ||
| expect(typeof parsedInvalidQuery.details).toBe("string"); | ||
| }); | ||
|
|
||
| test("GET should respond with the NOT_FOUND if the model does not exist", async () => { | ||
| // GIVEN a service that model does not exists | ||
| const givenOccupationGroupServiceMock = { | ||
| create: jest.fn(), | ||
| findById: jest.fn().mockResolvedValue(null), | ||
| findParent: jest.fn().mockResolvedValue(null), | ||
| findChildren: jest.fn().mockResolvedValue([]), | ||
| findPaginated: jest.fn(), | ||
| validateModelForOccupationGroup: jest | ||
| .fn() | ||
| .mockResolvedValue(ModelForOccupationGroupValidationErrorCode.MODEL_NOT_FOUND_BY_ID), | ||
| } as IOccupationGroupService; | ||
| const mockServiceRegistry = mockGetServiceRegistry(); | ||
| mockServiceRegistry.occupationGroup = givenOccupationGroupServiceMock; | ||
| checkRole.mockResolvedValueOnce(true); | ||
| // WHEN the occupationGroup handler is invoked with the given event | ||
| const actualResponse = await occupationGroupHandler({ | ||
| ...givenEvent, | ||
| path: `/models/${givenModelId}/occupationGroups`, | ||
| } as never); | ||
|
|
||
| // THEN expect the handler to return the NOT_FOUND status | ||
| expect(actualResponse.statusCode).toEqual(StatusCodes.NOT_FOUND); | ||
|
|
||
| // AND the response body contains the error information | ||
| const expectedErrorBody = { | ||
| errorCode: OccupationGroupAPISpecs.GET.Enums.Response.Status404.ErrorCodes.MODEL_NOT_FOUND, | ||
| message: "Model not found", | ||
| details: `No model found with id: ${givenModelId}`, | ||
| }; | ||
| expect(JSON.parse(actualResponse.body)).toEqual(expectedErrorBody); | ||
| }); | ||
| test("GET should respond with the INTERNAL_SERVER_ERROR if the model validation failed to fetch data from database", async () => { | ||
| // GIVEN a service that model does not exists | ||
| const givenOccupationGroupServiceMock = { | ||
| create: jest.fn(), | ||
| findById: jest.fn().mockResolvedValue(null), | ||
| findParent: jest.fn().mockResolvedValue(null), | ||
| findPaginated: jest.fn(), | ||
| findChildren: jest.fn(), | ||
| validateModelForOccupationGroup: jest | ||
| .fn() | ||
| .mockResolvedValue(ModelForOccupationGroupValidationErrorCode.FAILED_TO_FETCH_FROM_DB), | ||
| } as IOccupationGroupService; | ||
| const mockServiceRegistry = mockGetServiceRegistry(); | ||
| mockServiceRegistry.occupationGroup = givenOccupationGroupServiceMock; | ||
| checkRole.mockResolvedValueOnce(true); | ||
| // WHEN the occupationGroup handler is invoked with the given event | ||
| const actualResponse = await occupationGroupHandler({ | ||
| ...givenEvent, | ||
| path: `/models/${givenModelId}/occupationGroups`, | ||
| } as never); | ||
|
|
||
| // THEN expect the handler to return the INTERNAL_SERVER_ERROR status | ||
| expect(actualResponse.statusCode).toEqual(StatusCodes.INTERNAL_SERVER_ERROR); | ||
|
|
||
| // AND the response body contains the error information | ||
| const expectedErrorBody = { | ||
| errorCode: | ||
| OccupationGroupAPISpecs.GET.Enums.Response.Status500.ErrorCodes.DB_FAILED_TO_RETRIEVE_OCCUPATION_GROUPS, | ||
| message: "Failed to fetch the model details from the DB", | ||
| details: "", | ||
| }; | ||
| expect(JSON.parse(actualResponse.body)).toEqual(expectedErrorBody); | ||
| }); | ||
| test("GET should respond with the BAD_REQUEST if the cursor decoding failed", async () => { | ||
| // GIVEN a service that model does not exists | ||
| const givenOccupationGroupServiceMock = { | ||
| create: jest.fn(), | ||
| findById: jest.fn().mockResolvedValue(null), | ||
| findPaginated: jest.fn(), | ||
| findParent: jest.fn().mockResolvedValue(null), | ||
| validateModelForOccupationGroup: jest.fn(), | ||
| findChildren: jest.fn(), | ||
| decodeCursor: jest.fn().mockImplementation(() => { | ||
| throw new Error("Failed to decode cursor"); | ||
| }), | ||
| } as IOccupationGroupService; | ||
| const mockServiceRegistry = mockGetServiceRegistry(); | ||
| mockServiceRegistry.occupationGroup = givenOccupationGroupServiceMock; | ||
| checkRole.mockResolvedValueOnce(true); | ||
| const cursor = Buffer.from(getRandomString(10)).toString("base64"); | ||
| // WHEN the occupationGroup handler is invoked with the given event | ||
| const actualResponse = await occupationGroupHandler({ | ||
| ...givenEvent, | ||
| queryStringParameters: { cursor }, | ||
| path: `/models/${givenModelId}/occupationGroups`, | ||
| } as never); | ||
|
|
||
| // THEN expect the handler to return the BAD_REQUEST status | ||
| expect(actualResponse.statusCode).toEqual(StatusCodes.BAD_REQUEST); | ||
|
|
||
| // AND the response body contains the error information | ||
| const expectedErrorBody = { | ||
| errorCode: ErrorAPISpecs.Constants.GET.ErrorCodes.INVALID_QUERY_PARAMETER, | ||
| message: "Invalid cursor parameter", | ||
|
|
||
| details: "", | ||
| }; | ||
| expect(JSON.parse(actualResponse.body)).toEqual(expectedErrorBody); | ||
| }); | ||
|
|
||
| test("GET should respond with the INTERNAL_SERVER_ERROR status code if the repository fails to get the occupationGroups", async () => { | ||
| // AND GIVEN the repository fails to get the occupationGroups | ||
| const firstPageCursorObject = { | ||
| id: getMockStringId(1), | ||
| createdAt: new Date(), | ||
| }; | ||
| const firstPageCursor = Buffer.from(JSON.stringify(firstPageCursorObject)).toString("base64"); | ||
|
|
||
| const givenOccupationGroupRepositoryMock = { | ||
| Model: undefined as never, | ||
| hierarchyModel: undefined as never, | ||
| create: jest.fn().mockResolvedValue(null), | ||
| createMany: jest.fn().mockResolvedValue([]), | ||
| findById: jest.fn().mockResolvedValue(null), | ||
| findAll: jest.fn().mockResolvedValue(null), | ||
| findPaginated: jest.fn().mockRejectedValue(new Error("foo")), | ||
| getOccupationGroupByUUID: jest.fn().mockResolvedValue(null), | ||
| getHistory: jest.fn().mockResolvedValue([]), | ||
| findParent: jest.fn().mockResolvedValue(null), | ||
| findChildren: jest.fn().mockResolvedValue([]), | ||
| }; | ||
| jest.spyOn(getRepositoryRegistry(), "OccupationGroup", "get").mockReturnValue(givenOccupationGroupRepositoryMock); | ||
| const limit = 2; | ||
|
|
||
| // AND the user is not model manager | ||
| checkRole.mockResolvedValueOnce(true); | ||
|
|
||
| // WHEN the occupationGroup handler is invoked with the given event | ||
| const actualResponse = await occupationGroupHandler({ | ||
| ...givenEvent, | ||
| queryStringParameters: { limit: limit.toString(), cursor: firstPageCursor }, | ||
| path: `/models/${givenModelId}/occupationGroups`, | ||
| } as never); | ||
|
|
||
| // THEN expect the handler to return the INTERNAL_SERVER_ERROR status | ||
| expect(actualResponse.statusCode).toEqual(StatusCodes.INTERNAL_SERVER_ERROR); | ||
| // AND the response body contains the error information | ||
| const expectedErrorBody: ErrorAPISpecs.Types.Payload = { | ||
| errorCode: | ||
| OccupationGroupAPISpecs.GET.Enums.Response.Status500.ErrorCodes.DB_FAILED_TO_RETRIEVE_OCCUPATION_GROUPS, | ||
| message: "Failed to retrieve the occupation groups from the DB", | ||
| details: "", | ||
| }; | ||
| expect(JSON.parse(actualResponse.body)).toEqual(expectedErrorBody); | ||
| }); | ||
| testMethodsNotAllowed( | ||
| [HTTP_VERBS.PUT, HTTP_VERBS.DELETE, HTTP_VERBS.OPTIONS, HTTP_VERBS.PATCH], | ||
| occupationGroupHandler | ||
| ); | ||
| test("should route GET occupationGroups list to getHandler", async () => { | ||
| const event = { httpMethod: HTTP_VERBS.GET, path: "/models/1/occupationGroups" } as APIGatewayProxyEvent; | ||
| const response = await handler(event); | ||
| expect(getHandler).toHaveBeenCalledWith(event); | ||
| expect(response.body).toBe("GET"); | ||
| }); | ||
|
|
||
| describe("GET individual occupation group", () => { | ||
| test("GET /models/{modelId}/occupationGroups/{id} should return the occupation group for a valid ID", async () => { | ||
| // GIVEN a valid request with modelId and occupationGroup ID | ||
| const givenModelId = getMockStringId(1); | ||
| const givenOccupationGroupId = getMockStringId(2); | ||
| const givenEvent = { | ||
| httpMethod: HTTP_VERBS.GET, | ||
| headers: {}, | ||
| pathParameters: { modelId: givenModelId.toString(), id: givenOccupationGroupId.toString() }, | ||
| queryStringParameters: {}, | ||
| path: `/models/${givenModelId}/occupationGroups/${givenOccupationGroupId}`, | ||
| } as never; | ||
|
|
||
| // AND User has the required role | ||
| checkRole.mockResolvedValue(true); | ||
|
|
||
| // AND a configured base path for resource | ||
| const givenResourcesBaseUrl = "https://some/path/to/api/resources"; | ||
| jest.spyOn(config, "getResourcesBaseUrl").mockReturnValueOnce(givenResourcesBaseUrl); | ||
|
|
||
| // AND a repository that will successfully get the occupation group | ||
| const givenOccupationGroup: IOccupationGroup = { | ||
| ...getIOccupationGroupMockData(1, givenModelId), | ||
| id: givenOccupationGroupId, | ||
| UUID: "test-uuid", | ||
| UUIDHistory: ["test-uuid"], | ||
| importId: randomUUID(), | ||
| }; | ||
|
|
||
| const givenOccupationGroupServiceMock = { | ||
| create: jest.fn(), | ||
| findById: jest.fn().mockResolvedValue(givenOccupationGroup), | ||
| findPaginated: jest.fn().mockResolvedValue({ items: [], nextCursor: null }), | ||
| validateModelForOccupationGroup: jest.fn(), | ||
| findParent: jest.fn().mockResolvedValue(null), | ||
| findChildren: jest.fn().mockResolvedValue([]), | ||
| } as IOccupationGroupService; | ||
| const mockServiceRegistry = mockGetServiceRegistry(); | ||
| mockServiceRegistry.occupationGroup = givenOccupationGroupServiceMock; | ||
|
|
||
| // WHEN the occupationGroup handler is invoked with the given event | ||
| const actualResponse = await occupationGroupHandler(givenEvent); | ||
|
|
||
| // THEN respond with the OK status code | ||
| expect(actualResponse.statusCode).toEqual(StatusCodes.OK); | ||
| // AND the handler to return the correct headers | ||
| expect(actualResponse.headers).toMatchObject({ | ||
| "Content-Type": "application/json", | ||
| }); | ||
| // AND the transformation function is called correctly | ||
| expect(transformModule.transform).toHaveBeenCalledWith(givenOccupationGroup, givenResourcesBaseUrl); | ||
| // AND the handler to return the expected result | ||
| expect(JSON.parse(actualResponse.body)).toMatchObject(transformSpy.mock.results[0].value); | ||
| }); | ||
|
|
||
| test("GET /models/{modelId}/occupationGroups/{id} should respond with NOT_FOUND if model does not exist", async () => { | ||
| // GIVEN a valid request with modelId and occupationGroup ID | ||
| const givenModelId = getMockStringId(1); | ||
| const givenOccupationGroupId = getMockStringId(2); | ||
| const givenEvent = { | ||
| httpMethod: HTTP_VERBS.GET, | ||
| headers: {}, | ||
| pathParameters: { modelId: givenModelId.toString(), id: givenOccupationGroupId.toString() }, | ||
| queryStringParameters: {}, | ||
| path: `/models/${givenModelId}/occupationGroups/${givenOccupationGroupId}`, | ||
| } as never; | ||
|
|
||
| // AND User has the required role | ||
| checkRole.mockResolvedValue(true); | ||
|
|
||
| const givenOccupationGroupServiceMock = { | ||
| create: jest.fn(), | ||
| findById: jest.fn(), | ||
| findParent: jest.fn().mockResolvedValue(null), | ||
| findChildren: jest.fn().mockResolvedValue([]), | ||
| findPaginated: jest.fn().mockResolvedValue({ items: [], nextCursor: null }), | ||
| validateModelForOccupationGroup: jest | ||
| .fn() | ||
| .mockResolvedValue(ModelForOccupationGroupValidationErrorCode.MODEL_NOT_FOUND_BY_ID), | ||
| } as IOccupationGroupService; | ||
| const mockServiceRegistry = mockGetServiceRegistry(); | ||
| mockServiceRegistry.occupationGroup = givenOccupationGroupServiceMock; | ||
|
|
||
| // WHEN the occupationGroup handler is invoked with the given event | ||
| const actualResponse = await occupationGroupHandler(givenEvent); | ||
|
|
||
| // AND respond with the NOT_FOUND status | ||
| expect(actualResponse.statusCode).toEqual(StatusCodes.NOT_FOUND); | ||
| // AND the response body contains the error information | ||
| const expectedErrorBody: ErrorAPISpecs.Types.Payload = { | ||
| errorCode: OccupationGroupAPISpecs.OccupationGroup.GET.Enums.Response.Status404.ErrorCodes.MODEL_NOT_FOUND, | ||
| message: "Model not found", | ||
| details: `No model found with id: ${givenModelId}`, | ||
| }; | ||
| expect(JSON.parse(actualResponse.body)).toEqual(expectedErrorBody); | ||
| }); | ||
| test("GET /models/{modelId}/occupationGroups/{id} should respond with INTERNAL_SERVER_ERROR if model validator function failed to fetch from db", async () => { | ||
| // GIVEN a valid request with modelId and occupationGroup ID | ||
| const givenModelId = getMockStringId(1); | ||
| const givenOccupationGroupId = getMockStringId(2); | ||
| const givenEvent = { | ||
| httpMethod: HTTP_VERBS.GET, | ||
| headers: {}, | ||
| pathParameters: { modelId: givenModelId.toString(), id: givenOccupationGroupId.toString() }, | ||
| queryStringParameters: {}, | ||
| path: `/models/${givenModelId}/occupationGroups/${givenOccupationGroupId}`, | ||
| } as never; | ||
|
|
||
| // AND User has the required role | ||
| checkRole.mockResolvedValue(true); | ||
|
|
||
| const givenOccupationGroupServiceMock = { | ||
| create: jest.fn(), | ||
| findById: jest.fn(), | ||
| findParent: jest.fn().mockResolvedValue(null), | ||
| findChildren: jest.fn().mockResolvedValue([]), | ||
| findPaginated: jest.fn().mockResolvedValue({ items: [], nextCursor: null }), | ||
| validateModelForOccupationGroup: jest | ||
| .fn() | ||
| .mockResolvedValue(ModelForOccupationGroupValidationErrorCode.FAILED_TO_FETCH_FROM_DB), | ||
| } as IOccupationGroupService; | ||
| const mockServiceRegistry = mockGetServiceRegistry(); | ||
| mockServiceRegistry.occupationGroup = givenOccupationGroupServiceMock; | ||
|
|
||
| // WHEN the occupationGroup handler is invoked with the given event | ||
| const actualResponse = await occupationGroupHandler(givenEvent); | ||
|
|
||
| // AND respond with the INTERNAL_SERVER_ERROR status | ||
| expect(actualResponse.statusCode).toEqual(StatusCodes.INTERNAL_SERVER_ERROR); | ||
| // AND the response body contains the error information | ||
| const expectedErrorBody: ErrorAPISpecs.Types.Payload = { | ||
| errorCode: | ||
| OccupationGroupAPISpecs.OccupationGroup.GET.Enums.Response.Status500.ErrorCodes | ||
| .DB_FAILED_TO_RETRIEVE_OCCUPATION_GROUP_DETAIL, | ||
| message: "Failed to fetch the model details from the DB", | ||
| details: "", | ||
| }; | ||
| expect(JSON.parse(actualResponse.body)).toEqual(expectedErrorBody); | ||
| }); | ||
|
|
||
| test("GET /models/{modelId}/occupationGroups/{id} should response with BAD_REQUEST if the path validation failed ", async () => { | ||
| jest.doMock("validator", () => ({ | ||
| ajvInstance: { | ||
| getSchema: jest.fn(() => { | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| const validateFn: any = jest.fn().mockReturnValue(false); | ||
|
|
||
| validateFn.errors = [{ instancePath: "/id", message: "invalid id" }]; | ||
|
|
||
| return validateFn; | ||
| }), | ||
| }, | ||
| })); | ||
|
|
||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| let handler: any; | ||
|
|
||
| jest.isolateModules(() => { | ||
| ({ handler = handler } = require("./index")); | ||
| }); | ||
|
|
||
| const givenModelId = getMockStringId(100); | ||
| const givenOccupationGroupId = getMockStringId(100); | ||
| const givenEvent = { | ||
| httpMethod: HTTP_VERBS.GET, | ||
| headers: {}, | ||
| pathParameters: { modelId: givenModelId.toString(), id: givenOccupationGroupId.toString() }, | ||
| queryStringParameters: {}, | ||
| path: `/models/${givenModelId}/occupationGroups/${givenOccupationGroupId}`, | ||
| } as never; | ||
| // AND User has the required role | ||
| checkRole.mockResolvedValue(true); | ||
|
|
||
| // WHEN the occupationGroup handler is invoked with the given event | ||
| const actualResponse = await handler(givenEvent); | ||
|
|
||
| // AND respond with the BAD_REQUEST status | ||
| expect(actualResponse.statusCode).toEqual(StatusCodes.BAD_REQUEST); | ||
| // AND the response body contains the error information | ||
| const expectedErrorBody: ErrorAPISpecs.Types.Payload = { | ||
| errorCode: ErrorAPISpecs.Constants.ErrorCodes.INVALID_JSON_SCHEMA, | ||
| message: ErrorAPISpecs.Constants.ReasonPhrases.INVALID_JSON_SCHEMA, | ||
| details: expect.stringContaining("modelId"), | ||
| }; | ||
|
|
||
| expect(JSON.parse(actualResponse.body)).toMatchObject({ | ||
| errorCode: ErrorAPISpecs.Constants.ErrorCodes.INVALID_JSON_SCHEMA, | ||
| message: ErrorAPISpecs.Constants.ReasonPhrases.INVALID_JSON_SCHEMA, | ||
| }); | ||
| expect(typeof JSON.parse(actualResponse.body).details).toBe("string"); | ||
| expect(JSON.parse(actualResponse.body)).toEqual(expectedErrorBody); | ||
| }); | ||
|
|
||
| test("GET /models/{modelId}/occupationGroups/{id} should respond with NOT_FOUND if occupation group is not found", async () => { | ||
| // GIVEN a valid request with modelId and occupationGroup ID | ||
| const givenModel: IModelInfo = { | ||
| ...getIModelInfoMockData(1), | ||
| UUID: "foo", | ||
| UUIDHistory: ["foo"], | ||
| released: false, | ||
| }; | ||
| const givenOccupationGroupId = getMockStringId(2); | ||
| const givenEvent = { | ||
| httpMethod: HTTP_VERBS.GET, | ||
| headers: {}, | ||
| pathParameters: { modelId: givenModel.id.toString(), id: givenOccupationGroupId.toString() }, | ||
| queryStringParameters: {}, | ||
| path: `/models/${givenModel.id}/occupationGroups/${givenOccupationGroupId}`, | ||
| } as never; | ||
|
|
||
| // AND User has the required role | ||
| checkRole.mockResolvedValue(true); | ||
|
|
||
| const givenOccupationGroupServiceMock = { | ||
| create: jest.fn(), | ||
| findById: jest.fn().mockResolvedValue(null), | ||
| findParent: jest.fn().mockResolvedValue(null), | ||
| findPaginated: jest.fn().mockResolvedValue({ items: [], nextCursor: null }), | ||
| validateModelForOccupationGroup: jest.fn(), | ||
| findChildren: jest.fn().mockResolvedValue([]), | ||
| } as IOccupationGroupService; | ||
| const mockServiceRegistry = mockGetServiceRegistry(); | ||
| mockServiceRegistry.occupationGroup = givenOccupationGroupServiceMock; | ||
|
|
||
| // WHEN the occupationGroup handler is invoked with the given event | ||
| const actualResponse = await occupationGroupHandler(givenEvent); | ||
| // THEN respond with the NOT_FOUND status | ||
| expect(actualResponse.statusCode).toEqual(StatusCodes.NOT_FOUND); | ||
| // AND the response body contains the error information | ||
| const expectedErrorBody: ErrorAPISpecs.Types.Payload = { | ||
| errorCode: | ||
| OccupationGroupAPISpecs.OccupationGroup.GET.Enums.Response.Status404.ErrorCodes.OCCUPATION_GROUP_NOT_FOUND, | ||
| message: "Occupation group not found", | ||
| details: `No occupation group found with id: ${givenOccupationGroupId}`, | ||
| }; | ||
| expect(JSON.parse(actualResponse.body)).toEqual(expectedErrorBody); | ||
| }); | ||
|
|
||
| test("GET /models/{modelId}/occupationGroups/{id} should respond with BAD_REQUEST if modelId is missing", async () => { | ||
| const givenOccupationGroupId = getMockStringId(2); | ||
|
|
||
| const givenEvent = { | ||
| httpMethod: HTTP_VERBS.GET, | ||
| headers: {}, | ||
| pathParameters: { id: givenOccupationGroupId.toString() }, | ||
| queryStringParameters: {}, | ||
| path: `/models//occupationGroups/${givenOccupationGroupId}`, | ||
| } as never; | ||
|
|
||
| // AND role check passes for anonymous access | ||
| checkRole.mockResolvedValueOnce(true); | ||
|
|
||
| // WHEN the occupation group handler is invoked with the given event | ||
| const actualResponse = await occupationGroupHandler(givenEvent as never); | ||
|
|
||
| // THEN expect the handler to return the BAD_REQUEST status | ||
| expect(actualResponse.statusCode).toEqual(StatusCodes.BAD_REQUEST); | ||
| // AND the response body contains the error information | ||
| const parsedMissing = JSON.parse(actualResponse.body); | ||
| expect(parsedMissing).toMatchObject({ | ||
| errorCode: | ||
| OccupationGroupAPISpecs.GET.Enums.Response.Status500.ErrorCodes.DB_FAILED_TO_RETRIEVE_OCCUPATION_GROUPS, | ||
| message: "modelId is missing in the path", | ||
| }); | ||
| expect(typeof parsedMissing.details).toBe("string"); | ||
| }); | ||
|
|
||
| test("GET /models/{modelId}/occupationGroups/{id} should respond with INTERNAL_SERVER_ERROR if repository throws an error", async () => { | ||
| // GIVEN a valid request with modelId and occupationGroup ID | ||
| const givenModel: IModelInfo = { | ||
| ...getIModelInfoMockData(1), | ||
| UUID: "foo", | ||
| UUIDHistory: ["foo"], | ||
| released: false, | ||
| }; | ||
| const givenOccupationGroupId = getMockStringId(2); | ||
| const givenEvent = { | ||
| httpMethod: HTTP_VERBS.GET, | ||
| headers: {}, | ||
| pathParameters: { modelId: givenModel.id.toString(), id: givenOccupationGroupId.toString() }, | ||
| queryStringParameters: {}, | ||
| path: `/models/${givenModel.id}/occupationGroups/${givenOccupationGroupId}`, | ||
| } as never; | ||
|
|
||
| // AND User has the required role | ||
| checkRole.mockResolvedValue(true); | ||
|
|
||
| // AND a repository that will throw an error | ||
| const givenOccupationGroupRepositoryMock = { | ||
| Model: undefined as never, | ||
| hierarchyModel: undefined as never, | ||
| create: jest.fn().mockResolvedValue(null), | ||
| createMany: jest.fn().mockResolvedValue([]), | ||
| findById: jest.fn().mockRejectedValue(new Error("Database connection failed")), | ||
| findAll: jest.fn().mockResolvedValue(null), | ||
| findPaginated: jest.fn().mockResolvedValue({ items: [], nextCursor: null }), | ||
| getOccupationGroupByUUID: jest.fn().mockResolvedValue(null), | ||
| getHistory: jest.fn().mockResolvedValue([]), | ||
| findParent: jest.fn().mockResolvedValue(null), | ||
| findChildren: jest.fn().mockResolvedValue([]), | ||
| }; | ||
| jest.spyOn(getRepositoryRegistry(), "OccupationGroup", "get").mockReturnValue(givenOccupationGroupRepositoryMock); | ||
|
|
||
| const givenOccupationGroupServiceMock = { | ||
| create: jest.fn(), | ||
| findById: jest.fn().mockRejectedValue(new Error("foo")), | ||
| findPaginated: jest.fn().mockResolvedValue({ items: [], nextCursor: null }), | ||
| validateModelForOccupationGroup: jest.fn(), | ||
| findChildren: jest.fn().mockResolvedValue([]), | ||
| findParent: jest.fn().mockResolvedValue(null), | ||
| } as IOccupationGroupService; | ||
| const mockServiceRegistry = mockGetServiceRegistry(); | ||
| mockServiceRegistry.occupationGroup = givenOccupationGroupServiceMock; | ||
|
|
||
| // WHEN the occupationGroup handler is invoked with the given event | ||
| const actualResponse = await occupationGroupHandler(givenEvent); | ||
|
|
||
| // THEN respond with the INTERNAL_SERVER_ERROR status | ||
| expect(actualResponse.statusCode).toEqual(StatusCodes.INTERNAL_SERVER_ERROR); | ||
| // AND the response body contains the error information | ||
| const expectedErrorBody: ErrorAPISpecs.Types.Payload = { | ||
| errorCode: | ||
| OccupationGroupAPISpecs.OccupationGroup.GET.Enums.Response.Status500.ErrorCodes | ||
| .DB_FAILED_TO_RETRIEVE_OCCUPATION_GROUP_DETAIL, | ||
| message: "Failed to retrieve the occupation group from the DB", | ||
| details: "", | ||
| }; | ||
| expect(JSON.parse(actualResponse.body)).toEqual(expectedErrorBody); | ||
| }); | ||
| test("should route GET occupationGroup by ID to getByIdHandler", async () => { | ||
| const event = { httpMethod: HTTP_VERBS.GET, path: "/models/1/occupationGroups/2" } as APIGatewayProxyEvent; | ||
| const response = await handler(event); | ||
| expect(getByIdHandler).toHaveBeenCalledWith(event); | ||
| expect(response.body).toBe("GET_BY_ID"); | ||
| }); | ||
|
|
||
| describe("GET parent occupation group", () => { | ||
| test("GET /models/{modelId}/occupationGroups/{id}/parent should return the parent occupation group for a valid ID", async () => { | ||
| // GIVEN a valid request with modelId and occupationGroup ID | ||
| const givenModelId = getMockStringId(1); | ||
| const givenOccupationGroupId = getMockStringId(2); | ||
| const givenEvent = { | ||
| httpMethod: HTTP_VERBS.GET, | ||
| headers: {}, | ||
| pathParameters: { modelId: givenModelId.toString(), id: givenOccupationGroupId.toString() }, | ||
| queryStringParameters: {}, | ||
| path: `/models/${givenModelId}/occupationGroups/${givenOccupationGroupId}/parent`, | ||
| } as never; | ||
|
|
||
| // AND User has the required role | ||
| checkRole.mockResolvedValue(true); | ||
| // AND a configured base path for resource | ||
| const givenResourcesBaseUrl = "https://some/path/to/api/resources"; | ||
| jest.spyOn(config, "getResourcesBaseUrl").mockReturnValueOnce(givenResourcesBaseUrl); | ||
|
|
||
| // AND a repository that will successfully get the occupation group parent | ||
| const givenOccupationGroup: IOccupationGroup = { | ||
| ...getIOccupationGroupMockData(1, givenModelId), | ||
| id: getMockStringId(3), | ||
| UUID: "test-uuid", | ||
| UUIDHistory: ["test-uuid"], | ||
| importId: randomUUID(), | ||
| }; | ||
| const givenOccupationGroupServiceMock = { | ||
| create: jest.fn(), | ||
| findById: jest.fn().mockResolvedValue(null), | ||
| findPaginated: jest.fn().mockResolvedValue({ items: [], nextCursor: null }), | ||
| validateModelForOccupationGroup: jest.fn(), | ||
| findChildren: jest.fn().mockResolvedValue([]), | ||
| findParent: jest.fn().mockResolvedValue(givenOccupationGroup), | ||
| } as IOccupationGroupService; | ||
| const mockServiceRegistry = mockGetServiceRegistry(); | ||
| mockServiceRegistry.occupationGroup = givenOccupationGroupServiceMock; | ||
|
|
||
| // WHEN the occupationGroup handler is invoked with the given event | ||
| const actualResponse = await occupationGroupHandler(givenEvent); | ||
| // THEN respond with the OK status code | ||
| expect(actualResponse.statusCode).toEqual(StatusCodes.OK); | ||
| // AND the handler to return the correct headers | ||
| expect(actualResponse.headers).toMatchObject({ | ||
| "Content-Type": "application/json", | ||
| }); | ||
| // AND the handler to return the expected result | ||
| expect(JSON.parse(actualResponse.body)).toMatchObject({ | ||
| id: givenOccupationGroup.id, | ||
| UUID: givenOccupationGroup.UUID, | ||
| }); | ||
| }); | ||
| test("GET /models/{modelId}/occupationGroups/{id}/parent should respond with NOT_FOUND if model does not exist", async () => { | ||
| // GIVEN a valid request with modelId and occupationGroup ID | ||
| const givenModelId = getMockStringId(1); | ||
| const givenOccupationGroupId = getMockStringId(2); | ||
| const givenEvent = { | ||
| httpMethod: HTTP_VERBS.GET, | ||
| headers: {}, | ||
| pathParameters: { modelId: givenModelId.toString(), id: givenOccupationGroupId.toString() }, | ||
| queryStringParameters: {}, | ||
| path: `/models/${givenModelId}/occupationGroups/${givenOccupationGroupId}/parent`, | ||
| } as never; | ||
|
|
||
| // AND User has the required role | ||
| checkRole.mockResolvedValue(true); | ||
|
|
||
| const givenOccupationGroupServiceMock = { | ||
| create: jest.fn(), | ||
| findById: jest.fn(), | ||
| findParent: jest.fn(), | ||
| findChildren: jest.fn(), | ||
| findPaginated: jest.fn().mockResolvedValue({ items: [], nextCursor: null }), | ||
| validateModelForOccupationGroup: jest | ||
| .fn() | ||
| .mockResolvedValue(ModelForOccupationGroupValidationErrorCode.MODEL_NOT_FOUND_BY_ID), | ||
| } as IOccupationGroupService; | ||
| const mockServiceRegistry = mockGetServiceRegistry(); | ||
| mockServiceRegistry.occupationGroup = givenOccupationGroupServiceMock; | ||
|
|
||
| // WHEN the occupationGroup handler is invoked with the given event | ||
| const actualResponse = await occupationGroupHandler(givenEvent); | ||
|
|
||
| // AND respond with the NOT_FOUND status | ||
| expect(actualResponse.statusCode).toEqual(StatusCodes.NOT_FOUND); | ||
| // AND the response body contains the error information | ||
| const expectedErrorBody: ErrorAPISpecs.Types.Payload = { | ||
| errorCode: | ||
| OccupationGroupAPISpecs.OccupationGroup.Parent.GET.Enums.Response.Status404.ErrorCodes.MODEL_NOT_FOUND, | ||
| message: "Model not found", | ||
| details: `No model found with id: ${givenModelId}`, | ||
| }; | ||
| expect(JSON.parse(actualResponse.body)).toEqual(expectedErrorBody); | ||
| }); | ||
| test("GET /models/{modelId}/occupationGroups/{id}/parent should respond with INTERNAL_SERVER_ERROR if model validator function failed to fetch from db", async () => { | ||
| // GIVEN a valid request with modelId and occupationGroup ID | ||
| const givenModelId = getMockStringId(1); | ||
| const givenOccupationGroupId = getMockStringId(2); | ||
| const givenEvent = { | ||
| httpMethod: HTTP_VERBS.GET, | ||
| headers: {}, | ||
| pathParameters: { modelId: givenModelId.toString(), id: givenOccupationGroupId.toString() }, | ||
| queryStringParameters: {}, | ||
| path: `/models/${givenModelId}/occupationGroups/${givenOccupationGroupId}/parent`, | ||
| } as never; | ||
|
|
||
| // AND User has the required role | ||
| checkRole.mockResolvedValue(true); | ||
|
|
||
| const givenOccupationGroupServiceMock = { | ||
| create: jest.fn(), | ||
| findById: jest.fn(), | ||
| findParent: jest.fn(), | ||
| findChildren: jest.fn(), | ||
| findPaginated: jest.fn().mockResolvedValue({ items: [], nextCursor: null }), | ||
| validateModelForOccupationGroup: jest | ||
| .fn() | ||
| .mockResolvedValue(ModelForOccupationGroupValidationErrorCode.FAILED_TO_FETCH_FROM_DB), | ||
| } as IOccupationGroupService; | ||
| const mockServiceRegistry = mockGetServiceRegistry(); | ||
| mockServiceRegistry.occupationGroup = givenOccupationGroupServiceMock; | ||
|
|
||
| // WHEN the occupationGroup handler is invoked with the given event | ||
| const actualResponse = await occupationGroupHandler(givenEvent); | ||
|
|
||
| // AND respond with the INTERNAL_SERVER_ERROR status | ||
| expect(actualResponse.statusCode).toEqual(StatusCodes.INTERNAL_SERVER_ERROR); | ||
| // AND the response body contains the error information | ||
| const expectedErrorBody: ErrorAPISpecs.Types.Payload = { | ||
| errorCode: | ||
| OccupationGroupAPISpecs.OccupationGroup.Parent.GET.Enums.Response.Status500.ErrorCodes | ||
| .DB_FAILED_TO_RETRIEVE_OCCUPATION_GROUP_PARENT, | ||
| message: "Failed to fetch the model details from the DB", | ||
| details: "", | ||
| }; | ||
| expect(JSON.parse(actualResponse.body)).toEqual(expectedErrorBody); | ||
| }); | ||
| test("GET /models/{modelId}/occupationGroups/{id}/parent should response with BAD_REQUEST if the path validation failed ", async () => { | ||
| jest.doMock("validator", () => ({ | ||
| ajvInstance: { | ||
| getSchema: jest.fn(() => { | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| const validateFn: any = jest.fn().mockReturnValue(false); | ||
|
|
||
| validateFn.errors = [{ instancePath: "/id", message: "invalid id" }]; | ||
|
|
||
| return validateFn; | ||
| }), | ||
| }, | ||
| })); | ||
|
|
||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| let handler: any; | ||
|
|
||
| jest.isolateModules(() => { | ||
| ({ handler = handler } = require("./index")); | ||
| }); | ||
| const givenModelId = getMockStringId(100); | ||
| const givenOccupationGroupId = getMockStringId(100); | ||
| const givenEvent = { | ||
| httpMethod: HTTP_VERBS.GET, | ||
| headers: {}, | ||
| pathParameters: { modelId: givenModelId.toString(), id: givenOccupationGroupId.toString() }, | ||
| queryStringParameters: {}, | ||
| path: `/models/${givenModelId}/occupationGroups/${givenOccupationGroupId}/parent`, | ||
| } as never; | ||
| // AND User has the required role | ||
| checkRole.mockResolvedValue(true); | ||
|
|
||
| // AND the response body contains the error information | ||
| const expectedErrorBody: ErrorAPISpecs.Types.Payload = { | ||
| errorCode: ErrorAPISpecs.Constants.ErrorCodes.INVALID_JSON_SCHEMA, | ||
| message: ErrorAPISpecs.Constants.ReasonPhrases.INVALID_JSON_SCHEMA, | ||
| details: expect.stringContaining("modelId"), | ||
| }; | ||
|
|
||
| // WHEN the occupationGroup handler is invoked with the given event | ||
| const actualResponse = await handler(givenEvent); | ||
|
|
||
| // AND respond with the BAD_REQUEST status | ||
| expect(actualResponse.statusCode).toEqual(StatusCodes.BAD_REQUEST); | ||
|
|
||
| expect(JSON.parse(actualResponse.body)).toMatchObject({ | ||
| errorCode: ErrorAPISpecs.Constants.ErrorCodes.INVALID_JSON_SCHEMA, | ||
| message: ErrorAPISpecs.Constants.ReasonPhrases.INVALID_JSON_SCHEMA, | ||
| }); | ||
| expect(typeof JSON.parse(actualResponse.body).details).toBe("string"); | ||
| expect(JSON.parse(actualResponse.body)).toEqual(expectedErrorBody); | ||
| }); | ||
| test("GET /models/{modelId}/occupationGroups/{id}/parent should respond with NOT_FOUND if occupation group is not found", async () => { | ||
| // GIVEN a valid request with modelId and occupationGroup ID | ||
| const givenModel: IModelInfo = { | ||
| ...getIModelInfoMockData(1), | ||
| UUID: "foo", | ||
| UUIDHistory: ["foo"], | ||
| released: false, | ||
| }; | ||
| const givenOccupationGroupId = getMockStringId(2); | ||
| const givenEvent = { | ||
| httpMethod: HTTP_VERBS.GET, | ||
| headers: {}, | ||
| pathParameters: { modelId: givenModel.id.toString(), id: givenOccupationGroupId.toString() }, | ||
| queryStringParameters: {}, | ||
| path: `/models/${givenModel.id}/occupationGroups/${givenOccupationGroupId}/parent`, | ||
| } as never; | ||
|
|
||
| // AND User has the required role | ||
| checkRole.mockResolvedValue(true); | ||
|
|
||
| const givenOccupationGroupServiceMock = { | ||
| create: jest.fn(), | ||
| findById: jest.fn().mockResolvedValue(null), | ||
| findParent: jest.fn().mockResolvedValue(null), | ||
| findChildren: jest.fn().mockResolvedValue([]), | ||
| findPaginated: jest.fn().mockResolvedValue({ items: [], nextCursor: null }), | ||
| validateModelForOccupationGroup: jest.fn(), | ||
| } as IOccupationGroupService; | ||
| const mockServiceRegistry = mockGetServiceRegistry(); | ||
| mockServiceRegistry.occupationGroup = givenOccupationGroupServiceMock; | ||
|
|
||
| // WHEN the occupationGroup handler is invoked with the given event | ||
| const actualResponse = await occupationGroupHandler(givenEvent); | ||
| // THEN respond with the NOT_FOUND status | ||
| expect(actualResponse.statusCode).toEqual(StatusCodes.NOT_FOUND); | ||
| // AND the response body contains the error information | ||
| const expectedErrorBody: ErrorAPISpecs.Types.Payload = { | ||
| errorCode: | ||
| OccupationGroupAPISpecs.OccupationGroup.Parent.GET.Enums.Response.Status404.ErrorCodes | ||
| .OCCUPATION_GROUP_PARENT_NOT_FOUND, | ||
| message: "Occupation group or parent not found", | ||
| details: `No occupation group or parent found with occupation group id: ${givenOccupationGroupId}`, | ||
| }; | ||
| expect(JSON.parse(actualResponse.body)).toEqual(expectedErrorBody); | ||
| }); | ||
| test("GET /models/{modelId}/occupationGroups/{id}/parent should respond with BAD_REQUEST if modelId is missing", async () => { | ||
| const givenOccupationGroupId = getMockStringId(2); | ||
|
|
||
| const givenEvent = { | ||
| httpMethod: HTTP_VERBS.GET, | ||
| headers: {}, | ||
| pathParameters: { id: givenOccupationGroupId.toString() }, | ||
| queryStringParameters: {}, | ||
| path: `/models//occupationGroups/${givenOccupationGroupId}/parent`, | ||
| } as never; | ||
|
|
||
| // AND role check passes for anonymous access | ||
| checkRole.mockResolvedValueOnce(true); | ||
|
|
||
| // WHEN the occupation group handler is invoked with the given event | ||
| const actualResponse = await occupationGroupHandler(givenEvent as never); | ||
|
|
||
| // THEN expect the handler to return the BAD_REQUEST status | ||
| expect(actualResponse.statusCode).toEqual(StatusCodes.BAD_REQUEST); | ||
| // AND the response body contains the error information | ||
| const parsedMissing = JSON.parse(actualResponse.body); | ||
| expect(parsedMissing).toMatchObject({ | ||
| errorCode: | ||
| OccupationGroupAPISpecs.GET.Enums.Response.Status500.ErrorCodes.DB_FAILED_TO_RETRIEVE_OCCUPATION_GROUPS, | ||
| message: "modelId is missing in the path", | ||
| }); | ||
| expect(typeof parsedMissing.details).toBe("string"); | ||
| }); | ||
| test("GET /models/{modelId}/occupationGroups/{id}/parent should respond with INTERNAL_SERVER_ERROR if repository throws an error", async () => { | ||
| // GIVEN a valid request with modelId and occupationGroup ID | ||
| const givenModel: IModelInfo = { | ||
| ...getIModelInfoMockData(1), | ||
| UUID: "foo", | ||
| UUIDHistory: ["foo"], | ||
| released: false, | ||
| }; | ||
| const givenOccupationGroupId = getMockStringId(2); | ||
| const givenEvent = { | ||
| httpMethod: HTTP_VERBS.GET, | ||
| headers: {}, | ||
| pathParameters: { modelId: givenModel.id.toString(), id: givenOccupationGroupId.toString() }, | ||
| queryStringParameters: {}, | ||
| path: `/models/${givenModel.id}/occupationGroups/${givenOccupationGroupId}/parent`, | ||
| } as never; | ||
|
|
||
| // AND User has the required role | ||
| checkRole.mockResolvedValue(true); | ||
|
|
||
| // AND a repository that will throw an error | ||
| const givenOccupationGroupRepositoryMock = { | ||
| Model: undefined as never, | ||
| hierarchyModel: undefined as never, | ||
| create: jest.fn().mockResolvedValue(null), | ||
| createMany: jest.fn().mockResolvedValue([]), | ||
| findById: jest.fn().mockRejectedValue(new Error("Database connection failed")), | ||
| findAll: jest.fn().mockResolvedValue(null), | ||
| findPaginated: jest.fn().mockResolvedValue({ items: [], nextCursor: null }), | ||
| getOccupationGroupByUUID: jest.fn().mockResolvedValue(null), | ||
| getHistory: jest.fn().mockResolvedValue([]), | ||
| findParent: jest.fn().mockResolvedValue(null), | ||
| findChildren: jest.fn().mockResolvedValue([]), | ||
| }; | ||
| jest.spyOn(getRepositoryRegistry(), "OccupationGroup", "get").mockReturnValue(givenOccupationGroupRepositoryMock); | ||
|
|
||
| const givenOccupationGroupServiceMock = { | ||
| create: jest.fn(), | ||
| findById: jest.fn().mockResolvedValue(null), | ||
| findPaginated: jest.fn().mockResolvedValue({ items: [], nextCursor: null }), | ||
| validateModelForOccupationGroup: jest.fn(), | ||
| findChildren: jest.fn().mockResolvedValue([]), | ||
| findParent: jest.fn().mockRejectedValue(new Error("foo")), | ||
| } as IOccupationGroupService; | ||
| const mockServiceRegistry = mockGetServiceRegistry(); | ||
| mockServiceRegistry.occupationGroup = givenOccupationGroupServiceMock; | ||
|
|
||
| // WHEN the occupationGroup handler is invoked with the given event | ||
| const actualResponse = await occupationGroupHandler(givenEvent); | ||
|
|
||
| // THEN respond with the INTERNAL_SERVER_ERROR status | ||
| expect(actualResponse.statusCode).toEqual(StatusCodes.INTERNAL_SERVER_ERROR); | ||
| // AND the response body contains the error information | ||
| const expectedErrorBody: ErrorAPISpecs.Types.Payload = { | ||
| errorCode: | ||
| OccupationGroupAPISpecs.OccupationGroup.Parent.GET.Enums.Response.Status500.ErrorCodes | ||
| .DB_FAILED_TO_RETRIEVE_OCCUPATION_GROUP_PARENT, | ||
| message: "Failed to retrieve the parent occupation group from the DB", | ||
| details: "", | ||
| }; | ||
| expect(JSON.parse(actualResponse.body)).toEqual(expectedErrorBody); | ||
| }); | ||
| test("should route GET parent to getParentHandler", async () => { | ||
| const event = { httpMethod: HTTP_VERBS.GET, path: "/models/1/occupationGroups/2/parent" } as APIGatewayProxyEvent; | ||
| const response = await handler(event); | ||
| expect(getParentHandler).toHaveBeenCalledWith(event); | ||
| expect(response.body).toBe("GET_PARENT"); | ||
| }); | ||
|
|
||
| describe("GET children of occupation group", () => { | ||
| test("GET /models/{modelId}/occupationGroups/{id}/children should return the children of a given occupation group id", async () => { | ||
| // GIVEN a valid request with modelId and occupationGroup ID | ||
| const givenModelId = getMockStringId(1); | ||
| const givenOccupationGroupId = getMockStringId(2); | ||
| const givenEvent = { | ||
| httpMethod: HTTP_VERBS.GET, | ||
| headers: {}, | ||
| pathParameters: { modelId: givenModelId.toString(), id: givenOccupationGroupId.toString() }, | ||
| queryStringParameters: {}, | ||
| path: `/models/${givenModelId}/occupationGroups/${givenOccupationGroupId}/children`, | ||
| } as never; | ||
| // AND User has the required role | ||
| checkRole.mockResolvedValue(true); | ||
| // AND a configured base path for resource | ||
| const givenResourcesBaseUrl = "https://some/path/to/api/resources"; | ||
| jest.spyOn(config, "getResourcesBaseUrl").mockReturnValueOnce(givenResourcesBaseUrl); | ||
|
|
||
| // AND a repository that will successfully get the occupation group children | ||
| const givenOccupationGroup: IOccupationGroupChild = { | ||
| ...getIOccupationGroupMockData(1, givenModelId), | ||
| id: getMockStringId(3), | ||
| UUID: "test-uuid", | ||
| UUIDHistory: ["test-uuid"], | ||
| parentId: "", | ||
| objectType: ObjectTypes.ISCOGroup, | ||
| }; | ||
| const givenOccupationGroupServiceMock = { | ||
| create: jest.fn(), | ||
| findById: jest.fn().mockResolvedValue(null), | ||
| findPaginated: jest.fn().mockResolvedValue({ items: [], nextCursor: null }), | ||
| validateModelForOccupationGroup: jest.fn(), | ||
| findChildren: jest.fn().mockResolvedValue([givenOccupationGroup]), | ||
| findParent: jest.fn().mockResolvedValue(null), | ||
| } as IOccupationGroupService; | ||
| const mockServiceRegistry = mockGetServiceRegistry(); | ||
| mockServiceRegistry.occupationGroup = givenOccupationGroupServiceMock; | ||
| // WHEN the occupationGroup handler is invoked with the given event | ||
| const actualResponse = await occupationGroupHandler(givenEvent); | ||
| // THEN respond with the OK status code | ||
| expect(actualResponse.statusCode).toEqual(StatusCodes.OK); | ||
| // AND the handler to return the correct headers | ||
| expect(actualResponse.headers).toMatchObject({ | ||
| "Content-Type": "application/json", | ||
| }); | ||
| // AND the handler to return the expected result | ||
| expect(JSON.parse(actualResponse.body)).toMatchObject({ | ||
| data: expect.any(Array), | ||
| limit: null, | ||
| nextCursor: null, | ||
| }); | ||
| }); | ||
| test("GET /models/{modelId}/occupationGroups/{id}/children should respond with NOT_FOUND if model does not exist", async () => { | ||
| // GIVEN a valid request with modelId and occupationGroup ID | ||
| const givenModelId = getMockStringId(1); | ||
| const givenOccupationGroupId = getMockStringId(2); | ||
| const givenEvent = { | ||
| httpMethod: HTTP_VERBS.GET, | ||
| headers: {}, | ||
| pathParameters: { modelId: givenModelId.toString(), id: givenOccupationGroupId.toString() }, | ||
| queryStringParameters: {}, | ||
| path: `/models/${givenModelId}/occupationGroups/${givenOccupationGroupId}/children`, | ||
| } as never; | ||
| // AND User has the required role | ||
| checkRole.mockResolvedValue(true); | ||
|
|
||
| const givenOccupationGroupServiceMock = { | ||
| create: jest.fn(), | ||
| findById: jest.fn(), | ||
| findParent: jest.fn(), | ||
| findChildren: jest.fn(), | ||
| findPaginated: jest.fn().mockResolvedValue({ items: [], nextCursor: null }), | ||
| validateModelForOccupationGroup: jest | ||
| .fn() | ||
| .mockResolvedValue(ModelForOccupationGroupValidationErrorCode.MODEL_NOT_FOUND_BY_ID), | ||
| } as IOccupationGroupService; | ||
| const mockServiceRegistry = mockGetServiceRegistry(); | ||
| mockServiceRegistry.occupationGroup = givenOccupationGroupServiceMock; | ||
|
|
||
| // WHEN the occupationGroup handler is invoked with the given event | ||
| const actualResponse = await occupationGroupHandler(givenEvent); | ||
|
|
||
| // AND respond with the NOT_FOUND status | ||
| expect(actualResponse.statusCode).toEqual(StatusCodes.NOT_FOUND); | ||
| // AND the response body contains the error information | ||
| const expectedErrorBody: ErrorAPISpecs.Types.Payload = { | ||
| errorCode: | ||
| OccupationGroupAPISpecs.OccupationGroup.Parent.GET.Enums.Response.Status404.ErrorCodes.MODEL_NOT_FOUND, | ||
| message: "Model not found", | ||
| details: `No model found with id: ${givenModelId}`, | ||
| }; | ||
| expect(JSON.parse(actualResponse.body)).toEqual(expectedErrorBody); | ||
| }); | ||
| test("GET /models/{modelId}/occupationGroups/{id}/children should respond with INTERNAL_SERVER_ERROR if model validator function failed to fetch from db", async () => { | ||
| // GIVEN a valid request with modelId and occupationGroup ID | ||
| const givenModelId = getMockStringId(1); | ||
| const givenOccupationGroupId = getMockStringId(2); | ||
| const givenEvent = { | ||
| httpMethod: HTTP_VERBS.GET, | ||
| headers: {}, | ||
| pathParameters: { modelId: givenModelId.toString(), id: givenOccupationGroupId.toString() }, | ||
| queryStringParameters: {}, | ||
| path: `/models/${givenModelId}/occupationGroups/${givenOccupationGroupId}/children`, | ||
| } as never; | ||
| // AND User has the required role | ||
| checkRole.mockResolvedValue(true); | ||
|
|
||
| const givenOccupationGroupServiceMock = { | ||
| create: jest.fn(), | ||
| findById: jest.fn(), | ||
| findParent: jest.fn(), | ||
| findChildren: jest.fn(), | ||
| findPaginated: jest.fn().mockResolvedValue({ items: [], nextCursor: null }), | ||
| validateModelForOccupationGroup: jest | ||
| .fn() | ||
| .mockResolvedValue(ModelForOccupationGroupValidationErrorCode.FAILED_TO_FETCH_FROM_DB), | ||
| } as IOccupationGroupService; | ||
| const mockServiceRegistry = mockGetServiceRegistry(); | ||
| mockServiceRegistry.occupationGroup = givenOccupationGroupServiceMock; | ||
|
|
||
| // WHEN the occupationGroup handler is invoked with the given event | ||
| const actualResponse = await occupationGroupHandler(givenEvent); | ||
|
|
||
| // AND respond with the INTERNAL_SERVER_ERROR status | ||
| expect(actualResponse.statusCode).toEqual(StatusCodes.INTERNAL_SERVER_ERROR); | ||
| // AND the response body contains the error information | ||
| const expectedErrorBody: ErrorAPISpecs.Types.Payload = { | ||
| errorCode: | ||
| OccupationGroupAPISpecs.OccupationGroup.Children.GET.Enums.Response.Status500.ErrorCodes | ||
| .DB_FAILED_TO_RETRIEVE_OCCUPATION_GROUP_CHILDREN, | ||
| message: "Failed to fetch the model details from the DB", | ||
| details: "", | ||
| }; | ||
| expect(JSON.parse(actualResponse.body)).toEqual(expectedErrorBody); | ||
| }); | ||
| test("GET /models/{modelId}/occupationGroups/{id}/children should response with BAD_REQUEST if the path validation failed", async () => { | ||
| jest.doMock("validator", () => ({ | ||
| ajvInstance: { | ||
| getSchema: jest.fn(() => { | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| const validateFn: any = jest.fn().mockReturnValue(false); | ||
|
|
||
| validateFn.errors = [{ instancePath: "/id", message: "invalid id" }]; | ||
|
|
||
| return validateFn; | ||
| }), | ||
| }, | ||
| })); | ||
|
|
||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| let handler: any; | ||
|
|
||
| jest.isolateModules(() => { | ||
| ({ handler = handler } = require("./index")); | ||
| }); | ||
|
|
||
| const givenModelId = getMockStringId(100); | ||
| const givenOccupationGroupId = getMockStringId(100); | ||
| const givenEvent = { | ||
| httpMethod: HTTP_VERBS.GET, | ||
| headers: {}, | ||
| pathParameters: { modelId: givenModelId.toString(), id: givenOccupationGroupId.toString() }, | ||
| queryStringParameters: {}, | ||
| path: `/models/${givenModelId}/occupationGroups/${givenOccupationGroupId}/children`, | ||
| } as never; | ||
| // AND User has the required role | ||
| checkRole.mockResolvedValue(true); | ||
| // AND the response body contains the error information | ||
| const expectedErrorBody: ErrorAPISpecs.Types.Payload = { | ||
| errorCode: ErrorAPISpecs.Constants.ErrorCodes.INVALID_JSON_SCHEMA, | ||
| message: ErrorAPISpecs.Constants.ReasonPhrases.INVALID_JSON_SCHEMA, | ||
| details: expect.stringContaining("modelId"), | ||
| }; | ||
|
|
||
| // WHEN the occupationGroup handler is invoked with the given event | ||
| const actualResponse = await handler(givenEvent); | ||
| // AND respond with the BAD_REQUEST status | ||
| expect(actualResponse.statusCode).toEqual(StatusCodes.BAD_REQUEST); | ||
|
|
||
| expect(JSON.parse(actualResponse.body)).toMatchObject({ | ||
| errorCode: ErrorAPISpecs.Constants.ErrorCodes.INVALID_JSON_SCHEMA, | ||
| message: ErrorAPISpecs.Constants.ReasonPhrases.INVALID_JSON_SCHEMA, | ||
| }); | ||
| expect(typeof JSON.parse(actualResponse.body).details).toBe("string"); | ||
| expect(JSON.parse(actualResponse.body)).toEqual(expectedErrorBody); | ||
| }); | ||
| test("GET /models/{modelId}/occupationGroups/{id}/children should respond with BAD_REQUEST if modelId is missing", async () => { | ||
| const givenOccupationGroupId = getMockStringId(2); | ||
|
|
||
| const givenEvent = { | ||
| httpMethod: HTTP_VERBS.GET, | ||
| headers: {}, | ||
| pathParameters: { id: givenOccupationGroupId.toString() }, | ||
| queryStringParameters: {}, | ||
| path: `/models//occupationGroups/${givenOccupationGroupId}/children`, | ||
| } as never; | ||
| // AND role check passes for anonymous access | ||
| checkRole.mockResolvedValueOnce(true); | ||
|
|
||
| // WHEN the occupation group handler is invoked with the given event | ||
| const actualResponse = await occupationGroupHandler(givenEvent as never); | ||
|
|
||
| // THEN expect the handler to return the BAD_REQUEST status | ||
| expect(actualResponse.statusCode).toEqual(StatusCodes.BAD_REQUEST); | ||
| // AND the response body contains the error information | ||
| const parsedMissing = JSON.parse(actualResponse.body); | ||
| expect(parsedMissing).toMatchObject({ | ||
| errorCode: | ||
| OccupationGroupAPISpecs.GET.Enums.Response.Status500.ErrorCodes.DB_FAILED_TO_RETRIEVE_OCCUPATION_GROUPS, | ||
| message: "modelId is missing in the path", | ||
| }); | ||
| expect(typeof parsedMissing.details).toBe("string"); | ||
| }); | ||
| test("GET /models/{modelId}/occupationGroups/{id}/children should respond with INTERNAL_SERVER_ERROR if repository throws an error", async () => { | ||
| // GIVEN a valid request with modelId and occupationGroup ID | ||
| const givenModel: IModelInfo = { | ||
| ...getIModelInfoMockData(1), | ||
| UUID: "foo", | ||
| UUIDHistory: ["foo"], | ||
| released: false, | ||
| }; | ||
| const givenOccupationGroupId = getMockStringId(2); | ||
| const givenEvent = { | ||
| httpMethod: HTTP_VERBS.GET, | ||
| headers: {}, | ||
| pathParameters: { modelId: givenModel.id.toString(), id: givenOccupationGroupId.toString() }, | ||
| queryStringParameters: {}, | ||
| path: `/models/${givenModel.id}/occupationGroups/${givenOccupationGroupId}/children`, | ||
| } as never; | ||
| // AND User has the required role | ||
| checkRole.mockResolvedValue(true); | ||
|
|
||
| // AND a repository that will throw an error | ||
| const givenOccupationGroupRepositoryMock = { | ||
| Model: undefined as never, | ||
| hierarchyModel: undefined as never, | ||
| create: jest.fn().mockResolvedValue(null), | ||
| createMany: jest.fn().mockResolvedValue([]), | ||
| findById: jest.fn().mockRejectedValue(null), | ||
| findAll: jest.fn().mockResolvedValue(null), | ||
| findPaginated: jest.fn().mockResolvedValue({ items: [], nextCursor: null }), | ||
| getOccupationGroupByUUID: jest.fn().mockResolvedValue(null), | ||
| getHistory: jest.fn().mockResolvedValue([]), | ||
| findParent: jest.fn().mockResolvedValue(null), | ||
| findChildren: jest.fn().mockResolvedValue(new Error("Database connection failed")), | ||
| }; | ||
| jest.spyOn(getRepositoryRegistry(), "OccupationGroup", "get").mockReturnValue(givenOccupationGroupRepositoryMock); | ||
| const givenOccupationGroupServiceMock = { | ||
| create: jest.fn(), | ||
| findById: jest.fn().mockResolvedValue(null), | ||
| findPaginated: jest.fn().mockResolvedValue({ items: [], nextCursor: null }), | ||
| validateModelForOccupationGroup: jest.fn(), | ||
| findChildren: jest.fn().mockResolvedValue(new Error("foo")), | ||
| findParent: jest.fn().mockRejectedValue(null), | ||
| } as IOccupationGroupService; | ||
| const mockServiceRegistry = mockGetServiceRegistry(); | ||
| mockServiceRegistry.occupationGroup = givenOccupationGroupServiceMock; | ||
| // WHEN the occupationGroup handler is invoked with the given event | ||
| const actualResponse = await occupationGroupHandler(givenEvent); | ||
|
|
||
| // THEN respond with the INTERNAL_SERVER_ERROR status | ||
| expect(actualResponse.statusCode).toEqual(StatusCodes.INTERNAL_SERVER_ERROR); | ||
| // AND the response body contains the error information | ||
| const expectedErrorBody: ErrorAPISpecs.Types.Payload = { | ||
| errorCode: | ||
| OccupationGroupAPISpecs.OccupationGroup.Children.GET.Enums.Response.Status500.ErrorCodes | ||
| .DB_FAILED_TO_RETRIEVE_OCCUPATION_GROUP_CHILDREN, | ||
| message: "Failed to retrieve the occupation group children from the DB", | ||
| details: "", | ||
| }; | ||
| expect(JSON.parse(actualResponse.body)).toEqual(expectedErrorBody); | ||
| }); | ||
| test("should route GET children to getChildrenHandler", async () => { | ||
| const event = { httpMethod: HTTP_VERBS.GET, path: "/models/1/occupationGroups/2/children" } as APIGatewayProxyEvent; | ||
| const response = await handler(event); | ||
| expect(getChildrenHandler).toHaveBeenCalledWith(event); | ||
| expect(response.body).toBe("GET_CHILDREN"); | ||
| }); |
Comment on lines
+62
to
67
| test("should handle missing path in GET request", async () => { | ||
| const event = { httpMethod: HTTP_VERBS.GET } as unknown as APIGatewayProxyEvent; | ||
| const response = await handler(event); | ||
| expect(getHandler).toHaveBeenCalledWith(event); | ||
| expect(response.body).toBe("GET"); | ||
| }); |
Comment on lines
+22
to
+23
| createdAt, | ||
| }); |
…root unit test for router
This file contains hidden or 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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
OccupationGroupindex.test.tsfor testing endpoint functions and also refactoring the other endpoint tests for coverage testSkillGroupindex.test.tsrefactored and distributed over the submodules