Skip to content

Commit

Permalink
Merge pull request #29 from bcgov/feat/post-in-person-visits
Browse files Browse the repository at this point in the history
POST In Person Visits
  • Loading branch information
hannah-macdonald1 authored Nov 15, 2024
2 parents 8340318 + d4e567b commit de8a38e
Show file tree
Hide file tree
Showing 19 changed files with 521 additions and 9 deletions.
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ CLIENT_SECRET='secret here'
UPSTREAM_BASE_URL=http://www.google.com
SUPPORT_NETWORK_ENDPOINT=/endpoint/path/here
IN_PERSON_VISITS_ENDPOINT=/endpoint/path/here
IN_PERSON_VISITS_POST_ENDPOINT=/endpoint/path/here
CASE_ENDPOINT=/endpoint/path/here
INCIDENT_ENDPOINT=/endpoint/path/here
SR_ENDPOINT=/endpoint/path/here
Expand Down
5 changes: 5 additions & 0 deletions helm/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,11 @@ spec:
secretKeyRef:
name: visitz-api
key: ATTACHMENTS_ENDPOINT
- name: POST_IN_PERSON_VISITS_ENDPOINT
valueFrom:
secretKeyRef:
name: visitz-api
key: POST_IN_PERSON_VISITS_ENDPOINT
- name: VPI_APP_LABEL
value: {{ .Values.vpiAppBuildLabel.version }}
restartPolicy: Always
15 changes: 14 additions & 1 deletion src/common/constants/enumerations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,17 @@ const RecordEntityMap = {
[RecordType.SR]: EntityType.SR,
} as const;

export { RecordType, EntityType, RecordEntityMap };
enum VisitDetails {
PrivateVisitZeroToFive = 'Private visit age 0-5',
PrivateVisitInHome = 'Private visit in home',
PrivateVisitMedSupportNeeds = 'Private visit medical or support needs',
PrivateVisitNotInHome = 'Private visit not in home',
ExemptionVisitChildDeclined = 'Exemption to private visit - Child declined to meet',
ExemptionVisitOther = 'Exemption to private visit - Other',
NotPrivatePlanMeeting = 'Not private - Planning meeting',
NotPrivateRelation = 'Not private - Relational visit',
NotPrivateInHome = 'Not private - Visit in the home',
NotPrivateCaregiver = 'Not private - Visit with caregiver',
}

export { RecordType, EntityType, RecordEntityMap, VisitDetails };
10 changes: 10 additions & 0 deletions src/common/constants/upstream-constants.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
const baseUrlEnvVarName = 'UPSTREAM_BASE_URL';
const supportNetworkEndpointEnvVarName = 'SUPPORT_NETWORK_ENDPOINT';
const inPersonVisitsEndpointEnvVarName = 'IN_PERSON_VISITS_ENDPOINT';
const postInPersonVisitsEndpointEnvVarName = 'IN_PERSON_VISITS_POST_ENDPOINT';
const attachmentsEndpointEnvVarName = 'ATTACHMENTS_ENDPOINT';
const idirUsernameHeaderField = 'x-idir-username';
const upstreamDateFormat = 'MM/dd/yyyy HH:mm:ss';
const childVisitType = 'In Person Child Youth';
const childVisitIdirFieldName = 'Login Name';
const childVisitEntityIdFieldName = 'Parent Id';

