From 6d994bf1a55515985cab8a239d5dff419633f8b7 Mon Sep 17 00:00:00 2001 From: maxwellgithinji Date: Wed, 8 Sep 2021 16:34:25 +0300 Subject: [PATCH] chore: refactor onborarding service to be acessed at usecases level --- ...test.go => onboarding_integration_test.go} | 0 .../onboarding/onboarding_unit_test.go | 418 +++++++++++++++++ .../usecases/onboarding/onboarding.go | 103 +++++ .../onboarding/onboarding_integration_test.go | 388 ++++++++++++++++ .../onboarding/onboarding_unit_test.go | 419 ++++++++++++++++++ pkg/engagement/usecases/usecases.go | 4 + 6 files changed, 1332 insertions(+) rename pkg/engagement/infrastructure/services/onboarding/{onboarding_test.go => onboarding_integration_test.go} (100%) create mode 100644 pkg/engagement/infrastructure/services/onboarding/onboarding_unit_test.go create mode 100644 pkg/engagement/usecases/onboarding/onboarding.go create mode 100644 pkg/engagement/usecases/onboarding/onboarding_integration_test.go create mode 100644 pkg/engagement/usecases/onboarding/onboarding_unit_test.go diff --git a/pkg/engagement/infrastructure/services/onboarding/onboarding_test.go b/pkg/engagement/infrastructure/services/onboarding/onboarding_integration_test.go similarity index 100% rename from pkg/engagement/infrastructure/services/onboarding/onboarding_test.go rename to pkg/engagement/infrastructure/services/onboarding/onboarding_integration_test.go diff --git a/pkg/engagement/infrastructure/services/onboarding/onboarding_unit_test.go b/pkg/engagement/infrastructure/services/onboarding/onboarding_unit_test.go new file mode 100644 index 00000000..a3431132 --- /dev/null +++ b/pkg/engagement/infrastructure/services/onboarding/onboarding_unit_test.go @@ -0,0 +1,418 @@ +package onboarding_test + +import ( + "context" + "fmt" + "reflect" + "testing" + "time" + + "github.com/savannahghi/engagementcore/pkg/engagement/application/common/dto" + "github.com/savannahghi/engagementcore/pkg/engagement/infrastructure/services/onboarding" + onboardingMock "github.com/savannahghi/engagementcore/pkg/engagement/infrastructure/services/onboarding/mock" + "github.com/savannahghi/enumutils" + "github.com/savannahghi/firebasetools" + "github.com/savannahghi/interserviceclient" + "github.com/savannahghi/profileutils" + "github.com/segmentio/ksuid" +) + +var ( + fakeOnboardingService onboardingMock.FakeServiceOnboarding +) + +func getTestProfile() profileutils.UserProfile { + version := "1.0.0" + testText := "testText" + testTime := time.Now() + testPhonenumber := interserviceclient.TestUserPhoneNumber + testEmail := firebasetools.TestUserEmail + return profileutils.UserProfile{ + ID: ksuid.New().String(), + UserName: &testText, + VerifiedIdentifiers: []profileutils.VerifiedIdentifier{ + { + UID: ksuid.New().String(), + Timestamp: time.Now(), + LoginProvider: profileutils.LoginProviderTypePhone, + }, + }, + VerifiedUIDS: []string{ksuid.New().String()}, + PrimaryPhone: &testPhonenumber, + PrimaryEmailAddress: &testEmail, + SecondaryPhoneNumbers: []string{testPhonenumber}, + SecondaryEmailAddresses: []string{testEmail}, + PushTokens: []string{ksuid.New().String()}, + Role: profileutils.RoleTypeAgent, + Permissions: []profileutils.PermissionType{"test"}, + FavNavActions: []string{"test"}, + TermsAccepted: true, + Suspended: false, + PhotoUploadID: ksuid.New().String(), + UserBioData: profileutils.BioData{ + FirstName: &testText, + LastName: &testText, + Gender: enumutils.GenderMale, + }, + HomeAddress: &profileutils.Address{ + Longitude: "test", + Latitude: "test", + Locality: &testText, + Name: &testText, + PlaceID: &testText, + FormattedAddress: &testText, + }, + WorkAddress: &profileutils.Address{ + Longitude: "test", + Latitude: "test", + Locality: &testText, + Name: &testText, + PlaceID: &testText, + FormattedAddress: &testText, + }, + CreatedByID: &testText, + Created: &testTime, + ConsumerAppVersion: &version, + PROAppVersion: &version, + } +} + +func TestUnit_GetEmailAddresses(t *testing.T) { + var s onboarding.ProfileService = &fakeOnboardingService + + ctx := context.Background() + uids := onboarding.UserUIDs{ + UIDs: []string{ksuid.New().String()}, + } + type args struct { + ctx context.Context + uids onboarding.UserUIDs + } + tests := []struct { + name string + args args + want map[string][]string + wantErr bool + }{ + { + name: "valid: correct params passed", + args: args{ + ctx: ctx, + uids: uids, + }, + want: map[string][]string{"uids": {"testid"}}, + wantErr: false, + }, + { + name: "invalid: missing uids", + args: args{ + ctx: ctx, + uids: uids, + }, + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.name == "valid: correct params passed" { + fakeOnboardingService.GetEmailAddressesFn = func( + ctx context.Context, + uids onboarding.UserUIDs, + ) (map[string][]string, error) { + return map[string][]string{"uids": {"testid"}}, nil + } + } + if tt.name == "invalid: missing uids" { + fakeOnboardingService.GetEmailAddressesFn = func( + ctx context.Context, + uids onboarding.UserUIDs, + ) (map[string][]string, error) { + return nil, fmt.Errorf("test error") + } + } + got, err := s.GetEmailAddresses(tt.args.ctx, tt.args.uids) + if (err != nil) != tt.wantErr { + t.Errorf("RemoteProfileService.GetEmailAddresses() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("RemoteProfileService.GetEmailAddresses() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestUnit_GetPhoneNumbers(t *testing.T) { + var s onboarding.ProfileService = &fakeOnboardingService + + ctx := context.Background() + uids := onboarding.UserUIDs{ + UIDs: []string{ksuid.New().String()}, + } + + type args struct { + ctx context.Context + uids onboarding.UserUIDs + } + tests := []struct { + name string + args args + want map[string][]string + wantErr bool + }{ + { + name: "valid: correct params passed", + args: args{ + ctx: ctx, + uids: uids, + }, + want: map[string][]string{"uids": {"testid"}}, + wantErr: false, + }, + { + name: "invalid: missing uids", + args: args{ + ctx: ctx, + uids: uids, + }, + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.name == "valid: correct params passed" { + fakeOnboardingService.GetPhoneNumbersFn = func( + ctx context.Context, + uids onboarding.UserUIDs, + ) (map[string][]string, error) { + return map[string][]string{"uids": {"testid"}}, nil + } + } + if tt.name == "invalid: missing uids" { + fakeOnboardingService.GetPhoneNumbersFn = func( + ctx context.Context, + uids onboarding.UserUIDs, + ) (map[string][]string, error) { + return nil, fmt.Errorf("test error") + } + } + got, err := s.GetPhoneNumbers(tt.args.ctx, tt.args.uids) + if (err != nil) != tt.wantErr { + t.Errorf("RemoteProfileService.GetPhoneNumbers() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("RemoteProfileService.GetPhoneNumbers() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestUnit_GetDeviceTokens(t *testing.T) { + var s onboarding.ProfileService = &fakeOnboardingService + + ctx := context.Background() + uids := onboarding.UserUIDs{ + UIDs: []string{ksuid.New().String()}, + } + + type args struct { + ctx context.Context + uids onboarding.UserUIDs + } + tests := []struct { + name string + args args + want map[string][]string + wantErr bool + }{ + { + name: "valid: correct params passed", + args: args{ + ctx: ctx, + uids: uids, + }, + want: map[string][]string{"uids": {"testid"}}, + wantErr: false, + }, + { + name: "invalid: missing uids", + args: args{ + ctx: ctx, + uids: uids, + }, + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.name == "valid: correct params passed" { + fakeOnboardingService.GetDeviceTokensFn = func( + ctx context.Context, + uids onboarding.UserUIDs, + ) (map[string][]string, error) { + return map[string][]string{"uids": {"testid"}}, nil + } + } + if tt.name == "invalid: missing uids" { + fakeOnboardingService.GetDeviceTokensFn = func( + ctx context.Context, + uids onboarding.UserUIDs, + ) (map[string][]string, error) { + return nil, fmt.Errorf("test error") + } + } + got, err := s.GetDeviceTokens(tt.args.ctx, tt.args.uids) + if (err != nil) != tt.wantErr { + t.Errorf("RemoteProfileService.GetDeviceTokens() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("RemoteProfileService.GetDeviceTokens() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestUnit_GetUserProfile(t *testing.T) { + var s onboarding.ProfileService = &fakeOnboardingService + + ctx := context.Background() + uid := ksuid.New().String() + + profile := getTestProfile() + + type args struct { + ctx context.Context + uid string + } + tests := []struct { + name string + args args + want *profileutils.UserProfile + wantErr bool + }{ + { + name: "valid: correct params passed", + args: args{ + ctx: ctx, + uid: uid, + }, + want: &profile, + wantErr: false, + }, + { + name: "invalid: missing uid", + args: args{ + ctx: ctx, + uid: uid, + }, + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.name == "valid: correct params passed" { + fakeOnboardingService.GetUserProfileFn = func( + ctx context.Context, + uid string, + ) (*profileutils.UserProfile, error) { + return &profile, nil + } + } + if tt.name == "invalid: missing uid" { + fakeOnboardingService.GetUserProfileFn = func( + ctx context.Context, + uid string, + ) (*profileutils.UserProfile, error) { + return nil, fmt.Errorf("test error") + } + } + got, err := s.GetUserProfile(tt.args.ctx, tt.args.uid) + if (err != nil) != tt.wantErr { + t.Errorf("RemoteProfileService.GetUserProfile() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("RemoteProfileService.GetUserProfile() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestUnit_GetUserProfileByPhoneOrEmail(t *testing.T) { + + var s onboarding.ProfileService = &fakeOnboardingService + + testPhonenumber := interserviceclient.TestUserPhoneNumber + testEmail := firebasetools.TestUserEmail + + ctx := context.Background() + payload := dto.RetrieveUserProfileInput{ + PhoneNumber: &testPhonenumber, + EmailAddress: &testEmail, + } + + profile := getTestProfile() + + type args struct { + ctx context.Context + payload *dto.RetrieveUserProfileInput + } + tests := []struct { + name string + args args + want *profileutils.UserProfile + wantErr bool + }{ + { + name: "valid: correct params passed", + args: args{ + ctx: ctx, + payload: &payload, + }, + want: &profile, + wantErr: false, + }, + { + name: "invalid: missing payload", + args: args{ + ctx: ctx, + }, + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.name == "valid: correct params passed" { + fakeOnboardingService.GetUserProfileByPhoneOrEmailFn = func( + ctx context.Context, + payload *dto.RetrieveUserProfileInput, + ) (*profileutils.UserProfile, error) { + return &profile, nil + } + } + if tt.name == "invalid: missing payload" { + fakeOnboardingService.GetUserProfileByPhoneOrEmailFn = func( + ctx context.Context, + payload *dto.RetrieveUserProfileInput, + ) (*profileutils.UserProfile, error) { + return nil, fmt.Errorf("test error") + } + } + got, err := s.GetUserProfileByPhoneOrEmail(tt.args.ctx, tt.args.payload) + if (err != nil) != tt.wantErr { + t.Errorf("RemoteProfileService.GetUserProfileByPhoneOrEmail() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("RemoteProfileService.GetUserProfileByPhoneOrEmail() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/engagement/usecases/onboarding/onboarding.go b/pkg/engagement/usecases/onboarding/onboarding.go new file mode 100644 index 00000000..247f8ad6 --- /dev/null +++ b/pkg/engagement/usecases/onboarding/onboarding.go @@ -0,0 +1,103 @@ +package onboarding + +import ( + "context" + + "github.com/savannahghi/engagementcore/pkg/engagement/application/common/dto" + "github.com/savannahghi/engagementcore/pkg/engagement/infrastructure" + "github.com/savannahghi/engagementcore/pkg/engagement/infrastructure/services/onboarding" + "github.com/savannahghi/profileutils" +) + +// UsecaseOnboarding defines Onboarding service usecases interface +type UsecaseOnboarding interface { + GetEmailAddresses( + ctx context.Context, + uids onboarding.UserUIDs, + ) (map[string][]string, error) + GetPhoneNumbers( + ctx context.Context, + uids onboarding.UserUIDs, + ) (map[string][]string, error) + GetDeviceTokens( + ctx context.Context, + uid onboarding.UserUIDs, + ) (map[string][]string, error) + GetUserProfile(ctx context.Context, uid string) (*profileutils.UserProfile, error) + GetUserProfileByPhoneOrEmail(ctx context.Context, payload *dto.RetrieveUserProfileInput) (*profileutils.UserProfile, error) +} + +// ImplOnboarding is the Onboarding service implementation +type ImplOnboarding struct { + infrastructure infrastructure.Interactor +} + +// NewOnboarding initializes a Onboarding service instance +func NewOnboarding(infrastructure infrastructure.Interactor) *ImplOnboarding { + return &ImplOnboarding{ + infrastructure: infrastructure, + } +} + +// GetEmailAddresses gets the specified users' email addresses from the +// staging / testing / prod profile service +func (f *ImplOnboarding) GetEmailAddresses( + ctx context.Context, + uids onboarding.UserUIDs, +) (map[string][]string, error) { + i := f.infrastructure.ProfileService + return i.GetEmailAddresses( + ctx, + uids, + ) +} + +// GetPhoneNumbers gets the specified users' phone numbers from the +// staging / testing / prod profile service +func (f *ImplOnboarding) GetPhoneNumbers( + ctx context.Context, + uids onboarding.UserUIDs, +) (map[string][]string, error) { + i := f.infrastructure.ProfileService + return i.GetPhoneNumbers( + ctx, + uids, + ) +} + +// GetDeviceTokens gets the specified users' FCM push tokens from the +// staging / testing / prod profile service +func (f *ImplOnboarding) GetDeviceTokens( + ctx context.Context, + uid onboarding.UserUIDs, +) (map[string][]string, error) { + i := f.infrastructure.ProfileService + return i.GetDeviceTokens( + ctx, + uid, + ) +} + +// GetUserProfile gets the specified users' profile from the onboarding service +func (f *ImplOnboarding) GetUserProfile( + ctx context.Context, + uid string, +) (*profileutils.UserProfile, error) { + i := f.infrastructure.ProfileService + return i.GetUserProfile( + ctx, + uid, + ) +} + +// GetUserProfileByPhoneOrEmail gets the specified users' profile from the onboarding service +func (f *ImplOnboarding) GetUserProfileByPhoneOrEmail( + ctx context.Context, + payload *dto.RetrieveUserProfileInput, +) (*profileutils.UserProfile, error) { + i := f.infrastructure.ProfileService + return i.GetUserProfileByPhoneOrEmail( + ctx, + payload, + ) +} diff --git a/pkg/engagement/usecases/onboarding/onboarding_integration_test.go b/pkg/engagement/usecases/onboarding/onboarding_integration_test.go new file mode 100644 index 00000000..2c6316e9 --- /dev/null +++ b/pkg/engagement/usecases/onboarding/onboarding_integration_test.go @@ -0,0 +1,388 @@ +package onboarding_test + +import ( + "context" + "reflect" + "testing" + + "github.com/savannahghi/engagementcore/pkg/engagement/application/common/dto" + "github.com/savannahghi/engagementcore/pkg/engagement/infrastructure" + onboardingService "github.com/savannahghi/engagementcore/pkg/engagement/infrastructure/services/onboarding" + "github.com/savannahghi/engagementcore/pkg/engagement/usecases/onboarding" + "github.com/savannahghi/firebasetools" + "github.com/savannahghi/interserviceclient" + "github.com/savannahghi/profileutils" +) + +func InitializeTestNewOnboarding(ctx context.Context) (*onboarding.ImplOnboarding, infrastructure.Interactor, error) { + infra := infrastructure.NewInteractor() + onboarding := onboarding.NewOnboarding(infra) + return onboarding, infra, nil +} + +func onboardingISCClient(t *testing.T) *interserviceclient.InterServiceClient { + deps, err := interserviceclient.LoadDepsFromYAML() + if err != nil { + t.Errorf("can't load inter-service config from YAML: %v", err) + return nil + } + + profileClient, err := interserviceclient.SetupISCclient(*deps, "profile") + if err != nil { + t.Errorf("can't set up profile interservice client: %v", err) + return nil + } + + return profileClient +} + +func TestNewOnboarding(t *testing.T) { + ctx := context.Background() + f, i, err := InitializeTestNewOnboarding(ctx) + if err != nil { + t.Errorf("failed to initialize new FCM: %v", err) + } + + type args struct { + infrastructure infrastructure.Interactor + } + tests := []struct { + name string + args args + want *onboarding.ImplOnboarding + }{ + { + name: "default case", + args: args{ + infrastructure: i, + }, + want: f, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := onboarding.NewOnboarding(tt.args.infrastructure); !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewFCM() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestRemoteProfileService_GetEmailAddresses(t *testing.T) { + ctx, token, err := interserviceclient.GetPhoneNumberAuthenticatedContextAndToken(t, onboardingISCClient(t)) + if err != nil { + t.Errorf("cant get phone number authenticated context and token: %v", err) + return + } + + f, _, err := InitializeTestNewOnboarding(ctx) + if err != nil { + t.Errorf("failed to initialize new FCM: %v", err) + } + + type args struct { + ctx context.Context + uids onboardingService.UserUIDs + } + tests := []struct { + name string + args args + wantNil bool + wantErr bool + }{ + { + name: "happy case: get email addresses inter-service API call to profile", + args: args{ + ctx: ctx, + uids: onboardingService.UserUIDs{ + UIDs: []string{token.UID}, + }, + }, + wantNil: false, + wantErr: false, + }, + { + name: "sad case: get email addresses inter-service API call to profile", + args: args{ + ctx: ctx, + uids: onboardingService.UserUIDs{ + UIDs: []string{}, + }, + }, + wantNil: true, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := f.GetEmailAddresses(tt.args.ctx, tt.args.uids) + if (err != nil) != tt.wantErr { + t.Errorf("RemoteProfileService.GetEmailAddresses() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !tt.wantNil { + if got == nil { + t.Errorf("got back nil contact data") + return + } + } + }) + } +} + +func TestRemoteProfileService_GetPhoneNumbers(t *testing.T) { + ctx, token, err := interserviceclient.GetPhoneNumberAuthenticatedContextAndToken(t, onboardingISCClient(t)) + if err != nil { + t.Errorf("cant get phone number authenticated context and token: %v", err) + return + } + + f, _, err := InitializeTestNewOnboarding(ctx) + if err != nil { + t.Errorf("failed to initialize new FCM: %v", err) + } + + type args struct { + ctx context.Context + uids onboardingService.UserUIDs + } + tests := []struct { + name string + args args + wantNil bool + wantErr bool + }{ + { + name: "happy case: get phone numbers inter-service API call to profile", + args: args{ + ctx: ctx, + uids: onboardingService.UserUIDs{ + UIDs: []string{token.UID}, + }, + }, + wantNil: false, + wantErr: false, + }, + { + name: "sad case: get phone numbers inter-service API call to profile", + args: args{ + ctx: ctx, + uids: onboardingService.UserUIDs{ + UIDs: []string{}, // empty UID list + }, + }, + wantNil: true, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := f.GetPhoneNumbers(tt.args.ctx, tt.args.uids) + if (err != nil) != tt.wantErr { + t.Errorf("RemoteProfileService.GetPhoneNumbers() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !tt.wantNil { + if got == nil { + t.Errorf("got back nil contact data") + return + } + } + }) + } +} + +func TestRemoteProfileService_GetDeviceTokens(t *testing.T) { + ctx, token, err := interserviceclient.GetPhoneNumberAuthenticatedContextAndToken(t, onboardingISCClient(t)) + if err != nil { + t.Errorf("cant get phone number authenticated context and token: %v", err) + return + } + + f, _, err := InitializeTestNewOnboarding(ctx) + if err != nil { + t.Errorf("failed to initialize new FCM: %v", err) + } + + type args struct { + ctx context.Context + uids onboardingService.UserUIDs + } + tests := []struct { + name string + args args + wantNil bool + wantErr bool + }{ + { + name: "happy case: get device tokens inter-service API call to profile", + args: args{ + ctx: ctx, + uids: onboardingService.UserUIDs{ + UIDs: []string{token.UID}, + }, + }, + wantNil: false, + wantErr: false, + }, + { + name: "sad case: get device tokens inter-service API call to profile", + args: args{ + ctx: ctx, + uids: onboardingService.UserUIDs{ + UIDs: []string{}, + }, + }, + wantNil: true, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := f.GetDeviceTokens(tt.args.ctx, tt.args.uids) + if (err != nil) != tt.wantErr { + t.Errorf("RemoteProfileService.GetDeviceTokens() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !tt.wantNil { + if got == nil { + t.Errorf("got back nil contact data") + return + } + } + }) + } +} + +func TestRemoteProfileService_GetUserProfile(t *testing.T) { + ctx, _, err := interserviceclient.GetPhoneNumberAuthenticatedContextAndToken(t, onboardingISCClient(t)) + if err != nil { + t.Errorf("cant get phone number authenticated context and token: %v", err) + return + } + + f, _, err := InitializeTestNewOnboarding(ctx) + if err != nil { + t.Errorf("failed to initialize new FCM: %v", err) + } + type args struct { + ctx context.Context + uid string + } + + UID, err := firebasetools.GetLoggedInUserUID(ctx) + if err != nil { + t.Errorf("can't get logged in user: %v", err) + return + } + invalidUID := "9VwnREOH8GdSfaxH69J6MvCu1gp9" + + tests := []struct { + name string + args args + wantNil bool + wantErr bool + }{ + { + name: "happy case: got user profile", + args: args{ctx: ctx, uid: UID}, + wantNil: false, + wantErr: false, + }, + { + name: "sad case: unable to get user profile", + args: args{ctx: ctx, uid: invalidUID}, + wantNil: true, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := f.GetUserProfile(tt.args.ctx, tt.args.uid) + if (err != nil) != tt.wantErr { + t.Errorf("RemoteProfileService.GetUserProfile() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if !tt.wantNil { + if got == nil { + t.Errorf("got back nil profile data") + return + } + } + }) + } +} + +func TestRemoteProfileService_GetUserProfileByPhoneOrEmail(t *testing.T) { + ctx, _, err := interserviceclient.GetPhoneNumberAuthenticatedContextAndToken(t, onboardingISCClient(t)) + if err != nil { + t.Errorf("cant get phone number authenticated context and token: %v", err) + return + } + f, _, err := InitializeTestNewOnboarding(ctx) + if err != nil { + t.Errorf("failed to initialize new FCM: %v", err) + } + + validPhone := interserviceclient.TestUserPhoneNumber + invalidPhone := "+2547+" + invalidEmail := "test" + type args struct { + ctx context.Context + payload *dto.RetrieveUserProfileInput + } + tests := []struct { + name string + args args + want *profileutils.UserProfile + wantErr bool + }{ + { + name: "Happy case:phone", + args: args{ + ctx: ctx, + payload: &dto.RetrieveUserProfileInput{ + PhoneNumber: &validPhone, + }, + }, + wantErr: false, + }, + + { + name: "Sad case:phone", + args: args{ + ctx: context.Background(), + payload: &dto.RetrieveUserProfileInput{ + PhoneNumber: &invalidPhone, + }, + }, + want: nil, + wantErr: true, + }, + + { + name: "Sad case:email", + args: args{ + ctx: context.Background(), + payload: &dto.RetrieveUserProfileInput{ + EmailAddress: &invalidEmail, + }, + }, + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := f.GetUserProfileByPhoneOrEmail(tt.args.ctx, tt.args.payload) + if (err != nil) != tt.wantErr { + t.Errorf("RemoteProfileService.GetUserProfileByPhoneOrEmail() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !tt.wantErr && got == nil { + t.Errorf("RemoteProfileService.GetUserProfileByPhoneOrEmail() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/engagement/usecases/onboarding/onboarding_unit_test.go b/pkg/engagement/usecases/onboarding/onboarding_unit_test.go new file mode 100644 index 00000000..e6e6d9f4 --- /dev/null +++ b/pkg/engagement/usecases/onboarding/onboarding_unit_test.go @@ -0,0 +1,419 @@ +package onboarding_test + +import ( + "context" + "fmt" + "reflect" + "testing" + "time" + + "github.com/savannahghi/engagementcore/pkg/engagement/application/common/dto" + onboardingService "github.com/savannahghi/engagementcore/pkg/engagement/infrastructure/services/onboarding" + onboardingMock "github.com/savannahghi/engagementcore/pkg/engagement/infrastructure/services/onboarding/mock" + "github.com/savannahghi/engagementcore/pkg/engagement/usecases/onboarding" + "github.com/savannahghi/enumutils" + "github.com/savannahghi/firebasetools" + "github.com/savannahghi/interserviceclient" + "github.com/savannahghi/profileutils" + "github.com/segmentio/ksuid" +) + +var ( + fakeOnboardingService onboardingMock.FakeServiceOnboarding +) + +func getTestProfile() profileutils.UserProfile { + version := "1.0.0" + testText := "testText" + testTime := time.Now() + testPhonenumber := interserviceclient.TestUserPhoneNumber + testEmail := firebasetools.TestUserEmail + return profileutils.UserProfile{ + ID: ksuid.New().String(), + UserName: &testText, + VerifiedIdentifiers: []profileutils.VerifiedIdentifier{ + { + UID: ksuid.New().String(), + Timestamp: time.Now(), + LoginProvider: profileutils.LoginProviderTypePhone, + }, + }, + VerifiedUIDS: []string{ksuid.New().String()}, + PrimaryPhone: &testPhonenumber, + PrimaryEmailAddress: &testEmail, + SecondaryPhoneNumbers: []string{testPhonenumber}, + SecondaryEmailAddresses: []string{testEmail}, + PushTokens: []string{ksuid.New().String()}, + Role: profileutils.RoleTypeAgent, + Permissions: []profileutils.PermissionType{"test"}, + FavNavActions: []string{"test"}, + TermsAccepted: true, + Suspended: false, + PhotoUploadID: ksuid.New().String(), + UserBioData: profileutils.BioData{ + FirstName: &testText, + LastName: &testText, + Gender: enumutils.GenderMale, + }, + HomeAddress: &profileutils.Address{ + Longitude: "test", + Latitude: "test", + Locality: &testText, + Name: &testText, + PlaceID: &testText, + FormattedAddress: &testText, + }, + WorkAddress: &profileutils.Address{ + Longitude: "test", + Latitude: "test", + Locality: &testText, + Name: &testText, + PlaceID: &testText, + FormattedAddress: &testText, + }, + CreatedByID: &testText, + Created: &testTime, + ConsumerAppVersion: &version, + PROAppVersion: &version, + } +} + +func TestUnit_GetEmailAddresses(t *testing.T) { + var s onboarding.UsecaseOnboarding = &fakeOnboardingService + + ctx := context.Background() + uids := onboardingService.UserUIDs{ + UIDs: []string{ksuid.New().String()}, + } + type args struct { + ctx context.Context + uids onboardingService.UserUIDs + } + tests := []struct { + name string + args args + want map[string][]string + wantErr bool + }{ + { + name: "valid: correct params passed", + args: args{ + ctx: ctx, + uids: uids, + }, + want: map[string][]string{"uids": {"testid"}}, + wantErr: false, + }, + { + name: "invalid: missing uids", + args: args{ + ctx: ctx, + uids: uids, + }, + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.name == "valid: correct params passed" { + fakeOnboardingService.GetEmailAddressesFn = func( + ctx context.Context, + uids onboardingService.UserUIDs, + ) (map[string][]string, error) { + return map[string][]string{"uids": {"testid"}}, nil + } + } + if tt.name == "invalid: missing uids" { + fakeOnboardingService.GetEmailAddressesFn = func( + ctx context.Context, + uids onboardingService.UserUIDs, + ) (map[string][]string, error) { + return nil, fmt.Errorf("test error") + } + } + got, err := s.GetEmailAddresses(tt.args.ctx, tt.args.uids) + if (err != nil) != tt.wantErr { + t.Errorf("RemoteProfileService.GetEmailAddresses() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("RemoteProfileService.GetEmailAddresses() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestUnit_GetPhoneNumbers(t *testing.T) { + var s onboarding.UsecaseOnboarding = &fakeOnboardingService + + ctx := context.Background() + uids := onboardingService.UserUIDs{ + UIDs: []string{ksuid.New().String()}, + } + + type args struct { + ctx context.Context + uids onboardingService.UserUIDs + } + tests := []struct { + name string + args args + want map[string][]string + wantErr bool + }{ + { + name: "valid: correct params passed", + args: args{ + ctx: ctx, + uids: uids, + }, + want: map[string][]string{"uids": {"testid"}}, + wantErr: false, + }, + { + name: "invalid: missing uids", + args: args{ + ctx: ctx, + uids: uids, + }, + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.name == "valid: correct params passed" { + fakeOnboardingService.GetPhoneNumbersFn = func( + ctx context.Context, + uids onboardingService.UserUIDs, + ) (map[string][]string, error) { + return map[string][]string{"uids": {"testid"}}, nil + } + } + if tt.name == "invalid: missing uids" { + fakeOnboardingService.GetPhoneNumbersFn = func( + ctx context.Context, + uids onboardingService.UserUIDs, + ) (map[string][]string, error) { + return nil, fmt.Errorf("test error") + } + } + got, err := s.GetPhoneNumbers(tt.args.ctx, tt.args.uids) + if (err != nil) != tt.wantErr { + t.Errorf("RemoteProfileService.GetPhoneNumbers() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("RemoteProfileService.GetPhoneNumbers() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestUnit_GetDeviceTokens(t *testing.T) { + var s onboarding.UsecaseOnboarding = &fakeOnboardingService + + ctx := context.Background() + uids := onboardingService.UserUIDs{ + UIDs: []string{ksuid.New().String()}, + } + + type args struct { + ctx context.Context + uids onboardingService.UserUIDs + } + tests := []struct { + name string + args args + want map[string][]string + wantErr bool + }{ + { + name: "valid: correct params passed", + args: args{ + ctx: ctx, + uids: uids, + }, + want: map[string][]string{"uids": {"testid"}}, + wantErr: false, + }, + { + name: "invalid: missing uids", + args: args{ + ctx: ctx, + uids: uids, + }, + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.name == "valid: correct params passed" { + fakeOnboardingService.GetDeviceTokensFn = func( + ctx context.Context, + uids onboardingService.UserUIDs, + ) (map[string][]string, error) { + return map[string][]string{"uids": {"testid"}}, nil + } + } + if tt.name == "invalid: missing uids" { + fakeOnboardingService.GetDeviceTokensFn = func( + ctx context.Context, + uids onboardingService.UserUIDs, + ) (map[string][]string, error) { + return nil, fmt.Errorf("test error") + } + } + got, err := s.GetDeviceTokens(tt.args.ctx, tt.args.uids) + if (err != nil) != tt.wantErr { + t.Errorf("RemoteProfileService.GetDeviceTokens() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("RemoteProfileService.GetDeviceTokens() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestUnit_GetUserProfile(t *testing.T) { + var s onboarding.UsecaseOnboarding = &fakeOnboardingService + + ctx := context.Background() + uid := ksuid.New().String() + + profile := getTestProfile() + + type args struct { + ctx context.Context + uid string + } + tests := []struct { + name string + args args + want *profileutils.UserProfile + wantErr bool + }{ + { + name: "valid: correct params passed", + args: args{ + ctx: ctx, + uid: uid, + }, + want: &profile, + wantErr: false, + }, + { + name: "invalid: missing uid", + args: args{ + ctx: ctx, + uid: uid, + }, + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.name == "valid: correct params passed" { + fakeOnboardingService.GetUserProfileFn = func( + ctx context.Context, + uid string, + ) (*profileutils.UserProfile, error) { + return &profile, nil + } + } + if tt.name == "invalid: missing uid" { + fakeOnboardingService.GetUserProfileFn = func( + ctx context.Context, + uid string, + ) (*profileutils.UserProfile, error) { + return nil, fmt.Errorf("test error") + } + } + got, err := s.GetUserProfile(tt.args.ctx, tt.args.uid) + if (err != nil) != tt.wantErr { + t.Errorf("RemoteProfileService.GetUserProfile() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("RemoteProfileService.GetUserProfile() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestUnit_GetUserProfileByPhoneOrEmail(t *testing.T) { + + var s onboarding.UsecaseOnboarding = &fakeOnboardingService + + testPhonenumber := interserviceclient.TestUserPhoneNumber + testEmail := firebasetools.TestUserEmail + + ctx := context.Background() + payload := dto.RetrieveUserProfileInput{ + PhoneNumber: &testPhonenumber, + EmailAddress: &testEmail, + } + + profile := getTestProfile() + + type args struct { + ctx context.Context + payload *dto.RetrieveUserProfileInput + } + tests := []struct { + name string + args args + want *profileutils.UserProfile + wantErr bool + }{ + { + name: "valid: correct params passed", + args: args{ + ctx: ctx, + payload: &payload, + }, + want: &profile, + wantErr: false, + }, + { + name: "invalid: missing payload", + args: args{ + ctx: ctx, + }, + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.name == "valid: correct params passed" { + fakeOnboardingService.GetUserProfileByPhoneOrEmailFn = func( + ctx context.Context, + payload *dto.RetrieveUserProfileInput, + ) (*profileutils.UserProfile, error) { + return &profile, nil + } + } + if tt.name == "invalid: missing payload" { + fakeOnboardingService.GetUserProfileByPhoneOrEmailFn = func( + ctx context.Context, + payload *dto.RetrieveUserProfileInput, + ) (*profileutils.UserProfile, error) { + return nil, fmt.Errorf("test error") + } + } + got, err := s.GetUserProfileByPhoneOrEmail(tt.args.ctx, tt.args.payload) + if (err != nil) != tt.wantErr { + t.Errorf("RemoteProfileService.GetUserProfileByPhoneOrEmail() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("RemoteProfileService.GetUserProfileByPhoneOrEmail() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/engagement/usecases/usecases.go b/pkg/engagement/usecases/usecases.go index e83f1dad..0a6d235f 100644 --- a/pkg/engagement/usecases/usecases.go +++ b/pkg/engagement/usecases/usecases.go @@ -7,6 +7,7 @@ import ( "github.com/savannahghi/engagementcore/pkg/engagement/usecases/library" "github.com/savannahghi/engagementcore/pkg/engagement/usecases/mail" "github.com/savannahghi/engagementcore/pkg/engagement/usecases/messaging" + "github.com/savannahghi/engagementcore/pkg/engagement/usecases/onboarding" ) // Interactor is an implementation of the usecases interface @@ -17,6 +18,7 @@ type Interactor struct { *library.ImplLibrary *mail.ImplMail *messaging.ImplNotification + *onboarding.ImplOnboarding } // NewUsecasesInteractor initializes a new usecases interactor @@ -28,6 +30,7 @@ func NewUsecasesInteractor(infrastructure infrastructure.Interactor) Interactor library := library.NewLibrary(infrastructure) mail := mail.NewMail(infrastructure) messaging := messaging.NewNotification(infrastructure) + onboarding := onboarding.NewOnboarding(infrastructure) return Interactor{ feed, @@ -36,5 +39,6 @@ func NewUsecasesInteractor(infrastructure infrastructure.Interactor) Interactor library, mail, messaging, + onboarding, } }