From 74a97f8c240c54c191cfebead8860d0d626aaeae Mon Sep 17 00:00:00 2001 From: Charles Muchogo Date: Wed, 8 Mar 2023 14:04:20 +0300 Subject: [PATCH] chore: add search resource helper method (#155) --- .../datastore/cloudhealthcare/config_test.go | 24 - .../datastore/cloudhealthcare/fhir.go | 231 ++------- .../cloudhealthcare/fhir_unit_test.go | 458 ++---------------- .../cloudhealthcare/fhirdataset/dataset.go | 109 ++++- .../fhirdataset/mock/dataset_mock.go | 97 +--- .../datastore/cloudhealthcare/helpers.go | 120 ----- .../cloudhealthcare/mock/fhir_mock.go | 7 +- pkg/clinical/repository/repository.go | 3 +- 8 files changed, 202 insertions(+), 847 deletions(-) delete mode 100644 pkg/clinical/infrastructure/datastore/cloudhealthcare/config_test.go diff --git a/pkg/clinical/infrastructure/datastore/cloudhealthcare/config_test.go b/pkg/clinical/infrastructure/datastore/cloudhealthcare/config_test.go deleted file mode 100644 index 0e4fdc95..00000000 --- a/pkg/clinical/infrastructure/datastore/cloudhealthcare/config_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package fhir_test - -import ( - "log" - "os" - "testing" -) - -func TestMain(m *testing.M) { - os.Setenv("ENVIRONMENT", "staging") - os.Setenv("ROOT_COLLECTION_SUFFIX", "staging") - os.Setenv("CLOUD_HEALTH_PUBSUB_TOPIC", "healthcloud-bewell-staging") - os.Setenv("CLOUD_HEALTH_DATASET_ID", "sghi-healthcare-staging") - os.Setenv("CLOUD_HEALTH_FHIRSTORE_ID", "sghi-healthcare-fhir-staging") - os.Setenv("REPOSITORY", "firebase") - - // run the tests - log.Printf("about to run tests\n") - code := m.Run() - log.Printf("finished running tests\n") - - // cleanup here - os.Exit(code) -} diff --git a/pkg/clinical/infrastructure/datastore/cloudhealthcare/fhir.go b/pkg/clinical/infrastructure/datastore/cloudhealthcare/fhir.go index 4384f004..3f8f5036 100644 --- a/pkg/clinical/infrastructure/datastore/cloudhealthcare/fhir.go +++ b/pkg/clinical/infrastructure/datastore/cloudhealthcare/fhir.go @@ -4,16 +4,13 @@ import ( "context" "encoding/json" "fmt" - "net/url" "time" "github.com/labstack/gommon/log" "github.com/mitchellh/mapstructure" "github.com/savannahghi/clinical/pkg/clinical/application/common/helpers" "github.com/savannahghi/clinical/pkg/clinical/domain" - dataset "github.com/savannahghi/clinical/pkg/clinical/infrastructure/datastore/cloudhealthcare/fhirdataset" "github.com/savannahghi/converterandformatter" - "github.com/savannahghi/firebasetools" "github.com/savannahghi/scalarutils" ) @@ -39,14 +36,26 @@ const ( medicationResourceType = "Medication" ) +// Dataset ... +type Dataset interface { + CreateFHIRResource(resourceType string, payload map[string]interface{}) ([]byte, error) + DeleteFHIRResource(resourceType, fhirResourceID string) ([]byte, error) + PatchFHIRResource(resourceType, fhirResourceID string, payload []map[string]interface{}) ([]byte, error) + UpdateFHIRResource(resourceType, fhirResourceID string, payload map[string]interface{}) ([]byte, error) + SearchFHIRResource(resourceType string, params map[string]interface{}) ([]map[string]interface{}, error) + + GetFHIRPatientAllData(fhirResourceID string) ([]byte, error) + GetFHIRResource(resourceType, fhirResourceID string) ([]byte, error) +} + // StoreImpl represents the FHIR infrastructure implementation type StoreImpl struct { - Dataset dataset.FHIRRepository + Dataset Dataset } // NewFHIRStoreImpl initializes the new FHIR implementation func NewFHIRStoreImpl( - dataset dataset.FHIRRepository, + dataset Dataset, ) *StoreImpl { return &StoreImpl{ Dataset: dataset, @@ -61,45 +70,21 @@ func (fh StoreImpl) Encounters( patientReference string, status *domain.EncounterStatusEnum, ) ([]*domain.FHIREncounter, error) { - searchParams := url.Values{} - if status != nil { - searchParams.Add("status:exact", status.String()) + params := map[string]interface{}{ + "patient": patientReference, } - searchParams.Add("patient", patientReference) - - bs, err := fh.Dataset.POSTRequest(encounterResourceType, "_search", searchParams, nil) - if err != nil { - return nil, fmt.Errorf("unable to search for encounter: %w", err) + if status != nil { + params["status:exact"] = status.String() } - respMap, err := responseToMap(bs) + resources, err := fh.Dataset.SearchFHIRResource(encounterResourceType, params) if err != nil { return nil, err } output := []*domain.FHIREncounter{} - respEntries := respMap["entry"] - if respEntries == nil { - return output, nil - } - entries, ok := respEntries.([]interface{}) - if !ok { - return nil, fmt.Errorf("search: entries is not a list of maps, it is: %T", respEntries) - } - for _, en := range entries { - entry, ok := en.(map[string]interface{}) - if !ok { - return nil, fmt.Errorf("expected each entry to be map, they are %T instead", en) - } - expectedKeys := []string{"fullUrl", "resource", "search"} - for _, k := range expectedKeys { - _, found := entry[k] - if !found { - return nil, fmt.Errorf("search entry does not have key '%s'", k) - } - } - resource := entry["resource"] + for _, resource := range resources { var encounter domain.FHIREncounter resourceBs, err := json.Marshal(resource) if err != nil { @@ -111,6 +96,7 @@ func (fh StoreImpl) Encounters( } output = append(output, &encounter) } + return output, nil } @@ -119,7 +105,7 @@ func (fh StoreImpl) SearchFHIREpisodeOfCare(ctx context.Context, params map[stri output := domain.FHIREpisodeOfCareRelayConnection{} - resources, err := fh.searchFilterHelper(ctx, episodeOfCareResourceType, params) + resources, err := fh.Dataset.SearchFHIRResource(episodeOfCareResourceType, params) if err != nil { return nil, err } @@ -260,7 +246,7 @@ func (fh StoreImpl) CreateFHIROrganization(ctx context.Context, input domain.FHI // SearchFHIROrganization provides a search API for FHIROrganization func (fh StoreImpl) SearchFHIROrganization(ctx context.Context, params map[string]interface{}) (*domain.FHIROrganizationRelayConnection, error) { output := domain.FHIROrganizationRelayConnection{} - resources, err := fh.searchFilterHelper(ctx, organizationResource, params) + resources, err := fh.Dataset.SearchFHIRResource(organizationResource, params) if err != nil { return nil, err } @@ -307,41 +293,15 @@ func (fh StoreImpl) FindOrganizationByID(ctx context.Context, organizationID str } // SearchEpisodesByParam search episodes by params -func (fh StoreImpl) SearchEpisodesByParam(ctx context.Context, searchParams url.Values) ([]*domain.FHIREpisodeOfCare, error) { - bs, err := fh.Dataset.POSTRequest(episodeOfCareResourceType, "_search", searchParams, nil) - if err != nil { - return nil, fmt.Errorf("unable to search for episode of care: %w", err) - } - - respMap, err := responseToMap(bs) +func (fh StoreImpl) SearchEpisodesByParam(ctx context.Context, searchParams map[string]interface{}) ([]*domain.FHIREpisodeOfCare, error) { + resources, err := fh.Dataset.SearchFHIRResource(episodeOfCareResourceType, searchParams) if err != nil { return nil, err } output := []*domain.FHIREpisodeOfCare{} - respEntries := respMap["entry"] - if respEntries == nil { - return output, nil - } - entries, ok := respEntries.([]interface{}) - if !ok { - return nil, fmt.Errorf("search: entries is not a list of maps, it is: %T", respEntries) - } - - for _, en := range entries { - entry, ok := en.(map[string]interface{}) - if !ok { - return nil, fmt.Errorf("expected each entry to be map, they are %T instead", en) - } - expectedKeys := []string{"fullUrl", "resource", "search"} - for _, k := range expectedKeys { - _, found := entry[k] - if !found { - return nil, fmt.Errorf("search entry does not have key '%s'", k) - } - } - resource := entry["resource"].(map[string]interface{}) + for _, resource := range resources { period := resource["period"].(map[string]interface{}) // parse period->start as map[string]interface{} @@ -376,12 +336,13 @@ func (fh StoreImpl) SearchEpisodesByParam(ctx context.Context, searchParams url. } // OpenEpisodes returns the IDs of a patient's open episodes -func (fh StoreImpl) OpenEpisodes( - ctx context.Context, patientReference string) ([]*domain.FHIREpisodeOfCare, error) { - searchParams := url.Values{} - searchParams.Add("status:exact", domain.EpisodeOfCareStatusEnumActive.String()) - searchParams.Add("patient", patientReference) - return fh.SearchEpisodesByParam(ctx, searchParams) +func (fh StoreImpl) OpenEpisodes(ctx context.Context, patientReference string) ([]*domain.FHIREpisodeOfCare, error) { + params := map[string]interface{}{ + "status:exact": domain.EpisodeOfCareStatusEnumActive.String(), + "patient": patientReference, + } + + return fh.SearchEpisodesByParam(ctx, params) } // HasOpenEpisode determines if a patient has an open episode @@ -596,83 +557,25 @@ func (fh *StoreImpl) EndEpisode( return true, nil } -func responseToMap(response []byte) (map[string]interface{}, error) { - respMap := make(map[string]interface{}) - err := json.Unmarshal(response, &respMap) - if err != nil { - return nil, fmt.Errorf( - "unable to unmarshal search response: %w", err) - } - - mandatoryKeys := []string{"resourceType", "type", "total", "link"} - for _, k := range mandatoryKeys { - _, found := respMap[k] - if !found { - return nil, fmt.Errorf("search response does not have key '%s'", k) - } - } - resourceType, ok := respMap["resourceType"].(string) - if !ok { - return nil, fmt.Errorf("search: the resourceType is not a string") - } - if resourceType != "Bundle" { - return nil, fmt.Errorf("search: the resourceType value is not 'Bundle' as expected") - } - resultType, ok := respMap["type"].(string) - if !ok { - return nil, fmt.Errorf("search: the type is not a string") - } - if resultType != "searchset" { - return nil, fmt.Errorf("search: the type value is not 'searchset' as expected") - } - - return respMap, nil -} - // GetActiveEpisode returns any ACTIVE episode that has to the indicated ID func (fh *StoreImpl) GetActiveEpisode(ctx context.Context, episodeID string) (*domain.FHIREpisodeOfCare, error) { - - searchParams := url.Values{} - searchParams.Add("status:exact", domain.EpisodeOfCareStatusEnumActive.String()) - searchParams.Add("_id", episodeID) // logical ID of the resource - - bs, err := fh.Dataset.POSTRequest(episodeOfCareResourceType, "_search", searchParams, nil) - if err != nil { - return nil, fmt.Errorf("unable to search for episode of care: %w", err) + params := map[string]interface{}{ + "status:exact": domain.EpisodeOfCareStatusEnumActive.String(), + "_id": episodeID, } - respMap, err := responseToMap(bs) + resources, err := fh.Dataset.SearchFHIRResource(episodeOfCareResourceType, params) if err != nil { return nil, err } - respEntries := respMap["entry"] - if respEntries == nil { - return nil, fmt.Errorf("there is no ACTIVE episode with the ID %s", episodeID) - } - entries, ok := respEntries.([]interface{}) - if !ok { - return nil, fmt.Errorf("search: entries is not a list of maps, it is: %T", respEntries) - } - if len(entries) != 1 { + if len(resources) != 1 { return nil, fmt.Errorf( - "expected exactly one ACTIVE episode for episode ID %s, got %d", episodeID, len(entries)) + "expected exactly one ACTIVE episode for episode ID %s, got %d", episodeID, len(resources)) } - entry, ok := entries[0].(map[string]interface{}) - if !ok { - return nil, fmt.Errorf("expected each entry to be map, they are %T instead", entry) - } - expectedKeys := []string{"fullUrl", "resource", "search"} - for _, k := range expectedKeys { - _, found := entry[k] - if !found { - return nil, fmt.Errorf("search entry does not have key '%s'", k) - } - } - resource := entry["resource"] var episode domain.FHIREpisodeOfCare - resourceBs, err := json.Marshal(resource) + resourceBs, err := json.Marshal(resources[0]) if err != nil { return nil, fmt.Errorf("unable to marshal resource to JSON: %w", err) } @@ -688,7 +591,7 @@ func (fh *StoreImpl) SearchFHIRServiceRequest(ctx context.Context, params map[st output := domain.FHIRServiceRequestRelayConnection{} - resources, err := fh.searchFilterHelper(ctx, serviceRequestResourceType, params) + resources, err := fh.Dataset.SearchFHIRResource(serviceRequestResourceType, params) if err != nil { return nil, err } @@ -744,7 +647,7 @@ func (fh *StoreImpl) CreateFHIRServiceRequest(ctx context.Context, input domain. func (fh *StoreImpl) SearchFHIRAllergyIntolerance(ctx context.Context, params map[string]interface{}) (*domain.FHIRAllergyIntoleranceRelayConnection, error) { output := domain.FHIRAllergyIntoleranceRelayConnection{} - resources, err := fh.searchFilterHelper(ctx, allergyIntoleranceResourceType, params) + resources, err := fh.Dataset.SearchFHIRResource(allergyIntoleranceResourceType, params) if err != nil { return nil, err } @@ -832,7 +735,7 @@ func (fh *StoreImpl) UpdateFHIRAllergyIntolerance(ctx context.Context, input dom func (fh *StoreImpl) SearchFHIRComposition(ctx context.Context, params map[string]interface{}) (*domain.FHIRCompositionRelayConnection, error) { output := domain.FHIRCompositionRelayConnection{} - resources, err := fh.searchFilterHelper(ctx, compositionResourceType, params) + resources, err := fh.Dataset.SearchFHIRResource(compositionResourceType, params) if err != nil { return nil, err } @@ -932,7 +835,7 @@ func (fh *StoreImpl) DeleteFHIRComposition(ctx context.Context, id string) (bool func (fh *StoreImpl) SearchFHIRCondition(ctx context.Context, params map[string]interface{}) (*domain.FHIRConditionRelayConnection, error) { output := domain.FHIRConditionRelayConnection{} - resources, err := fh.searchFilterHelper(ctx, conditionResourceType, params) + resources, err := fh.Dataset.SearchFHIRResource(conditionResourceType, params) if err != nil { return nil, err } @@ -1014,7 +917,7 @@ func (fh *StoreImpl) GetFHIREncounter(ctx context.Context, id string) (*domain.F func (fh *StoreImpl) SearchFHIREncounter(ctx context.Context, params map[string]interface{}) (*domain.FHIREncounterRelayConnection, error) { output := domain.FHIREncounterRelayConnection{} - resources, err := fh.searchFilterHelper(ctx, encounterResourceType, params) + resources, err := fh.Dataset.SearchFHIRResource(encounterResourceType, params) if err != nil { return nil, err } @@ -1043,7 +946,7 @@ func (fh *StoreImpl) SearchFHIREncounter(ctx context.Context, params map[string] func (fh *StoreImpl) SearchFHIRMedicationRequest(ctx context.Context, params map[string]interface{}) (*domain.FHIRMedicationRequestRelayConnection, error) { output := domain.FHIRMedicationRequestRelayConnection{} - resources, err := fh.searchFilterHelper(ctx, medicationRequestResourceType, params) + resources, err := fh.Dataset.SearchFHIRResource(medicationRequestResourceType, params) if err != nil { return nil, err } @@ -1143,7 +1046,7 @@ func (fh *StoreImpl) DeleteFHIRMedicationRequest(ctx context.Context, id string) func (fh *StoreImpl) SearchFHIRObservation(ctx context.Context, params map[string]interface{}) (*domain.FHIRObservationRelayConnection, error) { output := domain.FHIRObservationRelayConnection{} - resources, err := fh.searchFilterHelper(ctx, observationResourceType, params) + resources, err := fh.Dataset.SearchFHIRResource(observationResourceType, params) if err != nil { return nil, err } @@ -1473,7 +1376,7 @@ func (fh *StoreImpl) CreateFHIRMedication(ctx context.Context, input domain.FHIR func (fh *StoreImpl) SearchFHIRMedicationStatement(ctx context.Context, params map[string]interface{}) (*domain.FHIRMedicationStatementRelayConnection, error) { output := domain.FHIRMedicationStatementRelayConnection{} - resources, err := fh.searchFilterHelper(ctx, medicationStatementResourceType, params) + resources, err := fh.Dataset.SearchFHIRResource(medicationStatementResourceType, params) if err != nil { return nil, err } @@ -1570,46 +1473,17 @@ func (fh *StoreImpl) UpdateFHIREpisodeOfCare(ctx context.Context, fhirResourceID // SearchFHIRPatient searches for a FHIR patient func (fh *StoreImpl) SearchFHIRPatient(ctx context.Context, searchParams string) (*domain.PatientConnection, error) { - params := url.Values{} - params.Add("_content", searchParams) - - bs, err := fh.Dataset.POSTRequest(patientResourceType, "_search", params, nil) - if err != nil { - return nil, fmt.Errorf("unable to search for patient: %w", err) + params := map[string]interface{}{ + "_content": searchParams, } - respMap, err := responseToMap(bs) + resources, err := fh.Dataset.SearchFHIRResource(patientResourceType, params) if err != nil { return nil, err } - respEntries := respMap["entry"] - if respEntries == nil { - return &domain.PatientConnection{ - Edges: []*domain.PatientEdge{}, - PageInfo: &firebasetools.PageInfo{}, - }, nil - } - entries, ok := respEntries.([]interface{}) - if !ok { - return nil, fmt.Errorf("could not find a patient with the provided parameters") - } - output := domain.PatientConnection{} - for _, en := range entries { - entry, ok := en.(map[string]interface{}) - if !ok { - return nil, fmt.Errorf("expected each entry to be map, they are %T instead", en) - } - expectedKeys := []string{"fullUrl", "resource", "search"} - for _, k := range expectedKeys { - _, found := entry[k] - if !found { - return nil, fmt.Errorf("could not find a patient with the provided parameters") - } - } - - resource := entry["resource"].(map[string]interface{}) + for _, resource := range resources { resource = birthdateMapper(resource) resource = identifierMapper(resource) @@ -1635,5 +1509,6 @@ func (fh *StoreImpl) SearchFHIRPatient(ctx context.Context, searchParams string) HasOpenEpisodes: hasOpenEpisodes, }) } + return &output, nil } diff --git a/pkg/clinical/infrastructure/datastore/cloudhealthcare/fhir_unit_test.go b/pkg/clinical/infrastructure/datastore/cloudhealthcare/fhir_unit_test.go index 8114a1cb..acdfe427 100644 --- a/pkg/clinical/infrastructure/datastore/cloudhealthcare/fhir_unit_test.go +++ b/pkg/clinical/infrastructure/datastore/cloudhealthcare/fhir_unit_test.go @@ -3,8 +3,6 @@ package fhir_test import ( "context" "fmt" - "io" - "net/url" "testing" "github.com/brianvoe/gofakeit" @@ -17,97 +15,6 @@ import ( fakeDataset "github.com/savannahghi/clinical/pkg/clinical/infrastructure/datastore/cloudhealthcare/fhirdataset/mock" ) -func TestStoreImpl_CreateEpisodeOfCare_Unittest(t *testing.T) { - - UUID := uuid.New().String() - PatientRef := "Patient/1" - OrgRef := "Organization/1" - status := domain.EpisodeOfCareStatusEnumFinished - - type args struct { - ctx context.Context - episode domain.FHIREpisodeOfCare - } - tests := []struct { - name string - args args - wantErr bool - }{ - { - name: "Happy case", - args: args{ - ctx: context.Background(), - episode: domain.FHIREpisodeOfCare{ - ID: &UUID, - Text: &domain.FHIRNarrative{}, - Identifier: []*domain.FHIRIdentifier{}, - Status: &(status), - StatusHistory: []*domain.FHIREpisodeofcareStatushistory{}, - Type: []*domain.FHIRCodeableConcept{}, - Diagnosis: []*domain.FHIREpisodeofcareDiagnosis{}, - Patient: &domain.FHIRReference{ - Reference: &PatientRef, - }, - ManagingOrganization: &domain.FHIRReference{ - Reference: &OrgRef, - }, - Period: &domain.FHIRPeriod{}, - ReferralRequest: []*domain.FHIRReference{}, - CareManager: &domain.FHIRReference{}, - Team: []*domain.FHIRReference{}, - Account: []*domain.FHIRReference{}, - }, - }, - wantErr: false, - }, - - { - name: "Sad case", - args: args{ - ctx: context.Background(), - episode: domain.FHIREpisodeOfCare{ - ID: nil, - Text: &domain.FHIRNarrative{}, - Identifier: []*domain.FHIRIdentifier{}, - StatusHistory: []*domain.FHIREpisodeofcareStatushistory{}, - Type: []*domain.FHIRCodeableConcept{}, - Diagnosis: []*domain.FHIREpisodeofcareDiagnosis{}, - Patient: &domain.FHIRReference{ - Reference: &PatientRef, - }, - ManagingOrganization: &domain.FHIRReference{ - Reference: &OrgRef, - }, - Period: &domain.FHIRPeriod{}, - ReferralRequest: []*domain.FHIRReference{}, - CareManager: &domain.FHIRReference{}, - Team: []*domain.FHIRReference{}, - Account: []*domain.FHIRReference{}, - }, - }, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var d = fakeDataset.NewFakeFHIRRepositoryMock() - - h := FHIR.NewFHIRStoreImpl(d) - - if tt.name == "Sad case" { - d.MockCreateFHIRResourceFn = func(resourceType string, payload map[string]interface{}) ([]byte, error) { - return nil, fmt.Errorf("error") - } - } - _, err := h.CreateEpisodeOfCare(tt.args.ctx, tt.args.episode) - if (err != nil) != tt.wantErr { - t.Errorf("FHIRStoreImpl.CreateEpisodeOfCare() error = %v, wantErr %v", err, tt.wantErr) - return - } - }) - } -} - func TestStoreImpl_CreateFHIRCondition(t *testing.T) { ID := uuid.New().String() @@ -190,15 +97,15 @@ func TestStoreImpl_CreateFHIRCondition(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - var d = fakeDataset.NewFakeFHIRRepositoryMock() - h := FHIR.NewFHIRStoreImpl(d) + dataset := fakeDataset.NewFakeFHIRRepositoryMock() + fh := FHIR.NewFHIRStoreImpl(dataset) if tt.name == "Sad case" { - d.MockCreateFHIRResourceFn = func(resourceType string, payload map[string]interface{}) ([]byte, error) { + dataset.MockCreateFHIRResourceFn = func(resourceType string, payload map[string]interface{}) ([]byte, error) { return nil, fmt.Errorf("error") } } - _, err := h.CreateFHIRCondition(tt.args.ctx, tt.args.input) + _, err := fh.CreateFHIRCondition(tt.args.ctx, tt.args.input) if (err != nil) != tt.wantErr { t.Errorf("StoreImpl.CreateFHIRCondition() error = %v, wantErr %v", err, tt.wantErr) return @@ -263,18 +170,18 @@ func TestStoreImpl_CreateFHIROrganization_Unittest(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - var d = fakeDataset.NewFakeFHIRRepositoryMock() + dataset := fakeDataset.NewFakeFHIRRepositoryMock() - h := FHIR.NewFHIRStoreImpl(d) + fh := FHIR.NewFHIRStoreImpl(dataset) if tt.name == "Sad case" { if tt.name == "Sad case" { - d.MockCreateFHIRResourceFn = func(resourceType string, payload map[string]interface{}) ([]byte, error) { + dataset.MockCreateFHIRResourceFn = func(resourceType string, payload map[string]interface{}) ([]byte, error) { return nil, fmt.Errorf("error") } } } - _, err := h.CreateFHIROrganization(tt.args.ctx, tt.args.input) + _, err := fh.CreateFHIROrganization(tt.args.ctx, tt.args.input) if (err != nil) != tt.wantErr { t.Errorf("FHIRUseCaseImpl.CreateFHIROrganization() error = %v, wantErr %v", err, tt.wantErr) return @@ -306,17 +213,17 @@ func TestStoreImpl_FindOrganizationByID_Unittest(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - var d = fakeDataset.NewFakeFHIRRepositoryMock() + dataset := fakeDataset.NewFakeFHIRRepositoryMock() - h := FHIR.NewFHIRStoreImpl(d) + fh := FHIR.NewFHIRStoreImpl(dataset) if tt.name == "Sad case" { - d.MockGetFHIRResourceFn = func(resourceType string, id string) ([]byte, error) { + dataset.MockGetFHIRResourceFn = func(resourceType string, id string) ([]byte, error) { return nil, fmt.Errorf("an error occurred") } } - got, err := h.FindOrganizationByID(tt.args.ctx, tt.args.organizationID) + got, err := fh.FindOrganizationByID(tt.args.ctx, tt.args.organizationID) if (err != nil) != tt.wantErr { t.Errorf("FHIRUseCaseImpl.FindOrganizationByID() error = %v, wantErr %v", err, tt.wantErr) return @@ -333,215 +240,6 @@ func TestStoreImpl_FindOrganizationByID_Unittest(t *testing.T) { } } -func TestStoreImpl_SearchEpisodesByParam(t *testing.T) { - - type args struct { - ctx context.Context - searchParams url.Values - } - tests := []struct { - name string - args args - wantErr bool - }{ - { - name: "Happy case", - args: args{ - ctx: context.Background(), - searchParams: map[string][]string{ - "patient": {"12233"}, - }, - }, - wantErr: false, - }, - { - name: "Sad case", - args: args{ - ctx: context.Background(), - }, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var d = fakeDataset.NewFakeFHIRRepositoryMock() - - h := FHIR.NewFHIRStoreImpl(d) - - if tt.name == "Sad case" { - d.MockPOSTRequestFn = func(resourceName, path string, params url.Values, body io.Reader) ([]byte, error) { - return nil, fmt.Errorf("an error occurred") - } - } - _, err := h.SearchEpisodesByParam(tt.args.ctx, tt.args.searchParams) - if (err != nil) != tt.wantErr { - t.Errorf("StoreImpl.SearchEpisodesByParam() error = %v, wantErr %v", err, tt.wantErr) - return - } - }) - } -} - -func TestStoreImpl_SearchFHIREpisodeOfCare(t *testing.T) { - - type args struct { - ctx context.Context - params map[string]interface{} - } - tests := []struct { - name string - args args - wantErr bool - }{ - { - name: "Happy case", - args: args{ - ctx: context.Background(), - params: map[string]interface{}{ - "patient": "12233", - }, - }, - wantErr: false, - }, - { - name: "Sad case", - args: args{ - ctx: context.Background(), - params: map[string]interface{}{ - "patient": "12233", - }, - }, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var d = fakeDataset.NewFakeFHIRRepositoryMock() - - h := FHIR.NewFHIRStoreImpl(d) - - if tt.name == "Sad case" { - d.MockPOSTRequestFn = func(resourceName, path string, params url.Values, body io.Reader) ([]byte, error) { - return nil, fmt.Errorf("an error occurred") - } - } - _, err := h.SearchFHIREpisodeOfCare(tt.args.ctx, tt.args.params) - if (err != nil) != tt.wantErr { - t.Errorf("StoreImpl.SearchFHIREpisodeOfCare() error = %v, wantErr %v", err, tt.wantErr) - return - } - }) - } -} - -func TestStoreImpl_OpenEpisodes(t *testing.T) { - - patientReference := fmt.Sprintf("Patient/%s", ksuid.New().String()) - - type args struct { - ctx context.Context - patientReference string - } - tests := []struct { - name string - args args - wantErr bool - }{ - { - name: "Happy case", - args: args{ - ctx: context.Background(), - patientReference: patientReference, - }, - wantErr: false, - }, - { - name: "Sad case", - args: args{ - ctx: context.Background(), - patientReference: patientReference, - }, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var d = fakeDataset.NewFakeFHIRRepositoryMock() - - h := FHIR.NewFHIRStoreImpl(d) - - if tt.name == "Sad case" { - d.MockPOSTRequestFn = func(resourceName, path string, params url.Values, body io.Reader) ([]byte, error) { - return nil, fmt.Errorf("an error occurred") - } - } - _, err := h.OpenEpisodes(tt.args.ctx, tt.args.patientReference) - if (err != nil) != tt.wantErr { - t.Errorf("FHIRUseCaseImpl.OpenEpisodes() error = %v, wantErr %v", err, tt.wantErr) - return - } - }) - } -} - -func TestStoreImpl_HasOpenEpisode(t *testing.T) { - - UUID := uuid.New().String() - - patient := domain.FHIRPatient{ - ID: &UUID, - } - - type args struct { - ctx context.Context - patient domain.FHIRPatient - } - tests := []struct { - name string - args args - want bool - wantErr bool - }{ - - { - name: "Happy case", - args: args{ - ctx: context.Background(), - patient: patient, - }, - wantErr: false, - want: true, - }, - { - name: "Sad case", - args: args{ - ctx: context.Background(), - patient: patient, - }, - wantErr: true, - want: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var d = fakeDataset.NewFakeFHIRRepositoryMock() - - h := FHIR.NewFHIRStoreImpl(d) - - if tt.name == "Sad case" { - d.MockPOSTRequestFn = func(resourceName, path string, params url.Values, body io.Reader) ([]byte, error) { - return nil, fmt.Errorf("an error occurred") - } - } - _, err := h.HasOpenEpisode(tt.args.ctx, tt.args.patient) - if (err != nil) != tt.wantErr { - t.Errorf("FHIRUseCaseImpl.HasOpenEpisode() error = %v, wantErr %v", err, tt.wantErr) - return - } - }) - } -} - func TestStoreImpl_CreateFHIREncounter(t *testing.T) { input := domain.FHIREncounterInput{} @@ -574,16 +272,16 @@ func TestStoreImpl_CreateFHIREncounter(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - var d = fakeDataset.NewFakeFHIRRepositoryMock() + dataset := fakeDataset.NewFakeFHIRRepositoryMock() - h := FHIR.NewFHIRStoreImpl(d) + fh := FHIR.NewFHIRStoreImpl(dataset) if tt.name == "Sad case" { - d.MockCreateFHIRResourceFn = func(resourceType string, payload map[string]interface{}) ([]byte, error) { + dataset.MockCreateFHIRResourceFn = func(resourceType string, payload map[string]interface{}) ([]byte, error) { return nil, fmt.Errorf("an error occurred") } } - _, err := h.CreateFHIREncounter(tt.args.ctx, tt.args.input) + _, err := fh.CreateFHIREncounter(tt.args.ctx, tt.args.input) if (err != nil) != tt.wantErr { t.Errorf("FHIRUseCaseImpl.CreateFHIREncounter() error = %v, wantErr %v", err, tt.wantErr) return @@ -625,16 +323,16 @@ func TestStoreImpl_GetFHIREpisodeOfCare(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - var d = fakeDataset.NewFakeFHIRRepositoryMock() + dataset := fakeDataset.NewFakeFHIRRepositoryMock() - h := FHIR.NewFHIRStoreImpl(d) + fh := FHIR.NewFHIRStoreImpl(dataset) if tt.name == "Sad case" { - d.MockGetFHIRResourceFn = func(resourceType, fhirResourceID string) ([]byte, error) { + dataset.MockGetFHIRResourceFn = func(resourceType, fhirResourceID string) ([]byte, error) { return nil, fmt.Errorf("an error occurred") } } - _, err := h.GetFHIREpisodeOfCare(tt.args.ctx, tt.args.id) + _, err := fh.GetFHIREpisodeOfCare(tt.args.ctx, tt.args.id) if (err != nil) != tt.wantErr { t.Errorf("FHIRUseCaseImpl.GetFHIREpisodeOfCare() error = %v, wantErr %v", err, tt.wantErr) return @@ -669,16 +367,16 @@ func TestClinicalUseCaseImpl_StartEncounter(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - var d = fakeDataset.NewFakeFHIRRepositoryMock() + dataset := fakeDataset.NewFakeFHIRRepositoryMock() - h := FHIR.NewFHIRStoreImpl(d) + fh := FHIR.NewFHIRStoreImpl(dataset) if tt.name == "Sad case" { - d.MockGetFHIRResourceFn = func(resourceType, fhirResourceID string) ([]byte, error) { + dataset.MockGetFHIRResourceFn = func(resourceType, fhirResourceID string) ([]byte, error) { return nil, fmt.Errorf("an error occurred") } } - _, err := h.StartEncounter(tt.args.ctx, tt.args.episodeID) + _, err := fh.StartEncounter(tt.args.ctx, tt.args.episodeID) if (err != nil) != tt.wantErr { t.Errorf("ClinicalUseCaseImpl.StartEncounter() error = %v, wantErr %v", err, tt.wantErr) return @@ -717,16 +415,16 @@ func TestStoreImpl_GetFHIREncounter(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - var d = fakeDataset.NewFakeFHIRRepositoryMock() + dataset := fakeDataset.NewFakeFHIRRepositoryMock() - h := FHIR.NewFHIRStoreImpl(d) + fh := FHIR.NewFHIRStoreImpl(dataset) if tt.name == "Sad case" { - d.MockGetFHIRResourceFn = func(resourceType, fhirResourceID string) ([]byte, error) { + dataset.MockGetFHIRResourceFn = func(resourceType, fhirResourceID string) ([]byte, error) { return nil, fmt.Errorf("an error occurred") } } - _, err := h.GetFHIREncounter(tt.args.ctx, tt.args.id) + _, err := fh.GetFHIREncounter(tt.args.ctx, tt.args.id) if (err != nil) != tt.wantErr { t.Errorf("StoreImpl.GetFHIREncounter() error = %v, wantErr %v", err, tt.wantErr) return @@ -768,16 +466,16 @@ func TestStoreImpl_CreateFHIRServiceRequest(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - var d = fakeDataset.NewFakeFHIRRepositoryMock() + dataset := fakeDataset.NewFakeFHIRRepositoryMock() - h := FHIR.NewFHIRStoreImpl(d) + fh := FHIR.NewFHIRStoreImpl(dataset) if tt.name == "Sad case" { - d.MockCreateFHIRResourceFn = func(resourceType string, payload map[string]interface{}) ([]byte, error) { + dataset.MockCreateFHIRResourceFn = func(resourceType string, payload map[string]interface{}) ([]byte, error) { return nil, fmt.Errorf("an error occurred") } } - _, err := h.CreateFHIRServiceRequest(tt.args.ctx, tt.args.input) + _, err := fh.CreateFHIRServiceRequest(tt.args.ctx, tt.args.input) if (err != nil) != tt.wantErr { t.Errorf("StoreImpl.CreateFHIRServiceRequest() error = %v, wantErr %v", err, tt.wantErr) return @@ -819,16 +517,16 @@ func TestStoreImpl_CreateFHIRAllergyIntolerance(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - var d = fakeDataset.NewFakeFHIRRepositoryMock() + dataset := fakeDataset.NewFakeFHIRRepositoryMock() - h := FHIR.NewFHIRStoreImpl(d) + fh := FHIR.NewFHIRStoreImpl(dataset) if tt.name == "Sad case" { - d.MockCreateFHIRResourceFn = func(resourceType string, payload map[string]interface{}) ([]byte, error) { + dataset.MockCreateFHIRResourceFn = func(resourceType string, payload map[string]interface{}) ([]byte, error) { return nil, fmt.Errorf("an error occurred") } } - _, err := h.CreateFHIRAllergyIntolerance(tt.args.ctx, tt.args.input) + _, err := fh.CreateFHIRAllergyIntolerance(tt.args.ctx, tt.args.input) if (err != nil) != tt.wantErr { t.Errorf("StoreImpl.CreateFHIRAllergyIntolerance() error = %v, wantErr %v", err, tt.wantErr) return @@ -874,16 +572,16 @@ func TestStoreImpl_UpdateFHIRAllergyIntolerance(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - var d = fakeDataset.NewFakeFHIRRepositoryMock() + dataset := fakeDataset.NewFakeFHIRRepositoryMock() - h := FHIR.NewFHIRStoreImpl(d) + fh := FHIR.NewFHIRStoreImpl(dataset) if tt.name == "Sad case" { - d.MockUpdateFHIRResourceFn = func(resourceType, fhirResourceID string, payload map[string]interface{}) ([]byte, error) { + dataset.MockUpdateFHIRResourceFn = func(resourceType, fhirResourceID string, payload map[string]interface{}) ([]byte, error) { return nil, fmt.Errorf("an error occurred") } } - _, err := h.UpdateFHIRAllergyIntolerance(tt.args.ctx, tt.args.input) + _, err := fh.UpdateFHIRAllergyIntolerance(tt.args.ctx, tt.args.input) if (err != nil) != tt.wantErr { t.Errorf("FHIRUseCaseImpl.UpdateFHIRAllergyIntolerance() error = %v, wantErr %v", err, tt.wantErr) return @@ -892,58 +590,6 @@ func TestStoreImpl_UpdateFHIRAllergyIntolerance(t *testing.T) { } } -func TestStoreImpl_SearchFHIRComposition(t *testing.T) { - - params := map[string]interface{}{"test": "123"} - - type args struct { - ctx context.Context - params map[string]interface{} - } - tests := []struct { - name string - args args - want *domain.FHIRCompositionRelayConnection - wantErr bool - }{ - { - name: "Happy case", - args: args{ - ctx: context.Background(), - params: params, - }, - wantErr: false, - }, - { - name: "Sad case", - args: args{ - ctx: context.Background(), - params: params, - }, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var d = fakeDataset.NewFakeFHIRRepositoryMock() - - h := FHIR.NewFHIRStoreImpl(d) - - if tt.name == "Sad case" { - d.MockPOSTRequestFn = func(resourceName, path string, params url.Values, body io.Reader) ([]byte, error) { - return nil, fmt.Errorf("an error occurred") - } - } - - _, err := h.SearchFHIRComposition(tt.args.ctx, tt.args.params) - if (err != nil) != tt.wantErr { - t.Errorf("FHIRUseCaseImpl.SearchFHIRComposition() error = %v, wantErr %v", err, tt.wantErr) - return - } - }) - } -} - func TestStoreImpl_CreateFHIRComposition(t *testing.T) { input := domain.FHIRCompositionInput{} @@ -977,17 +623,17 @@ func TestStoreImpl_CreateFHIRComposition(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - var d = fakeDataset.NewFakeFHIRRepositoryMock() + dataset := fakeDataset.NewFakeFHIRRepositoryMock() - h := FHIR.NewFHIRStoreImpl(d) + fh := FHIR.NewFHIRStoreImpl(dataset) if tt.name == "Sad case" { - d.MockCreateFHIRResourceFn = func(resourceType string, payload map[string]interface{}) ([]byte, error) { + dataset.MockCreateFHIRResourceFn = func(resourceType string, payload map[string]interface{}) ([]byte, error) { return nil, fmt.Errorf("an error occurred") } } - _, err := h.CreateFHIRComposition(tt.args.ctx, tt.args.input) + _, err := fh.CreateFHIRComposition(tt.args.ctx, tt.args.input) if (err != nil) != tt.wantErr { t.Errorf("FHIRUseCaseImpl.CreateFHIRComposition() error = %v, wantErr %v", err, tt.wantErr) return @@ -1033,16 +679,16 @@ func TestStoreImpl_UpdateFHIRComposition(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - var d = fakeDataset.NewFakeFHIRRepositoryMock() + dataset := fakeDataset.NewFakeFHIRRepositoryMock() - h := FHIR.NewFHIRStoreImpl(d) + fh := FHIR.NewFHIRStoreImpl(dataset) if tt.name == "Sad case" { - d.MockUpdateFHIRResourceFn = func(resourceType, fhirResourceID string, payload map[string]interface{}) ([]byte, error) { + dataset.MockUpdateFHIRResourceFn = func(resourceType, fhirResourceID string, payload map[string]interface{}) ([]byte, error) { return nil, fmt.Errorf("an error occurred") } } - _, err := h.UpdateFHIRComposition(tt.args.ctx, tt.args.input) + _, err := fh.UpdateFHIRComposition(tt.args.ctx, tt.args.input) if (err != nil) != tt.wantErr { t.Errorf("FHIRUseCaseImpl.UpdateFHIRComposition() error = %v, wantErr %v", err, tt.wantErr) return @@ -1086,16 +732,16 @@ func TestStoreImpl_DeleteFHIRComposition(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - var d = fakeDataset.NewFakeFHIRRepositoryMock() + dataset := fakeDataset.NewFakeFHIRRepositoryMock() - h := FHIR.NewFHIRStoreImpl(d) + fh := FHIR.NewFHIRStoreImpl(dataset) if tt.name == "Sad case" { - d.MockDeleteFHIRResourceFn = func(resourceType, fhirResourceID string) ([]byte, error) { + dataset.MockDeleteFHIRResourceFn = func(resourceType, fhirResourceID string) ([]byte, error) { return nil, fmt.Errorf("an error occurred") } } - got, err := h.DeleteFHIRComposition(tt.args.ctx, tt.args.id) + got, err := fh.DeleteFHIRComposition(tt.args.ctx, tt.args.id) if (err != nil) != tt.wantErr { t.Errorf("FHIRUseCaseImpl.DeleteFHIRComposition() error = %v, wantErr %v", err, tt.wantErr) return diff --git a/pkg/clinical/infrastructure/datastore/cloudhealthcare/fhirdataset/dataset.go b/pkg/clinical/infrastructure/datastore/cloudhealthcare/fhirdataset/dataset.go index a9e517af..e7e708dc 100644 --- a/pkg/clinical/infrastructure/datastore/cloudhealthcare/fhirdataset/dataset.go +++ b/pkg/clinical/infrastructure/datastore/cloudhealthcare/fhirdataset/dataset.go @@ -24,26 +24,6 @@ const ( defaultTimeoutSeconds = 10 ) -// FHIRRepository ... -type FHIRRepository interface { - CreateFHIRResource(resourceType string, payload map[string]interface{}) ([]byte, error) - DeleteFHIRResource(resourceType, fhirResourceID string) ([]byte, error) - PatchFHIRResource(resourceType, fhirResourceID string, payload []map[string]interface{}) ([]byte, error) - UpdateFHIRResource(resourceType, fhirResourceID string, payload map[string]interface{}) ([]byte, error) - - GetFHIRPatientAllData(fhirResourceID string) ([]byte, error) - FHIRRestURL() string - GetFHIRResource(resourceType, fhirResourceID string) ([]byte, error) - POSTRequest(resourceName string, path string, params url.Values, body io.Reader) ([]byte, error) - FHIRHeaders() (http.Header, error) - - CreateDataset() (*healthcare.Operation, error) - GetDataset() (*healthcare.Dataset, error) - - GetFHIRStore() (*healthcare.FhirStore, error) - CreateFHIRStore() (*healthcare.FhirStore, error) -} - // Repository accesses and updates patient data that is stored on Healthcare // FHIR repository type Repository struct { @@ -375,6 +355,95 @@ func (fr Repository) GetFHIRResource(resourceType, fhirResourceID string) ([]byt return respBytes, nil } +// SearchFHIRResource ... +func (fr Repository) SearchFHIRResource(resourceType string, params map[string]interface{}) ([]map[string]interface{}, error) { + if params == nil { + return nil, fmt.Errorf("can't search with nil params") + } + + urlParams := url.Values{} + for k, v := range params { + val, ok := v.(string) + if !ok { + return nil, fmt.Errorf("the search/filter params should all be sent as strings") + } + urlParams.Add(k, val) + } + + path := "_search" + bs, err := fr.POSTRequest(resourceType, path, urlParams, nil) + if err != nil { + return nil, fmt.Errorf("unable to search: %w", err) + } + + respMap := make(map[string]interface{}) + err = json.Unmarshal(bs, &respMap) + if err != nil { + return nil, fmt.Errorf( + "%s could not be found with search params %v: %w", resourceType, params, err) + } + + mandatoryKeys := []string{"resourceType", "type", "total", "link"} + for _, k := range mandatoryKeys { + _, found := respMap[k] + if !found { + return nil, fmt.Errorf("server error: mandatory search result key %s not found", k) + } + } + resourceType, ok := respMap["resourceType"].(string) + if !ok { + return nil, fmt.Errorf("server error: the resourceType is not a string") + } + if resourceType != "Bundle" { + return nil, fmt.Errorf( + "server error: the resourceType value is not 'Bundle' as expected") + } + + resultType, ok := respMap["type"].(string) + if !ok { + return nil, fmt.Errorf("server error: the search result type value is not a string") + } + if resultType != "searchset" { + return nil, fmt.Errorf("server error: the type value is not 'searchset' as expected") + } + + respEntries := respMap["entry"] + if respEntries == nil { + return []map[string]interface{}{}, nil + } + entries, ok := respEntries.([]interface{}) + if !ok { + return nil, fmt.Errorf( + "server error: entries is not a list of maps, it is: %T", respEntries) + } + + results := []map[string]interface{}{} + for _, en := range entries { + entry, ok := en.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf( + "server error: expected each entry to be map, they are %T instead", en) + } + expectedKeys := []string{"fullUrl", "resource", "search"} + for _, k := range expectedKeys { + _, found := entry[k] + if !found { + return nil, fmt.Errorf("server error: FHIR search entry does not have key '%s'", k) + } + } + + resource, ok := entry["resource"].(map[string]interface{}) + if !ok { + { + return nil, fmt.Errorf("server error: result entry %#v is not a map", entry["resource"]) + } + } + results = append(results, resource) + } + + return results, nil +} + // POSTRequest is used to manually compose POST requests to the FHIR service // // - `resourceName` is a FHIR resource name e.g "Patient" diff --git a/pkg/clinical/infrastructure/datastore/cloudhealthcare/fhirdataset/mock/dataset_mock.go b/pkg/clinical/infrastructure/datastore/cloudhealthcare/fhirdataset/mock/dataset_mock.go index 5b834bd5..222e68a3 100644 --- a/pkg/clinical/infrastructure/datastore/cloudhealthcare/fhirdataset/mock/dataset_mock.go +++ b/pkg/clinical/infrastructure/datastore/cloudhealthcare/fhirdataset/mock/dataset_mock.go @@ -3,12 +3,6 @@ package mock import ( "encoding/json" "fmt" - "io" - "net/http" - "net/url" - - "github.com/google/uuid" - "google.golang.org/api/healthcare/v1" ) // FakeFHIRRepository is a mock FHIR repository @@ -18,14 +12,8 @@ type FakeFHIRRepository struct { MockPatchFHIRResourceFn func(resourceType, fhirResourceID string, payload []map[string]interface{}) ([]byte, error) MockUpdateFHIRResourceFn func(resourceType, fhirResourceID string, payload map[string]interface{}) ([]byte, error) MockGetFHIRPatientAllDataFn func(fhirResourceID string) ([]byte, error) - MockFHIRRestURLFn func() string - MockFHIRHeadersFn func() (http.Header, error) MockGetFHIRResourceFn func(resourceType, fhirResourceID string) ([]byte, error) - MockPOSTRequestFn func(resourceName string, path string, params url.Values, body io.Reader) ([]byte, error) - MockCreateDatasetFn func() (*healthcare.Operation, error) - MockGetDatasetFn func() (*healthcare.Dataset, error) - MockGetFHIRStoreFn func() (*healthcare.FhirStore, error) - MockCreateFHIRStoreFn func() (*healthcare.FhirStore, error) + MockSearchFHIRResourceFn func(resourceType string, params map[string]interface{}) ([]map[string]interface{}, error) } // NewFakeFHIRRepositoryMock initializes a new FakeFHIRRepositoryMock @@ -75,9 +63,6 @@ func NewFakeFHIRRepositoryMock() *FakeFHIRRepository { } return bs, nil }, - MockFHIRRestURLFn: func() string { - return "https://healthcare.googleapis.com/v1" - }, MockGetFHIRResourceFn: func(resourceType, fhirResourceID string) ([]byte, error) { n := map[string]interface{}{"given": []string{"John"}, "family": []string{"Doe"}} p := map[string]interface{}{ @@ -97,50 +82,6 @@ func NewFakeFHIRRepositoryMock() *FakeFHIRRepository { } return bs, nil }, - MockPOSTRequestFn: func(resourceName string, path string, params url.Values, body io.Reader) ([]byte, error) { - m := map[string]string{ - "resourceType": "Bundle", - "type": "searchset", - "total": "10", - "link": "test", - } - bs, err := json.Marshal(m) - if err != nil { - return nil, fmt.Errorf("unable to marshal map to JSON: %w", err) - } - return bs, nil - }, - MockFHIRHeadersFn: func() (http.Header, error) { - return http.Header{ - "Authorization": []string{"Bearer " + uuid.NewString()}, - }, nil - }, - MockCreateDatasetFn: func() (*healthcare.Operation, error) { - return &healthcare.Operation{ - Done: false, - Error: &healthcare.Status{}, - Metadata: []byte{}, - Name: "", - Response: []byte{}, - }, nil - }, - MockGetDatasetFn: func() (*healthcare.Dataset, error) { - return &healthcare.Dataset{ - Name: "test", - }, nil - }, - MockGetFHIRStoreFn: func() (*healthcare.FhirStore, error) { - return &healthcare.FhirStore{ - DefaultSearchHandlingStrict: true, - DisableReferentialIntegrity: true, - }, nil - }, - MockCreateFHIRStoreFn: func() (*healthcare.FhirStore, error) { - return &healthcare.FhirStore{ - DefaultSearchHandlingStrict: true, - DisableReferentialIntegrity: true, - }, nil - }, } } @@ -169,42 +110,12 @@ func (f *FakeFHIRRepository) GetFHIRPatientAllData(fhirResourceID string) ([]byt return f.MockGetFHIRPatientAllDataFn(fhirResourceID) } -// FHIRRestURL ... -func (f *FakeFHIRRepository) FHIRRestURL() string { - return f.MockFHIRRestURLFn() -} - // GetFHIRResource ... func (f *FakeFHIRRepository) GetFHIRResource(resourceType, fhirResourceID string) ([]byte, error) { return f.MockGetFHIRResourceFn(resourceType, fhirResourceID) } -// POSTRequest is a mock implementation of POSTRequest method -func (f *FakeFHIRRepository) POSTRequest(resourceName string, path string, params url.Values, body io.Reader) ([]byte, error) { - return f.MockPOSTRequestFn(resourceName, path, params, body) -} - -// FHIRHeaders is a mock implementation of CreateFHIRMedication method -func (f *FakeFHIRRepository) FHIRHeaders() (http.Header, error) { - return f.MockFHIRHeadersFn() -} - -// CreateDataset is a mock implementation of CreateDataset method -func (f *FakeFHIRRepository) CreateDataset() (*healthcare.Operation, error) { - return f.MockCreateDatasetFn() -} - -// GetDataset is a mock implementation of GetDataset method -func (f *FakeFHIRRepository) GetDataset() (*healthcare.Dataset, error) { - return f.MockGetDatasetFn() -} - -// GetFHIRStore is a mock implementation of GetFHIRStore method -func (f *FakeFHIRRepository) GetFHIRStore() (*healthcare.FhirStore, error) { - return f.MockGetFHIRStoreFn() -} - -// CreateFHIRStore is a mock implementation of CreateFHIRStore method -func (f *FakeFHIRRepository) CreateFHIRStore() (*healthcare.FhirStore, error) { - return f.MockCreateFHIRStoreFn() +// SearchFHIRResource ... +func (f *FakeFHIRRepository) SearchFHIRResource(resourceType string, params map[string]interface{}) ([]map[string]interface{}, error) { + return f.MockSearchFHIRResourceFn(resourceType, params) } diff --git a/pkg/clinical/infrastructure/datastore/cloudhealthcare/helpers.go b/pkg/clinical/infrastructure/datastore/cloudhealthcare/helpers.go index f5e41122..d68ec049 100644 --- a/pkg/clinical/infrastructure/datastore/cloudhealthcare/helpers.go +++ b/pkg/clinical/infrastructure/datastore/cloudhealthcare/helpers.go @@ -1,130 +1,10 @@ package fhir import ( - "context" - "encoding/json" - "fmt" - "net/url" - - "github.com/labstack/gommon/log" "github.com/savannahghi/clinical/pkg/clinical/application/common/helpers" "github.com/savannahghi/scalarutils" ) -func validateSearchParams(params map[string]interface{}) (url.Values, error) { - if params == nil { - return nil, fmt.Errorf("can't search with nil params") - } - output := url.Values{} - for k, v := range params { - val, ok := v.(string) - if !ok { - return nil, fmt.Errorf("the search/filter params should all be sent as strings") - } - output.Add(k, val) - } - return output, nil -} - -// searchFilterHelper helps with the composition of FHIR REST search and filter requests. -// -// - the `resourceName` is a FHIR resource name e.g "Patient", "Appointment" etc -// - the `path` is a resource sub-path e.g "_search". If there is no sub-path, send a blank string -// - `params` should contain the filter parameters e.g -// -// params := url.Values{} -// params.Add("_content", search) -// -// TODO: remove receiver -func (fh *StoreImpl) searchFilterHelper( - _ context.Context, - resourceName string, - params map[string]interface{}, -) ([]map[string]interface{}, error) { - // s.checkPreconditions() - if params == nil { - return nil, fmt.Errorf("can't search with nil params") - } - - urlParams, err := validateSearchParams(params) - if err != nil { - return nil, err - } - - path := "_search" - bs, err := fh.Dataset.POSTRequest(resourceName, path, urlParams, nil) - if err != nil { - log.Errorf("unable to search: %v", err) - return nil, fmt.Errorf("unable to search: %w", err) - } - respMap := make(map[string]interface{}) - err = json.Unmarshal(bs, &respMap) - if err != nil { - log.Errorf("%s could not be found with search params %v: %s", resourceName, params, err) - return nil, fmt.Errorf( - "%s could not be found with search params %v: %w", resourceName, params, err) - } - - mandatoryKeys := []string{"resourceType", "type", "total", "link"} - for _, k := range mandatoryKeys { - _, found := respMap[k] - if !found { - return nil, fmt.Errorf("server error: mandatory search result key %s not found", k) - } - } - resourceType, ok := respMap["resourceType"].(string) - if !ok { - return nil, fmt.Errorf("server error: the resourceType is not a string") - } - if resourceType != "Bundle" { - return nil, fmt.Errorf( - "server error: the resourceType value is not 'Bundle' as expected") - } - - resultType, ok := respMap["type"].(string) - if !ok { - return nil, fmt.Errorf("server error: the search result type value is not a string") - } - if resultType != "searchset" { - return nil, fmt.Errorf("server error: the type value is not 'searchset' as expected") - } - - respEntries := respMap["entry"] - if respEntries == nil { - return []map[string]interface{}{}, nil - } - entries, ok := respEntries.([]interface{}) - if !ok { - return nil, fmt.Errorf( - "server error: entries is not a list of maps, it is: %T", respEntries) - } - - results := []map[string]interface{}{} - for _, en := range entries { - entry, ok := en.(map[string]interface{}) - if !ok { - return nil, fmt.Errorf( - "server error: expected each entry to be map, they are %T instead", en) - } - expectedKeys := []string{"fullUrl", "resource", "search"} - for _, k := range expectedKeys { - _, found := entry[k] - if !found { - return nil, fmt.Errorf("server error: FHIR search entry does not have key '%s'", k) - } - } - - resource, ok := entry["resource"].(map[string]interface{}) - if !ok { - { - return nil, fmt.Errorf("server error: result entry %#v is not a map", entry["resource"]) - } - } - results = append(results, resource) - } - return results, nil -} - func birthdateMapper(resource map[string]interface{}) map[string]interface{} { resourceCopy := resource diff --git a/pkg/clinical/infrastructure/datastore/cloudhealthcare/mock/fhir_mock.go b/pkg/clinical/infrastructure/datastore/cloudhealthcare/mock/fhir_mock.go index 4a9fc2ce..34a2248d 100644 --- a/pkg/clinical/infrastructure/datastore/cloudhealthcare/mock/fhir_mock.go +++ b/pkg/clinical/infrastructure/datastore/cloudhealthcare/mock/fhir_mock.go @@ -2,7 +2,6 @@ package mock import ( "context" - "net/url" "github.com/google/uuid" "github.com/savannahghi/clinical/pkg/clinical/domain" @@ -18,7 +17,7 @@ type FHIRMock struct { MockCreateFHIROrganizationFn func(ctx context.Context, input domain.FHIROrganizationInput) (*domain.FHIROrganizationRelayPayload, error) MockSearchFHIROrganizationFn func(ctx context.Context, params map[string]interface{}) (*domain.FHIROrganizationRelayConnection, error) MockFindOrganizationByIDFn func(ctx context.Context, organisationID string) (*domain.FHIROrganizationRelayPayload, error) - MockSearchEpisodesByParamFn func(ctx context.Context, searchParams url.Values) ([]*domain.FHIREpisodeOfCare, error) + MockSearchEpisodesByParamFn func(ctx context.Context, searchParams map[string]interface{}) ([]*domain.FHIREpisodeOfCare, error) MockHasOpenEpisodeFn func( ctx context.Context, patient domain.FHIRPatient, @@ -142,7 +141,7 @@ func NewFHIRMock() *FHIRMock { MockSearchFHIROrganizationFn: func(ctx context.Context, params map[string]interface{}) (*domain.FHIROrganizationRelayConnection, error) { return &domain.FHIROrganizationRelayConnection{}, nil }, - MockSearchEpisodesByParamFn: func(ctx context.Context, searchParams url.Values) ([]*domain.FHIREpisodeOfCare, error) { + MockSearchEpisodesByParamFn: func(ctx context.Context, searchParams map[string]interface{}) ([]*domain.FHIREpisodeOfCare, error) { return []*domain.FHIREpisodeOfCare{}, nil }, MockHasOpenEpisodeFn: func(ctx context.Context, patient domain.FHIRPatient) (bool, error) { @@ -477,7 +476,7 @@ func (fh *FHIRMock) SearchFHIROrganization(ctx context.Context, params map[strin } // SearchEpisodesByParam is a mock implementation of SearchEpisodesByParam method -func (fh *FHIRMock) SearchEpisodesByParam(ctx context.Context, searchParams url.Values) ([]*domain.FHIREpisodeOfCare, error) { +func (fh *FHIRMock) SearchEpisodesByParam(ctx context.Context, searchParams map[string]interface{}) ([]*domain.FHIREpisodeOfCare, error) { return fh.MockSearchEpisodesByParamFn(ctx, searchParams) } diff --git a/pkg/clinical/repository/repository.go b/pkg/clinical/repository/repository.go index 7a9ad23e..32797484 100644 --- a/pkg/clinical/repository/repository.go +++ b/pkg/clinical/repository/repository.go @@ -2,7 +2,6 @@ package repository import ( "context" - "net/url" "github.com/savannahghi/clinical/pkg/clinical/domain" ) @@ -38,7 +37,7 @@ type FHIRPatient interface { } type FHIREpisodeOfCare interface { SearchFHIREpisodeOfCare(ctx context.Context, params map[string]interface{}) (*domain.FHIREpisodeOfCareRelayConnection, error) - SearchEpisodesByParam(ctx context.Context, searchParams url.Values) ([]*domain.FHIREpisodeOfCare, error) + SearchEpisodesByParam(ctx context.Context, params map[string]interface{}) ([]*domain.FHIREpisodeOfCare, error) GetFHIREpisodeOfCare(ctx context.Context, id string) (*domain.FHIREpisodeOfCareRelayPayload, error) CreateEpisodeOfCare(ctx context.Context, episode domain.FHIREpisodeOfCare) (*domain.EpisodeOfCarePayload, error) UpdateFHIREpisodeOfCare(ctx context.Context, fhirResourceID string, payload map[string]interface{}) (*domain.FHIREpisodeOfCare, error)