export {
baseUrlEnvVarName,
supportNetworkEndpointEnvVarName,
inPersonVisitsEndpointEnvVarName,
postInPersonVisitsEndpointEnvVarName,
attachmentsEndpointEnvVarName,
idirUsernameHeaderField,
upstreamDateFormat,
childVisitType,
childVisitIdirFieldName,
childVisitEntityIdFieldName,
};
1 change: 1 addition & 0 deletions src/configuration/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export default () => ({
supportNetwork: undefined,
inPersonVisits: undefined,
attachments: 'int_lab',
postInPersonVisits: 'int_lab',
},
sinceFieldName: {
supportNetwork: 'Updated',
Expand Down
37 changes: 37 additions & 0 deletions src/controllers/cases/cases.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,18 @@ import { RequestPreparerService } from '../../external-api/request-preparer/requ
import {
InPersonVisitsEntity,
InPersonVisitsSingleResponseCaseExample,
NestedInPersonVisitsEntity,
PostInPersonVisitResponseExample,
} from '../../entities/in-person-visits.entity';
import { idName } from '../../common/constants/parameter-constants';
import { AttachmentsService } from '../../helpers/attachments/attachments.service';
import {
AttachmentsSingleResponseCaseExample,
AttachmentsEntity,
} from '../../entities/attachments.entity';
import { VisitDetails } from '../../common/constants/enumerations';
import { getMockReq } from '@jest-mock/express';
import { idirUsernameHeaderField } from '../../common/constants/upstream-constants';

describe('CasesController', () => {
let controller: CasesController;
Expand Down Expand Up @@ -113,6 +118,38 @@ describe('CasesController', () => {
);
});

describe('postSingleCaseInPersonVisitRecord tests', () => {
it.each([
[
{
'Date of visit': '2024-11-13T21:24:03',
'Visit Details Value': VisitDetails.NotPrivateInHome,
'Visit Description': 'comment',
},
{ [idName]: 'test' } as IdPathParams,
'idir',
PostInPersonVisitResponseExample,
],
])(
'should return single values given good input',
async (body, idPathParams, idir, data) => {
const casesServiceSpy = jest
.spyOn(casesService, 'postSingleCaseInPersonVisitRecord')
.mockReturnValueOnce(
Promise.resolve(new NestedInPersonVisitsEntity(data)),
);

const result = await controller.postSingleCaseInPersonVisitRecord(
body,
idPathParams,
getMockReq({ headers: { [idirUsernameHeaderField]: idir } }),
);
expect(casesServiceSpy).toHaveBeenCalledWith(body, idir, idPathParams);
expect(result).toEqual(new NestedInPersonVisitsEntity(data));
},
);
});

describe('getSingleCaseAttachmentRecord tests', () => {
it.each([
[
Expand Down
51 changes: 51 additions & 0 deletions src/controllers/cases/cases.controller.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import {
Body,
ClassSerializerInterceptor,
Controller,
Get,
Param,
Post,
Query,
Req,
UseGuards,
UseInterceptors,
ValidationPipe,
} from '@nestjs/common';
import {
ApiCreatedResponse,
ApiExtraModels,
ApiInternalServerErrorResponse,
ApiNotFoundResponse,
Expand Down Expand Up @@ -38,13 +42,17 @@ import {
InPersonVisitsListResponseCaseExample,
InPersonVisitsSingleResponseCaseExample,
NestedInPersonVisitsEntity,
PostInPersonVisitResponseExample,
} from '../../entities/in-person-visits.entity';
import {
AttachmentsEntity,
AttachmentsListResponseCaseExample,
AttachmentsSingleResponseCaseExample,
NestedAttachmentsEntity,
} from '../../entities/attachments.entity';
import { PostInPersonVisitDto } from '../../dto/post-in-person-visit.dto';
import { idirUsernameHeaderField } from '../../common/constants/upstream-constants';
import { Request } from 'express';

@Controller('case')
@UseGuards(AuthGuard)
Expand Down Expand Up @@ -156,6 +164,49 @@ export class CasesController {
return await this.casesService.getSingleCaseInPersonVisitRecord(id, since);
}

@UseInterceptors(ClassSerializerInterceptor)
@Post(`:${idName}/visits`)
@ApiOperation({
description:
'Create an in person visit record related to the given case id.',
})
@ApiCreatedResponse({
content: {
[CONTENT_TYPE]: {
examples: {
InPersonVisitCreatedResponse: {
value: PostInPersonVisitResponseExample,
},
},
},
},
})
async postSingleCaseInPersonVisitRecord(
@Body(
new ValidationPipe({
transform: true,
transformOptions: { enableImplicitConversion: true },
whitelist: true,
}),
)
inPersonVisitDto: PostInPersonVisitDto,
@Param(
new ValidationPipe({
transform: true,
transformOptions: { enableImplicitConversion: true },
forbidNonWhitelisted: true,
}),
)
id: IdPathParams,
@Req() req: Request,
): Promise<NestedInPersonVisitsEntity> {
return await this.casesService.postSingleCaseInPersonVisitRecord(
inPersonVisitDto,
req.headers[idirUsernameHeaderField] as string,
id,
);
}

@UseInterceptors(ClassSerializerInterceptor)
@Get(`:${idName}/attachments`)
@ApiOperation({
Expand Down
36 changes: 35 additions & 1 deletion src/controllers/cases/cases.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ import {
} from '../../entities/support-network.entity';
import { IdPathParams } from '../../dto/id-path-params.dto';
import { SinceQueryParams } from '../../dto/since-query-params.dto';
import { RecordType } from '../../common/constants/enumerations';
import { RecordType, VisitDetails } from '../../common/constants/enumerations';
import { TokenRefresherService } from '../../external-api/token-refresher/token-refresher.service';
import { InPersonVisitsService } from '../../helpers/in-person-visits/in-person-visits.service';
import { RequestPreparerService } from '../../external-api/request-preparer/request-preparer.service';
import {
InPersonVisitsEntity,
InPersonVisitsSingleResponseCaseExample,
NestedInPersonVisitsEntity,
PostInPersonVisitResponseExample,
} from '../../entities/in-person-visits.entity';
import {
casesAttachmentsFieldName,
Expand Down Expand Up @@ -130,6 +132,38 @@ describe('CasesService', () => {
);
});

describe('postSingleCaseInPersonVisitRecord tests', () => {
it.each([
[
{
'Date of visit': '11/08/2024 08:24:11',
'Visit Details Value': VisitDetails.NotPrivateInHome,
'Visit Description': 'comment',
},
'idir',
{ [idName]: 'test' } as IdPathParams,
PostInPersonVisitResponseExample,
],
])(
'should return single values given good input',
async (body, idir, idPathParams, data) => {
const InPersonVisitsSpy = jest
.spyOn(inPersonVisitsService, 'postSingleInPersonVisitRecord')
.mockReturnValueOnce(
Promise.resolve(new NestedInPersonVisitsEntity(data)),
);

const result = await service.postSingleCaseInPersonVisitRecord(
body,
idir,
idPathParams,
);
expect(InPersonVisitsSpy).toHaveBeenCalledTimes(1);
expect(result).toEqual(new NestedInPersonVisitsEntity(data));
},
);
});

describe('getSingleCaseAttachmentRecord tests', () => {
it.each([
[
Expand Down
29 changes: 28 additions & 1 deletion src/controllers/cases/cases.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,18 @@ import {
AttachmentsEntity,
NestedAttachmentsEntity,
} from '../../entities/attachments.entity';
import { casesAttachmentsFieldName } from '../../common/constants/parameter-constants';
import {
casesAttachmentsFieldName,
idName,
} from '../../common/constants/parameter-constants';
import {
PostInPersonVisitDto,
PostInPersonVisitDtoUpstream,
} from '../../dto/post-in-person-visit.dto';
import {
childVisitEntityIdFieldName,
childVisitIdirFieldName,
} from '../../common/constants/upstream-constants';

@Injectable()
export class CasesService {
Expand Down Expand Up @@ -49,6 +60,22 @@ export class CasesService {
);
}

async postSingleCaseInPersonVisitRecord(
inPersonVisitsDto: PostInPersonVisitDto,
idir: string,
id: IdPathParams,
): Promise<NestedInPersonVisitsEntity> {
const body = new PostInPersonVisitDtoUpstream({
...inPersonVisitsDto,
[childVisitIdirFieldName]: idir,
[childVisitEntityIdFieldName]: id[idName],
});
return await this.inPersonVisitsService.postSingleInPersonVisitRecord(
RecordType.Case,
body,
);
}

async getSingleCaseAttachmentRecord(
id: IdPathParams,
since?: SinceQueryParams,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
AttachmentsEntity,
AttachmentsSingleResponseSRExample,
} from '../../entities/attachments.entity';
import { AuthService } from '../../common/guards/auth/auth.service';

import { AuthService } from '../../common/guards/auth/auth.service';

Expand Down
24 changes: 24 additions & 0 deletions src/dto/post-in-person-visit.dto.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { plainToInstance } from 'class-transformer';
import { PostInPersonVisitDto } from './post-in-person-visit.dto';
import { VisitDetails } from '../common/constants/enumerations';

describe('PostInPersonVisitDto transform tests', () => {
it.each([
['2024-10-24T22:16:24+0000', '10/24/2024 22:16:24'],
['2020-12-31', '12/31/2020 00:00:00'],
])(
`should transform the date when given good, past ISO-8601 input`,
(date, expected) => {
const postInPersonVisit = {
'Date of visit': date,
'Visit Description': 'comment',
'Visit Details Value': VisitDetails.ExemptionVisitOther,
};
const postInPersonVisitDto = plainToInstance(
PostInPersonVisitDto,
postInPersonVisit,
);
expect(postInPersonVisitDto['Date of visit']).toBe(expected);
},
);
});
Loading

0 comments on commit de8a38e

Please sign in to comment.