Skip to content

Commit

Permalink
feat: add profile matching
Browse files Browse the repository at this point in the history
  • Loading branch information
Muchogoc committed Nov 25, 2024
1 parent 3190268 commit 07042f3
Show file tree
Hide file tree
Showing 6 changed files with 246 additions and 23 deletions.
23 changes: 23 additions & 0 deletions enums.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ type ContactType string

type GenderType string

type MatchResult string

const (
// Identifier types
IdentifierTypeNationalID IdentifierType = "NATIONAL_ID"
Expand Down Expand Up @@ -46,6 +48,12 @@ const (
GenderTypeUNK GenderType = "UNK"
)

const (
MatchResultMatch MatchResult = "MATCH"
MatchResultPossibleMatch MatchResult = "POSSIBLE_MATCH"
MatchResultNoMatch MatchResult = "NO_MATCH"
)

// IsValid returns true if a contact type is valid
func (f ContactType) IsValid() bool {
switch f {
Expand Down Expand Up @@ -165,3 +173,18 @@ func (f *GenderType) UnmarshalGQL(v interface{}) error {
func (f GenderType) MarshalGQL(w io.Writer) {
fmt.Fprint(w, strconv.Quote(f.String()))
}

// IsValid returns true if a match result is valid
func (m MatchResult) IsValid() bool {
switch m {
case MatchResultMatch, MatchResultPossibleMatch, MatchResultNoMatch:
return true
default:
return false
}
}

// String converts the match result enum to a string
func (m MatchResult) String() string {
return string(m)
}
47 changes: 47 additions & 0 deletions enums_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -374,3 +374,50 @@ func TestGenderType_MarshalGQL(t *testing.T) {
})
}
}

func TestMatchResult_String(t *testing.T) {
tests := []struct {
name string
e MatchResult
want string
}{
{
name: "happy case: enum to string",
e: MatchResultMatch,
want: "MATCH",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.e.String(); got != tt.want {
t.Errorf("MatchResult.String() = %v, want %v", got, tt.want)
}
})
}
}

func TestMatchResult_IsValid(t *testing.T) {
tests := []struct {
name string
e MatchResult
want bool
}{
{
name: "valid type",
e: MatchResultPossibleMatch,
want: true,
},
{
name: "invalid type",
e: MatchResult("invalid"),
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.e.IsValid(); got != tt.want {
t.Errorf("MatchResult.IsValid() = %v, want %v", got, tt.want)
}
})
}
}
52 changes: 33 additions & 19 deletions healthcrm.go
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,35 @@ func (h *HealthCRMLib) CreateProfile(ctx context.Context, profile *ProfileInput)
return profileResponse, nil
}

// MatchProfile is used to create profile in health CRM service
func (h *HealthCRMLib) MatchProfile(ctx context.Context, profile *ProfileInput) (*ProfileOutput, error) {
path := "/v1/identities/profiles/match_profile/"
response, err := h.client.MakeRequest(ctx, http.MethodPost, path, nil, profile)
if err != nil {
return nil, err
}

defer response.Body.Close()

respBytes, err := io.ReadAll(response.Body)
if err != nil {
return nil, fmt.Errorf("could not read response: %w", err)
}

if response.StatusCode != http.StatusOK {
return nil, errors.New(string(respBytes))
}

var profileResponse *ProfileOutput

err = json.Unmarshal(respBytes, &profileResponse)
if err != nil {
return nil, err
}

return profileResponse, nil
}

// GetMultipleServices is used to fetch multiple services
//
// Parameters:
Expand Down Expand Up @@ -518,27 +547,12 @@ func (h *HealthCRMLib) GetPersonIdentifiers(ctx context.Context, healthID string

var queryParams url.Values

if identifierTypes != nil {
if !identifierTypes[0].IsValid() {
return nil, fmt.Errorf("invalid identifier type provided: %s", identifierTypes[0])
}

identifierString := identifierTypes[0].String()

for idx, identifier := range identifierTypes {
if idx == 0 {
continue
}

if !identifier.IsValid() {
return nil, fmt.Errorf("invalid identifier type provided: %s", identifier)
}

identifierString += fmt.Sprintf(",%s", identifier.String())
for _, identifier := range identifierTypes {
if !identifier.IsValid() {
return nil, fmt.Errorf("invalid identifier type provided: %s", identifier)
}

queryParams = url.Values{}
queryParams.Add("identifier_type", identifierString)
queryParams.Add("identifier_type", identifier.String())
}

response, err := h.client.MakeRequest(ctx, http.MethodGet, path, queryParams, nil)
Expand Down
137 changes: 137 additions & 0 deletions healthcrm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1938,3 +1938,140 @@ func TestHealthCRMLib_GetPersonContacts(t *testing.T) {
})
}
}

