Skip to content

Commit

Permalink
chore: fetch by active status and ensure facility creation idempotency
Browse files Browse the repository at this point in the history
Signed-off-by: Kathurima Kimathi <kathurimakimathi415@gmail.com>
  • Loading branch information
KathurimaKimathi committed Oct 15, 2021
1 parent f494cf3 commit 2fb2bad
Show file tree
Hide file tree
Showing 20 changed files with 827 additions and 299 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import (

// Create contains all the methods used to perform a create operation in DB
type Create interface {
CreateFacility(ctx context.Context, facility *Facility) (*Facility, error)
GetOrCreateFacility(ctx context.Context, facility *Facility) (*Facility, error)
CollectMetrics(ctx context.Context, metrics *Metric) (*Metric, error)
}

// CreateFacility ...
func (db *PGInstance) CreateFacility(ctx context.Context, facility *Facility) (*Facility, error) {
// GetOrCreateFacility ...
func (db *PGInstance) GetOrCreateFacility(ctx context.Context, facility *Facility) (*Facility, error) {
err := db.DB.Create(facility).Error

if err != nil {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package mock

import (
"context"
"strconv"
"time"

"github.com/google/uuid"
Expand All @@ -15,17 +16,18 @@ import (
//
// This mock struct should be separate from our own internal methods.
type GormMock struct {
CreateFacilityFn func(ctx context.Context, facility *gorm.Facility) (*gorm.Facility, error)
RetrieveFacilityFn func(ctx context.Context, id *uuid.UUID) (*gorm.Facility, error)
GetFacilitiesFn func(ctx context.Context) ([]gorm.Facility, error)
DeleteFacilityFn func(ctx context.Context, mfl_code string) (bool, error)
CollectMetricsFn func(ctx context.Context, metrics *gorm.Metric) (*gorm.Metric, error)
GetOrCreateFacilityFn func(ctx context.Context, facility *gorm.Facility) (*gorm.Facility, error)
RetrieveFacilityFn func(ctx context.Context, id *uuid.UUID, isActive bool) (*gorm.Facility, error)
RetrieveFacilityByMFLCodeFn func(ctx context.Context, MFLCode string, isActive bool) (*gorm.Facility, error)
GetFacilitiesFn func(ctx context.Context) ([]gorm.Facility, error)
DeleteFacilityFn func(ctx context.Context, mfl_code string) (bool, error)
CollectMetricsFn func(ctx context.Context, metrics *gorm.Metric) (*gorm.Metric, error)
}

// NewGormMock initializes a new instance of `GormMock` then mocking the case of success.
func NewGormMock() *GormMock {
return &GormMock{
CreateFacilityFn: func(ctx context.Context, facility *gorm.Facility) (*gorm.Facility, error) {
GetOrCreateFacilityFn: func(ctx context.Context, facility *gorm.Facility) (*gorm.Facility, error) {
id := uuid.New()
name := "Kanairo One"
code := "KN001"
Expand All @@ -35,13 +37,13 @@ func NewGormMock() *GormMock {
FacilityID: &id,
Name: name,
Code: code,
Active: true,
Active: strconv.FormatBool(true),
County: county,
Description: description,
}, nil
},

RetrieveFacilityFn: func(ctx context.Context, id *uuid.UUID) (*gorm.Facility, error) {
RetrieveFacilityFn: func(ctx context.Context, id *uuid.UUID, isActive bool) (*gorm.Facility, error) {
facilityID := uuid.New()
name := "Kanairo One"
code := "KN001"
Expand All @@ -51,7 +53,7 @@ func NewGormMock() *GormMock {
FacilityID: &facilityID,
Name: name,
Code: code,
Active: true,
Active: strconv.FormatBool(true),
County: county,
Description: description,
}, nil
Expand All @@ -67,7 +69,7 @@ func NewGormMock() *GormMock {
FacilityID: &facilityID,
Name: name,
Code: code,
Active: true,
Active: strconv.FormatBool(true),
County: county,
Description: description,
})
Expand All @@ -89,17 +91,38 @@ func NewGormMock() *GormMock {
UID: ksuid.New().String(),
}, nil
},

RetrieveFacilityByMFLCodeFn: func(ctx context.Context, MFLCode string, isActive bool) (*gorm.Facility, error) {
facilityID := uuid.New()
name := "Kanairo One"
code := "KN001"
county := "Kanairo"
description := "This is just for mocking"
return &gorm.Facility{
FacilityID: &facilityID,
Name: name,
Code: code,
Active: strconv.FormatBool(true),
County: county,
Description: description,
}, nil
},
}
}

// CreateFacility mocks the implementation of `gorm's` CreateFacility method.
func (gm *GormMock) CreateFacility(ctx context.Context, facility *gorm.Facility) (*gorm.Facility, error) {
return gm.CreateFacilityFn(ctx, facility)
// GetOrCreateFacility mocks the implementation of `gorm's` GetOrCreateFacility method.
func (gm *GormMock) GetOrCreateFacility(ctx context.Context, facility *gorm.Facility) (*gorm.Facility, error) {
return gm.GetOrCreateFacilityFn(ctx, facility)
}

// RetrieveFacility mocks the implementation of `gorm's` RetrieveFacility method.
func (gm *GormMock) RetrieveFacility(ctx context.Context, id *uuid.UUID) (*gorm.Facility, error) {
return gm.RetrieveFacilityFn(ctx, id)
func (gm *GormMock) RetrieveFacility(ctx context.Context, id *uuid.UUID, isActive bool) (*gorm.Facility, error) {
return gm.RetrieveFacilityFn(ctx, id, isActive)
}

// RetrieveFacilityByMFLCode mocks the implementation of `gorm's` RetrieveFacility method.
func (gm *GormMock) RetrieveFacilityByMFLCode(ctx context.Context, MFLCode string, isActive bool) (*gorm.Facility, error) {
return gm.RetrieveFacilityByMFLCodeFn(ctx, MFLCode, isActive)
}

// GetFacilities mocks the implementation of `gorm's` GetFacilities method.
Expand Down
20 changes: 17 additions & 3 deletions pkg/onboarding/infrastructure/database/postgres/gorm/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,39 @@ import (
"context"
"fmt"
"log"
"strconv"

"github.com/google/uuid"
)

// Query contains all the db query methods
type Query interface {
RetrieveFacility(ctx context.Context, id *uuid.UUID) (*Facility, error)
RetrieveFacility(ctx context.Context, id *uuid.UUID, isActive bool) (*Facility, error)
RetrieveFacilityByMFLCode(ctx context.Context, MFLCode string, isActive bool) (*Facility, error)
GetFacilities(ctx context.Context) ([]Facility, error)
}

// RetrieveFacility fetches a single facility
func (db *PGInstance) RetrieveFacility(ctx context.Context, id *uuid.UUID) (*Facility, error) {
func (db *PGInstance) RetrieveFacility(ctx context.Context, id *uuid.UUID, isActive bool) (*Facility, error) {
var facility Facility
if err := db.DB.Where(&Facility{FacilityID: id}).First(&facility).Error; err != nil {
active := strconv.FormatBool(isActive)
err := db.DB.Where(&Facility{FacilityID: id, Active: active}).Find(&facility).Error
if err != nil {
return nil, fmt.Errorf("failed to get facility by ID %v: %v", id, err)
}
return &facility, nil
}

// RetrieveFacilityByMFLCode fetches a single facility using MFL Code
func (db *PGInstance) RetrieveFacilityByMFLCode(ctx context.Context, MFLCode string, isActive bool) (*Facility, error) {
var facility Facility
active := strconv.FormatBool(isActive)
if err := db.DB.Where(&Facility{Code: MFLCode, Active: active}).First(&facility).Error; err != nil {
return nil, fmt.Errorf("failed to get facility by MFL Code %v: %v", MFLCode, err)
}
return &facility, nil
}

// GetFacilities fetches all the healthcare facilities in the platform.
func (db *PGInstance) GetFacilities(ctx context.Context) ([]Facility, error) {
var facility []Facility
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ type Facility struct {
Name string `gorm:"column:name"`
// MFL Code for Kenyan facilities, globally unique
Code string `gorm:"unique;column:mfl_code"`
Active bool `gorm:"column:active"`
Active string `gorm:"column:active"`
County string `gorm:"column:county"` // TODO: Controlled list of counties
Description string `gorm:"column:description"`
}
Expand Down
9 changes: 8 additions & 1 deletion pkg/onboarding/infrastructure/database/postgres/mappers.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package postgres

import (
"strconv"

"github.com/savannahghi/onboarding-service/pkg/onboarding/domain"
"github.com/savannahghi/onboarding-service/pkg/onboarding/infrastructure/database/postgres/gorm"
)
Expand All @@ -12,11 +14,16 @@ func (d *OnboardingDb) mapFacilityObjectToDomain(facilityObject *gorm.Facility)
return nil
}

active, err := strconv.ParseBool(facilityObject.Active)
if err != nil {
return nil
}

return &domain.Facility{
ID: *facilityObject.FacilityID,
Name: facilityObject.Name,
Code: facilityObject.Code,
Active: facilityObject.Active,
Active: active,
County: facilityObject.County,
Description: facilityObject.Description,
}
Expand Down
22 changes: 11 additions & 11 deletions pkg/onboarding/infrastructure/database/postgres/mock/pg_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ import (

// PostgresMock struct implements mocks of `postgres's` internal methods.
type PostgresMock struct {
CreateFacilityFn func(ctx context.Context, facility *dto.FacilityInput) (*domain.Facility, error)
GetFacilitiesFn func(ctx context.Context) ([]*domain.Facility, error)
RetrieveFacilityFn func(ctx context.Context, id *uuid.UUID) (*domain.Facility, error)
GetOrCreateFacilityFn func(ctx context.Context, facility *dto.FacilityInput) (*domain.Facility, error)
GetFacilitiesFn func(ctx context.Context) ([]*domain.Facility, error)
RetrieveFacilityFn func(ctx context.Context, id *uuid.UUID, isActive bool) (*domain.Facility, error)
}

// NewPostgresMock initializes a new instance of `GormMock` then mocking the case of success.
func NewPostgresMock() *PostgresMock {
return &PostgresMock{
CreateFacilityFn: func(ctx context.Context, facility *dto.FacilityInput) (*domain.Facility, error) {
GetOrCreateFacilityFn: func(ctx context.Context, facility *dto.FacilityInput) (*domain.Facility, error) {
id := uuid.New()
name := "Kanairo One"
code := "KN001"
Expand Down Expand Up @@ -50,7 +50,7 @@ func NewPostgresMock() *PostgresMock {
},
}, nil
},
RetrieveFacilityFn: func(ctx context.Context, id *uuid.UUID) (*domain.Facility, error) {
RetrieveFacilityFn: func(ctx context.Context, id *uuid.UUID, isActive bool) (*domain.Facility, error) {
facilityID := uuid.New()
name := "test-facility"
code := "t-100"
Expand All @@ -68,12 +68,12 @@ func NewPostgresMock() *PostgresMock {
}
}

// CreateFacility mocks the implementation of `gorm's` CreateFacility method.
func (gm *PostgresMock) CreateFacility(ctx context.Context, facility *dto.FacilityInput) (*domain.Facility, error) {
return gm.CreateFacilityFn(ctx, facility)
// GetOrCreateFacility mocks the implementation of `gorm's` GetOrCreateFacility method.
func (gm *PostgresMock) GetOrCreateFacility(ctx context.Context, facility *dto.FacilityInput) (*domain.Facility, error) {
return gm.GetOrCreateFacilityFn(ctx, facility)
}

// RetrieveFacility mocks the implementation of `gorm's` CreateFacility method.
func (gm *PostgresMock) RetrieveFacility(ctx context.Context, id *uuid.UUID) (*domain.Facility, error) {
return gm.RetrieveFacilityFn(ctx, id)
// RetrieveFacility mocks the implementation of `gorm's` GetOrCreateFacility method.
func (gm *PostgresMock) RetrieveFacility(ctx context.Context, id *uuid.UUID, isActive bool) (*domain.Facility, error) {
return gm.RetrieveFacilityFn(ctx, id, isActive)
}
11 changes: 6 additions & 5 deletions pkg/onboarding/infrastructure/database/postgres/pg_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,33 @@ package postgres
import (
"context"
"fmt"
"strconv"

"github.com/savannahghi/onboarding-service/pkg/onboarding/application/dto"
"github.com/savannahghi/onboarding-service/pkg/onboarding/domain"
"github.com/savannahghi/onboarding-service/pkg/onboarding/infrastructure/database/postgres/gorm"
)

// CreateFacility is responsible from creating a representation of a facility
// GetOrCreateFacility is responsible from creating a representation of a facility
// A facility here is the healthcare facility that are on the platform.
// A facility MFL CODE must be unique across the platform. I forms part of the unique identifiers
//
// TODO: Create a helper the checks for all required fields
// TODO: Make the create method idempotent
func (d *OnboardingDb) CreateFacility(ctx context.Context, facility *dto.FacilityInput) (*domain.Facility, error) {
func (d *OnboardingDb) GetOrCreateFacility(ctx context.Context, facility *dto.FacilityInput) (*domain.Facility, error) {
if facility.Code == "" {
return nil, fmt.Errorf("`code` should be defined")
}

facilityObj := &gorm.Facility{
Name: facility.Name,
Code: facility.Code,
Active: facility.Active,
County: facility.Code,
Active: strconv.FormatBool(facility.Active),
County: facility.County,
Description: facility.Description,
}

facilitySession, err := d.create.CreateFacility(ctx, facilityObj)
facilitySession, err := d.create.GetOrCreateFacility(ctx, facilityObj)
if err != nil {
return nil, fmt.Errorf("failed to create facility: %v", err)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,19 +63,19 @@ func TestOnboardingDb_CreateFacility(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
var fakeGorm = gormMock.NewGormMock()
d := NewOnboardingDb(fakeGorm, fakeGorm, fakeGorm)
got, err := d.CreateFacility(tt.args.ctx, tt.args.facility)
got, err := d.GetOrCreateFacility(tt.args.ctx, tt.args.facility)
if tt.name == "sad case - facility code not defined" {
fakeGorm.CreateFacilityFn = func(ctx context.Context, facility *gorm.Facility) (*gorm.Facility, error) {
fakeGorm.GetOrCreateFacilityFn = func(ctx context.Context, facility *gorm.Facility) (*gorm.Facility, error) {
return nil, fmt.Errorf("failed to create facility")
}
}
if tt.name == "happy case - valid payload" {
fakeGorm.CreateFacilityFn = func(ctx context.Context, facility *gorm.Facility) (*gorm.Facility, error) {
fakeGorm.GetOrCreateFacilityFn = func(ctx context.Context, facility *gorm.Facility) (*gorm.Facility, error) {
return facility, nil
}
}
if (err != nil) != tt.wantErr {
t.Errorf("OnboardingDb.CreateFacility() error = %v, wantErr %v", err, tt.wantErr)
t.Errorf("OnboardingDb.GetOrCreateFacility() error = %v, wantErr %v", err, tt.wantErr)
return
}
if tt.wantErr && got != nil {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func TestOnboardingDb_DeleteFacility_Unittest(t *testing.T) {
}

// create a facility
facility, err := d.CreateFacility(ctx, facilityInput)
facility, err := d.GetOrCreateFacility(ctx, facilityInput)
if err != nil {
t.Errorf("failed to create new facility: %v", err)
}
Expand Down
24 changes: 21 additions & 3 deletions pkg/onboarding/infrastructure/database/postgres/pg_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package postgres
import (
"context"
"fmt"
"strconv"

"github.com/google/uuid"

Expand All @@ -21,11 +22,15 @@ func (d *OnboardingDb) GetFacilities(ctx context.Context) ([]*domain.Facility, e
return facility, nil
}
for _, m := range facilities {
active, err := strconv.ParseBool(m.Active)
if err != nil {
return nil, fmt.Errorf("failed to parse facility.Active to boolean")
}
singleFacility := domain.Facility{
ID: *m.FacilityID,
Name: m.Name,
Code: m.Code,
Active: m.Active,
Active: active,
County: m.County,
Description: m.Description,
}
Expand All @@ -37,14 +42,27 @@ func (d *OnboardingDb) GetFacilities(ctx context.Context) ([]*domain.Facility, e
}

// RetrieveFacility gets a facility by ID from the database
func (d *OnboardingDb) RetrieveFacility(ctx context.Context, id *uuid.UUID) (*domain.Facility, error) {
func (d *OnboardingDb) RetrieveFacility(ctx context.Context, id *uuid.UUID, isActive bool) (*domain.Facility, error) {
if id == nil {
return nil, fmt.Errorf("facility ID should be defined")
}
facilitySession, err := d.query.RetrieveFacility(ctx, id)
facilitySession, err := d.query.RetrieveFacility(ctx, id, isActive)
if err != nil {
return nil, fmt.Errorf("failed query and retrieve one facility: %s", err)
}

return d.mapFacilityObjectToDomain(facilitySession), nil
}

// RetrieveByFacilityMFLCode gets a facility by ID from the database
func (d *OnboardingDb) RetrieveByFacilityMFLCode(ctx context.Context, MFLCode string, isActive bool) (*domain.Facility, error) {
if MFLCode == "" {
return nil, fmt.Errorf("facility ID should be defined")
}
facilitySession, err := d.query.RetrieveFacilityByMFLCode(ctx, MFLCode, isActive)
if err != nil {
return nil, fmt.Errorf("failed query and retrieve facility by MFLCode: %s", err)
}

return d.mapFacilityObjectToDomain(facilitySession), nil
}
Loading

0 comments on commit 2fb2bad

Please sign in to comment.