diff --git a/pkg/clinical/application/common/defaults.go b/pkg/clinical/application/common/defaults.go index 46844914..88f5ddc8 100644 --- a/pkg/clinical/application/common/defaults.go +++ b/pkg/clinical/application/common/defaults.go @@ -38,6 +38,9 @@ const ( // OrganizationTopicName is the topic where organization(facility) details are published to OrganizationTopicName = "organization.create" + // TenantTopicName is the topic where program is registered in clinical as a tenant + TenantTopicName = "mycarehub.tenant.create" + // MedicalDataCount is the count of medical records MedicalDataCount = "3" diff --git a/pkg/clinical/application/dto/enums.go b/pkg/clinical/application/dto/enums.go index b5fd637a..872cff31 100644 --- a/pkg/clinical/application/dto/enums.go +++ b/pkg/clinical/application/dto/enums.go @@ -5,6 +5,7 @@ type OrganizationIdentifierType string const ( SladeCode OrganizationIdentifierType = "SladeCode" MFLCode OrganizationIdentifierType = "MFLCode" + ProgramID OrganizationIdentifierType = "MCHProgram" Other OrganizationIdentifierType = "Other" ) diff --git a/pkg/clinical/infrastructure/infrastructure.go b/pkg/clinical/infrastructure/infrastructure.go index 50bcde12..99070132 100644 --- a/pkg/clinical/infrastructure/infrastructure.go +++ b/pkg/clinical/infrastructure/infrastructure.go @@ -67,6 +67,8 @@ type IServiceMyCareHub interface { fhirID string, facilityID string, ) error + + UpdateProgramFHIRTenantID(ctx context.Context, programID string, tenantID string) error } // Infrastructure ... diff --git a/pkg/clinical/infrastructure/services/mycarehub/mock/service_mock.go b/pkg/clinical/infrastructure/services/mycarehub/mock/service_mock.go index a0500acf..5e99af1a 100644 --- a/pkg/clinical/infrastructure/services/mycarehub/mock/service_mock.go +++ b/pkg/clinical/infrastructure/services/mycarehub/mock/service_mock.go @@ -22,6 +22,8 @@ type FakeMyCareHubService struct { fhirID string, facilityID string, ) error + + MockUpdateProgramFHIRTenantIDFn func(ctx context.Context, programID string, tenantID string) error } // NewFakeMyCareHubServiceMock initializes a new instance of mycarehub mock @@ -62,6 +64,9 @@ func NewFakeMyCareHubServiceMock() *FakeMyCareHubService { ) error { return nil }, + MockUpdateProgramFHIRTenantIDFn: func(ctx context.Context, programID, tenantID string) error { + return nil + }, } } @@ -90,3 +95,8 @@ func (s *FakeMyCareHubService) AddFHIRIDToFacility( ) error { return s.MockAddFHIRIDToFacilityFn(ctx, fhirID, facilityID) } + +// UpdateProgramFHIRTenantID amocks the update of program mycarehub's program fhir tenant id +func (s *FakeMyCareHubService) UpdateProgramFHIRTenantID(ctx context.Context, programID string, tenantID string) error { + return s.MockUpdateProgramFHIRTenantIDFn(ctx, programID, tenantID) +} diff --git a/pkg/clinical/infrastructure/services/mycarehub/service.go b/pkg/clinical/infrastructure/services/mycarehub/service.go index 6e2af80a..06945338 100644 --- a/pkg/clinical/infrastructure/services/mycarehub/service.go +++ b/pkg/clinical/infrastructure/services/mycarehub/service.go @@ -12,9 +12,10 @@ import ( ) const ( - getUserProfile = "internal/user-profile/%s" - addFHIRIDToProfile = "internal/add-fhir-id" - addFHIRIDToFacility = "internal/facilities" + getUserProfile = "internal/user-profile/%s" + addFHIRIDToProfile = "internal/add-fhir-id" + addFHIRIDToFacility = "internal/facilities" + updateProgramTenantID = "internal/program" ) // ServiceMyCareHubImpl represents mycarehub usecases @@ -149,3 +150,29 @@ func (s ServiceMyCareHubImpl) AddFHIRIDToFacility( return nil } + +// UpdateProgramFHIRTenantID makes an isc call and updates mycarehub's tenant id in a certain program +func (s ServiceMyCareHubImpl) UpdateProgramFHIRTenantID(ctx context.Context, programID string, tenantID string) error { + type requestPayload struct { + ProgramID string `json:"programID"` + FHIRTenantID string `json:"fhirTenantID"` + } + + resp, err := s.MyCareHubClient.MakeRequest( + ctx, + http.MethodPost, + updateProgramTenantID, + &requestPayload{ProgramID: programID, FHIRTenantID: tenantID}, + ) + if err != nil { + return fmt.Errorf("failed to make a request to mycarehub service: %w", err) + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("failed to update program tenant ID : %w, with status code %v", err, resp.StatusCode) + } + + return nil +} diff --git a/pkg/clinical/infrastructure/services/mycarehub/service_test.go b/pkg/clinical/infrastructure/services/mycarehub/service_test.go index 8d0ba111..c7bd5559 100644 --- a/pkg/clinical/infrastructure/services/mycarehub/service_test.go +++ b/pkg/clinical/infrastructure/services/mycarehub/service_test.go @@ -328,3 +328,97 @@ func TestServiceMyCareHubImpl_AddFHIRIDToFacility(t *testing.T) { }) } } + +func TestServiceMyCareHubImpl_UpdateProgramFHIRTenantID(t *testing.T) { + type args struct { + ctx context.Context + programID string + tenantID string + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "Happy case: update program tenant id", + args: args{ + ctx: context.Background(), + programID: gofakeit.UUID(), + tenantID: gofakeit.UUID(), + }, + wantErr: false, + }, + { + name: "Sad Case: failed to make request", + args: args{ + ctx: context.Background(), + programID: gofakeit.UUID(), + tenantID: gofakeit.UUID(), + }, + wantErr: true, + }, + { + name: "Sad case: unable to update program tenant id", + args: args{ + ctx: context.Background(), + programID: gofakeit.UUID(), + tenantID: gofakeit.UUID(), + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fakeISC := extMock.NewFakeISCExtensionMock() + s := ServiceMyCareHubImpl{ + MyCareHubClient: fakeISC, + } + + if tt.name == "Happy case: update program tenant id" { + fakeISC.MockMakeRequestFn = func(ctx context.Context, method, path string, body interface{}) (*http.Response, error) { + requestBody := struct { + ProgramID string `json:"programID"` + FHIRTenantID string `json:"fhirTenantID"` + }{ + ProgramID: tt.args.programID, + FHIRTenantID: tt.args.tenantID, + } + + jsonBody, err := json.Marshal(requestBody) + if err != nil { + return nil, err + } + return &http.Response{ + Status: "200", + StatusCode: 200, + Body: io.NopCloser(bytes.NewReader(jsonBody)), + }, nil + } + } + if tt.name == "Sad Case: failed to make request" { + fakeISC.MockMakeRequestFn = func(ctx context.Context, method, path string, body interface{}) (*http.Response, error) { + return nil, fmt.Errorf("an error occurred") + } + } + + if tt.name == "Sad case: unable to update program tenant id" { + fakeISC.MockMakeRequestFn = func(ctx context.Context, method, path string, body interface{}) (*http.Response, error) { + jsonBody, err := json.Marshal(map[string]interface{}{"error": "error"}) + if err != nil { + return nil, err + } + return &http.Response{ + Status: "400", + StatusCode: 400, + Body: io.NopCloser(bytes.NewReader(jsonBody)), + }, nil + } + } + + if err := s.UpdateProgramFHIRTenantID(tt.args.ctx, tt.args.programID, tt.args.tenantID); (err != nil) != tt.wantErr { + t.Errorf("ServiceMyCareHubImpl.UpdateProgramFHIRTenantID() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/pkg/clinical/infrastructure/services/pubsub/service.go b/pkg/clinical/infrastructure/services/pubsub/service.go index 298808b9..e340d602 100644 --- a/pkg/clinical/infrastructure/services/pubsub/service.go +++ b/pkg/clinical/infrastructure/services/pubsub/service.go @@ -81,6 +81,7 @@ func (ps ServicePubSubMessaging) TopicIDs() []string { ps.AddPubSubNamespace(common.TestResultTopicName, common.ClinicalServiceName), ps.AddPubSubNamespace(common.TestOrderTopicName, common.ClinicalServiceName), ps.AddPubSubNamespace(common.OrganizationTopicName, common.ClinicalServiceName), + ps.AddPubSubNamespace(common.TenantTopicName, common.ClinicalServiceName), } } diff --git a/pkg/clinical/presentation/rest/handlers.go b/pkg/clinical/presentation/rest/handlers.go index 90e41517..db741b55 100644 --- a/pkg/clinical/presentation/rest/handlers.go +++ b/pkg/clinical/presentation/rest/handlers.go @@ -172,6 +172,29 @@ func (p PresentationHandlersImpl) ReceivePubSubPushMessage(c *gin.Context) { return } + case utils.AddPubSubNamespace(common.TenantTopicName, common.ClinicalServiceName): + var data dto.OrganizationInput + + err := json.Unmarshal(message.Message.Data, &data) + if err != nil { + serverutils.WriteJSONResponse(c.Writer, errorcodeutil.CustomError{ + Err: err, + Message: err.Error(), + }, http.StatusBadRequest) + + return + } + + err = p.usecases.CreatePubsubTenant(ctx, data) + if err != nil { + serverutils.WriteJSONResponse(c.Writer, errorcodeutil.CustomError{ + Err: err, + Message: err.Error(), + }, http.StatusBadRequest) + + return + } + case utils.AddPubSubNamespace(common.TestResultTopicName, common.ClinicalServiceName): var data dto.CreatePatientTestResultPubSubMessage diff --git a/pkg/clinical/presentation/rest/handlers_test.go b/pkg/clinical/presentation/rest/handlers_test.go index 8fbf59f1..8cd0e720 100644 --- a/pkg/clinical/presentation/rest/handlers_test.go +++ b/pkg/clinical/presentation/rest/handlers_test.go @@ -24,6 +24,7 @@ import ( fakeOCLMock "github.com/savannahghi/clinical/pkg/clinical/infrastructure/services/openconceptlab/mock" "github.com/savannahghi/clinical/pkg/clinical/presentation" "github.com/savannahghi/clinical/pkg/clinical/usecases" + "github.com/savannahghi/interserviceclient" "github.com/savannahghi/pubsubtools" "github.com/savannahghi/serverutils" ) @@ -173,6 +174,16 @@ func TestPresentationHandlersImpl_ReceivePubSubPushMessage(t *testing.T) { wantStatus: http.StatusOK, wantErr: false, }, + { + name: "happy case: publish tenant message", + args: args{ + url: "/pubsub", + httpMethod: http.MethodPost, + body: nil, + }, + wantStatus: http.StatusOK, + wantErr: false, + }, { name: "sad case: publish create results message", args: args{ @@ -511,6 +522,31 @@ func TestPresentationHandlersImpl_ReceivePubSubPushMessage(t *testing.T) { } } + if tt.name == "happy case: publish tenant message" { + message := dto.OrganizationInput{ + Name: gofakeit.BeerName(), + PhoneNumber: interserviceclient.TestUserPhoneNumber, + Identifiers: []dto.OrganizationIdentifier{ + { + Type: dto.OrganizationIdentifierType("MCHProgram"), + Value: gofakeit.UUID(), + }, + }, + } + data, _ := json.Marshal(message) + fakeExt.MockVerifyPubSubJWTAndDecodePayloadFn = func(w http.ResponseWriter, r *http.Request) (*pubsubtools.PubSubPayload, error) { + return &pubsubtools.PubSubPayload{ + Message: pubsubtools.PubSubMessage{ + Data: data, + }, + }, nil + } + + fakeExt.MockGetPubSubTopicFn = func(m *pubsubtools.PubSubPayload) (string, error) { + return utils.AddPubSubNamespace(common.TenantTopicName, common.ClinicalServiceName), nil + } + } + if tt.name == "sad case: publish create results message" { concept := "1234" msg := dto.CreatePatientTestResultPubSubMessage{ diff --git a/pkg/clinical/usecases/clinical/pubsub.go b/pkg/clinical/usecases/clinical/pubsub.go index 86299a27..86d077be 100644 --- a/pkg/clinical/usecases/clinical/pubsub.go +++ b/pkg/clinical/usecases/clinical/pubsub.go @@ -75,6 +75,7 @@ func (c *UseCasesClinicalImpl) CreatePubsubPatient(ctx context.Context, payload return nil } +// CreatePubsubOrganization creates a FHIR organisation resource func (c *UseCasesClinicalImpl) CreatePubsubOrganization(ctx context.Context, data dto.CreateFacilityPubSubMessage) error { use := domain.ContactPointUseEnumWork rank := int64(1) @@ -107,6 +108,7 @@ func (c *UseCasesClinicalImpl) CreatePubsubOrganization(ctx context.Context, dat return nil } +// CreatePubsubVitals creates FHIR observation vitals. func (c *UseCasesClinicalImpl) CreatePubsubVitals(ctx context.Context, data dto.CreateVitalSignPubSubMessage) error { input, err := c.ComposeVitalsInput(ctx, data) if err != nil { @@ -121,6 +123,7 @@ func (c *UseCasesClinicalImpl) CreatePubsubVitals(ctx context.Context, data dto. return nil } +// CreatePubsubAllergyIntolerance creates FHIR allergy intolerance func (c *UseCasesClinicalImpl) CreatePubsubAllergyIntolerance(ctx context.Context, data dto.CreatePatientAllergyPubSubMessage) error { input, err := c.ComposeAllergyIntoleranceInput(ctx, data) if err != nil { @@ -135,6 +138,7 @@ func (c *UseCasesClinicalImpl) CreatePubsubAllergyIntolerance(ctx context.Contex return nil } +// CreatePubsubTestResult creates a test result as an observation func (c *UseCasesClinicalImpl) CreatePubsubTestResult(ctx context.Context, data dto.CreatePatientTestResultPubSubMessage) error { input, err := c.ComposeTestResultInput(ctx, data) if err != nil { @@ -149,6 +153,7 @@ func (c *UseCasesClinicalImpl) CreatePubsubTestResult(ctx context.Context, data return nil } +// CreatePubsubMedicationStatement creates a FHIR medication statement func (c *UseCasesClinicalImpl) CreatePubsubMedicationStatement(ctx context.Context, data dto.CreateMedicationPubSubMessage) error { input, err := c.ComposeMedicationStatementInput(ctx, data) if err != nil { @@ -162,3 +167,22 @@ func (c *UseCasesClinicalImpl) CreatePubsubMedicationStatement(ctx context.Conte return nil } + +// CreatePubsubTenant registers a tenant in this service +func (c *UseCasesClinicalImpl) CreatePubsubTenant(ctx context.Context, data dto.OrganizationInput) error { + if data.Identifiers[0].Type != dto.OrganizationIdentifierType("MCHProgram") { + return fmt.Errorf("invalid identifier type %v", data.Identifiers[0].Type) + } + + organization, err := c.RegisterTenant(ctx, data) + if err != nil { + return err + } + + err = c.infrastructure.MyCareHub.UpdateProgramFHIRTenantID(ctx, data.Identifiers[0].Value, organization.ID) + if err != nil { + return err + } + + return nil +} diff --git a/pkg/clinical/usecases/clinical/pubsub_test.go b/pkg/clinical/usecases/clinical/pubsub_test.go index 5f27485b..986086aa 100644 --- a/pkg/clinical/usecases/clinical/pubsub_test.go +++ b/pkg/clinical/usecases/clinical/pubsub_test.go @@ -948,3 +948,93 @@ func TestUseCasesClinicalImpl_getConcept(t *testing.T) { }) } } + +func TestUseCasesClinicalImpl_CreatePubsubTenant(t *testing.T) { + type args struct { + ctx context.Context + data dto.OrganizationInput + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "Happy case: create tenant", + args: args{ + ctx: nil, + data: dto.OrganizationInput{ + Name: "test", + PhoneNumber: "test", + Identifiers: []dto.OrganizationIdentifier{ + { + Type: "MCHProgram", + Value: gofakeit.UUID(), + }, + }, + }, + }, + wantErr: false, + }, + { + name: "Sad case: unable to create tenant", + args: args{ + ctx: nil, + data: dto.OrganizationInput{ + Name: "test", + PhoneNumber: "test", + Identifiers: []dto.OrganizationIdentifier{ + { + Type: "other", + Value: gofakeit.UUID(), + }, + }, + }, + }, + wantErr: true, + }, + { + name: "Sad case: unable to update fhir patient id", + args: args{ + ctx: nil, + data: dto.OrganizationInput{ + Name: "test", + PhoneNumber: "test", + Identifiers: []dto.OrganizationIdentifier{ + { + Type: "MCHProgram", + Value: gofakeit.UUID(), + }, + }, + }, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fakeExt := fakeExtMock.NewFakeBaseExtensionMock() + fakeFHIR := fakeFHIRMock.NewFHIRMock() + fakeOCL := fakeOCLMock.NewFakeOCLMock() + fakeMCH := fakeMyCarehubMock.NewFakeMyCareHubServiceMock() + + infra := infrastructure.NewInfrastructureInteractor(fakeExt, fakeFHIR, fakeOCL, fakeMCH) + c := clinicalUsecase.NewUseCasesClinicalImpl(infra) + + if tt.name == "Sad case: unable to create tenant" { + fakeFHIR.MockCreateFHIROrganizationFn = func(ctx context.Context, input domain.FHIROrganizationInput) (*domain.FHIROrganizationRelayPayload, error) { + return nil, fmt.Errorf("error") + } + } + + if tt.name == "Sad case: unable to update fhir patient id" { + fakeMCH.MockUpdateProgramFHIRTenantIDFn = func(ctx context.Context, programID, tenantID string) error { + return fmt.Errorf("error") + } + } + if err := c.CreatePubsubTenant(tt.args.ctx, tt.args.data); (err != nil) != tt.wantErr { + t.Errorf("UseCasesClinicalImpl.CreatePubsubTenant() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/pkg/clinical/usecases/usecases.go b/pkg/clinical/usecases/usecases.go index d16c727b..d147cd0e 100644 --- a/pkg/clinical/usecases/usecases.go +++ b/pkg/clinical/usecases/usecases.go @@ -29,6 +29,7 @@ type Clinical interface { CreatePubsubAllergyIntolerance(ctx context.Context, data dto.CreatePatientAllergyPubSubMessage) error CreatePubsubTestResult(ctx context.Context, data dto.CreatePatientTestResultPubSubMessage) error CreatePubsubMedicationStatement(ctx context.Context, data dto.CreateMedicationPubSubMessage) error + CreatePubsubTenant(ctx context.Context, data dto.OrganizationInput) error CreatePatient(ctx context.Context, input dto.PatientInput) (*dto.Patient, error)