func TestHealthCRMLib_MatchProfile(t *testing.T) {
type args struct {
ctx context.Context
profile *ProfileInput
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "Happy Case: Match Profile",
args: args{
ctx: context.Background(),
profile: &ProfileInput{
ProfileID: gofakeit.UUID(),
HealthID: gofakeit.UUID(),
FirstName: gofakeit.FirstName(),
LastName: gofakeit.LastName(),
OtherName: gofakeit.FirstName(),
DateOfBirth: gofakeit.Date().String(),
Gender: "MALE",
EnrolmentDate: "2023-09-01",
SladeCode: "6000",
ServiceCode: "01",
Contacts: []*ProfileContactInput{
{
ContactType: "PHONE_NUMBER",
ContactValue: gofakeit.PhoneFormatted(),
},
},
Identifiers: []*ProfileIdentifierInput{
{
IdentifierType: "SLADE_CODE",
IdentifierValue: "3243",
ValidFrom: &scalarutils.Date{
Year: 2024,
Month: 1,
Day: 1,
},
ValidTo: &scalarutils.Date{
Year: 2024,
Month: 1,
Day: 1,
},
},
},
},
},
wantErr: false,
},
{
name: "Sad Case: Unable To Match Profile",
args: args{
ctx: context.Background(),
profile: &ProfileInput{
FirstName: gofakeit.FirstName(),
LastName: gofakeit.LastName(),
},
},
wantErr: true,
},
{
name: "Sad Case: Unable To Make Request",
args: args{
ctx: context.Background(),
profile: &ProfileInput{
FirstName: gofakeit.FirstName(),
LastName: gofakeit.LastName(),
},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.name == "Happy Case: Match Profile" {
path := fmt.Sprintf("%s/v1/identities/profiles/match_profile/", BaseURL)
httpmock.RegisterResponder(http.MethodPost, path, func(r *http.Request) (*http.Response, error) {
resp := &ProfileOutput{
ID: gofakeit.UUID(),
ProfileID: gofakeit.UUID(),
HealthID: "50932",
SladeCode: "50202",
Classification: "MATCH",
}
return httpmock.NewJsonResponse(http.StatusAccepted, resp)
})
}
if tt.name == "Sad Case: Unable To Match Profile" {
path := fmt.Sprintf("%s/v1/identities/profiles/match_profile/", BaseURL)
httpmock.RegisterResponder(http.MethodPost, path, func(r *http.Request) (*http.Response, error) {
resp := &ProfileInput{
ProfileID: gofakeit.UUID(),
FirstName: gofakeit.FirstName(),
LastName: gofakeit.LastName(),
OtherName: gofakeit.BeerName(),
DateOfBirth: gofakeit.Date().String(),
Gender: ConvertEnumutilsGenderToCRMGender(enumutils.GenderAgender),
EnrolmentDate: gofakeit.Date().String(),
SladeCode: "50202",
ServiceCode: "50",
Contacts: []*ProfileContactInput{},
Identifiers: []*ProfileIdentifierInput{},
}
return httpmock.NewJsonResponse(http.StatusBadRequest, resp)
})
}
if tt.name == "Sad Case: Unable To Make Request" {
httpmock.RegisterResponder(http.MethodPost, fmt.Sprintf("%s/oauth2/token/", serverutils.MustGetEnvVar("HEALTH_CRM_AUTH_SERVER_ENDPOINT")), func(r *http.Request) (*http.Response, error) {
resp := authutils.OAUTHResponse{
Scope: "",
ExpiresIn: 3600,
AccessToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
RefreshToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c",
TokenType: "Bearer",
}
return httpmock.NewJsonResponse(http.StatusBadRequest, resp)
})
}

httpmock.Activate()
defer httpmock.DeactivateAndReset()
MockAuthenticate()
h, err := NewHealthCRMLib()
if err != nil {
t.Errorf("unable to initialize sdk: %v", err)
}
_, err = h.MatchProfile(tt.args.ctx, tt.args.profile)
if (err != nil) != tt.wantErr {
t.Errorf("HealthCRMLib.MatchProfile() error = %v, wantErr %v", err, tt.wantErr)
return
}
})
}
}
1 change: 1 addition & 0 deletions input.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ type ServiceIdentifierInput struct {
// ProfileInput is the host of users data or a brief description of a person
type ProfileInput struct {
ProfileID string `json:"profile_id"`
HealthID string `json:"health_id,omitempty"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
OtherName string `json:"other_name,omitempty"`
Expand Down
9 changes: 5 additions & 4 deletions output.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,11 @@ type ServiceIdentifier struct {

// ProfileOutput is used to display profile(s)
type ProfileOutput struct {
ID string `json:"id"`
ProfileID string `json:"profile_id"`
HealthID string `json:"health_id,omitempty"`
SladeCode string `json:"slade_code"`
ID string `json:"id"`
ProfileID string `json:"profile_id"`
HealthID string `json:"health_id,omitempty"`
Classification MatchResult `json:"classification,omitempty"`
SladeCode string `json:"slade_code"`
}

// FacilityServices is used to get a list of facility Services
Expand Down

0 comments on commit 07042f3

Please sign in to comment.