Skip to content

Commit

Permalink
chore: refactor validate email otp and add firebase integration test
Browse files Browse the repository at this point in the history
Signed-off-by: Otieno Calvine <nyarangaotieno@gmail.com>
  • Loading branch information
NYARAS committed Sep 30, 2021
1 parent daa3806 commit 7a03ad6
Show file tree
Hide file tree
Showing 7 changed files with 238 additions and 16 deletions.
16 changes: 16 additions & 0 deletions pkg/clinical/application/utils/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package utils

import (
"fmt"

"github.com/asaskevich/govalidator"
)

// ValidateEmail returns an error if the supplied string does not have a
// valid format or resolvable host
func ValidateEmail(email string) error {
if !govalidator.IsEmail(email) {
return fmt.Errorf("invalid email format")
}
return nil
}
40 changes: 40 additions & 0 deletions pkg/clinical/application/utils/helpers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package utils

import (
"testing"

"github.com/savannahghi/firebasetools"
)

func TestValidateEmail(t *testing.T) {
type args struct {
email string
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "valid email address",
args: args{
email: firebasetools.TestUserEmail,
},
wantErr: false,
},
{
name: "invalid email address",
args: args{
email: "hey@notavalidemail",
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := ValidateEmail(tt.args.email); (err != nil) != tt.wantErr {
t.Errorf("ValidateEmail() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
2 changes: 1 addition & 1 deletion pkg/clinical/domain/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ type PhoneNumberPayload struct {

// EmailOptIn is used to persist and manage email communication whitelists
type EmailOptIn struct {
Email string `json:"email" firestore:"optedIn"`
Email string `json:"email" firestore:"email"`
OptedIn bool `json:"optedIn" firestore:"optedIn"`
}

Expand Down
12 changes: 4 additions & 8 deletions pkg/clinical/infrastructure/datastore/firebase/firebase.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"
"fmt"

"github.com/asaskevich/govalidator"
"github.com/savannahghi/clinical/pkg/clinical/domain"
"github.com/savannahghi/firebasetools"
)
Expand Down Expand Up @@ -38,16 +37,13 @@ func (fr Repository) GetEmailOptInCollectionName() string {
return suffixed
}

// ValidateEmail returns an error if the supplied string does not have a
// valid format or resolvable host
func (fr Repository) ValidateEmail(
// SaveEmailOTP persist the data of the newly created OTP to a datastore
func (fr Repository) SaveEmailOTP(
ctx context.Context,
email string, optIn bool) error {

if !govalidator.IsEmail(email) {
return fmt.Errorf("invalid email format")
if email == "" {
return fmt.Errorf("the email cannot be nil")
}

if optIn {
data := domain.EmailOptIn{
Email: email,
Expand Down
166 changes: 166 additions & 0 deletions pkg/clinical/infrastructure/datastore/firebase/firebase_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package firebase_test

import (
"context"
"fmt"
"log"
"os"
"testing"
"time"

"cloud.google.com/go/firestore"
"firebase.google.com/go/auth"
"github.com/savannahghi/clinical/pkg/clinical/infrastructure"
fb "github.com/savannahghi/clinical/pkg/clinical/infrastructure/datastore/firebase"
"github.com/savannahghi/firebasetools"
)

var testRepo *fb.Repository

func TestMain(m *testing.M) {
log.Printf("Setting tests up ...")
envOriginalValue := os.Getenv("ENVIRONMENT")
os.Setenv("ENVIRONMENT", "staging")
debugEnvValue := os.Getenv("DEBUG")
os.Setenv("DEBUG", "true")
os.Setenv("REPOSITORY", "firebase")
collectionEnvValue := os.Getenv("ROOT_COLLECTION_SUFFIX")
os.Setenv("CLOUD_HEALTH_PUBSUB_TOPIC", "pubtopic")
os.Setenv("CLOUD_HEALTH_DATASET_ID", "datasetid")
os.Setenv("CLOUD_HEALTH_FHIRSTORE_ID", "fhirid")

// !NOTE!
// Under no circumstances should you remove this env var when testing
// You risk purging important collections, like our prod collections
os.Setenv("ROOT_COLLECTION_SUFFIX", fmt.Sprintf("clinical_ci_%v", time.Now().Unix()))
ctx := context.Background()
r := fb.Repository{} // They are nil
fsc, fbc := InitializeTestFirebaseClient(ctx)
if fsc == nil {
log.Printf("failed to initialize test FireStore client")
return
}
if fbc == nil {
log.Printf("failed to initialize test FireBase client")
return
}
infra, err := InitializeTestFirebaseRepository()
if err != nil {
log.Printf("unable to initialize test repository: %v", err)
return
}

testRepo = infra

purgeRecords := func() {
collections := []string{
r.GetEmailOptInCollectionName(),
}
for _, collection := range collections {
ref := fsc.Collection(collection)
firebasetools.DeleteCollection(ctx, fsc, ref, 10)
}
}

// try clean up first
purgeRecords()

// do clean up
log.Printf("Running tests ...")
code := m.Run()

log.Printf("Tearing tests down ...")
purgeRecords()

// restore environment varibles to original values
os.Setenv(envOriginalValue, "ENVIRONMENT")
os.Setenv("DEBUG", debugEnvValue)
os.Setenv("ROOT_COLLECTION_SUFFIX", collectionEnvValue)

os.Exit(code)
}

func InitializeTestInfrastructure(ctx context.Context) (infrastructure.Infrastructure, error) {

return infrastructure.NewInfrastructureInteractor(), nil
}

func InitializeTestFirebaseRepository() (*fb.Repository, error) {
ctx := context.Background()
fsc, fbc := InitializeTestFirebaseClient(ctx)
if fsc == nil {
log.Printf("failed to initialize test Firestore client")
return nil, fmt.Errorf("failed to initialize test Firestore client")
}
if fbc == nil {
log.Printf("failed to initialize test Firebase client")
return nil, fmt.Errorf("failed to initialize test Firebase client")
}
firestoreExtension := fb.NewFirestoreClientExtension(fsc)
fr := fb.NewFirebaseRepository(firestoreExtension, fbc)
return fr, nil
}
func InitializeTestFirebaseClient(ctx context.Context) (*firestore.Client, *auth.Client) {
fc := firebasetools.FirebaseClient{}
fa, err := fc.InitFirebase()
if err != nil {
log.Panicf("unable to initialize Firebase: %s", err)
}

fsc, err := fa.Firestore(ctx)
if err != nil {
log.Panicf("unable to initialize Firestore: %s", err)
}

fbc, err := fa.Auth(ctx)
if err != nil {
log.Panicf("can't initialize Firebase auth when setting up tests: %s", err)
}
return fsc, fbc
}

func TestRepository_SaveEmailOTP(t *testing.T) {
fr := testRepo
ctx := context.Background()
type args struct {
ctx context.Context
email string
optIn bool
}
tests := []struct {
name string
args args
want bool
wantErr bool
}{
{
name: "valid save email OTP",
args: args{
ctx: ctx,
email: firebasetools.TestUserEmail,
optIn: true,
},
want: true,
wantErr: false,
},
{
name: "invalid save email OTP: nil email address",
args: args{
ctx: ctx,
optIn: true,
},
want: false,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := fr.SaveEmailOTP(tt.args.ctx, tt.args.email, tt.args.optIn)
if (err != nil) != tt.wantErr {
t.Errorf("Repository.SaveEmailOTP() error = %v, wantErr %v", err, tt.wantErr)
return
}
})

}
}
9 changes: 4 additions & 5 deletions pkg/clinical/infrastructure/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func (d FHIRService) GetFHIRPatientEverything(fhirResourceID string) ([]byte, er

// Repository ...
type Repository interface {
ValidateEmail(ctx context.Context, email string, optIn bool) error
SaveEmailOTP(ctx context.Context, email string, optIn bool) error
}

// DBService is an implementation of the database repository
Expand Down Expand Up @@ -127,10 +127,9 @@ func NewDBService() *DBService {
}
}

// ValidateEmail returns an error if the supplied string does not have a
// valid format or resolvable host
func (db DBService) ValidateEmail(
// SaveEmailOTP persist the data of the newly created OTP to a datastore
func (db DBService) SaveEmailOTP(
ctx context.Context,
email string, optIn bool) error {
return db.firestore.ValidateEmail(ctx, email, optIn)
return db.firestore.SaveEmailOTP(ctx, email, optIn)
}
9 changes: 7 additions & 2 deletions pkg/clinical/usecases/patient.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
auth "github.com/savannahghi/clinical/pkg/clinical/application/authorization"
"github.com/savannahghi/clinical/pkg/clinical/application/common"
"github.com/savannahghi/clinical/pkg/clinical/application/common/helpers"
"github.com/savannahghi/clinical/pkg/clinical/application/utils"
"github.com/savannahghi/clinical/pkg/clinical/domain"
"github.com/savannahghi/clinical/pkg/clinical/infrastructure"
"github.com/savannahghi/converterandformatter"
Expand Down Expand Up @@ -495,9 +496,13 @@ func (c *ClinicalUseCaseImpl) ContactsToContactPointInput(ctx context.Context, p

emailSystem := domain.ContactPointSystemEnumEmail
for _, email := range emails {
err := c.infrastructure.FirestoreRepo.ValidateEmail(ctx, email.Email, email.CommunicationOptIn)
emailErr := utils.ValidateEmail(email.Email)
if emailErr != nil {
return nil, fmt.Errorf("invalid email: %v", emailErr)
}
err := c.infrastructure.FirestoreRepo.SaveEmailOTP(ctx, email.Email, email.CommunicationOptIn)
if err != nil {
return nil, fmt.Errorf("invalid email: %v", err)
return nil, fmt.Errorf("unable to save email otp: %v", err)
}
emailContact := &domain.FHIRContactPointInput{
System: &emailSystem,
Expand Down

0 comments on commit 7a03ad6

Please sign in to comment.