From 3de76b6ab3153ee30ed9d7a82d8af9c9c448a856 Mon Sep 17 00:00:00 2001 From: Otieno Calvine <35563516+NYARAS@users.noreply.github.com> Date: Wed, 8 Sep 2021 15:54:39 +0300 Subject: [PATCH] release: develop changes to main (#148) * fix: allow empty email in registration input (#59) * feat: autolink covers for sladers who receive an SMS from EDI (#44) * feat: isc endpoint to check for permission (#60) - feat: retrive users with a role, get role by name - chore: remove unused role usecases * test: fix tests for getting navigation actions usecase (#53) * chore: filter roles using filter input struct (#57) * fix: ensure that returned navigation actions are in the order required (#58) * chore: update email templates (#62) Co-authored-by: Willard Shikami * ref: ordering navigation actions using priority (#68) - chore: add employee navigation actions * fix: change role filter to search role by role name (#67) * chore: update registered user welcome message (#70) fix: return empty list for missing role in role search * feat: add permission scopes in login response (#69) * feat: layout community onboarding service deployment (#72) * feat: layout community onboarding service deployment [ ] Build and push docker image to GCR [ ] Deploy the revision to Cloud Run via CI [ ] Make this workflow stage depend on CI test and lint workflow Signed-off-by: Otieno Calvine * feat: for lint and test workflow to complete and succeed before running deployment workflow Signed-off-by: Otieno Calvine * configure enviroment and job concurrency group Signed-off-by: Otieno Calvine * fix: flagged feature disabled on token refresh (#75) * refactor purge user (#77) * feat: add service deployment job (#74) [ ] Deploy test server to Google Cloud Run [ ] Supply secrets via Google Secret Manager Signed-off-by: Otieno Calvine * chore: add gitguardian security check and limit deployment to test servers to only develop branch (#78) Signed-off-by: Otieno Calvine * fix: set-env-vars for root collection suffix (#81) Signed-off-by: Otieno Calvine * tests: role management integration and acceptance tests (#63) Signed-off-by: Otieno Calvine * fix: root collection and sentry dns env (#83) Signed-off-by: Otieno Calvine * feat: listing role members in role output and role details in user response (#84) - assigning multiple roles * feat: add and save role revocation reason (#73) chore: roles in admin and agent response * feat: add AgentProfile to users registered as agents (#76) * feat: add searching admin by phone (#79) * feat: add sending temporary pin to users on demand (#54) * Revert "feat: add sending temporary pin to users on demand (#54)" (#89) This reverts commit 1ff99b799858580778cdab76e7dfc9218c7d4120. * chore: add ci schema validation command (#88) Signed-off-by: Otieno Calvine * chore: remove test roles (#90) * feat: add register user rest endpoint (#105) * feat: add register user rest endpoint (#115) * feat: add register user rest endpoint (#116) * chore: make createdByID an optional attribute when creating a user profile (#124) * chore: remove email address as required user response (#127) * fix: add a fix for empty navaction onTapRoutes (#128) * chore: add consumer registration route (#130) * fix: add required permission to consumer navigation action (#133) Co-authored-by: Charles Muchogo <48381664+Muchogoc@users.noreply.github.com> Co-authored-by: Salaton Co-authored-by: Richard Ochom Co-authored-by: Shikami <46521311+willshikami@users.noreply.github.com> Co-authored-by: Willard Shikami Co-authored-by: Kennedy Kori Co-authored-by: Kathurima Kimathi <41376826+KathurimaKimathi@users.noreply.github.com> --- .gitguardian.yaml | 14 + .github/workflows/ci.yml | 15 +- .github/workflows/deployment.yaml | 6 +- go.sum | 6 + pkg/onboarding/application/common/common.go | 74 -- pkg/onboarding/application/dto/input.go | 38 +- pkg/onboarding/application/dto/output.go | 24 +- pkg/onboarding/application/utils/actions.go | 10 +- pkg/onboarding/application/utils/helpers.go | 10 +- .../application/utils/helpers_test.go | 2 - .../application/utils/validators.go | 37 + .../application/utils/validators_test.go | 134 +++ pkg/onboarding/domain/actions.go | 180 +++- pkg/onboarding/domain/enum.go | 44 + pkg/onboarding/domain/enum_test.go | 117 +++ pkg/onboarding/domain/models.go | 36 + .../infrastructure/database/fb/firebase.go | 163 +++- .../database/fb/firebase_integration_test.go | 712 ++++++++++---- .../database/fb/firebase_test.go | 417 +++++++++ .../services/pubsub/subscriber.go | 14 +- pkg/onboarding/presentation/config.go | 33 +- .../presentation/graph/generated/generated.go | 512 +++++++++- .../presentation/graph/inputs.graphql | 1 + .../presentation/graph/profile.graphql | 6 +- .../presentation/graph/profile.resolvers.go | 23 +- .../presentation/graph/types.graphql | 8 + .../presentation/graph/types.resolvers.go | 9 + .../presentation/interactor/interactor.go | 2 - pkg/onboarding/presentation/rest/handlers.go | 110 ++- .../presentation/rest/handlers_helpers.go | 11 + .../presentation/rest/handlers_test.go | 855 +++++++++++++++-- pkg/onboarding/repository/mock/onboarding.go | 34 + pkg/onboarding/repository/onboarding.go | 18 + pkg/onboarding/usecases/admin.go | 159 ++-- pkg/onboarding/usecases/admin_unit_test.go | 801 ++++++---------- pkg/onboarding/usecases/agent.go | 156 ++-- pkg/onboarding/usecases/agent_unit_test.go | 872 +++++++----------- pkg/onboarding/usecases/login.go | 7 +- pkg/onboarding/usecases/login_test.go | 5 +- pkg/onboarding/usecases/profile.go | 4 + pkg/onboarding/usecases/roles.go | 261 +++++- pkg/onboarding/usecases/roles_unit_test.go | 502 +++++++++- pkg/onboarding/usecases/signup.go | 121 ++- pkg/onboarding/usecases/signup_unit_test.go | 366 ++++++++ .../usecases/supplier_integration_test.go | 217 +++-- .../usecases/ussd/main_integration_test.go | 8 +- tests/config_test.go | 310 +++++-- tests/login_acceptance_test.go | 54 +- tests/nhif_acceptance_test.go | 50 +- tests/otp_acceptance_test.go | 13 + tests/pin_acceptance_test.go | 64 +- tests/profile_acceptance_test.go | 52 ++ tests/roles_acceptance_test.go | 814 ++++++++++++++++ tests/signup_acceptance_test.go | 86 +- tests/supplier_acceptance_test.go | 555 ++--------- 55 files changed, 6822 insertions(+), 2330 deletions(-) create mode 100644 .gitguardian.yaml create mode 100644 tests/roles_acceptance_test.go diff --git a/.gitguardian.yaml b/.gitguardian.yaml new file mode 100644 index 00000000..f7a06fd5 --- /dev/null +++ b/.gitguardian.yaml @@ -0,0 +1,14 @@ +all-policies: false +api-url: https://api.gitguardian.com +exit-zero: false +matches-ignore: + - name: token + match: QP18DqWVyuOcPG8CcDUNcEDzU3A2 +paths-ignore: + - '**/README.md' + - doc/* + - LICENSE + - .dockerignore + - .gitignore +show-secrets: false +verbose: false \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 97bf4576..71b90f33 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,6 +51,11 @@ env: JWT_KEY: ${{ secrets.JWT_KEY }} SAVANNAH_ADMIN_EMAIL: ${{ secrets.SAVANNAH_ADMIN_EMAIL }} + # Schema Registry CLI command version + CLI_VERSION: v0.0.1 + #Schema Registry URL + REGISTRY_URL: ${{ secrets.TEST_SCHEMA_REGISTRY_URL }} + concurrency: group: lint-and-test @@ -67,9 +72,9 @@ jobs: - run: git config --global url."https://${user}:${ACCESS_TOKEN}@gitlab.slade360emr.com".insteadOf "https://gitlab.slade360emr.com" - uses: google-github-actions/setup-gcloud@master with: - project_id: ${{ secrets.GOOGLE_CLOUD_PROJECT }} - service_account_key: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }} - export_default_credentials: true + project_id: ${{ secrets.GOOGLE_CLOUD_PROJECT }} + service_account_key: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }} + export_default_credentials: true - name: Install Go uses: actions/setup-go@v2 with: @@ -89,10 +94,12 @@ jobs: go get github.com/fzipp/gocyclo go get github.com/stretchr/testify/assert@v1.7.0 go get github.com/ory/go-acc + go install github.com/savannahghi/bewellcli@$CLI_VERSION - name: Run lint and test run: | staticcheck ./... + bewellcli service validate-schema --name onboarding --version $GITHUB_SHA --url ${{ secrets.SERVICE_GRAPHQL_URL }} go fmt $(go list ./... | grep -v /vendor/) go vet $(go list ./... | grep -v /vendor/) golint -set_exit_status $(go list ./... | grep -v /vendor/) @@ -105,7 +112,7 @@ jobs: gocov convert coverage.out > coverage.json gocov report coverage.json > coverage_report.txt tail coverage_report.txt - + - name: Install goveralls env: GO111MODULE: off diff --git a/.github/workflows/deployment.yaml b/.github/workflows/deployment.yaml index 5dfef1c4..ea5166a0 100644 --- a/.github/workflows/deployment.yaml +++ b/.github/workflows/deployment.yaml @@ -69,8 +69,6 @@ jobs: --set-env-vars "FIREBASE_WEB_API_KEY=${{ secrets.FIREBASE_WEB_API_KEY }}" \ --set-env-vars "JWT_KEY=${{ secrets.JWT_KEY }}" \ --set-env-vars "ENVIRONMENT=${{ secrets.ENVIRONMENT }}" \ - --set-env-vars "SENTRY_DSN=${{ secrets.SENTRY_DSN }}" \ - --set-env-vars "ROOT_COLLECTION_SUFFIX="${{ secrets.ROOT_COLLECTION_SUFFIX }}" \ --set-env-vars "ERP_HOST=${{ secrets.ERP_HOST }}" \ --set-env-vars "ERP_API_SCHEME=${{ secrets.ERP_API_SCHEME }}" \ --set-env-vars "ERP_TOKEN_URL=${{ secrets.ERP_TOKEN_URL }}" \ @@ -109,4 +107,6 @@ jobs: --set-env-vars "SAVANNAH_ADMIN_EMAIL=${{ secrets.SAVANNAH_ADMIN_EMAIL }}" \ --set-env-vars "HUBSPOT_API_KEY=${{ secrets.HUBSPOT_API_KEY }}" \ --set-env-vars "HUBSPOT_API_URL=${{ secrets.HUBSPOT_API_URL }}" \ - --set-env-vars "JAEGER_URL=${{ secrets.JAEGER_URL }}" + --set-env-vars "JAEGER_URL=${{ secrets.JAEGER_URL }}" \ + --set-env-vars "SENTRY_DSN=${{ secrets.SENTRY_DSN }}" \ + --set-env-vars "ROOT_COLLECTION_SUFFIX=${{ secrets.ROOT_COLLECTION_SUFFIX }}" diff --git a/go.sum b/go.sum index 961f11db..128553a4 100644 --- a/go.sum +++ b/go.sum @@ -118,8 +118,10 @@ github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcju github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -363,7 +365,9 @@ github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/xid v1.3.0 h1:6NjYksEUlhurdVehpc7S7dk6DAmcKv8V9gG0FsVN2U4= github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/savannahghi/converterandformatter v0.0.3/go.mod h1:0o7yieYU10WabPqKuqj+5QL52eTL1eGElxjb+A68bbA= @@ -412,6 +416,7 @@ github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNX github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= @@ -445,6 +450,7 @@ github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGr github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k= github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= diff --git a/pkg/onboarding/application/common/common.go b/pkg/onboarding/application/common/common.go index 01819d57..ed7e74eb 100644 --- a/pkg/onboarding/application/common/common.go +++ b/pkg/onboarding/application/common/common.go @@ -6,80 +6,6 @@ var AuthorizedEmails = []string{"apa-dev@healthcloud.co.ke", "apa-prod@healthclo // AuthorizedPhones represent phonenumbers to check whether they have access to certain dto var AuthorizedPhones = []string{"+254700000000"} -// Icon links for navactions -const ( - // StaticBase is the default path at which static assets are hosted - StaticBase = "https://assets.healthcloud.co.ke" - - RoleNavActionIcon = StaticBase + "/actions/roles_navaction.png" - AgentNavActionIcon = StaticBase + "/actions/agent_navaction.png" - EmployeeNavActionIcon = StaticBase + "/actions/employee_navaction.png" - ConsumerNavActionIcon = StaticBase + "/actions/consumer_navaction.png" - HelpNavActionIcon = StaticBase + "/actions/help_navaction.png" - HomeNavActionIcon = StaticBase + "/actions/home_navaction.png" - KYCNavActionIcon = StaticBase + "/actions/kyc_navaction.png" - PartnerNavActionIcon = StaticBase + "/actions/partner_navaction.png" - PatientNavActionIcon = StaticBase + "/actions/patient_navaction.png" - RequestNavActionIcon = StaticBase + "/actions/request_navaction.png" -) - -// On Tap Routes -const ( - HomeRoute = "/home" - PatientRegistrationRoute = "/addPatient" - PatientIdentificationRoute = "/patients" - GetHelpRouteRoute = "/helpCenter" - - // Has KYC and Covers - RequestsRoute = "/admin" - - RoleViewRoute = "/viewCreatedRolesPage" - RoleCreationRoute = "/createRoleStepOne" - - AgentRegistrationRoute = "/agentRegistration" - AgentIdentificationRoute = "/agentIdentification" - - EmployeeRegistrationRoute = "/employeeRegistration" - EmployeeIdentificationRoute = "/employeeIdentification" -) - -// Navigation actions -const ( - HomeNavActionTitle = "Home" - HomeNavActionDescription = "Home Navigation action" - - HelpNavActionTitle = "Help" - HelpNavActionDescription = "Help Navigation action" - - RoleNavActionTitle = "Role Management" - RoleViewActionTitle = "View Roles" - RoleCreationActionTitle = "Create Role" - - PatientNavActionTitle = "Patients" - PatientNavActionDescription = "Patient Navigation action" - PatientRegistrationActionTitle = "Register Patient" - PatientIdentificationActionTitle = "Search Patient" - - RequestsNavActionTitle = "Requests" - RequestsNavActionDescription = "Requests Navigation action" - - AgentNavActionTitle = "Agents" - AgentNavActionDescription = "Agent Navigation action" - AgentRegistrationActionTitle = "Register Agent" - AgentIdentificationActionTitle = "View Agents" - - EmployeeNavActionTitle = "Employees" - EmployeeNavActionDescription = "Employee Navigation action" - EmployeeRegistrationActionTitle = "Register Employee" - EmployeeIdentificationActionTitle = "View Employees" - - ConsumerNavActionTitle = "Consumers" - ConsumerNavActionDescription = "Consumer Navigation action" - - PartnerNavActionTitle = "Partners" - PartnerNavActionDescription = "Partner Navigation action" -) - // PubSub topic names const ( // CreateCustomerTopic is the TopicID for customer creation Topic diff --git a/pkg/onboarding/application/dto/input.go b/pkg/onboarding/application/dto/input.go index 5cf5c9de..ab15066c 100644 --- a/pkg/onboarding/application/dto/input.go +++ b/pkg/onboarding/application/dto/input.go @@ -402,6 +402,18 @@ type CoverLinkingEvent struct { PhoneNumber string `firestore:"phoneNumber"` } +// AssignRolePayload is the payload used to assign a role to a user +type AssignRolePayload struct { + UserID string `json:"userID"` + RoleID string `json:"roleID"` +} + +// DeleteRolePayload is the payload used to delete a role +type DeleteRolePayload struct { + Name string `json:"name"` + RoleID string `json:"roleID"` +} + // RoleInput represents the information required when creating a role type RoleInput struct { Name string `json:"name"` @@ -429,8 +441,9 @@ type RetrieveUserProfileInput struct { //ProfileSuspensionInput is the input required to suspend/unsuspend a PRO account type ProfileSuspensionInput struct { - ID string `json:"id,omitempty"` - Reason string `json:"reason,omitempty"` + ID string `json:"id"` + RoleIDs []string `json:"roleIDs"` + Reason string `json:"reason"` } // EDICoverLinkingPubSubMessage holds the data required to add a cover to the profile @@ -447,3 +460,24 @@ type CheckPermissionPayload struct { UID *string `json:"uid"` Permission *profileutils.Permission `json:"permission"` } + +// RoleRevocationInput is the input when revoking a user's role +type RoleRevocationInput struct { + ProfileID string + RoleID string + Reason string +} + +// RegisterUserInput is the data required to creat a new user. +// this data can be used by cross service requests +type RegisterUserInput struct { + UID *string `json:"uid,omitempty"` + FirstName *string `json:"firstName,omitempty"` + LastName *string `json:"lastName,omitempty"` + Gender *enumutils.Gender `json:"gender,omitempty"` + PhoneNumber *string `json:"phoneNumber,omitempty"` + Email *string `json:"email,omitempty"` + DateOfBirth *scalarutils.Date `json:"dateOfBirth,omitempty"` + RoleIDs []string `json:"roleIDs,omitempty"` + WelcomeMessage *string `json:"welcomeMessage,omitempty"` +} diff --git a/pkg/onboarding/application/dto/output.go b/pkg/onboarding/application/dto/output.go index 6be1c4f6..4eca4f85 100644 --- a/pkg/onboarding/application/dto/output.go +++ b/pkg/onboarding/application/dto/output.go @@ -54,6 +54,8 @@ type Agent struct { // Resend PIN helps inform the whether a send new temporary PIN // True when the user hasn't performed the initial sign up to change PIN ResendPIN bool `json:"resendPIN"` + + Roles []RoleOutput `json:"roles"` } // Admin represents agent with details inferred from their user profile @@ -79,6 +81,8 @@ type Admin struct { // Resend PIN helps inform the whether a send new temporary PIN // True when the user hasn't performed the initial sign up to change PIN ResendPIN bool `json:"resendPIN"` + + Roles []RoleOutput `json:"roles"` } // AccountRecoveryPhonesResponse payload sent back to the frontend when recovery an account @@ -143,12 +147,13 @@ type Segment struct { // RoleOutput is the formatted output with scopes and permissions type RoleOutput struct { - ID string `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - Active bool `json:"active"` - Scopes []string `json:"scopes"` - Permissions []profileutils.Permission `json:"permissions"` + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Active bool `json:"active"` + Scopes []string `json:"scopes"` + Permissions []profileutils.Permission `json:"permissions"` + Users []*profileutils.UserProfile `json:"users"` } // GroupedNavigationActions is the list of Navigation Actions sorted into primary and secondary actions @@ -156,3 +161,10 @@ type GroupedNavigationActions struct { Primary []domain.NavigationAction `json:"primary,omitempty"` Secondary []domain.NavigationAction `json:"secondary,omitempty"` } + +// RegisteredUserResponse is used to return by creating a new user in ISC +type RegisteredUserResponse struct { + ID string `json:"id,omitempty"` + DisplayName string `json:"displayName,omitempty"` + PhoneNumber string `json:"phoneNumber,omitempty"` +} diff --git a/pkg/onboarding/application/utils/actions.go b/pkg/onboarding/application/utils/actions.go index 56d8b65d..1d9d408b 100644 --- a/pkg/onboarding/application/utils/actions.go +++ b/pkg/onboarding/application/utils/actions.go @@ -112,16 +112,18 @@ func GroupNested( parent.Nested = append(parent.Nested, action) } } - grouped = append(grouped, parent) + //remove all actions that do not have onTapRoute and has no nested children + //this removes unnecessary parents + if len(parent.OnTapRoute) > 0 || len(parent.Nested) > 0 { + grouped = append(grouped, parent) + } } return grouped } // GroupPriority groups navigation actions into primary and secondary actions -func GroupPriority( - actions []domain.NavigationAction, -) (primary, secondary []domain.NavigationAction) { +func GroupPriority(actions []domain.NavigationAction) (primary, secondary []domain.NavigationAction) { // sort actions based on priority using the sequence number // uses the inbuilt go sorting functionality diff --git a/pkg/onboarding/application/utils/helpers.go b/pkg/onboarding/application/utils/helpers.go index 47a6188c..3d200a21 100644 --- a/pkg/onboarding/application/utils/helpers.go +++ b/pkg/onboarding/application/utils/helpers.go @@ -192,8 +192,7 @@ func CheckEmptyString(text string) (*string, error) { func NewActionsMapper(ctx context.Context, grouped *dto.GroupedNavigationActions) *profileutils.NavigationActions { mapped := &profileutils.NavigationActions{} - for i := 0; i < len(grouped.Primary); i++ { - action := grouped.Primary[i] + for _, action := range grouped.Primary { c := profileutils.NavAction{ Title: action.Title, OnTapRoute: action.OnTapRoute, @@ -203,8 +202,7 @@ func NewActionsMapper(ctx context.Context, grouped *dto.GroupedNavigationActions mapped.Primary = append(mapped.Primary, c) } - for i := 0; i < len(grouped.Secondary); i++ { - action := grouped.Secondary[i] + for _, action := range grouped.Secondary { c := profileutils.NavAction{ Title: action.Title, OnTapRoute: action.OnTapRoute, @@ -214,8 +212,8 @@ func NewActionsMapper(ctx context.Context, grouped *dto.GroupedNavigationActions if len(action.Nested) > 0 { - for i := 0; i < len(action.Nested); i++ { - nestedAction := (action.Nested[i]).(domain.NavigationAction) + for _, child := range action.Nested { + nestedAction := (child).(domain.NavigationAction) m := profileutils.NestedNavAction{ Title: nestedAction.Title, OnTapRoute: nestedAction.OnTapRoute, diff --git a/pkg/onboarding/application/utils/helpers_test.go b/pkg/onboarding/application/utils/helpers_test.go index 5de0d91e..6f0a3c0c 100644 --- a/pkg/onboarding/application/utils/helpers_test.go +++ b/pkg/onboarding/application/utils/helpers_test.go @@ -10,7 +10,6 @@ import ( "github.com/savannahghi/onboarding/pkg/onboarding/application/utils" "github.com/savannahghi/onboarding/pkg/onboarding/domain" "github.com/savannahghi/profileutils" - "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "gitlab.slade360emr.com/go/apiclient" ) @@ -488,7 +487,6 @@ func TestNewActionsMapper(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := utils.NewActionsMapper(tt.args.ctx, tt.args.grouped) - logrus.Println(got) if got == nil { t.Errorf("NewActionsMapper() = %v, want %v", got, tt.want) } diff --git a/pkg/onboarding/application/utils/validators.go b/pkg/onboarding/application/utils/validators.go index 2322c4ab..21b9c2d3 100644 --- a/pkg/onboarding/application/utils/validators.go +++ b/pkg/onboarding/application/utils/validators.go @@ -273,3 +273,40 @@ func ValidateYearOfBirth(date string) string { return "" } + +//ValidateRegisterUserInput validates the user registration input +func ValidateRegisterUserInput(input dto.RegisterUserInput) (bool, error) { + var res error + + if input.UID == nil { + res := fmt.Errorf("expected `UID` to be defined") + return false, res + } + + if input.FirstName == nil { + res = fmt.Errorf("expected `firstName` to be defined") + return false, res + } + + if input.LastName == nil { + res = fmt.Errorf("expected `lastName` to be defined") + return false, res + } + + if input.PhoneNumber == nil { + res = fmt.Errorf("expected `phoneNumber` to be defined") + return false, res + } + + if input.Gender == nil { + res = fmt.Errorf("expected `gender` to be defined") + return false, res + } + + _, err := converterandformatter.NormalizeMSISDN(*input.PhoneNumber) + if err != nil { + return false, exceptions.NormalizeMSISDNError(err) + } + + return true, nil +} diff --git a/pkg/onboarding/application/utils/validators_test.go b/pkg/onboarding/application/utils/validators_test.go index 5e5c3b45..601c7f97 100644 --- a/pkg/onboarding/application/utils/validators_test.go +++ b/pkg/onboarding/application/utils/validators_test.go @@ -8,10 +8,12 @@ import ( "testing" "github.com/google/uuid" + "github.com/savannahghi/enumutils" "github.com/savannahghi/feedlib" "github.com/savannahghi/interserviceclient" "github.com/savannahghi/onboarding/pkg/onboarding/application/dto" "github.com/savannahghi/onboarding/pkg/onboarding/application/utils" + "github.com/savannahghi/scalarutils" "github.com/stretchr/testify/assert" ) @@ -349,3 +351,135 @@ func TestValidateUSSDDetails(t *testing.T) { }) } } + +func TestValidateRegisterUserInput(t *testing.T) { + uid := uuid.NewString() + fName := "Test" + lName := "test" + validPhone := interserviceclient.TestUserPhoneNumber + invalidPhone := "123" + gender := "male" + dob := scalarutils.Date{ + Day: 1, + Month: 1, + Year: 2000, + } + + tests := []struct { + name string + input dto.RegisterUserInput + want bool + wantErr bool + }{ + { + name: "invalid phone number", + input: dto.RegisterUserInput{ + UID: &uid, + PhoneNumber: &invalidPhone, + Gender: (*enumutils.Gender)(&gender), + DateOfBirth: &dob, + FirstName: &fName, + LastName: &lName, + }, + want: false, + wantErr: true, + }, + { + name: "invalid uid not provided", + input: dto.RegisterUserInput{ + PhoneNumber: &validPhone, + Gender: (*enumutils.Gender)(&gender), + DateOfBirth: &dob, + FirstName: &fName, + LastName: &lName, + }, + want: false, + wantErr: true, + }, + { + name: "invalid phone not provided", + input: dto.RegisterUserInput{ + UID: &uid, + Gender: (*enumutils.Gender)(&gender), + DateOfBirth: &dob, + FirstName: &fName, + LastName: &lName, + }, + want: false, + wantErr: true, + }, + { + name: "invalid first name not provided", + input: dto.RegisterUserInput{ + UID: &uid, + PhoneNumber: &validPhone, + Gender: (*enumutils.Gender)(&gender), + DateOfBirth: &dob, + LastName: &lName, + }, + want: false, + wantErr: true, + }, + { + name: "invalid last name not provided", + input: dto.RegisterUserInput{ + UID: &uid, + PhoneNumber: &invalidPhone, + Gender: (*enumutils.Gender)(&gender), + DateOfBirth: &dob, + FirstName: &fName, + }, + want: false, + wantErr: true, + }, + { + name: "invalid date of birth not provided", + input: dto.RegisterUserInput{ + UID: &uid, + PhoneNumber: &invalidPhone, + Gender: (*enumutils.Gender)(&gender), + FirstName: &fName, + LastName: &lName, + }, + want: false, + wantErr: true, + }, + { + name: "invalid gender not provided", + input: dto.RegisterUserInput{ + UID: &uid, + PhoneNumber: &invalidPhone, + DateOfBirth: &dob, + FirstName: &fName, + LastName: &lName, + }, + want: false, + wantErr: true, + }, + { + name: "valid user registration input", + input: dto.RegisterUserInput{ + UID: &uid, + PhoneNumber: &validPhone, + Gender: (*enumutils.Gender)(&gender), + DateOfBirth: &dob, + FirstName: &fName, + LastName: &lName, + }, + want: true, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := utils.ValidateRegisterUserInput(tt.input) + if (err != nil) != tt.wantErr { + t.Errorf("ValidateRegisterUserInput() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("ValidateRegisterUserInput() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/onboarding/domain/actions.go b/pkg/onboarding/domain/actions.go index 2999d925..26e85c6d 100644 --- a/pkg/onboarding/domain/actions.go +++ b/pkg/onboarding/domain/actions.go @@ -1,10 +1,81 @@ package domain import ( - "github.com/savannahghi/onboarding/pkg/onboarding/application/common" "github.com/savannahghi/profileutils" ) +// On Tap Routes +const ( + HomeRoute = "/home" + PatientRegistrationRoute = "/addPatient" + PatientIdentificationRoute = "/patients" + GetHelpRouteRoute = "/helpCenter" + RequestsRoute = "/admin" + RoleViewRoute = "/viewCreatedRolesPage" + RoleCreationRoute = "/createRoleStepOne" + RoleAssignmentRoute = "/bewellUserIdentification" + AgentRegistrationRoute = "/agentRegistration" + AgentIdentificationRoute = "/agentIdentification" + EmployeeRegistrationRoute = "/employeeRegistration" + EmployeeIdentificationRoute = "/employeeIdentification" + ConsumerRegistrationRoute = "/bewellUserRegistration" +) + +// Icon links for navactions +const ( + // StaticBase is the default path at which static assets are hosted + StaticBase = "https://assets.healthcloud.co.ke" + + RoleNavActionIcon = StaticBase + "/actions/roles_navaction.png" + AgentNavActionIcon = StaticBase + "/actions/agent_navaction.png" + EmployeeNavActionIcon = StaticBase + "/actions/employee_navaction.png" + ConsumerNavActionIcon = StaticBase + "/actions/consumer_navaction.png" + HelpNavActionIcon = StaticBase + "/actions/help_navaction.png" + HomeNavActionIcon = StaticBase + "/actions/home_navaction.png" + KYCNavActionIcon = StaticBase + "/actions/kyc_navaction.png" + PartnerNavActionIcon = StaticBase + "/actions/partner_navaction.png" + PatientNavActionIcon = StaticBase + "/actions/patient_navaction.png" + RequestNavActionIcon = StaticBase + "/actions/request_navaction.png" +) + +// Navigation actions +var ( + HomeNavActionTitle = "Home" + HomeNavActionDescription = "Home Navigation action" + + HelpNavActionTitle = "Help" + HelpNavActionDescription = "Help Navigation action" + + RoleNavActionTitle = "Role Management" + RoleViewActionTitle = "View Roles" + RoleCreationActionTitle = "Create Role" + RoleAssignActionTitle = "Assign Role" + + PatientNavActionTitle = "Patients" + PatientNavActionDescription = "Patient Navigation action" + PatientRegistrationActionTitle = "Register Patient" + PatientIdentificationActionTitle = "Search Patient" + + RequestsNavActionTitle = "Requests" + RequestsNavActionDescription = "Requests Navigation action" + + AgentNavActionTitle = "Agents" + AgentNavActionDescription = "Agent Navigation action" + AgentRegistrationActionTitle = "Register Agent" + AgentIdentificationActionTitle = "View Agents" + + EmployeeNavActionTitle = "Employees" + EmployeeNavActionDescription = "Employee Navigation action" + EmployeeRegistrationActionTitle = "Register Employee" + EmployeeIdentificationActionTitle = "View Employees" + + ConsumerNavActionTitle = "Consumers" + ConsumerNavActionDescription = "Consumer Navigation action" + + PartnerNavActionTitle = "Partners" + PartnerNavActionDescription = "Partner Navigation action" +) + const ( //HomeGroup groups all actions under the home resource HomeGroup NavigationGroup = "home" @@ -48,6 +119,7 @@ const ( RoleNavActionSequence RoleCreationNavActionSequence RoleViewingNavActionSequence + RoleAssignNavActionSequence RequestsNavActionSequence @@ -75,9 +147,9 @@ var ( // HomeNavAction is the primary home button HomeNavAction = NavigationAction{ Group: HomeGroup, - Title: common.HomeNavActionTitle, - OnTapRoute: common.HomeRoute, - Icon: common.HomeNavActionIcon, + Title: HomeNavActionTitle, + OnTapRoute: HomeRoute, + Icon: HomeNavActionIcon, RequiredPermission: nil, SequenceNumber: HomeNavActionSequence, } @@ -85,9 +157,9 @@ var ( // HelpNavAction navigation action to help and FAQs page HelpNavAction = NavigationAction{ Group: HelpGroup, - Title: common.HelpNavActionTitle, - OnTapRoute: common.GetHelpRouteRoute, - Icon: common.HelpNavActionIcon, + Title: HelpNavActionTitle, + OnTapRoute: GetHelpRouteRoute, + Icon: HelpNavActionIcon, RequiredPermission: nil, SequenceNumber: HelpNavActionSequence, } @@ -98,9 +170,9 @@ var ( // KYCNavActions is the navigation acction to KYC processing KYCNavActions = NavigationAction{ Group: KYCGroup, - Title: common.RequestsNavActionTitle, - OnTapRoute: common.RequestsRoute, - Icon: common.RequestNavActionIcon, + Title: RequestsNavActionTitle, + OnTapRoute: RequestsRoute, + Icon: RequestNavActionIcon, RequiredPermission: &profileutils.CanProcessKYC, SequenceNumber: RequestsNavActionSequence, } @@ -109,12 +181,10 @@ var ( var ( //PartnerNavActions is the navigation actions to partner management PartnerNavActions = NavigationAction{ - Group: PartnerGroup, - Title: common.PartnerNavActionTitle, - // Not provided yet - OnTapRoute: "", - Icon: common.PartnerNavActionIcon, - RequiredPermission: &profileutils.CanViewPartner, + Group: PartnerGroup, + Title: PartnerNavActionTitle, + Icon: PartnerNavActionIcon, + RequiredPermission: nil, SequenceNumber: PartnerNavactionSequence, } ) @@ -122,12 +192,11 @@ var ( var ( //ConsumerNavActions is the navigation actions to consumer management ConsumerNavActions = NavigationAction{ - Group: ConsumerGroup, - Title: common.ConsumerNavActionTitle, - // Not provided yet - OnTapRoute: "", - Icon: common.ConsumerNavActionIcon, - RequiredPermission: &profileutils.CanViewConsumers, + Group: ConsumerGroup, + Title: ConsumerNavActionTitle, + Icon: ConsumerNavActionIcon, + OnTapRoute: ConsumerRegistrationRoute, + RequiredPermission: &profileutils.CanCreateConsumer, SequenceNumber: ConsumerNavactionSequence, } ) @@ -137,17 +206,17 @@ var ( // it has nested navigation actions below RoleNavActions = NavigationAction{ Group: RoleGroup, - Title: common.RoleNavActionTitle, - Icon: common.RoleNavActionIcon, - RequiredPermission: &profileutils.CanViewRole, + Title: RoleNavActionTitle, + Icon: RoleNavActionIcon, + RequiredPermission: nil, SequenceNumber: RoleNavActionSequence, } //RoleCreationNavAction a child of the RoleNavActions RoleCreationNavAction = NavigationAction{ Group: RoleGroup, - Title: common.RoleCreationActionTitle, - OnTapRoute: common.RoleCreationRoute, + Title: RoleCreationActionTitle, + OnTapRoute: RoleCreationRoute, RequiredPermission: &profileutils.CanCreateRole, HasParent: true, SequenceNumber: RoleCreationNavActionSequence, @@ -156,12 +225,21 @@ var ( //RoleViewNavAction a child of the RoleNavActions RoleViewNavAction = NavigationAction{ Group: RoleGroup, - Title: common.RoleViewActionTitle, - OnTapRoute: common.RoleViewRoute, + Title: RoleViewActionTitle, + OnTapRoute: RoleViewRoute, RequiredPermission: &profileutils.CanViewRole, HasParent: true, SequenceNumber: RoleViewingNavActionSequence, } + //RoleAssignNavAction a child of the RoleNavActions + RoleAssignNavAction = NavigationAction{ + Group: RoleGroup, + Title: RoleAssignActionTitle, + OnTapRoute: RoleAssignmentRoute, + RequiredPermission: &profileutils.CanAssignRole, + HasParent: true, + SequenceNumber: RoleAssignNavActionSequence, + } ) var ( @@ -169,17 +247,17 @@ var ( // it has nested navigation actions below AgentNavActions = NavigationAction{ Group: AgentGroup, - Title: common.AgentNavActionTitle, - Icon: common.AgentNavActionIcon, - RequiredPermission: &profileutils.CanViewAgent, + Title: AgentNavActionTitle, + Icon: AgentNavActionIcon, + RequiredPermission: nil, SequenceNumber: AgentNavActionSequence, } //AgentRegistrationNavAction a child of the AgentNavActions AgentRegistrationNavAction = NavigationAction{ Group: AgentGroup, - Title: common.AgentRegistrationActionTitle, - OnTapRoute: common.AgentRegistrationRoute, + Title: AgentRegistrationActionTitle, + OnTapRoute: AgentRegistrationRoute, RequiredPermission: &profileutils.CanRegisterAgent, HasParent: true, SequenceNumber: AgentRegistrationActionSequence, @@ -188,8 +266,8 @@ var ( //AgentidentificationNavAction a child of the AgentNavActions AgentidentificationNavAction = NavigationAction{ Group: AgentGroup, - Title: common.AgentIdentificationActionTitle, - OnTapRoute: common.AgentIdentificationRoute, + Title: AgentIdentificationActionTitle, + OnTapRoute: AgentIdentificationRoute, RequiredPermission: &profileutils.CanIdentifyAgent, HasParent: true, SequenceNumber: AgentSearchNavActionSequence, @@ -201,17 +279,17 @@ var ( // it has nested navigation actions below EmployeeNavActions = NavigationAction{ Group: EmployeeGroup, - Title: common.EmployeeNavActionTitle, - Icon: common.EmployeeNavActionIcon, - RequiredPermission: &profileutils.CanViewEmployee, + Title: EmployeeNavActionTitle, + Icon: EmployeeNavActionIcon, + RequiredPermission: nil, SequenceNumber: EmployeeNavActionSequence, } //EmployeeRegistrationNavAction a child of the EmployeeNavActions EmployeeRegistrationNavAction = NavigationAction{ Group: EmployeeGroup, - Title: common.EmployeeRegistrationActionTitle, - OnTapRoute: common.EmployeeRegistrationRoute, + Title: EmployeeRegistrationActionTitle, + OnTapRoute: EmployeeRegistrationRoute, RequiredPermission: &profileutils.CanCreateEmployee, HasParent: true, SequenceNumber: EmployeeRegistrationActionSequence, @@ -220,8 +298,8 @@ var ( //EmployeeidentificationNavAction a child of the EmployeeNavActions EmployeeidentificationNavAction = NavigationAction{ Group: EmployeeGroup, - Title: common.EmployeeIdentificationActionTitle, - OnTapRoute: common.EmployeeIdentificationRoute, + Title: EmployeeIdentificationActionTitle, + OnTapRoute: EmployeeIdentificationRoute, RequiredPermission: &profileutils.CanViewEmployee, HasParent: true, SequenceNumber: EmployeeSearchNavActionSequence, @@ -233,17 +311,17 @@ var ( // it has nested navigation actions below PatientNavActions = NavigationAction{ Group: PatientGroup, - Title: common.PatientNavActionTitle, - Icon: common.PatientNavActionIcon, - RequiredPermission: &profileutils.CanViewPatient, + Title: PatientNavActionTitle, + Icon: PatientNavActionIcon, + RequiredPermission: nil, SequenceNumber: PatientNavActionSequence, } //PatientRegistrationNavAction a child of the PatientNavActions PatientRegistrationNavAction = NavigationAction{ Group: PatientGroup, - Title: common.PatientRegistrationActionTitle, - OnTapRoute: common.PatientRegistrationRoute, + Title: PatientRegistrationActionTitle, + OnTapRoute: PatientRegistrationRoute, RequiredPermission: &profileutils.CanCreatePatient, HasParent: true, SequenceNumber: PatientRegistrationNavActionSequence, @@ -252,8 +330,8 @@ var ( //PatientIdentificationNavAction a child of the PatientNavActions PatientIdentificationNavAction = NavigationAction{ Group: PatientGroup, - Title: common.PatientIdentificationActionTitle, - OnTapRoute: common.PatientIdentificationRoute, + Title: PatientIdentificationActionTitle, + OnTapRoute: PatientIdentificationRoute, RequiredPermission: &profileutils.CanIdentifyPatient, HasParent: true, SequenceNumber: PatientSearchNavActionSequence, @@ -272,5 +350,5 @@ var AllNavigationActions = []NavigationAction{ PatientNavActions, PatientRegistrationNavAction, PatientIdentificationNavAction, - RoleNavActions, RoleCreationNavAction, RoleViewNavAction, + RoleNavActions, RoleCreationNavAction, RoleViewNavAction, RoleAssignNavAction, } diff --git a/pkg/onboarding/domain/enum.go b/pkg/onboarding/domain/enum.go index 590ead6c..080452da 100644 --- a/pkg/onboarding/domain/enum.go +++ b/pkg/onboarding/domain/enum.go @@ -361,3 +361,47 @@ func (e *EmploymentType) UnmarshalGQL(v interface{}) error { func (e EmploymentType) MarshalGQL(w io.Writer) { fmt.Fprint(w, strconv.Quote(e.String())) } + +//AgentType is the different kind of agent groups +type AgentType string + +// Valid AgentTypes that can possibly be given to a user +const ( + //FreelanceAgent are agents that work at part time with savannah + FreelanceAgent AgentType = "Independent Agent" + + //CompanyAgent are agents who are fully employed by savannah + CompanyAgent AgentType = "SIL Agent" +) + +// IsValid .. +func (e AgentType) IsValid() bool { + switch e { + case FreelanceAgent, CompanyAgent: + return true + } + return false +} + +func (e AgentType) String() string { + return string(e) +} + +// UnmarshalGQL .. +func (e *AgentType) UnmarshalGQL(v interface{}) error { + str, ok := v.(string) + if !ok { + return fmt.Errorf("enums must be strings") + } + + *e = AgentType(str) + if !e.IsValid() { + return fmt.Errorf("%s is not a valid AgentType", str) + } + return nil +} + +// MarshalGQL .. +func (e AgentType) MarshalGQL(w io.Writer) { + fmt.Fprint(w, strconv.Quote(e.String())) +} diff --git a/pkg/onboarding/domain/enum_test.go b/pkg/onboarding/domain/enum_test.go index d269ffa7..f371c9be 100644 --- a/pkg/onboarding/domain/enum_test.go +++ b/pkg/onboarding/domain/enum_test.go @@ -632,3 +632,120 @@ func TestPractitionerService_MarshalGQL(t *testing.T) { }) } } + +func TestAgentType_IsValid(t *testing.T) { + tests := []struct { + name string + e domain.AgentType + want bool + }{ + { + name: "valid", + e: domain.CompanyAgent, + want: true, + }, + { + name: "invalid", + e: domain.AgentType("FreelanceAgent"), + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.e.IsValid(); got != tt.want { + t.Errorf("AgentType.IsValid() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestAgentType_String(t *testing.T) { + tests := []struct { + name string + e domain.AgentType + want string + }{ + { + name: "valid", + e: domain.CompanyAgent, + want: "SIL Agent", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.e.String(); got != tt.want { + t.Errorf("AgentType.String() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestAgentType_UnmarshalGQL(t *testing.T) { + valid := domain.CompanyAgent + invalid := domain.AgentType("CompanyAgent") + + type args struct { + v interface{} + } + tests := []struct { + name string + e *domain.AgentType + args args + wantErr bool + }{ + { + name: "invalid type", + e: &invalid, + args: args{ + v: 1, + }, + wantErr: true, + }, + { + name: "invalid string", + e: &invalid, + args: args{ + v: "CompanyAgent", + }, + wantErr: true, + }, + { + name: "valid type", + e: &valid, + args: args{ + v: "SIL Agent", + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.e.UnmarshalGQL(tt.args.v); (err != nil) != tt.wantErr { + t.Errorf("AgentType.UnmarshalGQL() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestAgentType_MarshalGQL(t *testing.T) { + tests := []struct { + name string + e domain.AgentType + wantW string + }{ + { + name: "valid", + e: domain.CompanyAgent, + wantW: strconv.Quote("SIL Agent"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + w := &bytes.Buffer{} + tt.e.MarshalGQL(w) + if gotW := w.String(); gotW != tt.wantW { + t.Errorf("AgentType.MarshalGQL() = %v, want %v", gotW, tt.wantW) + } + }) + } +} diff --git a/pkg/onboarding/domain/models.go b/pkg/onboarding/domain/models.go index 37609265..15a6618f 100644 --- a/pkg/onboarding/domain/models.go +++ b/pkg/onboarding/domain/models.go @@ -179,3 +179,39 @@ type NavigationAction struct { // Actions with a higher sequence number appear at the top i.e ascending order SequenceNumber int `json:"sequenceNumber"` } + +// RoleRevocationLog represents a log for revoking a users role +// used when removing a role from a user i.e user deactivation +type RoleRevocationLog struct { + // Unique identifier for a revocation + ID string `json:"id" firestore:"id"` + + // profile of user whose role is being revoked + ProfileID string `json:"profileID" firestore:"profileID"` + + // ID of role being revoked + RoleID string `json:"roleID" firestore:"roleID"` + + // Reason role is being revoked + Reason string `json:"reason" firestore:"reason"` + + // CreatedBy is the Profile ID of the user removing the role. + CreatedBy string `json:"createdBy,omitempty" firestore:"createdBy"` + + // Created is the timestamp indicating when the role was created + Created time.Time `json:"created" firestore:"created"` +} + +//AdminProfile is the profile of all users who permform admin tasks +type AdminProfile struct { + ID string `json:"id,omitempty"` + ProfileID string `json:"profileID,omitempty"` + OrganizationID string `json:"organizationID,omitempty"` +} + +//AgentProfile is the profile of all users who permform agent tasks +type AgentProfile struct { + ID string `json:"id,omitempty"` + ProfileID string `json:"profileID,omitempty"` + AgentType AgentType `json:"agentType,omitempty"` +} diff --git a/pkg/onboarding/infrastructure/database/fb/firebase.go b/pkg/onboarding/infrastructure/database/fb/firebase.go index abbd79bf..80026951 100644 --- a/pkg/onboarding/infrastructure/database/fb/firebase.go +++ b/pkg/onboarding/infrastructure/database/fb/firebase.go @@ -51,7 +51,10 @@ const ( marketingDataCollectionName = "marketing_data" ussdEventsCollectionName = "ussd_events" coverLinkingEventsCollectionName = "coverlinking_events" + rolesRevocationCollectionName = "role_revocations" rolesCollectionName = "user_roles" + adminProfileCollectionName = "admin_profiles" + agentProfileCollectionName = "agent_profiles" ) // Repository accesses and updates an item that is stored on Firebase @@ -167,6 +170,24 @@ func (fr Repository) GetRolesCollectionName() string { return suffixed } +// GetRolesRevocationCollectionName ... +func (fr Repository) GetRolesRevocationCollectionName() string { + suffixed := firebasetools.SuffixCollection(rolesRevocationCollectionName) + return suffixed +} + +// GetAdminProfileCollectionName ... +func (fr Repository) GetAdminProfileCollectionName() string { + suffixed := firebasetools.SuffixCollection(adminProfileCollectionName) + return suffixed +} + +// GetAgentProfileCollectionName ... +func (fr Repository) GetAgentProfileCollectionName() string { + suffixed := firebasetools.SuffixCollection(agentProfileCollectionName) + return suffixed +} + // GetUserProfileByUID retrieves the user profile by UID func (fr *Repository) GetUserProfileByUID( ctx context.Context, @@ -4095,7 +4116,7 @@ func (fr *Repository) GetRoleByID(ctx context.Context, roleID string) (*profileu } if len(docs) != 1 { - err = fmt.Errorf("role with id %v not found", roleID) + err = fmt.Errorf("role not found: %v", roleID) utils.RecordSpanError(span, err) return nil, err } @@ -4304,6 +4325,38 @@ func (fr *Repository) GetRoleByName(ctx context.Context, roleName string) (*prof return role, nil } +// SaveRoleRevocation records a log for a role revocation +// +// userId is the ID of the user removing a role from a user +func (fr *Repository) SaveRoleRevocation(ctx context.Context, userID string, revocation dto.RoleRevocationInput) error { + ctx, span := tracer.Start(ctx, "SaveRoleRevocation") + defer span.End() + + timestamp := time.Now().In(pubsubtools.TimeLocation) + + role := domain.RoleRevocationLog{ + ID: uuid.New().String(), + ProfileID: revocation.ProfileID, + RoleID: revocation.RoleID, + Reason: revocation.Reason, + CreatedBy: userID, + Created: timestamp, + } + + createCommad := &CreateCommand{ + CollectionName: fr.GetRolesRevocationCollectionName(), + Data: role, + } + + _, err := fr.FirestoreClient.Create(ctx, createCommad) + if err != nil { + utils.RecordSpanError(span, err) + return err + } + + return nil +} + //CheckIfUserHasPermission checks if a user has the required permission func (fr *Repository) CheckIfUserHasPermission( ctx context.Context, @@ -4333,3 +4386,111 @@ func (fr *Repository) CheckIfUserHasPermission( return false, nil } + +//CreateAdminProfile creates an admin profile related to user profile +func (fr *Repository) CreateAdminProfile(ctx context.Context, adminProfile domain.AdminProfile) error { + + ctx, span := tracer.Start(ctx, "CreateAdminProfile") + defer span.End() + + exists, err := fr.CheckIfAdminProfileExists(ctx, adminProfile.ProfileID) + if err != nil { + utils.RecordSpanError(span, err) + return err + } + + if exists { + err := fmt.Errorf("user %s already has admin profile", adminProfile.ProfileID) + utils.RecordSpanError(span, err) + return err + } + + createCommad := &CreateCommand{ + CollectionName: fr.GetAdminProfileCollectionName(), + Data: adminProfile, + } + + _, err = fr.FirestoreClient.Create(ctx, createCommad) + if err != nil { + utils.RecordSpanError(span, err) + return exceptions.InternalServerError(err) + } + + return nil +} + +//CheckIfAdminProfileExists return true if admin profile with profile ID provide +//exists, otherwise false +func (fr *Repository) CheckIfAdminProfileExists(ctx context.Context, profileID string) (bool, error) { + ctx, span := tracer.Start(ctx, "CheckIfAdminProfileExists") + defer span.End() + + query := &GetAllQuery{ + CollectionName: fr.GetAdminProfileCollectionName(), + FieldName: "profileID", + Operator: "==", + Value: profileID, + } + + docs, err := fr.FirestoreClient.GetAll(ctx, query) + if err != nil { + utils.RecordSpanError(span, err) + return false, exceptions.InternalServerError(err) + } + + return len(docs) == 1, nil +} + +//CreateAgentProfile creates an agent profile related to user profile +func (fr *Repository) CreateAgentProfile(ctx context.Context, agentProfile domain.AgentProfile) error { + + ctx, span := tracer.Start(ctx, "CreateAgentProfile") + defer span.End() + + exists, err := fr.CheckIfAgentProfileExists(ctx, agentProfile.ProfileID) + if err != nil { + utils.RecordSpanError(span, err) + return err + } + + if exists { + err := fmt.Errorf("user %s already has agent profile", agentProfile.ProfileID) + utils.RecordSpanError(span, err) + return err + } + + createCommad := &CreateCommand{ + CollectionName: fr.GetAgentProfileCollectionName(), + Data: agentProfile, + } + + _, err = fr.FirestoreClient.Create(ctx, createCommad) + if err != nil { + utils.RecordSpanError(span, err) + return exceptions.InternalServerError(err) + } + + return nil +} + +//CheckIfAgentProfileExists return true if agent profile with profile ID provide +//exists, otherwise false +func (fr *Repository) CheckIfAgentProfileExists(ctx context.Context, profileID string) (bool, error) { + ctx, span := tracer.Start(ctx, "CheckIfAgentProfileExists") + defer span.End() + + query := &GetAllQuery{ + CollectionName: fr.GetAgentProfileCollectionName(), + FieldName: "profileID", + Operator: "==", + Value: profileID, + } + + docs, err := fr.FirestoreClient.GetAll(ctx, query) + if err != nil { + utils.RecordSpanError(span, err) + return false, exceptions.InternalServerError(err) + } + + return len(docs) == 1, nil +} diff --git a/pkg/onboarding/infrastructure/database/fb/firebase_integration_test.go b/pkg/onboarding/infrastructure/database/fb/firebase_integration_test.go index 997b588a..a068edd0 100644 --- a/pkg/onboarding/infrastructure/database/fb/firebase_integration_test.go +++ b/pkg/onboarding/infrastructure/database/fb/firebase_integration_test.go @@ -71,10 +71,12 @@ func TestMain(m *testing.M) { r := fb.Repository{} // They are nil fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + log.Printf("failed to initialize test FireStore client") + return } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + log.Printf("failed to initialize test FireBase client") + return } purgeRecords := func() { @@ -122,10 +124,12 @@ func TestMain(m *testing.M) { func InitializeTestService(ctx context.Context) (*interactor.Interactor, error) { fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + return nil, fmt.Errorf("failed to initialize test FireStore client") + } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + return nil, fmt.Errorf("failed to initialize test FireBase client") + } projectID, err := serverutils.GetEnvVar(serverutils.GoogleCloudProjectIDEnvVarName) @@ -317,10 +321,12 @@ func TestRemoveKYCProcessingRequest(t *testing.T) { fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + t.Errorf("failed to initialize test FireStore client") + return } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + t.Errorf("failed to initialize test FireBase client") + return } firestoreExtension := fb.NewFirestoreClientExtension(fsc) fr := fb.NewFirebaseRepository(firestoreExtension, fbc) @@ -404,10 +410,12 @@ func TestPurgeUserByPhoneNumber(t *testing.T) { assert.NotNil(t, auth) fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + t.Errorf("failed to initialize test FireStore client") + return } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + t.Errorf("failed to initialize test FireBase client") + return } firestoreExtension := fb.NewFirestoreClientExtension(fsc) fr := fb.NewFirebaseRepository(firestoreExtension, fbc) @@ -489,10 +497,12 @@ func TestCreateEmptyCustomerProfile(t *testing.T) { ctx := context.Background() fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + t.Errorf("failed to initialize test FireStore client") + return } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + t.Errorf("failed to initialize test FireBase client") + return } firestoreExtension := fb.NewFirestoreClientExtension(fsc) firestoreDB := fb.NewFirebaseRepository(firestoreExtension, fbc) @@ -540,10 +550,12 @@ func TestGetCustomerProfileByProfileID(t *testing.T) { ctx := context.Background() fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + t.Errorf("failed to initialize test FireStore client") + return } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + t.Errorf("failed to initialize test FireBase client") + return } firestoreExtension := fb.NewFirestoreClientExtension(fsc) firestoreDB := fb.NewFirebaseRepository(firestoreExtension, fbc) @@ -603,10 +615,12 @@ func TestRepository_GetCustomerOrSupplierProfileByProfileID(t *testing.T) { ctx := context.Background() fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + t.Errorf("failed to initialize test FireStore client") + return } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + t.Errorf("failed to initialize test FireBase client") + return } firestoreExtension := fb.NewFirestoreClientExtension(fsc) fr := fb.NewFirebaseRepository(firestoreExtension, fbc) @@ -696,10 +710,12 @@ func TestRepository_GetCustomerProfileByID(t *testing.T) { ctx := context.Background() fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + t.Errorf("failed to initialize test FireStore client") + return } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + t.Errorf("failed to initialize test FireBase client") + return } firestoreExtension := fb.NewFirestoreClientExtension(fsc) fr := fb.NewFirebaseRepository(firestoreExtension, fbc) @@ -763,10 +779,12 @@ func TestRepository_ExchangeRefreshTokenForIDToken(t *testing.T) { fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + t.Errorf("failed to initialize test FireStore client") + return } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + t.Errorf("failed to initialize test FireBase client") + return } firestoreExtension := fb.NewFirestoreClientExtension(fsc) fr := fb.NewFirebaseRepository(firestoreExtension, fbc) @@ -863,10 +881,12 @@ func TestRepository_GetUserProfileByPhoneNumber(t *testing.T) { fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + t.Errorf("failed to initialize test FireStore client") + return } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + t.Errorf("failed to initialize test FireBase client") + return } firestoreExtension := fb.NewFirestoreClientExtension(fsc) fr := fb.NewFirebaseRepository(firestoreExtension, fbc) @@ -925,10 +945,12 @@ func TestRepository_GetUserProfileByPrimaryPhoneNumber(t *testing.T) { fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + t.Errorf("failed to initialize test FireStore client") + return } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + t.Errorf("failed to initialize test FireBase client") + return } firestoreExtension := fb.NewFirestoreClientExtension(fsc) fr := fb.NewFirebaseRepository(firestoreExtension, fbc) @@ -995,10 +1017,12 @@ func TestRepository_GetSupplierProfileByProfileID(t *testing.T) { fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + t.Errorf("failed to initialize test FireStore client") + return } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + t.Errorf("failed to initialize test FireBase client") + return } firestoreExtension := fb.NewFirestoreClientExtension(fsc) fr := fb.NewFirebaseRepository(firestoreExtension, fbc) @@ -1065,10 +1089,12 @@ func TestRepository_GetSupplierProfileByID(t *testing.T) { fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + t.Errorf("failed to initialize test FireStore client") + return } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + t.Errorf("failed to initialize test FireBase client") + return } firestoreExtension := fb.NewFirestoreClientExtension(fsc) fr := fb.NewFirebaseRepository(firestoreExtension, fbc) @@ -1142,10 +1168,12 @@ func TestRepository_GetUserProfileByUID(t *testing.T) { fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + t.Errorf("failed to initialize test FireStore client") + return } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + t.Errorf("failed to initialize test FireBase client") + return } firestoreExtension := fb.NewFirestoreClientExtension(fsc) fr := fb.NewFirebaseRepository(firestoreExtension, fbc) @@ -1209,10 +1237,12 @@ func TestRepository_GetUserProfileByID(t *testing.T) { fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + t.Errorf("failed to initialize test FireStore client") + return } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + t.Errorf("failed to initialize test FireBase client") + return } firestoreExtension := fb.NewFirestoreClientExtension(fsc) fr := fb.NewFirebaseRepository(firestoreExtension, fbc) @@ -1282,10 +1312,12 @@ func TestRepository_CheckIfPhoneNumberExists(t *testing.T) { fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + t.Errorf("failed to initialize test FireStore client") + return } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + t.Errorf("failed to initialize test FireBase client") + return } firestoreExtension := fb.NewFirestoreClientExtension(fsc) fr := fb.NewFirebaseRepository(firestoreExtension, fbc) @@ -1366,10 +1398,12 @@ func TestRepository_CheckIfUsernameExists(t *testing.T) { fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + t.Errorf("failed to initialize test FireStore client") + return } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + t.Errorf("failed to initialize test FireBase client") + return } firestoreExtension := fb.NewFirestoreClientExtension(fsc) fr := fb.NewFirebaseRepository(firestoreExtension, fbc) @@ -1450,10 +1484,12 @@ func TestRepository_GetPINByProfileID(t *testing.T) { fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + t.Errorf("failed to initialize test FireStore client") + return } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + t.Errorf("failed to initialize test FireBase client") + return } firestoreExtension := fb.NewFirestoreClientExtension(fsc) fr := fb.NewFirebaseRepository(firestoreExtension, fbc) @@ -1544,10 +1580,12 @@ func TestRepository_SavePIN(t *testing.T) { fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + t.Errorf("failed to initialize test FireStore client") + return } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + t.Errorf("failed to initialize test FireBase client") + return } firestoreExtension := fb.NewFirestoreClientExtension(fsc) fr := fb.NewFirebaseRepository(firestoreExtension, fbc) @@ -1622,10 +1660,12 @@ func TestRepository_UpdatePIN(t *testing.T) { fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + t.Errorf("failed to initialize test FireStore client") + return } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + t.Errorf("failed to initialize test FireBase client") + return } firestoreExtension := fb.NewFirestoreClientExtension(fsc) fr := fb.NewFirebaseRepository(firestoreExtension, fbc) @@ -1700,10 +1740,12 @@ func TestRepository_ActivateSupplierProfile(t *testing.T) { fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + t.Errorf("failed to initialize test FireStore client") + return } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + t.Errorf("failed to initialize test FireBase client") + return } firestoreExtension := fb.NewFirestoreClientExtension(fsc) fr := fb.NewFirebaseRepository(firestoreExtension, fbc) @@ -1781,10 +1823,12 @@ func TestRepository_AddPartnerType(t *testing.T) { fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + t.Errorf("failed to initialize test FireStore client") + return } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + t.Errorf("failed to initialize test FireBase client") + return } firestoreExtension := fb.NewFirestoreClientExtension(fsc) fr := fb.NewFirebaseRepository(firestoreExtension, fbc) @@ -1902,10 +1946,12 @@ func TestRepository_RecordPostVisitSurvey(t *testing.T) { fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + t.Errorf("failed to initialize test FireStore client") + return } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + t.Errorf("failed to initialize test FireBase client") + return } firestoreExtension := fb.NewFirestoreClientExtension(fsc) fr := fb.NewFirebaseRepository(firestoreExtension, fbc) @@ -1969,10 +2015,12 @@ func TestRepository_UpdateSuspended(t *testing.T) { fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + t.Errorf("failed to initialize test FireStore client") + return } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + t.Errorf("failed to initialize test FireBase client") + return } firestoreExtension := fb.NewFirestoreClientExtension(fsc) fr := fb.NewFirebaseRepository(firestoreExtension, fbc) @@ -2039,10 +2087,12 @@ func TestRepository_UpdateVerifiedUIDS(t *testing.T) { fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + t.Errorf("failed to initialize test FireStore client") + return } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + t.Errorf("failed to initialize test FireBase client") + return } firestoreExtension := fb.NewFirestoreClientExtension(fsc) fr := fb.NewFirebaseRepository(firestoreExtension, fbc) @@ -2106,10 +2156,12 @@ func TestRepository_UpdateVerifiedIdentifiers(t *testing.T) { fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + t.Errorf("failed to initialize test FireStore client") + return } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + t.Errorf("failed to initialize test FireBase client") + return } firestoreExtension := fb.NewFirestoreClientExtension(fsc) fr := fb.NewFirebaseRepository(firestoreExtension, fbc) @@ -2211,10 +2263,12 @@ func TestRepository_UpdateCovers(t *testing.T) { fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + t.Errorf("failed to initialize test FireStore client") + return } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + t.Errorf("failed to initialize test FireBase client") + return } firestoreExtension := fb.NewFirestoreClientExtension(fsc) fr := fb.NewFirebaseRepository(firestoreExtension, fbc) @@ -2288,10 +2342,12 @@ func TestRepository_UpdateSecondaryEmailAddresses(t *testing.T) { fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + t.Errorf("failed to initialize test FireStore client") + return } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + t.Errorf("failed to initialize test FireBase client") + return } firestoreExtension := fb.NewFirestoreClientExtension(fsc) fr := fb.NewFirebaseRepository(firestoreExtension, fbc) @@ -2350,10 +2406,12 @@ func TestRepository_UpdateSupplierProfile(t *testing.T) { fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + t.Errorf("failed to initialize test FireStore client") + return } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + t.Errorf("failed to initialize test FireBase client") + return } firestoreExtension := fb.NewFirestoreClientExtension(fsc) fr := fb.NewFirebaseRepository(firestoreExtension, fbc) @@ -2423,10 +2481,12 @@ func TestRepositoryFetchKYCProcessingRequests(t *testing.T) { fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + t.Errorf("failed to initialize test FireStore client") + return } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + t.Errorf("failed to initialize test FireBase client") + return } firestoreExtension := fb.NewFirestoreClientExtension(fsc) fr := fb.NewFirebaseRepository(firestoreExtension, fbc) @@ -2495,10 +2555,12 @@ func TestRepository_UpdatePrimaryEmailAddress(t *testing.T) { fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + t.Errorf("failed to initialize test FireStore client") + return } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + t.Errorf("failed to initialize test FireBase client") + return } firestoreExtension := fb.NewFirestoreClientExtension(fsc) fr := fb.NewFirebaseRepository(firestoreExtension, fbc) @@ -2569,10 +2631,12 @@ func TestRepository_UpdatePrimaryPhoneNumber(t *testing.T) { fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + t.Errorf("failed to initialize test FireStore client") + return } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + t.Errorf("failed to initialize test FireBase client") + return } firestoreExtension := fb.NewFirestoreClientExtension(fsc) fr := fb.NewFirebaseRepository(firestoreExtension, fbc) @@ -2642,10 +2706,12 @@ func TestRepository_UpdateSecondaryPhoneNumbers(t *testing.T) { fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + t.Errorf("failed to initialize test FireStore client") + return } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + t.Errorf("failed to initialize test FireBase client") + return } firestoreExtension := fb.NewFirestoreClientExtension(fsc) fr := fb.NewFirebaseRepository(firestoreExtension, fbc) @@ -2716,10 +2782,12 @@ func TestRepository_UpdateBioData(t *testing.T) { fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + t.Errorf("failed to initialize test FireStore client") + return } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + t.Errorf("failed to initialize test FireBase client") + return } firestoreExtension := fb.NewFirestoreClientExtension(fsc) fr := fb.NewFirebaseRepository(firestoreExtension, fbc) @@ -2843,10 +2911,12 @@ func TestRepositoryFetchKYCProcessingRequestByID(t *testing.T) { fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + t.Errorf("failed to initialize test FireStore client") + return } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + t.Errorf("failed to initialize test FireBase client") + return } firestoreExtension := fb.NewFirestoreClientExtension(fsc) fr := fb.NewFirebaseRepository(firestoreExtension, fbc) @@ -2927,10 +2997,12 @@ func TestRepositoryUpdateKYCProcessingRequest(t *testing.T) { fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + t.Errorf("failed to initialize test FireStore client") + return } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + t.Errorf("failed to initialize test FireBase client") + return } firestoreExtension := fb.NewFirestoreClientExtension(fsc) fr := fb.NewFirebaseRepository(firestoreExtension, fbc) @@ -3016,10 +3088,12 @@ func TestRepositoryGenerateAuthCredentialsForAnonymousUser(t *testing.T) { fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + t.Errorf("failed to initialize test FireStore client") + return } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + t.Errorf("failed to initialize test FireBase client") + return } firestoreExtension := fb.NewFirestoreClientExtension(fsc) fr := fb.NewFirebaseRepository(firestoreExtension, fbc) @@ -3112,10 +3186,12 @@ func TestRepositoryGenerateAuthCredentials(t *testing.T) { fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + t.Errorf("failed to initialize test FireStore client") + return } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + t.Errorf("failed to initialize test FireBase client") + return } firestoreExtension := fb.NewFirestoreClientExtension(fsc) fr := fb.NewFirebaseRepository(firestoreExtension, fbc) @@ -3229,10 +3305,12 @@ func TestRepositoryFetchAdminUsers(t *testing.T) { fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + t.Errorf("failed to initialize test FireStore client") + return } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + t.Errorf("failed to initialize test FireBase client") + return } firestoreExtension := fb.NewFirestoreClientExtension(fsc) fr := fb.NewFirebaseRepository(firestoreExtension, fbc) @@ -3295,11 +3373,11 @@ func TestUpdateAddresses(t *testing.T) { fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + t.Errorf("failed to initialize test FireStore client") return } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + t.Errorf("failed to initialize test FireBase client") return } firestoreExtension := fb.NewFirestoreClientExtension(fsc) @@ -3383,11 +3461,11 @@ func TestAddNHIFDetails(t *testing.T) { fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + t.Errorf("failed to initialize test FireStore client") return } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + t.Errorf("failed to initialize test FireBase client") return } firestoreExtension := fb.NewFirestoreClientExtension(fsc) @@ -3467,11 +3545,11 @@ func TestGetNHIFDetailsByProfileID(t *testing.T) { fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + t.Errorf("failed to initialize test FireStore client") return } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + t.Errorf("failed to initialize test FireBase client") return } firestoreExtension := fb.NewFirestoreClientExtension(fsc) @@ -3537,11 +3615,11 @@ func TestUpdateCustomerProfile(t *testing.T) { fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + t.Errorf("failed to initialize test FireStore client") return } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + t.Errorf("failed to initialize test FireBase client") return } firestoreExtension := fb.NewFirestoreClientExtension(fsc) @@ -3614,10 +3692,12 @@ func TestRepository_PersistIncomingSMSData(t *testing.T) { ctx := context.Background() fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + t.Errorf("failed to initialize test FireStore client") + return } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + t.Errorf("failed to initialize test FireBase client") + return } firestoreExtension := fb.NewFirestoreClientExtension(fsc) firestoreDB := fb.NewFirebaseRepository(firestoreExtension, fbc) @@ -3697,10 +3777,12 @@ func TestRepository_AddAITSessionDetails(t *testing.T) { ctx := context.Background() fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + t.Errorf("failed to initialize test FireStore client") + return } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + t.Errorf("failed to initialize test FireBase client") + return } firestoreExtension := fb.NewFirestoreClientExtension(fsc) firestoreDB := fb.NewFirebaseRepository(firestoreExtension, fbc) @@ -3791,10 +3873,12 @@ func TestRepository_GetAITSessionDetailss(t *testing.T) { ctx := context.Background() fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + t.Errorf("failed to initialize test FireStore client") + return } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + t.Errorf("failed to initialize test FireBase client") + return } firestoreExtension := fb.NewFirestoreClientExtension(fsc) firestoreDB := fb.NewFirebaseRepository(firestoreExtension, fbc) @@ -3852,10 +3936,12 @@ func TestRepository_UpdatePIN_IntegrationTest(t *testing.T) { ctx := context.Background() fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + t.Errorf("failed to initialize test FireStore client") + return } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + t.Errorf("failed to initialize test FireBase client") + return } firestoreExtension := fb.NewFirestoreClientExtension(fsc) firestoreDB := fb.NewFirebaseRepository(firestoreExtension, fbc) @@ -3946,10 +4032,12 @@ func TestRepository_UpdateSessionLevel(t *testing.T) { ctx := context.Background() fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + t.Errorf("failed to initialize test FireStore client") + return } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + t.Errorf("failed to initialize test FireBase client") + return } firestoreExtension := fb.NewFirestoreClientExtension(fsc) firestoreDB := fb.NewFirebaseRepository(firestoreExtension, fbc) @@ -4023,10 +4111,12 @@ func TestRepository_SaveUSSDEvent_IntegrationTest(t *testing.T) { ctx := context.Background() fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + t.Errorf("failed to initialize test FireStore client") + return } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + t.Errorf("failed to initialize test FireBase client") + return } firestoreExtension := fb.NewFirestoreClientExtension(fsc) firestoreDB := fb.NewFirebaseRepository(firestoreExtension, fbc) @@ -4094,10 +4184,12 @@ func TestRepository_SaveCoverAutolinkingEvents_Integration_Test(t *testing.T) { ctx := context.Background() fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + t.Errorf("failed to initialize test FireStore client") + return } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + t.Errorf("failed to initialize test FireBase client") + return } firestoreExtension := fb.NewFirestoreClientExtension(fsc) firestoreDB := fb.NewFirebaseRepository(firestoreExtension, fbc) @@ -4172,10 +4264,12 @@ func TestRepository_GetAITDetails_Integration(t *testing.T) { ctx := context.Background() fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + t.Errorf("failed to initialize test FireStore client") + return } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + t.Errorf("failed to initialize test FireBase client") + return } firestoreExtension := fb.NewFirestoreClientExtension(fsc) firestoreDB := fb.NewFirebaseRepository(firestoreExtension, fbc) @@ -4243,10 +4337,12 @@ func TestRepository_UpdateAITSessionDetails_Integration(t *testing.T) { ctx := context.Background() fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + t.Errorf("failed to initialize test FireStore client") + return } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + t.Errorf("failed to initialize test FireBase client") + return } firestoreExtension := fb.NewFirestoreClientExtension(fsc) firestoreDB := fb.NewFirebaseRepository(firestoreExtension, fbc) @@ -4322,6 +4418,105 @@ func TestRepository_UpdateAITSessionDetails_Integration(t *testing.T) { } } +func TestRepository_GetUserProfileByPhoneOrEmail_Integration(t *testing.T) { + ctx := context.Background() + fsc, fbc := InitializeTestFirebaseClient(ctx) + if fsc == nil { + t.Errorf("failed to initialize test FireStore client") + return + } + if fbc == nil { + t.Errorf("failed to initialize test FireBase client") + return + } + firestoreExtension := fb.NewFirestoreClientExtension(fsc) + firestoreDB := fb.NewFirebaseRepository(firestoreExtension, fbc) + + testPhone := "+254706060060" + invalidTestPhone := "+2547060660" + testEmail := "test@kathurima.com" + invalidTestEmail := "+test@" + + _, err := firestoreDB.CreateUserProfile(ctx, testPhone, uuid.NewString()) + if err != nil { + t.Errorf("unable to create phone number user") + return + } + + err = firestoreDB.UpdateUserProfileEmail(ctx, testPhone, testEmail) + if err != nil { + t.Errorf("unable to create phone number user") + return + } + + 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: &testPhone, + }, + }, + wantErr: false, + }, + { + name: "Sad case:phone", + args: args{ + ctx: ctx, + payload: &dto.RetrieveUserProfileInput{ + PhoneNumber: &invalidTestPhone, + }, + }, + want: nil, + wantErr: true, + }, + { + name: "Happy case:email", + args: args{ + ctx: ctx, + payload: &dto.RetrieveUserProfileInput{ + Email: &testEmail, + }, + }, + wantErr: false, + }, + { + name: "Sad case:email", + args: args{ + ctx: ctx, + payload: &dto.RetrieveUserProfileInput{ + Email: &invalidTestEmail, + }, + }, + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := firestoreDB.GetUserProfileByPhoneOrEmail(tt.args.ctx, tt.args.payload) + if (err != nil) != tt.wantErr { + t.Errorf("Repository.GetUserProfileByPhoneOrEmail() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !tt.wantErr && got == nil { + t.Errorf("Repository.GetUserProfileByPhoneOrEmail() error = %v, wantErr %v", err, tt.wantErr) + return + } + }) + } +} + func TestRepository_UpdateUserRoleIDs_Integration(t *testing.T) { ctx, token, err := GetTestAuthenticatedContext(t) if err != nil { @@ -4331,10 +4526,12 @@ func TestRepository_UpdateUserRoleIDs_Integration(t *testing.T) { fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") + t.Errorf("failed to initialize test FireStore client") + return } if fbc == nil { - log.Panicf("failed to initialize test FireBase client") + t.Errorf("failed to initialize test FireBase client") + return } firestoreExtension := fb.NewFirestoreClientExtension(fsc) fr := fb.NewFirebaseRepository(firestoreExtension, fbc) @@ -4344,6 +4541,7 @@ func TestRepository_UpdateUserRoleIDs_Integration(t *testing.T) { t.Errorf("failed to get a user profile") return } + type args struct { ctx context.Context id string @@ -4373,99 +4571,303 @@ func TestRepository_UpdateUserRoleIDs_Integration(t *testing.T) { } } -func TestRepository_GetUserProfileByPhoneOrEmail_Integration(t *testing.T) { +func TestRepository_CreateRole_Integration(t *testing.T) { ctx := context.Background() + fsc, fbc := InitializeTestFirebaseClient(ctx) if fsc == nil { - log.Panicf("failed to initialize test FireStore client") - } - if fbc == nil { - log.Panicf("failed to initialize test FireBase client") - } - firestoreExtension := fb.NewFirestoreClientExtension(fsc) - firestoreDB := fb.NewFirebaseRepository(firestoreExtension, fbc) - - testPhone := "+254706060060" - invalidTestPhone := "+2547060660" - testEmail := "test@kathurima.com" - invalidTestEmail := "+test@" - - _, err := firestoreDB.CreateUserProfile(ctx, testPhone, uuid.NewString()) - if err != nil { - t.Errorf("unable to create phone number user") + t.Errorf("failed to initialize test FireStore client") return } - err = firestoreDB.UpdateUserProfileEmail(ctx, testPhone, testEmail) - if err != nil { - t.Errorf("unable to create phone number user") + if fbc == nil { + t.Errorf("failed to initialize test FireBase client") return } + firestoreExtension := fb.NewFirestoreClientExtension(fsc) + fr := fb.NewFirebaseRepository(firestoreExtension, fbc) + + userID := uuid.NewString() + type args struct { - ctx context.Context - payload *dto.RetrieveUserProfileInput + ctx context.Context + profileID string + input dto.RoleInput } tests := []struct { name string args args - want *profileutils.UserProfile wantErr bool }{ { - name: "Happy case:phone", + name: "valid:create a new role", args: args{ - ctx: ctx, - payload: &dto.RetrieveUserProfileInput{ - PhoneNumber: &testPhone, + ctx: ctx, + profileID: userID, + input: dto.RoleInput{ + Name: "Created Test Role", + Description: "Can run tests", }, }, wantErr: false, }, { - name: "Sad case:phone", + name: "fail:create using existing role name", args: args{ - ctx: ctx, - payload: &dto.RetrieveUserProfileInput{ - PhoneNumber: &invalidTestPhone, + ctx: ctx, + profileID: userID, + input: dto.RoleInput{ + Name: "Created Test Role", + Description: "Can run tests", }, }, - want: nil, wantErr: true, }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := fr.CreateRole(tt.args.ctx, tt.args.profileID, tt.args.input) + if (err != nil) != tt.wantErr { + t.Errorf("Repository.CreateRole() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !tt.wantErr && got == nil { + t.Errorf("Repository.CreateRole() = %v", got) + return + } + }) + } +} + +func TestRepository_UpdateRoleDetails_Integration(t *testing.T) { + ctx := context.Background() + + fsc, fbc := InitializeTestFirebaseClient(ctx) + if fsc == nil { + t.Errorf("failed to initialize test FireStore client") + return + } + + if fbc == nil { + t.Errorf("failed to initialize test FireBase client") + return + } + + firestoreExtension := fb.NewFirestoreClientExtension(fsc) + fr := fb.NewFirebaseRepository(firestoreExtension, fbc) + + profileID := uuid.NewString() + input := dto.RoleInput{ + Name: "Updated Test Role", + Description: "Can run tests", + } + + role, err := fr.CreateRole(ctx, profileID, input) + if err != nil { + t.Errorf("failed to create test role") + return + } + + // Update some details in roles + role.Description = " Can still run tests" + + type args struct { + ctx context.Context + profileID string + role profileutils.Role + } + tests := []struct { + name string + args args + want *profileutils.Role + wantErr bool + }{ { - name: "Happy case:email", + name: "valid:success update role", args: args{ - ctx: ctx, - payload: &dto.RetrieveUserProfileInput{ - Email: &testEmail, - }, + ctx: ctx, + profileID: profileID, + role: *role, }, + want: role, wantErr: false, }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + got, err := fr.UpdateRoleDetails(tt.args.ctx, tt.args.profileID, tt.args.role) + if (err != nil) != tt.wantErr { + t.Errorf("Repository.UpdateRoleDetails() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if !reflect.DeepEqual(got.Description, tt.want.Description) { + t.Errorf("Repository.UpdateRoleDetails().Description = %v, want %v", got.Description, tt.want.Description) + } + + if !reflect.DeepEqual(got.UpdatedBy, profileID) { + t.Errorf("Repository.UpdateRoleDetails().UpdatedBy = %v, want %v", got.UpdatedBy, profileID) + } + }) + } +} + +func TestRepository_GetRoleByID_Integration(t *testing.T) { + ctx := context.Background() + + fsc, fbc := InitializeTestFirebaseClient(ctx) + if fsc == nil { + t.Errorf("failed to initialize test FireStore client") + return + } + + if fbc == nil { + t.Errorf("failed to initialize test FireBase client") + return + } + + firestoreExtension := fb.NewFirestoreClientExtension(fsc) + fr := fb.NewFirebaseRepository(firestoreExtension, fbc) + + profileID := uuid.NewString() + input := dto.RoleInput{ + Name: "Retrieved Role", + Description: "Can run tests", + } + + role, err := fr.CreateRole(ctx, profileID, input) + if err != nil { + t.Errorf("failed to create test role") + return + } + + type args struct { + ctx context.Context + roleID string + } + tests := []struct { + name string + args args + want profileutils.Role + wantErr bool + }{ { - name: "Sad case:email", + name: "success: retrieve role", args: args{ - ctx: ctx, - payload: &dto.RetrieveUserProfileInput{ - Email: &invalidTestEmail, - }, + ctx: ctx, + roleID: role.ID, }, - want: nil, - wantErr: true, + want: *role, + wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := firestoreDB.GetUserProfileByPhoneOrEmail(tt.args.ctx, tt.args.payload) + + got, err := fr.GetRoleByID(tt.args.ctx, tt.args.roleID) if (err != nil) != tt.wantErr { - t.Errorf("Repository.GetUserProfileByPhoneOrEmail() error = %v, wantErr %v", err, tt.wantErr) + t.Errorf("Repository.GetRoleByID() error = %v, wantErr %v", err, tt.wantErr) return } - if !tt.wantErr && got == nil { - t.Errorf("Repository.GetUserProfileByPhoneOrEmail() error = %v, wantErr %v", err, tt.wantErr) - return + if !reflect.DeepEqual(got.ID, tt.want.ID) { + t.Errorf("Repository.GetRoleByID() = %v, want %v", got, tt.want) + } + if !reflect.DeepEqual(got.Name, tt.want.Name) { + t.Errorf("Repository.GetRoleByID() = %v, want %v", got, tt.want) } }) } } + +// func TestRepository_CheckIfUserHasPermission_Integration(t *testing.T) { +// ctx, token, err := GetTestAuthenticatedContext(t) +// if err != nil { +// t.Errorf("failed to get test authenticated context: %v", err) +// return +// } + +// fsc, fbc := InitializeTestFirebaseClient(ctx) +// if fsc == nil { +// t.Errorf("failed to initialize test FireStore client") +// return +// } +// if fbc == nil { +// t.Errorf("failed to initialize test FireBase client") +// return +// } + +// firestoreExtension := fb.NewFirestoreClientExtension(fsc) +// fr := fb.NewFirebaseRepository(firestoreExtension, fbc) + +// userProfile, err := fr.GetUserProfileByUID(ctx, token.UID, false) +// if err != nil { +// t.Errorf("failed to get a user profile") +// return +// } + +// input := dto.RoleInput{ +// Name: "Check Permission Role", +// Description: "Can run tests", +// Scopes: []string{profileutils.CanAssignRole.Scope}, +// } + +// role, err := fr.CreateRole(ctx, uuid.NewString(), input) +// if err != nil { +// t.Errorf("failed to create test role") +// return +// } + +// err = fr.UpdateUserRoleIDs(ctx, userProfile.ID, []string{role.ID}) +// if err != nil { +// t.Errorf("failed to add role to user") +// return +// } + +// type args struct { +// ctx context.Context +// UID string +// requiredPermission profileutils.Permission +// } + +// tests := []struct { +// name string +// args args +// want bool +// wantErr bool +// }{ +// { +// name: "fail: user doesn't have required permission", +// args: args{ +// ctx: ctx, +// UID: token.UID, +// requiredPermission: profileutils.CanEditRole, +// }, +// want: false, +// wantErr: false, +// }, +// { +// name: "pass: user has required permission", +// args: args{ +// ctx: ctx, +// UID: token.UID, +// requiredPermission: profileutils.CanAssignRole, +// }, +// want: true, +// wantErr: false, +// }, +// } +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// got, err := fr.CheckIfUserHasPermission(tt.args.ctx, tt.args.UID, tt.args.requiredPermission) +// if (err != nil) != tt.wantErr { +// t.Errorf("Repository.CheckIfUserHasPermission() error = %v, wantErr %v", err, tt.wantErr) +// return +// } +// if got != tt.want { +// t.Errorf("Repository.CheckIfUserHasPermission() = %v, want %v", got, tt.want) +// } +// }) +// } +// } diff --git a/pkg/onboarding/infrastructure/database/fb/firebase_test.go b/pkg/onboarding/infrastructure/database/fb/firebase_test.go index 8f456c30..ab5e3b41 100644 --- a/pkg/onboarding/infrastructure/database/fb/firebase_test.go +++ b/pkg/onboarding/infrastructure/database/fb/firebase_test.go @@ -2156,3 +2156,420 @@ func TestRepository_GetUserProfilesByRoleID(t *testing.T) { }) } } + +func TestRepository_CreateAdminProfile(t *testing.T) { + ctx := context.Background() + var fireStoreClientExt fb.FirestoreClientExtension = &fakeFireStoreClientExt + repo := fb.NewFirebaseRepository(fireStoreClientExt, fireBaseClientExt) + + type args struct { + ctx context.Context + adminProfile domain.AdminProfile + } + profileID := "c9d62c7e-93e5-44a6-b503-6fc159c1782f" + adminProfileID := "c9d62c7e-93e5-44a6-b503-6fc159c1782f" + + tests := []struct { + name string + args args + wantErr bool + }{ + + { + name: "fail: cannot retrieve firestore docs", + args: args{ + ctx: ctx, + adminProfile: domain.AdminProfile{ + ID: adminProfileID, + ProfileID: profileID, + OrganizationID: "1", + }, + }, + wantErr: true, + }, + { + name: "fail: document already exists", + args: args{ + ctx: ctx, + adminProfile: domain.AdminProfile{ + ID: adminProfileID, + ProfileID: profileID, + OrganizationID: "1", + }, + }, + wantErr: true, + }, + { + name: "fail: unable to create document", + args: args{ + ctx: ctx, + adminProfile: domain.AdminProfile{ + ID: adminProfileID, + ProfileID: profileID, + OrganizationID: "1", + }, + }, + wantErr: true, + }, + { + name: "success: created document", + args: args{ + ctx: ctx, + adminProfile: domain.AdminProfile{ + ID: adminProfileID, + ProfileID: profileID, + OrganizationID: "1", + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.name == "fail: cannot retrieve firestore docs" { + fakeFireStoreClientExt.GetAllFn = func(ctx context.Context, query *fb.GetAllQuery) ([]*firestore.DocumentSnapshot, error) { + return nil, fmt.Errorf("error unable to get docs") + } + } + if tt.name == "fail: document already exists" { + fakeFireStoreClientExt.GetAllFn = func(ctx context.Context, query *fb.GetAllQuery) ([]*firestore.DocumentSnapshot, error) { + docRef := &firestore.DocumentRef{ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f"} + docs := []*firestore.DocumentSnapshot{{Ref: docRef}} + return docs, nil + } + } + if tt.name == "fail: unable to create document" { + fakeFireStoreClientExt.GetAllFn = func(ctx context.Context, query *fb.GetAllQuery) ([]*firestore.DocumentSnapshot, error) { + return nil, nil + } + + fakeFireStoreClientExt.CreateFn = func(ctx context.Context, command *fb.CreateCommand) (*firestore.DocumentRef, error) { + return nil, fmt.Errorf("error unable to create document") + } + } + if tt.name == "success: created document" { + fakeFireStoreClientExt.GetAllFn = func(ctx context.Context, query *fb.GetAllQuery) ([]*firestore.DocumentSnapshot, error) { + return nil, nil + } + + fakeFireStoreClientExt.CreateFn = func(ctx context.Context, command *fb.CreateCommand) (*firestore.DocumentRef, error) { + return &firestore.DocumentRef{ID: adminProfileID}, nil + } + } + if err := repo.CreateAdminProfile(tt.args.ctx, tt.args.adminProfile); (err != nil) != tt.wantErr { + t.Errorf("Repository.CreateAgentProfile() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestRepository_CheckIfAdminProfileExists(t *testing.T) { + ctx := context.Background() + var fireStoreClientExt fb.FirestoreClientExtension = &fakeFireStoreClientExt + repo := fb.NewFirebaseRepository(fireStoreClientExt, fireBaseClientExt) + + type args struct { + ctx context.Context + profileID string + } + tests := []struct { + name string + args args + want bool + wantErr bool + }{ + { + name: "sad: unable to get documents", + args: args{ctx: ctx, + profileID: uuid.New().String(), + }, + want: false, + wantErr: true, + }, + { + name: "sad: got multiple documents", + args: args{ctx: ctx, + profileID: uuid.New().String(), + }, + want: false, + wantErr: false, + }, + { + name: "happy: got document", + args: args{ctx: ctx, + profileID: uuid.New().String(), + }, + want: true, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.name == "sad: unable to get documents" { + fakeFireStoreClientExt.GetAllFn = func(ctx context.Context, query *fb.GetAllQuery) ([]*firestore.DocumentSnapshot, error) { + return nil, fmt.Errorf("error unable to get docs") + } + } + if tt.name == "sad: got multiple documents" { + fakeFireStoreClientExt.GetAllFn = func(ctx context.Context, query *fb.GetAllQuery) ([]*firestore.DocumentSnapshot, error) { + docRef := &firestore.DocumentRef{ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f"} + docs := []*firestore.DocumentSnapshot{{Ref: docRef}, {Ref: docRef}} + return docs, nil + } + } + if tt.name == "happy: got document" { + fakeFireStoreClientExt.GetAllFn = func(ctx context.Context, query *fb.GetAllQuery) ([]*firestore.DocumentSnapshot, error) { + docRef := &firestore.DocumentRef{ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f"} + docs := []*firestore.DocumentSnapshot{{Ref: docRef}} + return docs, nil + } + } + got, err := repo.CheckIfAdminProfileExists(tt.args.ctx, tt.args.profileID) + if (err != nil) != tt.wantErr { + t.Errorf("Repository.CheckIfAdminProfileExists() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("Repository.CheckIfAdminProfileExists() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestRepository_CreateAgentProfile(t *testing.T) { + ctx := context.Background() + var fireStoreClientExt fb.FirestoreClientExtension = &fakeFireStoreClientExt + repo := fb.NewFirebaseRepository(fireStoreClientExt, fireBaseClientExt) + + type args struct { + ctx context.Context + agentProfile domain.AgentProfile + } + profileID := "c9d62c7e-93e5-44a6-b503-6fc159c1782f" + agentProfileID := "c9d62c7e-93e5-44a6-b503-6fc159c1782f" + + tests := []struct { + name string + args args + wantErr bool + }{ + + { + name: "fail: cannot retrieve firestore docs", + args: args{ + ctx: ctx, + agentProfile: domain.AgentProfile{ + ID: agentProfileID, + ProfileID: profileID, + }, + }, + wantErr: true, + }, + { + name: "fail: document already exists", + args: args{ + ctx: ctx, + agentProfile: domain.AgentProfile{ + ID: agentProfileID, + ProfileID: profileID, + }, + }, + wantErr: true, + }, + { + name: "fail: unable to create document", + args: args{ + ctx: ctx, + agentProfile: domain.AgentProfile{ + ID: agentProfileID, + ProfileID: profileID, + }, + }, + wantErr: true, + }, + { + name: "success: created document", + args: args{ + ctx: ctx, + agentProfile: domain.AgentProfile{ + ID: agentProfileID, + ProfileID: profileID, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.name == "fail: cannot retrieve firestore docs" { + fakeFireStoreClientExt.GetAllFn = func(ctx context.Context, query *fb.GetAllQuery) ([]*firestore.DocumentSnapshot, error) { + return nil, fmt.Errorf("error unable to get docs") + } + } + if tt.name == "fail: document already exists" { + fakeFireStoreClientExt.GetAllFn = func(ctx context.Context, query *fb.GetAllQuery) ([]*firestore.DocumentSnapshot, error) { + docRef := &firestore.DocumentRef{ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f"} + docs := []*firestore.DocumentSnapshot{{Ref: docRef}} + return docs, nil + } + } + if tt.name == "fail: unable to create document" { + fakeFireStoreClientExt.GetAllFn = func(ctx context.Context, query *fb.GetAllQuery) ([]*firestore.DocumentSnapshot, error) { + return nil, nil + } + + fakeFireStoreClientExt.CreateFn = func(ctx context.Context, command *fb.CreateCommand) (*firestore.DocumentRef, error) { + return nil, fmt.Errorf("error unable to create document") + } + } + if tt.name == "success: created document" { + fakeFireStoreClientExt.GetAllFn = func(ctx context.Context, query *fb.GetAllQuery) ([]*firestore.DocumentSnapshot, error) { + return nil, nil + } + + fakeFireStoreClientExt.CreateFn = func(ctx context.Context, command *fb.CreateCommand) (*firestore.DocumentRef, error) { + return &firestore.DocumentRef{ID: agentProfileID}, nil + } + } + if err := repo.CreateAgentProfile(tt.args.ctx, tt.args.agentProfile); (err != nil) != tt.wantErr { + t.Errorf("Repository.CreateAgentProfile() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestRepository_CheckIfAgentProfileExists(t *testing.T) { + ctx := context.Background() + var fireStoreClientExt fb.FirestoreClientExtension = &fakeFireStoreClientExt + repo := fb.NewFirebaseRepository(fireStoreClientExt, fireBaseClientExt) + + type args struct { + ctx context.Context + profileID string + } + tests := []struct { + name string + args args + want bool + wantErr bool + }{ + { + name: "sad: unable to get documents", + args: args{ctx: ctx, + profileID: uuid.New().String(), + }, + want: false, + wantErr: true, + }, + { + name: "sad: got multiple documents", + args: args{ctx: ctx, + profileID: uuid.New().String(), + }, + want: false, + wantErr: false, + }, + { + name: "happy: got document", + args: args{ctx: ctx, + profileID: uuid.New().String(), + }, + want: true, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.name == "sad: unable to get documents" { + fakeFireStoreClientExt.GetAllFn = func(ctx context.Context, query *fb.GetAllQuery) ([]*firestore.DocumentSnapshot, error) { + return nil, fmt.Errorf("error unable to get docs") + } + } + if tt.name == "sad: got multiple documents" { + fakeFireStoreClientExt.GetAllFn = func(ctx context.Context, query *fb.GetAllQuery) ([]*firestore.DocumentSnapshot, error) { + docRef := &firestore.DocumentRef{ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f"} + docs := []*firestore.DocumentSnapshot{{Ref: docRef}, {Ref: docRef}} + return docs, nil + } + } + if tt.name == "happy: got document" { + fakeFireStoreClientExt.GetAllFn = func(ctx context.Context, query *fb.GetAllQuery) ([]*firestore.DocumentSnapshot, error) { + docRef := &firestore.DocumentRef{ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f"} + docs := []*firestore.DocumentSnapshot{{Ref: docRef}} + return docs, nil + } + } + got, err := repo.CheckIfAgentProfileExists(tt.args.ctx, tt.args.profileID) + if (err != nil) != tt.wantErr { + t.Errorf("Repository.CheckIfAgentProfileExists() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("Repository.CheckIfAgentProfileExists() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestRepository_SaveRoleRevocation(t *testing.T) { + ctx := context.Background() + var fireStoreClientExt fb.FirestoreClientExtension = &fakeFireStoreClientExt + repo := fb.NewFirebaseRepository(fireStoreClientExt, fireBaseClientExt) + + type args struct { + ctx context.Context + userID string + revocation dto.RoleRevocationInput + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "fail: cannot save a role revocation", + args: args{ + ctx: ctx, + userID: uuid.NewString(), + revocation: dto.RoleRevocationInput{ + ProfileID: uuid.NewString(), + RoleID: uuid.NewString(), + Reason: "test reason", + }, + }, + wantErr: true, + }, + { + name: "success: save a role revocation", + args: args{ + ctx: ctx, + userID: uuid.NewString(), + revocation: dto.RoleRevocationInput{ + ProfileID: uuid.NewString(), + RoleID: uuid.NewString(), + Reason: "test reason", + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.name == "fail: cannot save a role revocation" { + fakeFireStoreClientExt.CreateFn = func(ctx context.Context, command *fb.CreateCommand) (*firestore.DocumentRef, error) { + return nil, fmt.Errorf("cannot create firestore document") + } + } + + if tt.name == "success: save a role revocation" { + fakeFireStoreClientExt.CreateFn = func(ctx context.Context, command *fb.CreateCommand) (*firestore.DocumentRef, error) { + return nil, nil + } + } + + if err := repo.SaveRoleRevocation(tt.args.ctx, tt.args.userID, tt.args.revocation); (err != nil) != tt.wantErr { + t.Errorf("Repository.SaveRoleRevocation() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/pkg/onboarding/infrastructure/services/pubsub/subscriber.go b/pkg/onboarding/infrastructure/services/pubsub/subscriber.go index a91020ba..0988d21f 100644 --- a/pkg/onboarding/infrastructure/services/pubsub/subscriber.go +++ b/pkg/onboarding/infrastructure/services/pubsub/subscriber.go @@ -8,6 +8,8 @@ import ( "github.com/savannahghi/onboarding/pkg/onboarding/application/common" "github.com/savannahghi/onboarding/pkg/onboarding/application/dto" "gitlab.slade360emr.com/go/commontools/crm/pkg/domain" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" ) // ReceivePubSubPushMessages receives and processes a Pub/Sub push message. @@ -15,6 +17,9 @@ func (ps ServicePubSubMessaging) ReceivePubSubPushMessages( w http.ResponseWriter, r *http.Request, ) { + ctx := r.Context() + span := trace.SpanFromContext(ctx) + message, err := ps.baseExt.VerifyPubSubJWTAndDecodePayload(w, r) if err != nil { ps.baseExt.WriteJSONResponse( @@ -25,6 +30,10 @@ func (ps ServicePubSubMessaging) ReceivePubSubPushMessages( return } + span.AddEvent("published message", trace.WithAttributes( + attribute.Any("message", message), + )) + topicID, err := ps.baseExt.GetPubSubTopic(message) if err != nil { ps.baseExt.WriteJSONResponse( @@ -35,7 +44,10 @@ func (ps ServicePubSubMessaging) ReceivePubSubPushMessages( return } - ctx := r.Context() + span.AddEvent("published message topic", trace.WithAttributes( + attribute.String("topic", topicID), + )) + switch topicID { case ps.AddPubSubNamespace(common.CreateCustomerTopic): var data dto.CustomerPubSubMessagePayload diff --git a/pkg/onboarding/presentation/config.go b/pkg/onboarding/presentation/config.go index ce57df1f..e56c77d2 100644 --- a/pkg/onboarding/presentation/config.go +++ b/pkg/onboarding/presentation/config.go @@ -153,11 +153,11 @@ func Router(ctx context.Context) (*mux.Router, error) { sms := usecases.NewSMSUsecase(repo, baseExt) role := usecases.NewRoleUseCases(repo, baseExt) admin := usecases.NewAdminUseCases(repo, engage, baseExt, userpin) - agent := usecases.NewAgentUseCases(repo, engage, baseExt, userpin) + agent := usecases.NewAgentUseCases(repo, engage, baseExt, userpin, role) adminSrv := adminSrv.NewService(baseExt) i, err := interactor.NewOnboardingInteractor( - repo, profile, su, supplier, login, survey, + profile, su, supplier, login, survey, userpin, erp, chrg, engage, mes, nhif, pubSub, sms, aitUssd, agent, admin, edi, adminSrv, crmExt, role, @@ -310,6 +310,19 @@ func Router(ctx context.Context) (*mux.Router, error) { // Authenticated routes rs := r.PathPrefix("/roles").Subrouter() rs.Use(apiclient.AuthenticationMiddleware(firebaseApp)) + rs.Path("/create_role").Methods( + http.MethodPost, + http.MethodOptions). + HandlerFunc(h.CreateRole()) + rs.Path("/assign_role").Methods( + http.MethodPost, + http.MethodOptions). + HandlerFunc(h.AssignRole()) + rs.Path("/remove_role").Methods( + http.MethodPost, + http.MethodOptions). + HandlerFunc(h.RemoveRoleByName()) + rs.Path("/add_user_role").Methods( http.MethodPost, http.MethodOptions). @@ -323,6 +336,10 @@ func Router(ctx context.Context) (*mux.Router, error) { // Interservice Authenticated routes isc := r.PathPrefix("/internal").Subrouter() isc.Use(interserviceclient.InterServiceAuthenticationMiddleware()) + isc.Path("/register_user").Methods( + http.MethodPost, + http.MethodOptions). + HandlerFunc(h.RegisterUser()) isc.Path("/supplier").Methods( http.MethodPost, http.MethodOptions). @@ -391,6 +408,18 @@ func Router(ctx context.Context) (*mux.Router, error) { http.MethodPost, http.MethodOptions). HandlerFunc(h.UpdateUserProfile()) + iscTesting.Path("/create_role").Methods( + http.MethodPost, + http.MethodOptions). + HandlerFunc(h.CreateRole()) + iscTesting.Path("/assign_role").Methods( + http.MethodPost, + http.MethodOptions). + HandlerFunc(h.AssignRole()) + iscTesting.Path("/remove_role").Methods( + http.MethodPost, + http.MethodOptions). + HandlerFunc(h.RemoveRoleByName()) // Authenticated routes authR := r.Path("/graphql").Subrouter() diff --git a/pkg/onboarding/presentation/graph/generated/generated.go b/pkg/onboarding/presentation/graph/generated/generated.go index ba56ad1f..900bbcc3 100644 --- a/pkg/onboarding/presentation/graph/generated/generated.go +++ b/pkg/onboarding/presentation/graph/generated/generated.go @@ -48,6 +48,7 @@ type ResolverRoot interface { Entity() EntityResolver Mutation() MutationResolver Query() QueryResolver + UserProfile() UserProfileResolver VerifiedIdentifier() VerifiedIdentifierResolver } @@ -70,6 +71,7 @@ type ComplexityRoot struct { PrimaryEmailAddress func(childComplexity int) int PrimaryPhone func(childComplexity int) int ResendPIN func(childComplexity int) int + Roles func(childComplexity int) int SecondaryEmailAddresses func(childComplexity int) int SecondaryPhoneNumbers func(childComplexity int) int Suspended func(childComplexity int) int @@ -83,6 +85,7 @@ type ComplexityRoot struct { PrimaryEmailAddress func(childComplexity int) int PrimaryPhone func(childComplexity int) int ResendPIN func(childComplexity int) int + Roles func(childComplexity int) int SecondaryEmailAddresses func(childComplexity int) int SecondaryPhoneNumbers func(childComplexity int) int Suspended func(childComplexity int) int @@ -278,6 +281,7 @@ type ComplexityRoot struct { AddPermissionsToRole func(childComplexity int, input dto.RolePermissionInput) int AddSecondaryEmailAddress func(childComplexity int, email []string) int AddSecondaryPhoneNumber func(childComplexity int, phone []string) int + AssignMultipleRoles func(childComplexity int, userID string, roleIDs []string) int AssignRole func(childComplexity int, userID string, roleID string) int CompleteSignup func(childComplexity int, flavour feedlib.Flavour) int CreateRole func(childComplexity int, input dto.RoleInput) int @@ -297,7 +301,7 @@ type ComplexityRoot struct { RetireKYCProcessingRequest func(childComplexity int) int RetireSecondaryEmailAddresses func(childComplexity int, emails []string) int RetireSecondaryPhoneNumbers func(childComplexity int, phones []string) int - RevokeRole func(childComplexity int, userID string, roleID string) int + RevokeRole func(childComplexity int, userID string, roleID string, reason string) int RevokeRolePermission func(childComplexity int, input dto.RolePermissionInput) int SaveFavoriteNavAction func(childComplexity int, title string) int SetPrimaryEmailAddress func(childComplexity int, email string, otp string) int @@ -465,6 +469,7 @@ type ComplexityRoot struct { FetchKYCProcessingRequests func(childComplexity int) int FetchSupplierAllowedLocations func(childComplexity int) int FetchUserNavigationActions func(childComplexity int) int + FindAdminByNameOrPhone func(childComplexity int, nameOrPhone *string) int FindAgentbyPhone func(childComplexity int, phoneNumber *string) int FindBranch func(childComplexity int, pagination *firebasetools.PaginationInput, filter []*dto.BranchFilterInput, sort []*dto.BranchSortInput) int FindProvider func(childComplexity int, pagination *firebasetools.PaginationInput, filter []*dto.BusinessPartnerFilterInput, sort []*dto.BusinessPartnerSortInput) int @@ -500,6 +505,7 @@ type ComplexityRoot struct { Name func(childComplexity int) int Permissions func(childComplexity int) int Scopes func(childComplexity int) int + Users func(childComplexity int) int } ServicesOffered struct { @@ -566,6 +572,7 @@ type ComplexityRoot struct { PrimaryEmailAddress func(childComplexity int) int PrimaryPhone func(childComplexity int) int PushTokens func(childComplexity int) int + RoleDetails func(childComplexity int) int Roles func(childComplexity int) int SecondaryEmailAddresses func(childComplexity int) int SecondaryPhoneNumbers func(childComplexity int) int @@ -645,7 +652,8 @@ type MutationResolver interface { RevokeRolePermission(ctx context.Context, input dto.RolePermissionInput) (*dto.RoleOutput, error) UpdateRolePermissions(ctx context.Context, input dto.RolePermissionInput) (*dto.RoleOutput, error) AssignRole(ctx context.Context, userID string, roleID string) (bool, error) - RevokeRole(ctx context.Context, userID string, roleID string) (bool, error) + AssignMultipleRoles(ctx context.Context, userID string, roleIDs []string) (bool, error) + RevokeRole(ctx context.Context, userID string, roleID string, reason string) (bool, error) ActivateRole(ctx context.Context, roleID string) (*dto.RoleOutput, error) DeactivateRole(ctx context.Context, roleID string) (*dto.RoleOutput, error) } @@ -665,6 +673,7 @@ type QueryResolver interface { FetchAdmins(ctx context.Context) ([]*dto.Admin, error) FetchAgents(ctx context.Context) ([]*dto.Agent, error) FindAgentbyPhone(ctx context.Context, phoneNumber *string) (*dto.Agent, error) + FindAdminByNameOrPhone(ctx context.Context, nameOrPhone *string) ([]*dto.Admin, error) FetchUserNavigationActions(ctx context.Context) (*profileutils.NavigationActions, error) ListMicroservices(ctx context.Context) ([]*domain.Microservice, error) GetAllRoles(ctx context.Context) ([]*dto.RoleOutput, error) @@ -673,6 +682,9 @@ type QueryResolver interface { FindUserByPhone(ctx context.Context, phoneNumber string) (*profileutils.UserProfile, error) GetNavigationActions(ctx context.Context) (*dto.GroupedNavigationActions, error) } +type UserProfileResolver interface { + RoleDetails(ctx context.Context, obj *profileutils.UserProfile) ([]*dto.RoleOutput, error) +} type VerifiedIdentifierResolver interface { Timestamp(ctx context.Context, obj *profileutils.VerifiedIdentifier) (*scalarutils.Date, error) } @@ -769,6 +781,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Admin.ResendPIN(childComplexity), true + case "Admin.roles": + if e.complexity.Admin.Roles == nil { + break + } + + return e.complexity.Admin.Roles(childComplexity), true + case "Admin.secondaryEmailAddresses": if e.complexity.Admin.SecondaryEmailAddresses == nil { break @@ -839,6 +858,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Agent.ResendPIN(childComplexity), true + case "Agent.roles": + if e.complexity.Agent.Roles == nil { + break + } + + return e.complexity.Agent.Roles(childComplexity), true + case "Agent.secondaryEmailAddresses": if e.complexity.Agent.SecondaryEmailAddresses == nil { break @@ -1836,6 +1862,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Mutation.AddSecondaryPhoneNumber(childComplexity, args["phone"].([]string)), true + case "Mutation.assignMultipleRoles": + if e.complexity.Mutation.AssignMultipleRoles == nil { + break + } + + args, err := ec.field_Mutation_assignMultipleRoles_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.AssignMultipleRoles(childComplexity, args["userID"].(string), args["roleIDs"].([]string)), true + case "Mutation.assignRole": if e.complexity.Mutation.AssignRole == nil { break @@ -2064,7 +2102,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return 0, false } - return e.complexity.Mutation.RevokeRole(childComplexity, args["userID"].(string), args["roleID"].(string)), true + return e.complexity.Mutation.RevokeRole(childComplexity, args["userID"].(string), args["roleID"].(string), args["reason"].(string)), true case "Mutation.revokeRolePermission": if e.complexity.Mutation.RevokeRolePermission == nil { @@ -2990,6 +3028,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Query.FetchUserNavigationActions(childComplexity), true + case "Query.findAdminByNameOrPhone": + if e.complexity.Query.FindAdminByNameOrPhone == nil { + break + } + + args, err := ec.field_Query_findAdminByNameOrPhone_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Query.FindAdminByNameOrPhone(childComplexity, args["nameOrPhone"].(*string)), true + case "Query.findAgentbyPhone": if e.complexity.Query.FindAgentbyPhone == nil { break @@ -3228,6 +3278,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.RoleOutput.Scopes(childComplexity), true + case "RoleOutput.users": + if e.complexity.RoleOutput.Users == nil { + break + } + + return e.complexity.RoleOutput.Users(childComplexity), true + case "ServicesOffered.otherServices": if e.complexity.ServicesOffered.OtherServices == nil { break @@ -3522,6 +3579,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.UserProfile.PushTokens(childComplexity), true + case "UserProfile.roleDetails": + if e.complexity.UserProfile.RoleDetails == nil { + break + } + + return e.complexity.UserProfile.RoleDetails(childComplexity), true + case "UserProfile.roles": if e.complexity.UserProfile.Roles == nil { break @@ -4310,6 +4374,7 @@ input RolePermissionInput { input ProfileSuspensionInput { id: ID! + roleIDs: [ID] reason: String! } `, BuiltIn: false}, @@ -4352,6 +4417,8 @@ input ProfileSuspensionInput { findAgentbyPhone(phoneNumber: String): Agent + findAdminByNameOrPhone(nameOrPhone: String): [Admin] + fetchUserNavigationActions: NavigationActions listMicroservices: [Microservice!]! @@ -4496,7 +4563,9 @@ extend type Mutation { assignRole(userID: ID!, roleID: ID!): Boolean! - revokeRole(userID: ID!, roleID: ID!): Boolean! + assignMultipleRoles(userID: ID!, roleIDs: [ID!]!): Boolean! + + revokeRole(userID: ID!, roleID: ID!, reason: String!): Boolean! activateRole(roleID: ID!): RoleOutput! @@ -4548,6 +4617,11 @@ type UserProfile @key(fields: "id") { homeAddress: Address workAddress: Address roles: [String] + + """ + Details of the user's roles + """ + roleDetails: [RoleOutput] } type Customer { @@ -4941,6 +5015,7 @@ type Admin { photoUploadID: String userBioData: BioData resendPIN: Boolean + roles: [RoleOutput] } type Agent { @@ -4954,6 +5029,7 @@ type Agent { photoUploadID: String userBioData: BioData resendPIN: Boolean + roles: [RoleOutput] } extend type Link { @@ -4997,6 +5073,7 @@ type RoleOutput { active: Boolean! scopes: [String] permissions: [Permission] + users: [UserProfile] } type Permission { @@ -5420,6 +5497,30 @@ func (ec *executionContext) field_Mutation_addSecondaryPhoneNumber_args(ctx cont return args, nil } +func (ec *executionContext) field_Mutation_assignMultipleRoles_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 string + if tmp, ok := rawArgs["userID"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("userID")) + arg0, err = ec.unmarshalNID2string(ctx, tmp) + if err != nil { + return nil, err + } + } + args["userID"] = arg0 + var arg1 []string + if tmp, ok := rawArgs["roleIDs"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("roleIDs")) + arg1, err = ec.unmarshalNID2ᚕstringᚄ(ctx, tmp) + if err != nil { + return nil, err + } + } + args["roleIDs"] = arg1 + return args, nil +} + func (ec *executionContext) field_Mutation_assignRole_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -5738,6 +5839,15 @@ func (ec *executionContext) field_Mutation_revokeRole_args(ctx context.Context, } } args["roleID"] = arg1 + var arg2 string + if tmp, ok := rawArgs["reason"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("reason")) + arg2, err = ec.unmarshalNString2string(ctx, tmp) + if err != nil { + return nil, err + } + } + args["reason"] = arg2 return args, nil } @@ -6038,6 +6148,21 @@ func (ec *executionContext) field_Query__entities_args(ctx context.Context, rawA return args, nil } +func (ec *executionContext) field_Query_findAdminByNameOrPhone_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 *string + if tmp, ok := rawArgs["nameOrPhone"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("nameOrPhone")) + arg0, err = ec.unmarshalOString2ᚖstring(ctx, tmp) + if err != nil { + return nil, err + } + } + args["nameOrPhone"] = arg0 + return args, nil +} + func (ec *executionContext) field_Query_findAgentbyPhone_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -6726,6 +6851,38 @@ func (ec *executionContext) _Admin_resendPIN(ctx context.Context, field graphql. return ec.marshalOBoolean2bool(ctx, field.Selections, res) } +func (ec *executionContext) _Admin_roles(ctx context.Context, field graphql.CollectedField, obj *dto.Admin) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Admin", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Roles, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.([]dto.RoleOutput) + fc.Result = res + return ec.marshalORoleOutput2ᚕgithubᚗcomᚋsavannahghiᚋonboardingᚋpkgᚋonboardingᚋapplicationᚋdtoᚐRoleOutput(ctx, field.Selections, res) +} + func (ec *executionContext) _Agent_id(ctx context.Context, field graphql.CollectedField, obj *dto.Agent) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -7052,6 +7209,38 @@ func (ec *executionContext) _Agent_resendPIN(ctx context.Context, field graphql. return ec.marshalOBoolean2bool(ctx, field.Selections, res) } +func (ec *executionContext) _Agent_roles(ctx context.Context, field graphql.CollectedField, obj *dto.Agent) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Agent", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Roles, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.([]dto.RoleOutput) + fc.Result = res + return ec.marshalORoleOutput2ᚕgithubᚗcomᚋsavannahghiᚋonboardingᚋpkgᚋonboardingᚋapplicationᚋdtoᚐRoleOutput(ctx, field.Selections, res) +} + func (ec *executionContext) _Beneficiary_name(ctx context.Context, field graphql.CollectedField, obj *model.Beneficiary) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -12619,6 +12808,48 @@ func (ec *executionContext) _Mutation_assignRole(ctx context.Context, field grap return ec.marshalNBoolean2bool(ctx, field.Selections, res) } +func (ec *executionContext) _Mutation_assignMultipleRoles(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Mutation", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Mutation_assignMultipleRoles_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + fc.Args = args + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().AssignMultipleRoles(rctx, args["userID"].(string), args["roleIDs"].([]string)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(bool) + fc.Result = res + return ec.marshalNBoolean2bool(ctx, field.Selections, res) +} + func (ec *executionContext) _Mutation_revokeRole(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -12644,7 +12875,7 @@ func (ec *executionContext) _Mutation_revokeRole(ctx context.Context, field grap fc.Args = args resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Mutation().RevokeRole(rctx, args["userID"].(string), args["roleID"].(string)) + return ec.resolvers.Mutation().RevokeRole(rctx, args["userID"].(string), args["roleID"].(string), args["reason"].(string)) }) if err != nil { ec.Error(ctx, err) @@ -16683,6 +16914,45 @@ func (ec *executionContext) _Query_findAgentbyPhone(ctx context.Context, field g return ec.marshalOAgent2ᚖgithubᚗcomᚋsavannahghiᚋonboardingᚋpkgᚋonboardingᚋapplicationᚋdtoᚐAgent(ctx, field.Selections, res) } +func (ec *executionContext) _Query_findAdminByNameOrPhone(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Query", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Query_findAdminByNameOrPhone_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + fc.Args = args + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().FindAdminByNameOrPhone(rctx, args["nameOrPhone"].(*string)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.([]*dto.Admin) + fc.Result = res + return ec.marshalOAdmin2ᚕᚖgithubᚗcomᚋsavannahghiᚋonboardingᚋpkgᚋonboardingᚋapplicationᚋdtoᚐAdmin(ctx, field.Selections, res) +} + func (ec *executionContext) _Query_fetchUserNavigationActions(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -17492,6 +17762,38 @@ func (ec *executionContext) _RoleOutput_permissions(ctx context.Context, field g return ec.marshalOPermission2ᚕgithubᚗcomᚋsavannahghiᚋprofileutilsᚐPermission(ctx, field.Selections, res) } +func (ec *executionContext) _RoleOutput_users(ctx context.Context, field graphql.CollectedField, obj *dto.RoleOutput) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "RoleOutput", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Users, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.([]*profileutils.UserProfile) + fc.Result = res + return ec.marshalOUserProfile2ᚕᚖgithubᚗcomᚋsavannahghiᚋprofileutilsᚐUserProfile(ctx, field.Selections, res) +} + func (ec *executionContext) _ServicesOffered_services(ctx context.Context, field graphql.CollectedField, obj *model.ServicesOffered) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -19217,6 +19519,38 @@ func (ec *executionContext) _UserProfile_roles(ctx context.Context, field graphq return ec.marshalOString2ᚕstring(ctx, field.Selections, res) } +func (ec *executionContext) _UserProfile_roleDetails(ctx context.Context, field graphql.CollectedField, obj *profileutils.UserProfile) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "UserProfile", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.UserProfile().RoleDetails(rctx, obj) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.([]*dto.RoleOutput) + fc.Result = res + return ec.marshalORoleOutput2ᚕᚖgithubᚗcomᚋsavannahghiᚋonboardingᚋpkgᚋonboardingᚋapplicationᚋdtoᚐRoleOutput(ctx, field.Selections, res) +} + func (ec *executionContext) _VerifiedIdentifier_uid(ctx context.Context, field graphql.CollectedField, obj *profileutils.VerifiedIdentifier) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -21951,6 +22285,14 @@ func (ec *executionContext) unmarshalInputProfileSuspensionInput(ctx context.Con if err != nil { return it, err } + case "roleIDs": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("roleIDs")) + it.RoleIDs, err = ec.unmarshalOID2ᚕstring(ctx, v) + if err != nil { + return it, err + } case "reason": var err error @@ -22474,6 +22816,8 @@ func (ec *executionContext) _Admin(ctx context.Context, sel ast.SelectionSet, ob out.Values[i] = ec._Admin_userBioData(ctx, field, obj) case "resendPIN": out.Values[i] = ec._Admin_resendPIN(ctx, field, obj) + case "roles": + out.Values[i] = ec._Admin_roles(ctx, field, obj) default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -22522,6 +22866,8 @@ func (ec *executionContext) _Agent(ctx context.Context, sel ast.SelectionSet, ob out.Values[i] = ec._Agent_userBioData(ctx, field, obj) case "resendPIN": out.Values[i] = ec._Agent_resendPIN(ctx, field, obj) + case "roles": + out.Values[i] = ec._Agent_roles(ctx, field, obj) default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -23706,6 +24052,11 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) if out.Values[i] == graphql.Null { invalids++ } + case "assignMultipleRoles": + out.Values[i] = ec._Mutation_assignMultipleRoles(ctx, field) + if out.Values[i] == graphql.Null { + invalids++ + } case "revokeRole": out.Values[i] = ec._Mutation_revokeRole(ctx, field) if out.Values[i] == graphql.Null { @@ -24620,6 +24971,17 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr res = ec._Query_findAgentbyPhone(ctx, field) return res }) + case "findAdminByNameOrPhone": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_findAdminByNameOrPhone(ctx, field) + return res + }) case "fetchUserNavigationActions": field := field out.Concurrently(i, func() (res graphql.Marshaler) { @@ -24836,6 +25198,8 @@ func (ec *executionContext) _RoleOutput(ctx context.Context, sel ast.SelectionSe out.Values[i] = ec._RoleOutput_scopes(ctx, field, obj) case "permissions": out.Values[i] = ec._RoleOutput_permissions(ctx, field, obj) + case "users": + out.Values[i] = ec._RoleOutput_users(ctx, field, obj) default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -25167,19 +25531,19 @@ func (ec *executionContext) _UserProfile(ctx context.Context, sel ast.SelectionS case "id": out.Values[i] = ec._UserProfile_id(ctx, field, obj) if out.Values[i] == graphql.Null { - invalids++ + atomic.AddUint32(&invalids, 1) } case "userName": out.Values[i] = ec._UserProfile_userName(ctx, field, obj) if out.Values[i] == graphql.Null { - invalids++ + atomic.AddUint32(&invalids, 1) } case "verifiedIdentifiers": out.Values[i] = ec._UserProfile_verifiedIdentifiers(ctx, field, obj) case "primaryPhone": out.Values[i] = ec._UserProfile_primaryPhone(ctx, field, obj) if out.Values[i] == graphql.Null { - invalids++ + atomic.AddUint32(&invalids, 1) } case "primaryEmailAddress": out.Values[i] = ec._UserProfile_primaryEmailAddress(ctx, field, obj) @@ -25207,6 +25571,17 @@ func (ec *executionContext) _UserProfile(ctx context.Context, sel ast.SelectionS out.Values[i] = ec._UserProfile_workAddress(ctx, field, obj) case "roles": out.Values[i] = ec._UserProfile_roles(ctx, field, obj) + case "roleDetails": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._UserProfile_roleDetails(ctx, field, obj) + return res + }) default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -25737,6 +26112,36 @@ func (ec *executionContext) marshalNID2string(ctx context.Context, sel ast.Selec return res } +func (ec *executionContext) unmarshalNID2ᚕstringᚄ(ctx context.Context, v interface{}) ([]string, error) { + var vSlice []interface{} + if v != nil { + if tmp1, ok := v.([]interface{}); ok { + vSlice = tmp1 + } else { + vSlice = []interface{}{v} + } + } + var err error + res := make([]string, len(vSlice)) + for i := range vSlice { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithIndex(i)) + res[i], err = ec.unmarshalNID2string(ctx, vSlice[i]) + if err != nil { + return nil, err + } + } + return res, nil +} + +func (ec *executionContext) marshalNID2ᚕstringᚄ(ctx context.Context, sel ast.SelectionSet, v []string) graphql.Marshaler { + ret := make(graphql.Array, len(v)) + for i := range v { + ret[i] = ec.marshalNID2string(ctx, sel, v[i]) + } + + return ret +} + func (ec *executionContext) marshalNIdentification2githubᚗcomᚋsavannahghiᚋonboardingᚋpkgᚋonboardingᚋdomainᚐIdentification(ctx context.Context, sel ast.SelectionSet, v domain.Identification) graphql.Marshaler { return ec._Identification(ctx, sel, &v) } @@ -27888,6 +28293,50 @@ func (ec *executionContext) marshalOReceivablesAccount2githubᚗcomᚋsavannahgh return ec._ReceivablesAccount(ctx, sel, &v) } +func (ec *executionContext) marshalORoleOutput2githubᚗcomᚋsavannahghiᚋonboardingᚋpkgᚋonboardingᚋapplicationᚋdtoᚐRoleOutput(ctx context.Context, sel ast.SelectionSet, v dto.RoleOutput) graphql.Marshaler { + return ec._RoleOutput(ctx, sel, &v) +} + +func (ec *executionContext) marshalORoleOutput2ᚕgithubᚗcomᚋsavannahghiᚋonboardingᚋpkgᚋonboardingᚋapplicationᚋdtoᚐRoleOutput(ctx context.Context, sel ast.SelectionSet, v []dto.RoleOutput) graphql.Marshaler { + if v == nil { + return graphql.Null + } + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalORoleOutput2githubᚗcomᚋsavannahghiᚋonboardingᚋpkgᚋonboardingᚋapplicationᚋdtoᚐRoleOutput(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + return ret +} + func (ec *executionContext) marshalORoleOutput2ᚕᚖgithubᚗcomᚋsavannahghiᚋonboardingᚋpkgᚋonboardingᚋapplicationᚋdtoᚐRoleOutput(ctx context.Context, sel ast.SelectionSet, v []*dto.RoleOutput) graphql.Marshaler { if v == nil { return graphql.Null @@ -28168,6 +28617,53 @@ func (ec *executionContext) marshalOTime2timeᚐTime(ctx context.Context, sel as return graphql.MarshalTime(v) } +func (ec *executionContext) marshalOUserProfile2ᚕᚖgithubᚗcomᚋsavannahghiᚋprofileutilsᚐUserProfile(ctx context.Context, sel ast.SelectionSet, v []*profileutils.UserProfile) graphql.Marshaler { + if v == nil { + return graphql.Null + } + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalOUserProfile2ᚖgithubᚗcomᚋsavannahghiᚋprofileutilsᚐUserProfile(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + return ret +} + +func (ec *executionContext) marshalOUserProfile2ᚖgithubᚗcomᚋsavannahghiᚋprofileutilsᚐUserProfile(ctx context.Context, sel ast.SelectionSet, v *profileutils.UserProfile) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return ec._UserProfile(ctx, sel, v) +} + func (ec *executionContext) marshalOVerifiedIdentifier2githubᚗcomᚋsavannahghiᚋprofileutilsᚐVerifiedIdentifier(ctx context.Context, sel ast.SelectionSet, v profileutils.VerifiedIdentifier) graphql.Marshaler { return ec._VerifiedIdentifier(ctx, sel, &v) } diff --git a/pkg/onboarding/presentation/graph/inputs.graphql b/pkg/onboarding/presentation/graph/inputs.graphql index bab343ab..cca44811 100644 --- a/pkg/onboarding/presentation/graph/inputs.graphql +++ b/pkg/onboarding/presentation/graph/inputs.graphql @@ -340,5 +340,6 @@ input RolePermissionInput { input ProfileSuspensionInput { id: ID! + roleIDs: [ID] reason: String! } diff --git a/pkg/onboarding/presentation/graph/profile.graphql b/pkg/onboarding/presentation/graph/profile.graphql index 613f43cd..70bf64b2 100644 --- a/pkg/onboarding/presentation/graph/profile.graphql +++ b/pkg/onboarding/presentation/graph/profile.graphql @@ -37,6 +37,8 @@ extend type Query { findAgentbyPhone(phoneNumber: String): Agent + findAdminByNameOrPhone(nameOrPhone: String): [Admin] + fetchUserNavigationActions: NavigationActions listMicroservices: [Microservice!]! @@ -181,7 +183,9 @@ extend type Mutation { assignRole(userID: ID!, roleID: ID!): Boolean! - revokeRole(userID: ID!, roleID: ID!): Boolean! + assignMultipleRoles(userID: ID!, roleIDs: [ID!]!): Boolean! + + revokeRole(userID: ID!, roleID: ID!, reason: String!): Boolean! activateRole(roleID: ID!): RoleOutput! diff --git a/pkg/onboarding/presentation/graph/profile.resolvers.go b/pkg/onboarding/presentation/graph/profile.resolvers.go index 6b53b978..143a81a7 100644 --- a/pkg/onboarding/presentation/graph/profile.resolvers.go +++ b/pkg/onboarding/presentation/graph/profile.resolvers.go @@ -654,10 +654,19 @@ func (r *mutationResolver) AssignRole(ctx context.Context, userID string, roleID return status, err } -func (r *mutationResolver) RevokeRole(ctx context.Context, userID string, roleID string) (bool, error) { +func (r *mutationResolver) AssignMultipleRoles(ctx context.Context, userID string, roleIDs []string) (bool, error) { startTime := time.Now() - status, err := r.interactor.Role.RevokeRole(ctx, userID, roleID) + status, err := r.interactor.Role.AssignMultipleRoles(ctx, userID, roleIDs) + defer serverutils.RecordGraphqlResolverMetrics(ctx, startTime, "assignMultipleRoles", err) + + return status, err +} + +func (r *mutationResolver) RevokeRole(ctx context.Context, userID string, roleID string, reason string) (bool, error) { + startTime := time.Now() + + status, err := r.interactor.Role.RevokeRole(ctx, userID, roleID, reason) defer serverutils.RecordGraphqlResolverMetrics(ctx, startTime, "revokeRole", err) return status, err @@ -846,6 +855,16 @@ func (r *queryResolver) FindAgentbyPhone(ctx context.Context, phoneNumber *strin return agent, err } +func (r *queryResolver) FindAdminByNameOrPhone(ctx context.Context, nameOrPhone *string) ([]*dto.Admin, error) { + startTime := time.Now() + + admins, err := r.interactor.Admin.FindAdminByNameOrPhone(ctx, nameOrPhone) + + defer serverutils.RecordGraphqlResolverMetrics(ctx, startTime, "findAdminByNameOrPhone", err) + + return admins, err +} + func (r *queryResolver) FetchUserNavigationActions(ctx context.Context) (*profileutils.NavigationActions, error) { startTime := time.Now() diff --git a/pkg/onboarding/presentation/graph/types.graphql b/pkg/onboarding/presentation/graph/types.graphql index b44b8ddf..dc7207e7 100644 --- a/pkg/onboarding/presentation/graph/types.graphql +++ b/pkg/onboarding/presentation/graph/types.graphql @@ -43,6 +43,11 @@ type UserProfile @key(fields: "id") { homeAddress: Address workAddress: Address roles: [String] + + """ + Details of the user's roles + """ + roleDetails: [RoleOutput] } type Customer { @@ -436,6 +441,7 @@ type Admin { photoUploadID: String userBioData: BioData resendPIN: Boolean + roles: [RoleOutput] } type Agent { @@ -449,6 +455,7 @@ type Agent { photoUploadID: String userBioData: BioData resendPIN: Boolean + roles: [RoleOutput] } extend type Link { @@ -492,6 +499,7 @@ type RoleOutput { active: Boolean! scopes: [String] permissions: [Permission] + users: [UserProfile] } type Permission { diff --git a/pkg/onboarding/presentation/graph/types.resolvers.go b/pkg/onboarding/presentation/graph/types.resolvers.go index f03d9323..cfbdbe53 100644 --- a/pkg/onboarding/presentation/graph/types.resolvers.go +++ b/pkg/onboarding/presentation/graph/types.resolvers.go @@ -6,11 +6,16 @@ package graph import ( "context" + "github.com/savannahghi/onboarding/pkg/onboarding/application/dto" "github.com/savannahghi/onboarding/pkg/onboarding/presentation/graph/generated" "github.com/savannahghi/profileutils" "github.com/savannahghi/scalarutils" ) +func (r *userProfileResolver) RoleDetails(ctx context.Context, obj *profileutils.UserProfile) ([]*dto.RoleOutput, error) { + return r.interactor.Role.GetRolesByIDs(ctx, obj.Roles) +} + func (r *verifiedIdentifierResolver) Timestamp(ctx context.Context, obj *profileutils.VerifiedIdentifier) (*scalarutils.Date, error) { return &scalarutils.Date{ Year: obj.Timestamp.Year(), @@ -19,9 +24,13 @@ func (r *verifiedIdentifierResolver) Timestamp(ctx context.Context, obj *profile }, nil } +// UserProfile returns generated.UserProfileResolver implementation. +func (r *Resolver) UserProfile() generated.UserProfileResolver { return &userProfileResolver{r} } + // VerifiedIdentifier returns generated.VerifiedIdentifierResolver implementation. func (r *Resolver) VerifiedIdentifier() generated.VerifiedIdentifierResolver { return &verifiedIdentifierResolver{r} } +type userProfileResolver struct{ *Resolver } type verifiedIdentifierResolver struct{ *Resolver } diff --git a/pkg/onboarding/presentation/interactor/interactor.go b/pkg/onboarding/presentation/interactor/interactor.go index c3e87b7a..8ebade64 100644 --- a/pkg/onboarding/presentation/interactor/interactor.go +++ b/pkg/onboarding/presentation/interactor/interactor.go @@ -10,7 +10,6 @@ import ( "github.com/savannahghi/onboarding/pkg/onboarding/infrastructure/services/crm" "github.com/savannahghi/onboarding/pkg/onboarding/infrastructure/services/messaging" pubsubmessaging "github.com/savannahghi/onboarding/pkg/onboarding/infrastructure/services/pubsub" - "github.com/savannahghi/onboarding/pkg/onboarding/repository" "github.com/savannahghi/onboarding/pkg/onboarding/usecases" "github.com/savannahghi/onboarding/pkg/onboarding/usecases/admin" "github.com/savannahghi/onboarding/pkg/onboarding/usecases/ussd" @@ -43,7 +42,6 @@ type Interactor struct { // NewOnboardingInteractor returns a new onboarding interactor func NewOnboardingInteractor( - fr repository.OnboardingRepository, profile usecases.ProfileUseCase, su usecases.SignUpUseCases, supplier usecases.SupplierUseCases, diff --git a/pkg/onboarding/presentation/rest/handlers.go b/pkg/onboarding/presentation/rest/handlers.go index d24bed94..3f98d6a7 100644 --- a/pkg/onboarding/presentation/rest/handlers.go +++ b/pkg/onboarding/presentation/rest/handlers.go @@ -53,6 +53,12 @@ type HandlersInterfaces interface { // USSDEndNotificationHandler() http.HandlerFunc PollServices() http.HandlerFunc CheckHasPermission() http.HandlerFunc + + CreateRole() http.HandlerFunc + AssignRole() http.HandlerFunc + RemoveRoleByName() http.HandlerFunc + + RegisterUser() http.HandlerFunc } // HandlersInterfacesImpl represents the usecase implementation object @@ -684,7 +690,9 @@ func (h *HandlersInterfacesImpl) GetUserProfileByPhoneOrEmail() http.HandlerFunc } if payload.Email != nil && payload.PhoneNumber != nil { - err := fmt.Errorf("only one parameter can be used to retrieve user profile. use either email or phone") + err := fmt.Errorf( + "only one parameter can be used to retrieve user profile. use either email or phone", + ) serverutils.WriteJSONResponse(w, err, http.StatusBadRequest) return } @@ -1214,3 +1222,103 @@ func (h *HandlersInterfacesImpl) CheckHasPermission() http.HandlerFunc { serverutils.WriteJSONResponse(rw, nil, http.StatusOK) } } + +// CreateRole creates a new role given the required role creation input +func (h *HandlersInterfacesImpl) CreateRole() http.HandlerFunc { + return func(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + input := &dto.RoleInput{} + serverutils.DecodeJSONToTargetStruct(rw, r, input) + + if input.Name == "" { + serverutils.WriteJSONResponse(rw, nil, http.StatusBadRequest) + return + } + + role, err := h.interactor.Role.CreateUnauthorizedRole(ctx, *input) + if err != nil { + serverutils.WriteJSONResponse(rw, err, http.StatusInternalServerError) + return + } + + serverutils.WriteJSONResponse(rw, role, http.StatusCreated) + } +} + +// AssignRole assigns a role to the provided user +func (h *HandlersInterfacesImpl) AssignRole() http.HandlerFunc { + return func(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + input := &dto.AssignRolePayload{} + serverutils.DecodeJSONToTargetStruct(rw, r, input) + + if input.UserID == "" || input.RoleID == "" { + serverutils.WriteJSONResponse(rw, nil, http.StatusBadRequest) + return + } + + _, err := h.interactor.Role.AssignRole(ctx, input.UserID, input.RoleID) + if err != nil { + serverutils.WriteJSONResponse(rw, err, http.StatusInternalServerError) + return + } + + serverutils.WriteJSONResponse(rw, nil, http.StatusOK) + } +} + +// RemoveRoleByName removes a role given a valid name +func (h *HandlersInterfacesImpl) RemoveRoleByName() http.HandlerFunc { + return func(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + input := &dto.DeleteRolePayload{} + serverutils.DecodeJSONToTargetStruct(rw, r, input) + + if input.Name == "" { + serverutils.WriteJSONResponse(rw, nil, http.StatusBadRequest) + return + } + + role, err := h.interactor.Role.GetRoleByName(ctx, input.Name) + if err != nil { + serverutils.WriteJSONResponse(rw, err, http.StatusInternalServerError) + return + } + + _, err = h.interactor.Role.UnauthorizedDeleteRole(ctx, role.ID) + if err != nil { + serverutils.WriteJSONResponse(rw, err, http.StatusInternalServerError) + return + } + + serverutils.WriteJSONResponse(rw, nil, http.StatusOK) + } +} + +// RegisterUser creates a new user profile using provided input +func (h *HandlersInterfacesImpl) RegisterUser() http.HandlerFunc { + return func(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + input := &dto.RegisterUserInput{} + serverutils.DecodeJSONToTargetStruct(rw, r, input) + + valid, err := utils.ValidateRegisterUserInput(*input) + if !valid { + errorcodeutil.ReportErr(rw, err, http.StatusBadRequest) + return + } + context := addUIDToContext(ctx, *input.UID) + + profile, err := h.interactor.Signup.RegisterUser(context, *input) + if err != nil { + serverutils.WriteJSONResponse(rw, err, http.StatusInternalServerError) + return + } + + serverutils.WriteJSONResponse(rw, profile, http.StatusOK) + } +} diff --git a/pkg/onboarding/presentation/rest/handlers_helpers.go b/pkg/onboarding/presentation/rest/handlers_helpers.go index a88f5e45..99d04f50 100644 --- a/pkg/onboarding/presentation/rest/handlers_helpers.go +++ b/pkg/onboarding/presentation/rest/handlers_helpers.go @@ -1,9 +1,12 @@ package rest import ( + "context" "fmt" "net/http" + "firebase.google.com/go/auth" + "github.com/savannahghi/firebasetools" "github.com/savannahghi/onboarding/pkg/onboarding/application/dto" "github.com/savannahghi/serverutils" "go.opentelemetry.io/otel/attribute" @@ -51,3 +54,11 @@ func decodeOTPPayload( return payload, nil } + +func addUIDToContext(ctx context.Context, uid string) context.Context { + return context.WithValue( + context.Background(), + firebasetools.AuthTokenContextKey, + &auth.Token{UID: uid}, + ) +} diff --git a/pkg/onboarding/presentation/rest/handlers_test.go b/pkg/onboarding/presentation/rest/handlers_test.go index 32b89145..69b0009f 100644 --- a/pkg/onboarding/presentation/rest/handlers_test.go +++ b/pkg/onboarding/presentation/rest/handlers_test.go @@ -16,26 +16,20 @@ import ( "time" "github.com/google/uuid" + "github.com/savannahghi/enumutils" "github.com/savannahghi/feedlib" "github.com/savannahghi/interserviceclient" "github.com/savannahghi/onboarding/pkg/onboarding/application/dto" "github.com/savannahghi/onboarding/pkg/onboarding/application/extension" - "github.com/savannahghi/profileutils" - erp "gitlab.slade360emr.com/go/commontools/accounting/pkg/usecases" - erpMock "gitlab.slade360emr.com/go/commontools/accounting/pkg/usecases/mock" - crmDomain "gitlab.slade360emr.com/go/commontools/crm/pkg/domain" - "gitlab.slade360emr.com/go/commontools/crm/pkg/infrastructure/services/hubspot" - extMock "github.com/savannahghi/onboarding/pkg/onboarding/application/extension/mock" "github.com/savannahghi/onboarding/pkg/onboarding/domain" "github.com/savannahghi/onboarding/pkg/onboarding/infrastructure/services/chargemaster" chargemasterMock "github.com/savannahghi/onboarding/pkg/onboarding/infrastructure/services/chargemaster/mock" + crmExt "github.com/savannahghi/onboarding/pkg/onboarding/infrastructure/services/crm" "github.com/savannahghi/onboarding/pkg/onboarding/infrastructure/services/edi" ediMock "github.com/savannahghi/onboarding/pkg/onboarding/infrastructure/services/edi/mock" "github.com/savannahghi/onboarding/pkg/onboarding/infrastructure/services/engagement" engagementMock "github.com/savannahghi/onboarding/pkg/onboarding/infrastructure/services/engagement/mock" - - crmExt "github.com/savannahghi/onboarding/pkg/onboarding/infrastructure/services/crm" "github.com/savannahghi/onboarding/pkg/onboarding/infrastructure/services/messaging" messagingMock "github.com/savannahghi/onboarding/pkg/onboarding/infrastructure/services/messaging/mock" pubsubmessaging "github.com/savannahghi/onboarding/pkg/onboarding/infrastructure/services/pubsub" @@ -47,7 +41,13 @@ import ( "github.com/savannahghi/onboarding/pkg/onboarding/usecases" adminSrv "github.com/savannahghi/onboarding/pkg/onboarding/usecases/admin" "github.com/savannahghi/onboarding/pkg/onboarding/usecases/ussd" + "github.com/savannahghi/profileutils" + "github.com/savannahghi/scalarutils" + erp "gitlab.slade360emr.com/go/commontools/accounting/pkg/usecases" + erpMock "gitlab.slade360emr.com/go/commontools/accounting/pkg/usecases/mock" + crmDomain "gitlab.slade360emr.com/go/commontools/crm/pkg/domain" hubspotRepo "gitlab.slade360emr.com/go/commontools/crm/pkg/infrastructure/database/fs" + "gitlab.slade360emr.com/go/commontools/crm/pkg/infrastructure/services/hubspot" hubspotUsecases "gitlab.slade360emr.com/go/commontools/crm/pkg/usecases" ) @@ -91,13 +91,13 @@ func InitializeFakeOnboardingInteractor() (*interactor.Interactor, error) { sms := usecases.NewSMSUsecase(r, ext) role := usecases.NewRoleUseCases(r, ext) admin := usecases.NewAdminUseCases(r, engagementSvc, ext, userpin) - agent := usecases.NewAgentUseCases(r, engagementSvc, ext, userpin) + agent := usecases.NewAgentUseCases(r, engagementSvc, ext, userpin, role) aitUssd := ussd.NewUssdUsecases(r, ext, profile, userpin, su, pinExt, ps, crmExt) adminSrv := adminSrv.NewService(ext) i, err := interactor.NewOnboardingInteractor( - r, profile, su, supplier, login, + profile, su, supplier, login, survey, userpin, erpSvc, chargemasterSvc, engagementSvc, messagingSvc, nhif, ps, sms, aitUssd, agent, admin, ediSvc, adminSrv, crmExt, @@ -135,6 +135,147 @@ func composeValidRolePayload(t *testing.T, phone string, role profileutils.RoleT return bytes.NewBuffer(bs) } +func composeValidUserPayload(t *testing.T, phoneNumber string) *bytes.Buffer { + uid := uuid.NewString() + fName := "Test" + lName := "Test" + dob := scalarutils.Date{ + Month: 1, + Day: 1, + Year: 2002, + } + gender := "male" + inputData := &dto.RegisterUserInput{ + UID: &uid, + FirstName: &fName, + LastName: &lName, + PhoneNumber: &phoneNumber, + DateOfBirth: &dob, + Gender: (*enumutils.Gender)(&gender), + } + bs, err := json.Marshal(inputData) + if err != nil { + t.Errorf("unable to marshal token string to JSON: %s", err) + } + return bytes.NewBuffer(bs) +} + +func composeInvalidUserPayload0(t *testing.T, phoneNumber string) *bytes.Buffer { + fName := "Test" + lName := "Test" + dob := scalarutils.Date{ + Month: 1, + Day: 1, + Year: 2002, + } + gender := "male" + inputData := &dto.RegisterUserInput{ + FirstName: &fName, + LastName: &lName, + PhoneNumber: &phoneNumber, + DateOfBirth: &dob, + Gender: (*enumutils.Gender)(&gender), + } + bs, err := json.Marshal(inputData) + if err != nil { + t.Errorf("unable to marshal token string to JSON: %s", err) + } + return bytes.NewBuffer(bs) +} + +func composeInvalidUserPayload1(t *testing.T, phoneNumber string) *bytes.Buffer { + uid := uuid.NewString() + lName := "Test" + dob := scalarutils.Date{ + Month: 1, + Day: 1, + Year: 2002, + } + gender := "male" + inputData := &dto.RegisterUserInput{ + UID: &uid, + LastName: &lName, + PhoneNumber: &phoneNumber, + DateOfBirth: &dob, + Gender: (*enumutils.Gender)(&gender), + } + bs, err := json.Marshal(inputData) + if err != nil { + t.Errorf("unable to marshal token string to JSON: %s", err) + } + return bytes.NewBuffer(bs) +} + +func composeInvalidUserPayload2(t *testing.T, phoneNumber string) *bytes.Buffer { + uid := uuid.NewString() + fName := "Test" + dob := scalarutils.Date{ + Month: 1, + Day: 1, + Year: 2002, + } + gender := "male" + inputData := &dto.RegisterUserInput{ + UID: &uid, + FirstName: &fName, + PhoneNumber: &phoneNumber, + DateOfBirth: &dob, + Gender: (*enumutils.Gender)(&gender), + } + bs, err := json.Marshal(inputData) + if err != nil { + t.Errorf("unable to marshal token string to JSON: %s", err) + } + return bytes.NewBuffer(bs) +} + +func composeInvalidUserPayload3(t *testing.T, phoneNumber string) *bytes.Buffer { + uid := uuid.NewString() + fName := "Test" + lName := "Test" + dob := scalarutils.Date{ + Month: 1, + Day: 1, + Year: 2002, + } + inputData := &dto.RegisterUserInput{ + UID: &uid, + FirstName: &fName, + LastName: &lName, + PhoneNumber: &phoneNumber, + DateOfBirth: &dob, + } + bs, err := json.Marshal(inputData) + if err != nil { + t.Errorf("unable to marshal token string to JSON: %s", err) + } + return bytes.NewBuffer(bs) +} + +func composeInvalidUserPayload4(t *testing.T, phoneNumber string) *bytes.Buffer { + uid := uuid.NewString() + fName := "Test" + lName := "Test" + dob := scalarutils.Date{ + Month: 1, + Day: 1, + Year: 2002, + } + gender := "male" + inputData := &dto.RegisterUserInput{ + UID: &uid, + FirstName: &fName, + LastName: &lName, + DateOfBirth: &dob, + Gender: (*enumutils.Gender)(&gender), + } + bs, err := json.Marshal(inputData) + if err != nil { + t.Errorf("unable to marshal token string to JSON: %s", err) + } + return bytes.NewBuffer(bs) +} + func composeSignupPayload( t *testing.T, phone, pin, otp string, @@ -2607,65 +2748,43 @@ func TestHandlersInterfacesImpl_RemoveUserByPhoneNumber(t *testing.T) { } func TestHandlersInterfacesImpl_SetPrimaryPhoneNumber(t *testing.T) { - i, err := InitializeFakeOnboardingInteractor() if err != nil { t.Errorf("failed to initialize onboarding interactor: %v", err) return } - h := rest.NewHandlersInterfaces(i) + phone := interserviceclient.TestUserPhoneNumber + oto := "112233" + payload := composeSetPrimaryPhoneNumberPayload(t, phone, oto) - primaryPhone := "+254701567839" - otp := "890087" - validPayload := composeSetPrimaryPhoneNumberPayload(t, primaryPhone, otp) - - primaryPhone1 := "+254765738293" - otp1 := "345678" - payload1 := composeSetPrimaryPhoneNumberPayload(t, primaryPhone1, otp1) - - primaryPhone2 := " " - otp2 := " " - payload2 := composeSetPrimaryPhoneNumberPayload(t, primaryPhone2, otp2) type args struct { url string httpMethod string body io.Reader } + + input := args{ + url: fmt.Sprintf("%s/set_primary_phonenumber", serverUrl), + httpMethod: http.MethodPost, + body: payload, + } + tests := []struct { name string args args - want http.HandlerFunc wantStatus int wantErr bool }{ { - name: "valid:_successfully_set_a_primary_phonenumber", - args: args{ - url: fmt.Sprintf("%s/set_primary_phonenumber", serverUrl), - httpMethod: http.MethodPost, - body: validPayload, - }, + name: "valid:set_primary_phoneNumber", + args: input, wantStatus: http.StatusOK, wantErr: false, }, { - name: "invalid:_fail_to_set_a_primary_phonenumber", - args: args{ - url: fmt.Sprintf("%s/set_primary_phonenumber", serverUrl), - httpMethod: http.MethodPost, - body: payload1, - }, - wantStatus: http.StatusBadRequest, - wantErr: true, - }, - { - name: "invalid:_phonenumber_and_otp_missing", - args: args{ - url: fmt.Sprintf("%s/set_primary_phonenumber", serverUrl), - httpMethod: http.MethodPost, - body: payload2, - }, + name: "invalid:failed_to__primary_phoneNumber", + args: input, wantStatus: http.StatusBadRequest, wantErr: true, }, @@ -2678,28 +2797,27 @@ func TestHandlersInterfacesImpl_SetPrimaryPhoneNumber(t *testing.T) { return } - if tt.name == "valid:_successfully_set_a_primary_phonenumber" { + response := httptest.NewRecorder() + if tt.name == "valid:set_primary_phoneNumber" { fakeBaseExt.NormalizeMSISDNFn = func(msisdn string) (*string, error) { - phone := "+254799774466" + phone := "+254777886622" return &phone, nil } - fakeEngagementSvs.VerifyOTPFn = func(ctx context.Context, phone, OTP string) (bool, error) { return true, nil } - fakeBaseExt.NormalizeMSISDNFn = func(msisdn string) (*string, error) { - phone := "+254799774466" + phone := "+254755889922" return &phone, nil } fakeRepo.GetUserProfileByPhoneNumberFn = func(ctx context.Context, phoneNumber string, suspended bool) (*profileutils.UserProfile, error) { return &profileutils.UserProfile{ - ID: "123", + ID: "ABCDE", PrimaryPhone: &phoneNumber, SecondaryPhoneNumbers: []string{ - "0721521456", "0721856741", + "0765839203", "0789437282", }, }, nil } @@ -2713,44 +2831,16 @@ func TestHandlersInterfacesImpl_SetPrimaryPhoneNumber(t *testing.T) { } } - if tt.name == "invalid:_fail_to_set_a_primary_phonenumber" { + if tt.name == "invalid:failed_to__primary_phoneNumber" { fakeBaseExt.NormalizeMSISDNFn = func(msisdn string) (*string, error) { - phone := "+254799774466" + phone := "+254777886622" return &phone, nil } - fakeEngagementSvs.VerifyOTPFn = func(ctx context.Context, phone, OTP string) (bool, error) { - return true, nil - } - - fakeBaseExt.NormalizeMSISDNFn = func(msisdn string) (*string, error) { - phone := "+254799774466" - return &phone, nil - } - - fakeRepo.GetUserProfileByPhoneNumberFn = func(ctx context.Context, phoneNumber string, suspended bool) (*profileutils.UserProfile, error) { - return &profileutils.UserProfile{ - ID: "123", - PrimaryPhone: &phoneNumber, - SecondaryPhoneNumbers: []string{ - "0721521456", "0721856741", - }, - }, nil - } - - fakeRepo.UpdatePrimaryPhoneNumberFn = func(ctx context.Context, id string, phoneNumber string) error { - return fmt.Errorf("failed to set a primary phone number") - } - } - - if tt.name == "invalid:_phonenumber_and_otp_missing" { - fakeBaseExt.NormalizeMSISDNFn = func(msisdn string) (*string, error) { - return nil, fmt.Errorf("empty phone number provided") + return false, fmt.Errorf("unable to verify otp") } } - response := httptest.NewRecorder() - svr := h.SetPrimaryPhoneNumber() svr.ServeHTTP(response, req) @@ -3927,3 +4017,600 @@ func TestHandlers_CheckPermission(t *testing.T) { }) } } + +func TestHandlers_CreateRole(t *testing.T) { + i, err := InitializeFakeOnboardingInteractor() + if err != nil { + t.Errorf("failed to initialize onboarding interactor: %v", err) + return + } + h := rest.NewHandlersInterfaces(i) + + invalidPayload, err := json.Marshal(dto.RoleInput{Description: "Test Role"}) + if err != nil { + t.Errorf("unable to marshal payload to JSON: %s", err) + return + } + + validPayload, err := json.Marshal(dto.RoleInput{Name: "Test Role", Description: "Test Role"}) + if err != nil { + t.Errorf("unable to marshal payload to JSON: %s", err) + return + } + + type args struct { + url string + httpMethod string + body io.Reader + } + + tests := []struct { + name string + args args + wantStatus int + wantErr bool + }{ + { + name: "fail: missing name in request payload", + args: args{ + url: fmt.Sprintf("%s/create_role", serverUrl), + httpMethod: http.MethodPost, + body: bytes.NewBuffer(invalidPayload), + }, + wantStatus: http.StatusBadRequest, + wantErr: false, + }, + { + name: "fail: create unauthorized role error", + args: args{ + url: fmt.Sprintf("%s/create_role", serverUrl), + httpMethod: http.MethodPost, + body: bytes.NewBuffer(validPayload), + }, + wantStatus: http.StatusInternalServerError, + wantErr: false, + }, + { + name: "success: create role", + args: args{ + url: fmt.Sprintf("%s/create_role", serverUrl), + httpMethod: http.MethodPost, + body: bytes.NewBuffer(validPayload), + }, + wantStatus: http.StatusCreated, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.name == "fail: create unauthorized role error" { + fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { + return &dto.UserInfo{}, nil + } + fakeRepo.GetUserProfileByUIDFn = func(ctx context.Context, uid string, suspended bool) (*profileutils.UserProfile, error) { + return &profileutils.UserProfile{}, nil + } + fakeRepo.CreateRoleFn = func(ctx context.Context, profileID string, role dto.RoleInput) (*profileutils.Role, error) { + return nil, fmt.Errorf("duplicate role") + } + } + + if tt.name == "success: create role" { + fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { + return &dto.UserInfo{}, nil + } + fakeRepo.GetUserProfileByUIDFn = func(ctx context.Context, uid string, suspended bool) (*profileutils.UserProfile, error) { + return &profileutils.UserProfile{}, nil + } + fakeRepo.CreateRoleFn = func(ctx context.Context, profileID string, role dto.RoleInput) (*profileutils.Role, error) { + return &profileutils.Role{ + Scopes: []string{"role.edit"}, + }, nil + } + } + + // Create a request to pass to our handler. + req, err := http.NewRequest(tt.args.httpMethod, tt.args.url, tt.args.body) + if err != nil { + t.Errorf("can't create new request: %v", err) + return + } + // We create a ResponseRecorder to record the response. + response := httptest.NewRecorder() + + // call its ServeHTTP method and pass in our Request and ResponseRecorder. + svr := h.CreateRole() + svr.ServeHTTP(response, req) + + if tt.wantStatus != response.Code { + t.Errorf("expected status %d, got %d", tt.wantStatus, response.Code) + return + } + }) + } +} + +func TestHandlers_AssignRole(t *testing.T) { + i, err := InitializeFakeOnboardingInteractor() + if err != nil { + t.Errorf("failed to initialize onboarding interactor: %v", err) + return + } + h := rest.NewHandlersInterfaces(i) + + emptyRoleIDPayload, err := json.Marshal(dto.AssignRolePayload{UserID: "123456", RoleID: ""}) + if err != nil { + t.Errorf("unable to marshal payload to JSON: %s", err) + return + } + + emptyUserIDPayload, err := json.Marshal(dto.AssignRolePayload{RoleID: "12345", UserID: ""}) + if err != nil { + t.Errorf("unable to marshal payload to JSON: %s", err) + return + } + + validPayload, err := json.Marshal(dto.AssignRolePayload{UserID: "123456", RoleID: "123456"}) + if err != nil { + t.Errorf("unable to marshal payload to JSON: %s", err) + return + } + + type args struct { + url string + httpMethod string + body io.Reader + } + + tests := []struct { + name string + args args + wantStatus int + wantErr bool + }{ + { + name: "fail: missing role ID in request payload", + args: args{ + url: fmt.Sprintf("%s/assign_role", serverUrl), + httpMethod: http.MethodPost, + body: bytes.NewBuffer(emptyRoleIDPayload), + }, + wantStatus: http.StatusBadRequest, + wantErr: false, + }, + { + name: "fail: missing user ID in request payload", + args: args{ + url: fmt.Sprintf("%s/assign_role", serverUrl), + httpMethod: http.MethodPost, + body: bytes.NewBuffer(emptyUserIDPayload), + }, + wantStatus: http.StatusBadRequest, + wantErr: false, + }, + { + name: "fail: assign role error", + args: args{ + url: fmt.Sprintf("%s/assign_role", serverUrl), + httpMethod: http.MethodPost, + body: bytes.NewBuffer(validPayload), + }, + wantStatus: http.StatusInternalServerError, + wantErr: false, + }, + { + name: "success: assign role", + args: args{ + url: fmt.Sprintf("%s/assign_role", serverUrl), + httpMethod: http.MethodPost, + body: bytes.NewBuffer(validPayload), + }, + wantStatus: http.StatusOK, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.name == "fail: assign role error" { + fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { + return &dto.UserInfo{UID: ""}, nil + } + + fakeRepo.CheckIfUserHasPermissionFn = func(ctx context.Context, UID string, requiredPermission profileutils.Permission) (bool, error) { + return true, nil + } + + fakeRepo.GetRoleByIDFn = func(ctx context.Context, roleID string) (*profileutils.Role, error) { + return &profileutils.Role{ + ID: "", + Scopes: []string{profileutils.CanAssignRole.Scope}, + }, nil + } + + fakeRepo.GetUserProfileByIDFn = func(ctx context.Context, id string, suspended bool) (*profileutils.UserProfile, error) { + return &profileutils.UserProfile{ID: ""}, nil + } + + fakeRepo.UpdateUserRoleIDsFn = func(ctx context.Context, id string, roleIDs []string) error { + return fmt.Errorf("cannot update uids") + } + } + + if tt.name == "success: assign role" { + fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { + return &dto.UserInfo{UID: ""}, nil + } + + fakeRepo.CheckIfUserHasPermissionFn = func(ctx context.Context, UID string, requiredPermission profileutils.Permission) (bool, error) { + return true, nil + } + + fakeRepo.GetRoleByIDFn = func(ctx context.Context, roleID string) (*profileutils.Role, error) { + return &profileutils.Role{ + ID: "", + Scopes: []string{profileutils.CanAssignRole.Scope}, + }, nil + } + + fakeRepo.GetUserProfileByIDFn = func(ctx context.Context, id string, suspended bool) (*profileutils.UserProfile, error) { + return &profileutils.UserProfile{ID: ""}, nil + } + + fakeRepo.UpdateUserRoleIDsFn = func(ctx context.Context, id string, roleIDs []string) error { + return nil + } + } + + // Create a request to pass to our handler. + req, err := http.NewRequest(tt.args.httpMethod, tt.args.url, tt.args.body) + if err != nil { + t.Errorf("can't create new request: %v", err) + return + } + // We create a ResponseRecorder to record the response. + response := httptest.NewRecorder() + + // call its ServeHTTP method and pass in our Request and ResponseRecorder. + svr := h.AssignRole() + svr.ServeHTTP(response, req) + + if tt.wantStatus != response.Code { + t.Errorf("expected status %d, got %d", tt.wantStatus, response.Code) + return + } + }) + } +} + +func TestHandlers_RemoveRoleByName(t *testing.T) { + i, err := InitializeFakeOnboardingInteractor() + if err != nil { + t.Errorf("failed to initialize onboarding interactor: %v", err) + return + } + h := rest.NewHandlersInterfaces(i) + + invalidPayload, err := json.Marshal(dto.DeleteRolePayload{Name: ""}) + if err != nil { + t.Errorf("unable to marshal payload to JSON: %s", err) + return + } + + validPayload, err := json.Marshal(dto.DeleteRolePayload{Name: "Test Role"}) + if err != nil { + t.Errorf("unable to marshal payload to JSON: %s", err) + return + } + + type args struct { + url string + httpMethod string + body io.Reader + } + + tests := []struct { + name string + args args + wantStatus int + wantErr bool + }{ + { + name: "fail: missing name in request payload", + args: args{ + url: fmt.Sprintf("%s/remove_role", serverUrl), + httpMethod: http.MethodPost, + body: bytes.NewBuffer(invalidPayload), + }, + wantStatus: http.StatusBadRequest, + wantErr: false, + }, + { + name: "fail: find role by name error", + args: args{ + url: fmt.Sprintf("%s/remove_role", serverUrl), + httpMethod: http.MethodPost, + body: bytes.NewBuffer(validPayload), + }, + wantStatus: http.StatusInternalServerError, + wantErr: false, + }, + { + name: "fail: delete role error", + args: args{ + url: fmt.Sprintf("%s/remove_role", serverUrl), + httpMethod: http.MethodPost, + body: bytes.NewBuffer(validPayload), + }, + wantStatus: http.StatusInternalServerError, + wantErr: false, + }, + { + name: "success: remove role", + args: args{ + url: fmt.Sprintf("%s/remove_role", serverUrl), + httpMethod: http.MethodPost, + body: bytes.NewBuffer(validPayload), + }, + wantStatus: http.StatusOK, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + if tt.name == "fail: find role by name error" { + fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { + return &dto.UserInfo{}, nil + } + fakeRepo.CheckIfUserHasPermissionFn = func(ctx context.Context, UID string, requiredPermission profileutils.Permission) (bool, error) { + return true, nil + } + fakeRepo.GetRoleByNameFn = func(ctx context.Context, roleName string) (*profileutils.Role, error) { + return nil, fmt.Errorf("cannot find role") + } + + } + + if tt.name == "fail: delete role error" { + fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { + return &dto.UserInfo{}, nil + } + fakeRepo.CheckIfUserHasPermissionFn = func(ctx context.Context, UID string, requiredPermission profileutils.Permission) (bool, error) { + return true, nil + } + fakeRepo.GetRoleByNameFn = func(ctx context.Context, roleName string) (*profileutils.Role, error) { + return &profileutils.Role{ID: "", Scopes: []string{profileutils.CanAssignRole.Scope}}, nil + } + + fakeRepo.GetRoleByIDFn = func(ctx context.Context, roleID string) (*profileutils.Role, error) { + return &profileutils.Role{ID: uuid.NewString(), Name: "Happy Test Role", Scopes: []string{profileutils.CanAssignRole.Scope}}, nil + } + + fakeRepo.DeleteRoleFn = func(ctx context.Context, roleID string) (bool, error) { + return false, fmt.Errorf("cannot delete role") + } + } + + if tt.name == "success: remove role" { + fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { + return &dto.UserInfo{}, nil + } + fakeRepo.CheckIfUserHasPermissionFn = func(ctx context.Context, UID string, requiredPermission profileutils.Permission) (bool, error) { + return true, nil + } + fakeRepo.GetRoleByNameFn = func(ctx context.Context, roleName string) (*profileutils.Role, error) { + return &profileutils.Role{ID: "", Scopes: []string{profileutils.CanAssignRole.Scope}}, nil + } + fakeRepo.GetRoleByIDFn = func(ctx context.Context, roleID string) (*profileutils.Role, error) { + return &profileutils.Role{ID: uuid.NewString(), Name: "Happy Test Role", Scopes: []string{profileutils.CanAssignRole.Scope}}, nil + } + fakeRepo.DeleteRoleFn = func(ctx context.Context, roleID string) (bool, error) { + return true, nil + } + } + + // Create a request to pass to our handler. + req, err := http.NewRequest(tt.args.httpMethod, tt.args.url, tt.args.body) + if err != nil { + t.Errorf("can't create new request: %v", err) + return + } + // We create a ResponseRecorder to record the response. + response := httptest.NewRecorder() + + // call its ServeHTTP method and pass in our Request and ResponseRecorder. + svr := h.RemoveRoleByName() + svr.ServeHTTP(response, req) + + if tt.wantStatus != response.Code { + t.Errorf("expected status %d, got %d", tt.wantStatus, response.Code) + return + } + }) + } +} + +func TestHandlersInterfacesImpl_RegisterUser(t *testing.T) { + i, err := InitializeFakeOnboardingInteractor() + if err != nil { + t.Errorf("failed to initialize onboarding interactor: %v", err) + return + } + h := rest.NewHandlersInterfaces(i) + + phoneNumber := interserviceclient.TestUserPhoneNumber + fName := "Test" + lName := "Test" + email := "test@email.com" + + payload := composeValidUserPayload(t, phoneNumber) + payload1 := composeInvalidUserPayload0(t, phoneNumber) + payload2 := composeInvalidUserPayload1(t, phoneNumber) + payload3 := composeInvalidUserPayload2(t, phoneNumber) + payload4 := composeInvalidUserPayload3(t, phoneNumber) + payload5 := composeInvalidUserPayload4(t, phoneNumber) + + type args struct { + url string + httpMethod string + body io.Reader + } + + tests := []struct { + name string + args args + wantStatus int + wantErr bool + }{ + { + name: "sad: expected UID", + args: args{ + url: fmt.Sprintf("%s/interna/register_user", serverUrl), + httpMethod: http.MethodPost, + body: payload1, + }, + wantStatus: http.StatusBadRequest, + wantErr: true, + }, + { + name: "sad: expected firstName", + args: args{ + url: fmt.Sprintf("%s/interna/register_user", serverUrl), + httpMethod: http.MethodPost, + body: payload2, + }, + wantStatus: http.StatusBadRequest, + wantErr: true, + }, + { + name: "sad: expected lastName", + args: args{ + url: fmt.Sprintf("%s/interna/register_user", serverUrl), + httpMethod: http.MethodPost, + body: payload3, + }, + wantStatus: http.StatusBadRequest, + wantErr: true, + }, + { + name: "sad: expected gender", + args: args{ + url: fmt.Sprintf("%s/interna/register_user", serverUrl), + httpMethod: http.MethodPost, + body: payload4, + }, + wantStatus: http.StatusBadRequest, + wantErr: true, + }, + { + name: "sad: expected phoneNumber", + args: args{ + url: fmt.Sprintf("%s/interna/register_user", serverUrl), + httpMethod: http.MethodPost, + body: payload5, + }, + wantStatus: http.StatusBadRequest, + wantErr: true, + }, + { + name: "sad: unable to create user profile", + args: args{ + url: fmt.Sprintf("%s/interna/register_user", serverUrl), + httpMethod: http.MethodPost, + body: payload, + }, + wantStatus: http.StatusInternalServerError, + wantErr: true, + }, + { + name: "happy: registered user", + args: args{ + url: fmt.Sprintf("%s/interna/register_user", serverUrl), + httpMethod: http.MethodPost, + body: payload, + }, + wantStatus: http.StatusBadRequest, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + req, err := http.NewRequest(tt.args.httpMethod, tt.args.url, tt.args.body) + if err != nil { + t.Errorf("can't create new request: %v", err) + return + } + + response := httptest.NewRecorder() + + if tt.name == "sad: unable to create user profile" { + fakeBaseExt.GetLoggedInUserUIDFn = func(ctx context.Context) (string, error) { + return "", fmt.Errorf("unable to get logged in user") + } + } + + if tt.name == "happy: registered user" { + fakeBaseExt.GetLoggedInUserUIDFn = func(ctx context.Context) (string, error) { + return uuid.NewString(), nil + } + + fakeRepo.GetUserProfileByUIDFn = func(ctx context.Context, uid string, suspended bool) (*profileutils.UserProfile, error) { + return nil, fmt.Errorf("unable to get user profile") + } + fakeBaseExt.NormalizeMSISDNFn = func(msisdn string) (*string, error) { + return &phoneNumber, nil + } + fakeRepo.CreateDetailedUserProfileFn = func(ctx context.Context, phoneNumber string, profile profileutils.UserProfile) (*profileutils.UserProfile, error) { + return &profileutils.UserProfile{ + ID: uuid.NewString(), + UserBioData: profileutils.BioData{ + FirstName: &fName, + LastName: &lName, + }, + PrimaryPhone: &phoneNumber, + PrimaryEmailAddress: &email, + }, nil + } + fakeRepo.CreateEmptySupplierProfileFn = func(ctx context.Context, profileID string) (*profileutils.Supplier, error) { + return &profileutils.Supplier{}, nil + } + fakeRepo.CreateEmptyCustomerProfileFn = func(ctx context.Context, profileID string) (*profileutils.Customer, error) { + return &profileutils.Customer{}, nil + } + fakeRepo.SetUserCommunicationsSettingsFn = func(ctx context.Context, profileID string, allowWhatsApp, allowTextSms, allowPush, allowEmail *bool) (*profileutils.UserCommunicationsSetting, error) { + return &profileutils.UserCommunicationsSetting{}, nil + } + fakePinExt.GenerateTempPINFn = func(ctx context.Context) (string, error) { + return "123", nil + } + fakePinExt.EncryptPINFn = func(rawPwd string, options *extension.Options) (string, string) { + return "pin", "sha" + } + fakeRepo.SavePINFn = func(ctx context.Context, pin *domain.PIN) (bool, error) { + return true, nil + } + fakeEngagementSvs.SendSMSFn = func(ctx context.Context, phoneNumbers []string, message string) error { + return nil + } + } + + svr := h.RegisterUser() + svr.ServeHTTP(response, req) + + if tt.wantStatus != response.Code { + t.Errorf("expected status %d, got %d", tt.wantStatus, response.Code) + return + } + + dataResponse, err := ioutil.ReadAll(response.Body) + if err != nil { + t.Errorf("can't read response body: %v", err) + return + } + if dataResponse == nil { + t.Errorf("nil response body data") + return + } + }) + } +} diff --git a/pkg/onboarding/repository/mock/onboarding.go b/pkg/onboarding/repository/mock/onboarding.go index b8bb232c..8e8a8b55 100644 --- a/pkg/onboarding/repository/mock/onboarding.go +++ b/pkg/onboarding/repository/mock/onboarding.go @@ -197,6 +197,15 @@ type FakeOnboardingRepository struct { CheckIfUserHasPermissionFn func(ctx context.Context, UID string, requiredPermission profileutils.Permission) (bool, error) UpdateUserProfileEmailFn func(ctx context.Context, phone string, email string) error GetUserProfilesByRoleIDFn func(ctx context.Context, role string) ([]*profileutils.UserProfile, error) + SaveRoleRevocationFn func(ctx context.Context, userID string, revocation dto.RoleRevocationInput) error + + //admins + CreateAdminProfileFn func(ctx context.Context, adminProfile domain.AdminProfile) error + CheckIfAdminProfileExistsFn func(ctx context.Context, profileID string) (bool, error) + + //agents + CreateAgentProfileFn func(ctx context.Context, agentProfile domain.AgentProfile) error + CheckIfAgentProfileExistsFn func(ctx context.Context, profileID string) (bool, error) } // GetSupplierProfileByID ... @@ -983,3 +992,28 @@ func (f *FakeOnboardingRepository) GetRoleByName(ctx context.Context, roleName s func (f *FakeOnboardingRepository) GetUserProfilesByRoleID(ctx context.Context, role string) ([]*profileutils.UserProfile, error) { return f.GetUserProfilesByRoleIDFn(ctx, role) } + +// SaveRoleRevocation ... +func (f *FakeOnboardingRepository) SaveRoleRevocation(ctx context.Context, userID string, revocation dto.RoleRevocationInput) error { + return f.SaveRoleRevocationFn(ctx, userID, revocation) +} + +//CreateAdminProfile ... +func (f *FakeOnboardingRepository) CreateAdminProfile(ctx context.Context, adminProfile domain.AdminProfile) error { + return f.CreateAdminProfileFn(ctx, adminProfile) +} + +//CheckIfAdminProfileExists ... +func (f *FakeOnboardingRepository) CheckIfAdminProfileExists(ctx context.Context, profileID string) (bool, error) { + return f.CheckIfAdminProfileExistsFn(ctx, profileID) +} + +//CreateAgentProfile ... +func (f *FakeOnboardingRepository) CreateAgentProfile(ctx context.Context, agentProfile domain.AgentProfile) error { + return f.CreateAgentProfileFn(ctx, agentProfile) +} + +//CheckIfAgentProfileExists ... +func (f *FakeOnboardingRepository) CheckIfAgentProfileExists(ctx context.Context, profileID string) (bool, error) { + return f.CheckIfAgentProfileExistsFn(ctx, profileID) +} diff --git a/pkg/onboarding/repository/onboarding.go b/pkg/onboarding/repository/onboarding.go index de43e074..0edea419 100644 --- a/pkg/onboarding/repository/onboarding.go +++ b/pkg/onboarding/repository/onboarding.go @@ -91,6 +91,10 @@ type OnboardingRepository interface { RolesRepository + AdminsRepository + + AgentsRepository + // creates a user profile of using the provided phone number and uid CreateUserProfile( ctx context.Context, @@ -345,4 +349,18 @@ type RolesRepository interface { // GetUserProfilesByRole retrieves userprofiles with a particular role GetUserProfilesByRoleID(ctx context.Context, role string) ([]*profileutils.UserProfile, error) + + SaveRoleRevocation(ctx context.Context, userID string, revocation dto.RoleRevocationInput) error +} + +//AdminsRepository interface that provide access to all persistent storage operations for admins +type AdminsRepository interface { + CreateAdminProfile(ctx context.Context, adminProfile domain.AdminProfile) error + CheckIfAdminProfileExists(ctx context.Context, profileID string) (bool, error) +} + +//AgentsRepository interface that provide access to all persistent storage operations for agents +type AgentsRepository interface { + CreateAgentProfile(ctx context.Context, agentProfile domain.AgentProfile) error + CheckIfAgentProfileExists(ctx context.Context, profileID string) (bool, error) } diff --git a/pkg/onboarding/usecases/admin.go b/pkg/onboarding/usecases/admin.go index 81a59d4c..a4a34a5b 100644 --- a/pkg/onboarding/usecases/admin.go +++ b/pkg/onboarding/usecases/admin.go @@ -1,13 +1,12 @@ package usecases import ( - "bytes" "context" "fmt" - "html/template" - "log" + "strings" "time" + "github.com/google/uuid" "github.com/savannahghi/onboarding/pkg/onboarding/application/dto" "github.com/savannahghi/onboarding/pkg/onboarding/application/exceptions" "github.com/savannahghi/onboarding/pkg/onboarding/application/extension" @@ -19,11 +18,6 @@ import ( "github.com/savannahghi/pubsubtools" ) -const ( - adminWelcomeMessage = "You have been successfully registered as an admin. We look forward to working with you." - adminWelcomeEmailSubject = "Successfully registered as an admin" -) - // AdminUseCase represent the business logic required for management of admins type AdminUseCase interface { RegisterAdmin( @@ -33,6 +27,7 @@ type AdminUseCase interface { FetchAdmins(ctx context.Context) ([]*dto.Admin, error) ActivateAdmin(ctx context.Context, input dto.ProfileSuspensionInput) (bool, error) DeactivateAdmin(ctx context.Context, input dto.ProfileSuspensionInput) (bool, error) + FindAdminByNameOrPhone(ctx context.Context, nameOrPhone *string) ([]*dto.Admin, error) } // AdminUseCaseImpl represents usecase implementation object @@ -67,19 +62,25 @@ func (a *AdminUseCaseImpl) RegisterAdmin( ctx, span := tracer.Start(ctx, "RegisterAdmin") defer span.End() - msisdn, err := a.baseExt.NormalizeMSISDN(input.PhoneNumber) + // Check logged in user has permissions to register admin + p, err := a.baseExt.GetLoggedInUser(ctx) if err != nil { utils.RecordSpanError(span, err) - return nil, exceptions.NormalizeMSISDNError(err) + return nil, err } - // Check logged in user has permissions/role of employee - p, err := a.baseExt.GetLoggedInUser(ctx) + allowed, err := a.repo.CheckIfUserHasPermission(ctx, p.UID, profileutils.CanCreateEmployee) if err != nil { utils.RecordSpanError(span, err) return nil, err } + if !allowed { + err = fmt.Errorf("error, user do not have required permissions") + utils.RecordSpanError(span, err) + return nil, err + } + // Get Logged In user profile usp, err := a.repo.GetUserProfileByUID(ctx, p.UID, false) if err != nil { @@ -87,8 +88,14 @@ func (a *AdminUseCaseImpl) RegisterAdmin( return nil, err } + phoneNumber, err := a.baseExt.NormalizeMSISDN(input.PhoneNumber) + if err != nil { + utils.RecordSpanError(span, err) + return nil, exceptions.NormalizeMSISDNError(err) + } + timestamp := time.Now().In(pubsubtools.TimeLocation) - adminProfile := profileutils.UserProfile{ + userProfile := profileutils.UserProfile{ PrimaryEmailAddress: &input.Email, UserBioData: profileutils.BioData{ FirstName: &input.FirstName, @@ -104,7 +111,7 @@ func (a *AdminUseCaseImpl) RegisterAdmin( } // create a user profile in bewell - profile, err := a.repo.CreateDetailedUserProfile(ctx, *msisdn, adminProfile) + profile, err := a.repo.CreateDetailedUserProfile(ctx, *phoneNumber, userProfile) if err != nil { utils.RecordSpanError(span, err) // wrapped error @@ -131,6 +138,19 @@ func (a *AdminUseCaseImpl) RegisterAdmin( return nil, exceptions.InternalServerError(err) } + adminProfile := domain.AdminProfile{ + ID: uuid.New().String(), + ProfileID: profile.ID, + OrganizationID: SavannahSladeCode, + } + + err = a.repo.CreateAdminProfile(ctx, adminProfile) + + if err != nil { + utils.RecordSpanError(span, err) + return nil, exceptions.InternalServerError(err) + } + // set the user default communications settings defaultCommunicationSetting := true _, err = a.repo.SetUserCommunicationsSettings( @@ -154,48 +174,13 @@ func (a *AdminUseCaseImpl) RegisterAdmin( return nil, err } - if err := a.notifyNewAdmin(ctx, input.Email, input.PhoneNumber, *profile.UserBioData.FirstName, otp); err != nil { - utils.RecordSpanError(span, err) - return nil, fmt.Errorf("unable to send admin registration notifications: %w", err) - } - - return profile, nil -} + message := fmt.Sprintf(domain.WelcomeMessage, input.FirstName, otp) -func (a *AdminUseCaseImpl) notifyNewAdmin( - ctx context.Context, - email, phoneNumber, firstName, tempPIN string, -) error { - type pin struct { - Name string - Pin string + if err := a.engagement.SendSMS(ctx, []string{*phoneNumber}, message); err != nil { + return nil, fmt.Errorf("unable to send admin registration message: %w", err) } - message := fmt.Sprintf(domain.WelcomeMessage, firstName, tempPIN) - - if err := a.engagement.SendSMS(ctx, []string{phoneNumber}, message); err != nil { - return fmt.Errorf("unable to send admin registration message: %w", err) - } - - if email != "" { - t := template.Must(template.New("adminApprovalEmail").Parse(utils.AdminApprovalEmail)) - - buf := new(bytes.Buffer) - - err := t.Execute(buf, pin{firstName, tempPIN}) - if err != nil { - log.Fatalf("error while generating admin approval email template: %s", err) - } - - text := buf.String() - - if err := a.engagement.SendMail(ctx, email, text, adminWelcomeEmailSubject); err != nil { - return fmt.Errorf("unable to send admin registration email: %w", err) - } - - } - - return nil + return profile, nil } // FetchAdmins fetches registered admins @@ -220,6 +205,33 @@ func (a *AdminUseCaseImpl) FetchAdmins(ctx context.Context) ([]*dto.Admin, error return nil, err } + roles, err := a.repo.GetRolesByIDs(ctx, profile.Roles) + if err != nil { + utils.RecordSpanError(span, err) + // the error is wrapped already. No need to wrap it again + return nil, err + } + + // role output + ro := []dto.RoleOutput{} + for _, role := range *roles { + perms, err := role.Permissions(ctx) + if err != nil { + utils.RecordSpanError(span, err) + return nil, err + } + o := dto.RoleOutput{ + ID: role.ID, + Name: role.Name, + Description: role.Description, + Active: role.Active, + Scopes: role.Scopes, + Permissions: perms, + } + + ro = append(ro, o) + } + admin := &dto.Admin{ ID: profile.ID, PhotoUploadID: profile.PhotoUploadID, @@ -231,6 +243,7 @@ func (a *AdminUseCaseImpl) FetchAdmins(ctx context.Context) ([]*dto.Admin, error TermsAccepted: profile.TermsAccepted, Suspended: profile.Suspended, ResendPIN: pin.IsOTP, + Roles: ro, } admins = append(admins, admin) @@ -318,3 +331,43 @@ func (a *AdminUseCaseImpl) DeactivateAdmin( } return true, nil } + +// FindAdminByNameOrPhone is used to find an Admin using their phone number +func (a *AdminUseCaseImpl) FindAdminByNameOrPhone( + ctx context.Context, + nameOrPhone *string, +) ([]*dto.Admin, error) { + ctx, span := tracer.Start(ctx, "FindAdminByNameOrPhone") + defer span.End() + + profiles, err := a.repo.ListUserProfiles(ctx, profileutils.RoleTypeEmployee) + if err != nil { + utils.RecordSpanError(span, err) + return nil, exceptions.UserNotFoundError(err) + } + + admins := []*dto.Admin{} + + for _, profile := range profiles { + + fullName := strings.ToLower(fmt.Sprintf("%v %v", *profile.UserBioData.FirstName, *profile.UserBioData.LastName)) + phoneNumber := profile.PrimaryPhone + + if strings.Contains(*phoneNumber, *nameOrPhone) || strings.Contains(fullName, strings.ToLower(*nameOrPhone)) { + admin := dto.Admin{ + ID: profile.ID, + PhotoUploadID: profile.PhotoUploadID, + UserBioData: profile.UserBioData, + PrimaryPhone: *profile.PrimaryPhone, + PrimaryEmailAddress: profile.PrimaryEmailAddress, + SecondaryPhoneNumbers: profile.SecondaryPhoneNumbers, + SecondaryEmailAddresses: profile.SecondaryEmailAddresses, + TermsAccepted: profile.TermsAccepted, + Suspended: profile.Suspended, + } + admins = append(admins, &admin) + } + } + + return admins, nil +} diff --git a/pkg/onboarding/usecases/admin_unit_test.go b/pkg/onboarding/usecases/admin_unit_test.go index 18a503d0..57671f44 100644 --- a/pkg/onboarding/usecases/admin_unit_test.go +++ b/pkg/onboarding/usecases/admin_unit_test.go @@ -26,7 +26,9 @@ func TestAdminUseCaseImpl_RegisterAdmin(t *testing.T) { return } - // admin 47 + // admin + UID := "c9d62c7e-93e5-44a6-b503-6fc159c1782f" + id := "c9d62c7e-93e5-44a6-b503-6fc159c1782f" fName := "Tobias" lName := "Rieper" dob := scalarutils.Date{ @@ -54,26 +56,16 @@ func TestAdminUseCaseImpl_RegisterAdmin(t *testing.T) { wantErr bool }{ { - name: "valid:register_new_admin", + name: "sad: unable to get logged in user", args: args{ ctx: ctx, input: admin, }, - want: &profileutils.UserProfile{ - ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", - VerifiedUIDS: []string{"f4f39af7-5b64-4c2f-91bd-42b3af315a4e"}, - UserBioData: profileutils.BioData{ - FirstName: &fName, - LastName: &lName, - Gender: enumutils.GenderMale, - DateOfBirth: &dob, - }, - Role: profileutils.RoleTypeEmployee, - }, - wantErr: false, + want: nil, + wantErr: true, }, { - name: "invalid:cannot_create_user_profile", + name: "sad: unable to check user permissions", args: args{ ctx: ctx, input: admin, @@ -82,7 +74,7 @@ func TestAdminUseCaseImpl_RegisterAdmin(t *testing.T) { wantErr: true, }, { - name: "invalid:cannot_create_customer_profile", + name: "sad: user do not have required permissions", args: args{ ctx: ctx, input: admin, @@ -91,7 +83,7 @@ func TestAdminUseCaseImpl_RegisterAdmin(t *testing.T) { wantErr: true, }, { - name: "invalid:cannot_create_supplier_profile", + name: "sad: unable to get user profile", args: args{ ctx: ctx, input: admin, @@ -100,7 +92,7 @@ func TestAdminUseCaseImpl_RegisterAdmin(t *testing.T) { wantErr: true, }, { - name: "invalid:cannot_set_communication_settings", + name: "sad: unable to normalize phonenumber", args: args{ ctx: ctx, input: admin, @@ -109,7 +101,7 @@ func TestAdminUseCaseImpl_RegisterAdmin(t *testing.T) { wantErr: true, }, { - name: "invalid:cannot_notify_new_admin_sms", + name: "sad: unable to create detailed user profile", args: args{ ctx: ctx, input: admin, @@ -118,7 +110,7 @@ func TestAdminUseCaseImpl_RegisterAdmin(t *testing.T) { wantErr: true, }, { - name: "invalid:cannot_notify_new_admin_email", + name: "sad: unable to create customer profile", args: args{ ctx: ctx, input: admin, @@ -127,7 +119,7 @@ func TestAdminUseCaseImpl_RegisterAdmin(t *testing.T) { wantErr: true, }, { - name: "invalid:get_logged_in_user_error", + name: "sad: unable to create supplier profile", args: args{ ctx: ctx, input: admin, @@ -136,7 +128,7 @@ func TestAdminUseCaseImpl_RegisterAdmin(t *testing.T) { wantErr: true, }, { - name: "invalid:get_profile_by_uid_error", + name: "sad: unable to create admin profile", args: args{ ctx: ctx, input: admin, @@ -145,7 +137,7 @@ func TestAdminUseCaseImpl_RegisterAdmin(t *testing.T) { wantErr: true, }, { - name: "invalid:invalid_logged_in_user_role", + name: "sad: unable to create communication settings", args: args{ ctx: ctx, input: admin, @@ -154,7 +146,7 @@ func TestAdminUseCaseImpl_RegisterAdmin(t *testing.T) { wantErr: true, }, { - name: "invalid:normalizing_phonenumber_failed", + name: "sad: unable to create temporary pin", args: args{ ctx: ctx, input: admin, @@ -163,7 +155,7 @@ func TestAdminUseCaseImpl_RegisterAdmin(t *testing.T) { wantErr: true, }, { - name: "invalid:cannot_set_admin_temporary_pin", + name: "sad: unable to notify user", args: args{ ctx: ctx, input: admin, @@ -171,611 +163,318 @@ func TestAdminUseCaseImpl_RegisterAdmin(t *testing.T) { want: nil, wantErr: true, }, + { + name: "happy: registered new admin", + args: args{ + ctx: ctx, + input: admin, + }, + want: &profileutils.UserProfile{ + ID: id, + UserBioData: profileutils.BioData{ + FirstName: &fName, + LastName: &lName, + }, + }, + wantErr: false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - - if tt.name == "invalid:normalizing_phonenumber_failed" { - fakeBaseExt.NormalizeMSISDNFn = func(msisdn string) (*string, error) { - return nil, fmt.Errorf("cannot normalize the mobile number") + if tt.name == "sad: unable to get logged in user" { + fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { + return nil, fmt.Errorf("error unable to get logged in user profile") } } - if tt.name == "invalid:get_logged_in_user_error" { - fakeBaseExt.NormalizeMSISDNFn = func(msisdn string) (*string, error) { - phone := "+254777886622" - return &phone, nil - } - + if tt.name == "sad: unable to check user permissions" { fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { - return nil, fmt.Errorf("cannot get logged in user") + return &dto.UserInfo{UID: UID}, nil + } + fakeRepo.CheckIfUserHasPermissionFn = func(ctx context.Context, UID string, requiredPermission profileutils.Permission) (bool, error) { + return false, fmt.Errorf("error unable to check user permissions") } } - if tt.name == "invalid:get_profile_by_uid_error" { - fakeBaseExt.NormalizeMSISDNFn = func(msisdn string) (*string, error) { - phone := "+254777886622" - return &phone, nil + if tt.name == "sad: user do not have required permissions" { + fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { + return &dto.UserInfo{UID: UID}, nil + } + fakeRepo.CheckIfUserHasPermissionFn = func(ctx context.Context, UID string, requiredPermission profileutils.Permission) (bool, error) { + return false, nil } + } + if tt.name == "sad: unable to get user profile" { fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { - return &dto.UserInfo{ - UID: "f4f39af7-5b64-4c2f-91bd-42b3af315a4e", - }, nil + return &dto.UserInfo{UID: UID}, nil + } + fakeRepo.CheckIfUserHasPermissionFn = func(ctx context.Context, UID string, requiredPermission profileutils.Permission) (bool, error) { + return true, nil } fakeRepo.GetUserProfileByUIDFn = func(ctx context.Context, uid string, suspended bool) (*profileutils.UserProfile, error) { - return nil, fmt.Errorf("failed to get user bu UID") + return nil, fmt.Errorf("error unable to get user profile") } } - if tt.name == "invalid:invalid_logged_in_user_role" { + if tt.name == "sad: unable to normalize phonenumber" { + fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { + return &dto.UserInfo{UID: UID}, nil + } + fakeRepo.CheckIfUserHasPermissionFn = func(ctx context.Context, UID string, requiredPermission profileutils.Permission) (bool, error) { + return true, nil + } + fakeRepo.GetUserProfileByUIDFn = func(ctx context.Context, uid string, suspended bool) (*profileutils.UserProfile, error) { + return &profileutils.UserProfile{ID: id}, nil + } fakeBaseExt.NormalizeMSISDNFn = func(msisdn string) (*string, error) { - phone := "+254777886622" - return &phone, nil + return nil, fmt.Errorf("error unable to normalize phone number") } + } + if tt.name == "sad: unable to create detailed user profile" { fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { - return &dto.UserInfo{ - UID: "f4f39af7-5b64-4c2f-91bd-42b3af315a4e", - }, nil + return &dto.UserInfo{UID: UID}, nil + } + fakeRepo.CheckIfUserHasPermissionFn = func(ctx context.Context, UID string, requiredPermission profileutils.Permission) (bool, error) { + return true, nil } fakeRepo.GetUserProfileByUIDFn = func(ctx context.Context, uid string, suspended bool) (*profileutils.UserProfile, error) { - return &profileutils.UserProfile{ - ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", - VerifiedUIDS: []string{"f4f39af7-5b64-4c2f-91bd-42b3af315a4e"}, - UserBioData: profileutils.BioData{ - FirstName: &fName, - LastName: &lName, - Gender: enumutils.GenderMale, - DateOfBirth: &dob, - }, - Permissions: profileutils.DefaultAdminPermissions, - }, fmt.Errorf("user do not have required permissions") + return &profileutils.UserProfile{ID: id}, nil } - } - - if tt.name == "valid:register_new_admin" { fakeBaseExt.NormalizeMSISDNFn = func(msisdn string) (*string, error) { - phone := "+254777886622" - return &phone, nil + phoneNumber := interserviceclient.TestUserPhoneNumber + return &phoneNumber, nil + } + fakeRepo.CreateDetailedUserProfileFn = func(ctx context.Context, phoneNumber string, profile profileutils.UserProfile) (*profileutils.UserProfile, error) { + return nil, fmt.Errorf("error unable to create user profile") } + } + if tt.name == "sad: unable to create customer profile" { fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { - return &dto.UserInfo{ - UID: "f4f39af7-5b64-4c2f-91bd-42b3af315a4e", - }, nil + return &dto.UserInfo{UID: UID}, nil + } + fakeRepo.CheckIfUserHasPermissionFn = func(ctx context.Context, UID string, requiredPermission profileutils.Permission) (bool, error) { + return true, nil } fakeRepo.GetUserProfileByUIDFn = func(ctx context.Context, uid string, suspended bool) (*profileutils.UserProfile, error) { - return &profileutils.UserProfile{ - ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", - VerifiedUIDS: []string{"f4f39af7-5b64-4c2f-91bd-42b3af315a4e"}, - UserBioData: profileutils.BioData{ - FirstName: &fName, - LastName: &lName, - Gender: enumutils.GenderMale, - }, - Permissions: profileutils.DefaultSuperAdminPermissions, - }, nil + return &profileutils.UserProfile{ID: id}, nil + } + fakeBaseExt.NormalizeMSISDNFn = func(msisdn string) (*string, error) { + phoneNumber := interserviceclient.TestUserPhoneNumber + return &phoneNumber, nil } - fakeRepo.CreateDetailedUserProfileFn = func(ctx context.Context, phoneNumber string, profile profileutils.UserProfile) (*profileutils.UserProfile, error) { - return &profileutils.UserProfile{ - ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", - VerifiedUIDS: []string{"f4f39af7-5b64-4c2f-91bd-42b3af315a4e"}, - UserBioData: profileutils.BioData{ - FirstName: &fName, - LastName: &lName, - Gender: enumutils.GenderMale, - DateOfBirth: &dob, - }, - Role: profileutils.RoleTypeEmployee, - }, nil + return &profileutils.UserProfile{ID: id, UserBioData: profileutils.BioData{FirstName: &fName, LastName: &lName}}, nil } - fakeRepo.CreateEmptyCustomerProfileFn = func(ctx context.Context, profileID string) (*profileutils.Customer, error) { - prID := "c9d62c7e-93e5-44a6-b503-6fc159c1782f" - return &profileutils.Customer{ - ID: "5e6e41f4-846b-4ba5-ae3f-a92cc7a997ba", - ProfileID: &prID, - }, nil + return nil, fmt.Errorf("error unable to create customer profile") } + } - fakeRepo.CreateDetailedSupplierProfileFn = func(ctx context.Context, profileID string, supplier profileutils.Supplier) (*profileutils.Supplier, error) { - return &profileutils.Supplier{}, nil + if tt.name == "sad: unable to create supplier profile" { + fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { + return &dto.UserInfo{UID: UID}, nil } - - fakeRepo.SetUserCommunicationsSettingsFn = func(ctx context.Context, profileID string, allowWhatsApp *bool, allowTextSms *bool, allowPush *bool, allowEmail *bool) (*profileutils.UserCommunicationsSetting, error) { - return &profileutils.UserCommunicationsSetting{ - ID: "4711a5e4-a211-4e2b-b40b-b1160049b984", - ProfileID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", - }, nil + fakeRepo.CheckIfUserHasPermissionFn = func(ctx context.Context, UID string, requiredPermission profileutils.Permission) (bool, error) { + return true, nil } - - fakePinExt.GenerateTempPINFn = func(ctx context.Context) (string, error) { - return "1234", nil + fakeRepo.GetUserProfileByUIDFn = func(ctx context.Context, uid string, suspended bool) (*profileutils.UserProfile, error) { + return &profileutils.UserProfile{ID: id}, nil } - - fakePinExt.EncryptPINFn = func(rawPwd string, options *extension.Options) (string, string) { - return "salt", "passw" + fakeBaseExt.NormalizeMSISDNFn = func(msisdn string) (*string, error) { + phoneNumber := interserviceclient.TestUserPhoneNumber + return &phoneNumber, nil } - - fakeRepo.SavePINFn = func(ctx context.Context, pin *domain.PIN) (bool, error) { - return true, nil + fakeRepo.CreateDetailedUserProfileFn = func(ctx context.Context, phoneNumber string, profile profileutils.UserProfile) (*profileutils.UserProfile, error) { + return &profileutils.UserProfile{ID: id, UserBioData: profileutils.BioData{FirstName: &fName, LastName: &lName}}, nil } - - fakeEngagementSvs.SendSMSFn = func(ctx context.Context, phoneNumbers []string, message string) error { - return nil + fakeRepo.CreateEmptyCustomerProfileFn = func(ctx context.Context, profileID string) (*profileutils.Customer, error) { + return &profileutils.Customer{}, nil } - - fakeEngagementSvs.SendMailFn = func(ctx context.Context, email string, message string, subject string) error { - return nil + fakeRepo.CreateDetailedSupplierProfileFn = func(ctx context.Context, profileID string, supplier profileutils.Supplier) (*profileutils.Supplier, error) { + return nil, fmt.Errorf("error unable to create supplier profile") } } - if tt.name == "invalid:cannot_notify_new_admin_sms" { - fakeBaseExt.NormalizeMSISDNFn = func(msisdn string) (*string, error) { - phone := "+254777886622" - return &phone, nil - } + if tt.name == "sad: unable to create admin profile" { fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { - return &dto.UserInfo{ - UID: "f4f39af7-5b64-4c2f-91bd-42b3af315a4e", - }, nil + return &dto.UserInfo{UID: UID}, nil + } + fakeRepo.CheckIfUserHasPermissionFn = func(ctx context.Context, UID string, requiredPermission profileutils.Permission) (bool, error) { + return true, nil } fakeRepo.GetUserProfileByUIDFn = func(ctx context.Context, uid string, suspended bool) (*profileutils.UserProfile, error) { - return &profileutils.UserProfile{ - ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", - VerifiedUIDS: []string{"f4f39af7-5b64-4c2f-91bd-42b3af315a4e"}, - UserBioData: profileutils.BioData{ - FirstName: &fName, - LastName: &lName, - Gender: enumutils.GenderMale, - }, - Permissions: profileutils.DefaultEmployeePermissions, - }, nil + return &profileutils.UserProfile{ID: id}, nil + } + fakeBaseExt.NormalizeMSISDNFn = func(msisdn string) (*string, error) { + phoneNumber := interserviceclient.TestUserPhoneNumber + return &phoneNumber, nil } - fakeRepo.CreateDetailedUserProfileFn = func(ctx context.Context, phoneNumber string, profile profileutils.UserProfile) (*profileutils.UserProfile, error) { - return &profileutils.UserProfile{ - ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", - VerifiedUIDS: []string{"f4f39af7-5b64-4c2f-91bd-42b3af315a4e"}, - UserBioData: profileutils.BioData{ - FirstName: &fName, - LastName: &lName, - Gender: enumutils.GenderMale, - DateOfBirth: &dob, - }, - Role: profileutils.RoleTypeEmployee, - }, nil + return &profileutils.UserProfile{ID: id, UserBioData: profileutils.BioData{FirstName: &fName, LastName: &lName}}, nil } - fakeRepo.CreateEmptyCustomerProfileFn = func(ctx context.Context, profileID string) (*profileutils.Customer, error) { - prID := "c9d62c7e-93e5-44a6-b503-6fc159c1782f" - return &profileutils.Customer{ - ID: "5e6e41f4-846b-4ba5-ae3f-a92cc7a997ba", - ProfileID: &prID, - }, nil + return &profileutils.Customer{}, nil } - fakeRepo.CreateDetailedSupplierProfileFn = func(ctx context.Context, profileID string, supplier profileutils.Supplier) (*profileutils.Supplier, error) { - prID := "c9d62c7e-93e5-44a6-b503-6fc159c1782f" - return &profileutils.Supplier{ - ID: "5e6e41f4-846b-4ba5-ae3f-a92cc7a997ba", - ProfileID: &prID, - }, nil - } - - fakeRepo.SetUserCommunicationsSettingsFn = func(ctx context.Context, profileID string, allowWhatsApp *bool, allowTextSms *bool, allowPush *bool, allowEmail *bool) (*profileutils.UserCommunicationsSetting, error) { - return &profileutils.UserCommunicationsSetting{ - ID: "4711a5e4-a211-4e2b-b40b-b1160049b984", - ProfileID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", - }, nil + return &profileutils.Supplier{}, nil } - - fakePinExt.GenerateTempPINFn = func(ctx context.Context) (string, error) { - return "1234", nil + fakeRepo.CreateAdminProfileFn = func(ctx context.Context, adminProfile domain.AdminProfile) error { + return fmt.Errorf("error unable to create admin profile") } + } - fakePinExt.EncryptPINFn = func(rawPwd string, options *extension.Options) (string, string) { - return "salt", "passw" + if tt.name == "sad: unable to create communication settings" { + fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { + return &dto.UserInfo{UID: UID}, nil } - - fakeRepo.SavePINFn = func(ctx context.Context, pin *domain.PIN) (bool, error) { + fakeRepo.CheckIfUserHasPermissionFn = func(ctx context.Context, UID string, requiredPermission profileutils.Permission) (bool, error) { return true, nil } - - fakeEngagementSvs.SendSMSFn = func(ctx context.Context, phoneNumbers []string, message string) error { - return fmt.Errorf("cannot send notification sms") + fakeRepo.GetUserProfileByUIDFn = func(ctx context.Context, uid string, suspended bool) (*profileutils.UserProfile, error) { + return &profileutils.UserProfile{ID: id}, nil } - } - - if tt.name == "invalid:cannot_set_admin_temporary_pin" { fakeBaseExt.NormalizeMSISDNFn = func(msisdn string) (*string, error) { - phone := "+254777886622" - return &phone, nil + phoneNumber := interserviceclient.TestUserPhoneNumber + return &phoneNumber, nil } - fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { - return &dto.UserInfo{ - UID: "f4f39af7-5b64-4c2f-91bd-42b3af315a4e", - }, nil - } - fakeRepo.GetUserProfileByUIDFn = func(ctx context.Context, uid string, suspended bool) (*profileutils.UserProfile, error) { - return &profileutils.UserProfile{ - ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", - VerifiedUIDS: []string{"f4f39af7-5b64-4c2f-91bd-42b3af315a4e"}, - UserBioData: profileutils.BioData{ - FirstName: &fName, - LastName: &lName, - Gender: enumutils.GenderMale, - }, - Role: profileutils.RoleTypeEmployee, - }, nil - } - fakeRepo.CreateDetailedUserProfileFn = func(ctx context.Context, phoneNumber string, profile profileutils.UserProfile) (*profileutils.UserProfile, error) { - return &profileutils.UserProfile{ - ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", - VerifiedUIDS: []string{"f4f39af7-5b64-4c2f-91bd-42b3af315a4e"}, - UserBioData: profileutils.BioData{ - FirstName: &fName, - LastName: &lName, - Gender: enumutils.GenderMale, - }, - Role: profileutils.RoleTypeEmployee, - }, nil + return &profileutils.UserProfile{ID: id, UserBioData: profileutils.BioData{FirstName: &fName, LastName: &lName}}, nil } - fakeRepo.CreateEmptyCustomerProfileFn = func(ctx context.Context, profileID string) (*profileutils.Customer, error) { - prID := "c9d62c7e-93e5-44a6-b503-6fc159c1782f" - return &profileutils.Customer{ - ID: "5e6e41f4-846b-4ba5-ae3f-a92cc7a997ba", - ProfileID: &prID, - }, nil + return &profileutils.Customer{}, nil } - fakeRepo.CreateDetailedSupplierProfileFn = func(ctx context.Context, profileID string, supplier profileutils.Supplier) (*profileutils.Supplier, error) { - prID := "c9d62c7e-93e5-44a6-b503-6fc159c1782f" - return &profileutils.Supplier{ - ID: "5e6e41f4-846b-4ba5-ae3f-a92cc7a997ba", - ProfileID: &prID, - }, nil + return &profileutils.Supplier{}, nil } - - fakeRepo.SetUserCommunicationsSettingsFn = func(ctx context.Context, profileID string, allowWhatsApp *bool, allowTextSms *bool, allowPush *bool, allowEmail *bool) (*profileutils.UserCommunicationsSetting, error) { - return &profileutils.UserCommunicationsSetting{ - ID: "4711a5e4-a211-4e2b-b40b-b1160049b984", - ProfileID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", - }, nil + fakeRepo.CreateAdminProfileFn = func(ctx context.Context, adminProfile domain.AdminProfile) error { + return nil } - - fakePinExt.GenerateTempPINFn = func(ctx context.Context) (string, error) { - return "1234", fmt.Errorf("cannot generate temporary PIN") + fakeRepo.SetUserCommunicationsSettingsFn = func(ctx context.Context, profileID string, allowWhatsApp, allowTextSms, allowPush, allowEmail *bool) (*profileutils.UserCommunicationsSetting, error) { + return nil, fmt.Errorf("error unable to create communication settings") } - } - if tt.name == "invalid:cannot_set_admin_temporary_pin" { - fakeBaseExt.NormalizeMSISDNFn = func(msisdn string) (*string, error) { - phone := "+254777886622" - return &phone, nil - } + if tt.name == "sad: unable to create temporary pin" { fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { - return &dto.UserInfo{ - UID: "f4f39af7-5b64-4c2f-91bd-42b3af315a4e", - }, nil + return &dto.UserInfo{UID: UID}, nil + } + fakeRepo.CheckIfUserHasPermissionFn = func(ctx context.Context, UID string, requiredPermission profileutils.Permission) (bool, error) { + return true, nil } fakeRepo.GetUserProfileByUIDFn = func(ctx context.Context, uid string, suspended bool) (*profileutils.UserProfile, error) { - return &profileutils.UserProfile{ - ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", - VerifiedUIDS: []string{"f4f39af7-5b64-4c2f-91bd-42b3af315a4e"}, - UserBioData: profileutils.BioData{ - FirstName: &fName, - LastName: &lName, - Gender: enumutils.GenderMale, - }, - Permissions: profileutils.DefaultEmployeePermissions, - }, nil + return &profileutils.UserProfile{ID: id}, nil + } + fakeBaseExt.NormalizeMSISDNFn = func(msisdn string) (*string, error) { + phoneNumber := interserviceclient.TestUserPhoneNumber + return &phoneNumber, nil } - fakeRepo.CreateDetailedUserProfileFn = func(ctx context.Context, phoneNumber string, profile profileutils.UserProfile) (*profileutils.UserProfile, error) { - return &profileutils.UserProfile{ - ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", - VerifiedUIDS: []string{"f4f39af7-5b64-4c2f-91bd-42b3af315a4e"}, - UserBioData: profileutils.BioData{ - FirstName: &fName, - LastName: &lName, - Gender: enumutils.GenderMale, - DateOfBirth: &dob, - }, - Role: profileutils.RoleTypeEmployee, - }, nil + return &profileutils.UserProfile{ID: id, UserBioData: profileutils.BioData{FirstName: &fName, LastName: &lName}}, nil } - fakeRepo.CreateEmptyCustomerProfileFn = func(ctx context.Context, profileID string) (*profileutils.Customer, error) { - prID := "c9d62c7e-93e5-44a6-b503-6fc159c1782f" - return &profileutils.Customer{ - ID: "5e6e41f4-846b-4ba5-ae3f-a92cc7a997ba", - ProfileID: &prID, - }, nil + return &profileutils.Customer{}, nil } - fakeRepo.CreateDetailedSupplierProfileFn = func(ctx context.Context, profileID string, supplier profileutils.Supplier) (*profileutils.Supplier, error) { - prID := "c9d62c7e-93e5-44a6-b503-6fc159c1782f" - return &profileutils.Supplier{ - ID: "5e6e41f4-846b-4ba5-ae3f-a92cc7a997ba", - ProfileID: &prID, - }, nil + return &profileutils.Supplier{}, nil } - - fakeRepo.SetUserCommunicationsSettingsFn = func(ctx context.Context, profileID string, allowWhatsApp *bool, allowTextSms *bool, allowPush *bool, allowEmail *bool) (*profileutils.UserCommunicationsSetting, error) { - return &profileutils.UserCommunicationsSetting{ - ID: "4711a5e4-a211-4e2b-b40b-b1160049b984", - ProfileID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", - }, nil + fakeRepo.CreateAdminProfileFn = func(ctx context.Context, adminProfile domain.AdminProfile) error { + return nil + } + fakeRepo.SetUserCommunicationsSettingsFn = func(ctx context.Context, profileID string, allowWhatsApp, allowTextSms, allowPush, allowEmail *bool) (*profileutils.UserCommunicationsSetting, error) { + return &profileutils.UserCommunicationsSetting{}, nil } fakePinExt.GenerateTempPINFn = func(ctx context.Context) (string, error) { - return "1234", fmt.Errorf("cannot generate temporary PIN") + return "nil", fmt.Errorf("error, unable to generate user pin") } - } - if tt.name == "invalid:cannot_notify_new_admin_email" { - fakeBaseExt.NormalizeMSISDNFn = func(msisdn string) (*string, error) { - phone := "+254777886622" - return &phone, nil - } - + if tt.name == "sad: unable to notify user" { fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { - return &dto.UserInfo{ - UID: "f4f39af7-5b64-4c2f-91bd-42b3af315a4e", - }, nil + return &dto.UserInfo{UID: UID}, nil + } + fakeRepo.CheckIfUserHasPermissionFn = func(ctx context.Context, UID string, requiredPermission profileutils.Permission) (bool, error) { + return true, nil } fakeRepo.GetUserProfileByUIDFn = func(ctx context.Context, uid string, suspended bool) (*profileutils.UserProfile, error) { - return &profileutils.UserProfile{ - ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", - VerifiedUIDS: []string{"f4f39af7-5b64-4c2f-91bd-42b3af315a4e"}, - UserBioData: profileutils.BioData{ - FirstName: &fName, - LastName: &lName, - Gender: enumutils.GenderMale, - }, - Permissions: profileutils.DefaultEmployeePermissions, - }, nil + return &profileutils.UserProfile{ID: id}, nil + } + fakeBaseExt.NormalizeMSISDNFn = func(msisdn string) (*string, error) { + phoneNumber := interserviceclient.TestUserPhoneNumber + return &phoneNumber, nil } fakeRepo.CreateDetailedUserProfileFn = func(ctx context.Context, phoneNumber string, profile profileutils.UserProfile) (*profileutils.UserProfile, error) { - return &profileutils.UserProfile{ - ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", - VerifiedUIDS: []string{"f4f39af7-5b64-4c2f-91bd-42b3af315a4e"}, - UserBioData: profileutils.BioData{ - FirstName: &fName, - LastName: &lName, - Gender: enumutils.GenderMale, - DateOfBirth: &dob, - }, - Role: profileutils.RoleTypeEmployee, - }, nil + return &profileutils.UserProfile{ID: id, UserBioData: profileutils.BioData{FirstName: &fName, LastName: &lName}}, nil } - fakeRepo.CreateEmptyCustomerProfileFn = func(ctx context.Context, profileID string) (*profileutils.Customer, error) { - prID := "c9d62c7e-93e5-44a6-b503-6fc159c1782f" - return &profileutils.Customer{ - ID: "5e6e41f4-846b-4ba5-ae3f-a92cc7a997ba", - ProfileID: &prID, - }, nil + return &profileutils.Customer{}, nil } - fakeRepo.CreateDetailedSupplierProfileFn = func(ctx context.Context, profileID string, supplier profileutils.Supplier) (*profileutils.Supplier, error) { - prID := "c9d62c7e-93e5-44a6-b503-6fc159c1782f" - return &profileutils.Supplier{ - ID: "5e6e41f4-846b-4ba5-ae3f-a92cc7a997ba", - ProfileID: &prID, - }, nil + return &profileutils.Supplier{}, nil } - - fakeRepo.SetUserCommunicationsSettingsFn = func(ctx context.Context, profileID string, allowWhatsApp *bool, allowTextSms *bool, allowPush *bool, allowEmail *bool) (*profileutils.UserCommunicationsSetting, error) { - return &profileutils.UserCommunicationsSetting{ - ID: "4711a5e4-a211-4e2b-b40b-b1160049b984", - ProfileID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", - }, nil + fakeRepo.CreateAdminProfileFn = func(ctx context.Context, adminProfile domain.AdminProfile) error { + return nil + } + fakeRepo.SetUserCommunicationsSettingsFn = func(ctx context.Context, profileID string, allowWhatsApp, allowTextSms, allowPush, allowEmail *bool) (*profileutils.UserCommunicationsSetting, error) { + return &profileutils.UserCommunicationsSetting{}, nil } - fakePinExt.GenerateTempPINFn = func(ctx context.Context) (string, error) { - return "1234", nil + return "123", nil } - fakePinExt.EncryptPINFn = func(rawPwd string, options *extension.Options) (string, string) { - return "salt", "passw" + return "pin", "sha" } - fakeRepo.SavePINFn = func(ctx context.Context, pin *domain.PIN) (bool, error) { return true, nil } - fakeEngagementSvs.SendSMSFn = func(ctx context.Context, phoneNumbers []string, message string) error { - return nil - } - - fakeEngagementSvs.SendMailFn = func(ctx context.Context, email string, message string, subject string) error { - return fmt.Errorf("cannot send notification email") + return fmt.Errorf("error unable to notify user") } } - if tt.name == "invalid:cannot_set_communication_settings" { - fakeBaseExt.NormalizeMSISDNFn = func(msisdn string) (*string, error) { - phone := "+254777886622" - return &phone, nil - } - + if tt.name == "happy: registered new admin" { fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { - return &dto.UserInfo{ - UID: "f4f39af7-5b64-4c2f-91bd-42b3af315a4e", - }, nil - } - fakeRepo.GetUserProfileByUIDFn = func(ctx context.Context, uid string, suspended bool) (*profileutils.UserProfile, error) { - return &profileutils.UserProfile{ - ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", - VerifiedUIDS: []string{"f4f39af7-5b64-4c2f-91bd-42b3af315a4e"}, - UserBioData: profileutils.BioData{ - FirstName: &fName, - LastName: &lName, - Gender: enumutils.GenderMale, - }, - Permissions: profileutils.DefaultEmployeePermissions, - }, nil + return &dto.UserInfo{UID: UID}, nil } - fakeRepo.CreateDetailedUserProfileFn = func(ctx context.Context, phoneNumber string, profile profileutils.UserProfile) (*profileutils.UserProfile, error) { - return &profileutils.UserProfile{ - ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", - VerifiedUIDS: []string{"f4f39af7-5b64-4c2f-91bd-42b3af315a4e"}, - UserBioData: profileutils.BioData{ - FirstName: &fName, - LastName: &lName, - Gender: enumutils.GenderMale, - DateOfBirth: &dob, - }, - Role: profileutils.RoleTypeEmployee, - }, nil - } - - fakeRepo.CreateEmptyCustomerProfileFn = func(ctx context.Context, profileID string) (*profileutils.Customer, error) { - prID := "c9d62c7e-93e5-44a6-b503-6fc159c1782f" - return &profileutils.Customer{ - ID: "5e6e41f4-846b-4ba5-ae3f-a92cc7a997ba", - ProfileID: &prID, - }, nil - } - - fakeRepo.CreateDetailedSupplierProfileFn = func(ctx context.Context, profileID string, supplier profileutils.Supplier) (*profileutils.Supplier, error) { - prID := "c9d62c7e-93e5-44a6-b503-6fc159c1782f" - return &profileutils.Supplier{ - ID: "5e6e41f4-846b-4ba5-ae3f-a92cc7a997ba", - ProfileID: &prID, - }, nil + fakeRepo.CheckIfUserHasPermissionFn = func(ctx context.Context, UID string, requiredPermission profileutils.Permission) (bool, error) { + return true, nil } - - fakeRepo.SetUserCommunicationsSettingsFn = func(ctx context.Context, profileID string, allowWhatsApp *bool, allowTextSms *bool, allowPush *bool, allowEmail *bool) (*profileutils.UserCommunicationsSetting, error) { - return nil, fmt.Errorf("") + fakeRepo.GetUserProfileByUIDFn = func(ctx context.Context, uid string, suspended bool) (*profileutils.UserProfile, error) { + return &profileutils.UserProfile{ID: id}, nil } - } - - if tt.name == "invalid:cannot_create_supplier_profile" { fakeBaseExt.NormalizeMSISDNFn = func(msisdn string) (*string, error) { - phone := "+254777886622" - return &phone, nil - } - - fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { - return &dto.UserInfo{ - UID: "f4f39af7-5b64-4c2f-91bd-42b3af315a4e", - }, nil - } - fakeRepo.GetUserProfileByUIDFn = func(ctx context.Context, uid string, suspended bool) (*profileutils.UserProfile, error) { - return &profileutils.UserProfile{ - ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", - VerifiedUIDS: []string{"f4f39af7-5b64-4c2f-91bd-42b3af315a4e"}, - UserBioData: profileutils.BioData{ - FirstName: &fName, - LastName: &lName, - Gender: enumutils.GenderMale, - }, - Permissions: profileutils.DefaultEmployeePermissions, - }, nil + phoneNumber := interserviceclient.TestUserPhoneNumber + return &phoneNumber, nil } fakeRepo.CreateDetailedUserProfileFn = func(ctx context.Context, phoneNumber string, profile profileutils.UserProfile) (*profileutils.UserProfile, error) { - return &profileutils.UserProfile{ - ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", - VerifiedUIDS: []string{"f4f39af7-5b64-4c2f-91bd-42b3af315a4e"}, - UserBioData: profileutils.BioData{ - FirstName: &fName, - LastName: &lName, - Gender: enumutils.GenderMale, - DateOfBirth: &dob, - }, - Role: profileutils.RoleTypeEmployee, - }, nil + return &profileutils.UserProfile{ID: id, UserBioData: profileutils.BioData{FirstName: &fName, LastName: &lName}}, nil } - fakeRepo.CreateEmptyCustomerProfileFn = func(ctx context.Context, profileID string) (*profileutils.Customer, error) { - prID := "c9d62c7e-93e5-44a6-b503-6fc159c1782f" - return &profileutils.Customer{ - ID: "5e6e41f4-846b-4ba5-ae3f-a92cc7a997ba", - ProfileID: &prID, - }, nil + return &profileutils.Customer{}, nil } - fakeRepo.CreateDetailedSupplierProfileFn = func(ctx context.Context, profileID string, supplier profileutils.Supplier) (*profileutils.Supplier, error) { - return nil, fmt.Errorf("cannot create supplier profile") - } - } - - if tt.name == "invalid:cannot_create_customer_profile" { - - fakeBaseExt.NormalizeMSISDNFn = func(msisdn string) (*string, error) { - phone := "+254777886622" - return &phone, nil - } - fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { - return &dto.UserInfo{ - UID: "f4f39af7-5b64-4c2f-91bd-42b3af315a4e", - }, nil - } - fakeRepo.GetUserProfileByUIDFn = func(ctx context.Context, uid string, suspended bool) (*profileutils.UserProfile, error) { - return &profileutils.UserProfile{ - ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", - VerifiedUIDS: []string{"f4f39af7-5b64-4c2f-91bd-42b3af315a4e"}, - UserBioData: profileutils.BioData{ - FirstName: &fName, - LastName: &lName, - Gender: enumutils.GenderMale, - }, - Permissions: profileutils.DefaultEmployeePermissions, - }, nil + return &profileutils.Supplier{}, nil } - fakeRepo.CreateDetailedUserProfileFn = func(ctx context.Context, phoneNumber string, profile profileutils.UserProfile) (*profileutils.UserProfile, error) { - return &profileutils.UserProfile{ - ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", - VerifiedUIDS: []string{"f4f39af7-5b64-4c2f-91bd-42b3af315a4e"}, - UserBioData: profileutils.BioData{ - FirstName: &fName, - LastName: &lName, - Gender: enumutils.GenderMale, - DateOfBirth: &dob, - }, - Role: profileutils.RoleTypeEmployee, - }, nil + fakeRepo.CreateAdminProfileFn = func(ctx context.Context, adminProfile domain.AdminProfile) error { + return nil } - - fakeRepo.CreateEmptyCustomerProfileFn = func(ctx context.Context, profileID string) (*profileutils.Customer, error) { - return nil, fmt.Errorf("cannot create customer profile") + fakeRepo.SetUserCommunicationsSettingsFn = func(ctx context.Context, profileID string, allowWhatsApp, allowTextSms, allowPush, allowEmail *bool) (*profileutils.UserCommunicationsSetting, error) { + return &profileutils.UserCommunicationsSetting{}, nil } - } - - if tt.name == "invalid:cannot_create_user_profile" { - fakeBaseExt.NormalizeMSISDNFn = func(msisdn string) (*string, error) { - phone := "+254777886622" - return &phone, nil + fakePinExt.GenerateTempPINFn = func(ctx context.Context) (string, error) { + return "123", nil } - - fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { - return &dto.UserInfo{ - UID: "f4f39af7-5b64-4c2f-91bd-42b3af315a4e", - }, nil + fakePinExt.EncryptPINFn = func(rawPwd string, options *extension.Options) (string, string) { + return "pin", "sha" } - fakeRepo.GetUserProfileByUIDFn = func(ctx context.Context, uid string, suspended bool) (*profileutils.UserProfile, error) { - return &profileutils.UserProfile{ - ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", - VerifiedUIDS: []string{"f4f39af7-5b64-4c2f-91bd-42b3af315a4e"}, - UserBioData: profileutils.BioData{ - FirstName: &fName, - LastName: &lName, - Gender: enumutils.GenderMale, - }, - Permissions: profileutils.DefaultEmployeePermissions, - }, nil + fakeRepo.SavePINFn = func(ctx context.Context, pin *domain.PIN) (bool, error) { + return true, nil } - fakeRepo.CreateDetailedUserProfileFn = func(ctx context.Context, phoneNumber string, profile profileutils.UserProfile) (*profileutils.UserProfile, error) { - return nil, fmt.Errorf("cannot create user profile") + fakeEngagementSvs.SendSMSFn = func(ctx context.Context, phoneNumbers []string, message string) error { + return nil } } @@ -821,11 +520,13 @@ func TestAdminUseCaseImpl_FetchAdmins(t *testing.T) { ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", PrimaryPhone: interserviceclient.TestUserPhoneNumber, ResendPIN: true, + Roles: []dto.RoleOutput{}, }, { ID: "f4f39af7-5b64-4c2f-91bd-42b3af315a4e", PrimaryPhone: interserviceclient.TestUserPhoneNumber, ResendPIN: true, + Roles: []dto.RoleOutput{}, }, }, wantErr: false, @@ -872,6 +573,11 @@ func TestAdminUseCaseImpl_FetchAdmins(t *testing.T) { fakeRepo.GetPINByProfileIDFn = func(ctx context.Context, ProfileID string) (*domain.PIN, error) { return &domain.PIN{IsOTP: true}, nil } + + fakeRepo.GetRolesByIDsFn = func(ctx context.Context, roleIDs []string) (*[]profileutils.Role, error) { + roles := []profileutils.Role{} + return &roles, nil + } } if tt.name == "success:_empty_list_of_user_admins" { fakeRepo.ListUserProfilesFn = func(ctx context.Context, role profileutils.RoleType) ([]*profileutils.UserProfile, error) { @@ -1180,3 +886,96 @@ func TestAdminUseCaseImpl_DeactivateAdmin(t *testing.T) { }) } } + +func TestAdminUseCaseImpl_FindAdminByNameOrPhone(t *testing.T) { + ctx := context.Background() + + i, err := InitializeFakeOnboardingInteractor() + if err != nil { + t.Errorf("failed to fake initialize onboarding interactor: %v", + err, + ) + return + } + + nameOrPhone := "Test" + fName := "Test" + lName := "User" + + type args struct { + ctx context.Context + nameOrPhone *string + } + tests := []struct { + name string + args args + want int + wantErr bool + }{ + { + name: "sad: unable get user profiles", + args: args{ctx: ctx, nameOrPhone: &nameOrPhone}, + want: 0, + wantErr: true, + }, + { + name: "sad: did not get any user", + args: args{ctx: ctx, nameOrPhone: &nameOrPhone}, + want: 0, + wantErr: false, + }, + { + name: "happy: got user profiles", + args: args{ctx: ctx, nameOrPhone: &nameOrPhone}, + want: 1, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.name == "sad: unable get user profiles" { + fakeRepo.ListUserProfilesFn = func(ctx context.Context, role profileutils.RoleType) ([]*profileutils.UserProfile, error) { + return nil, fmt.Errorf("error unable to get user profile by phone") + } + } + + if tt.name == "sad: did not get any user" { + fakeRepo.ListUserProfilesFn = func(ctx context.Context, role profileutils.RoleType) ([]*profileutils.UserProfile, error) { + return []*profileutils.UserProfile{}, nil + } + } + + if tt.name == "happy: got user profiles" { + fakeRepo.ListUserProfilesFn = func(ctx context.Context, role profileutils.RoleType) ([]*profileutils.UserProfile, error) { + phone := interserviceclient.TestUserPhoneNumber + profile1 := profileutils.UserProfile{ + UserBioData: profileutils.BioData{ + FirstName: &fName, + LastName: &lName, + }, + PrimaryPhone: &phone, + } + profile2 := profileutils.UserProfile{ + UserBioData: profileutils.BioData{ + FirstName: &lName, + LastName: &lName, + }, + PrimaryPhone: &phone, + } + return []*profileutils.UserProfile{ + &profile1, + &profile2, + }, nil + } + } + got, err := i.Admin.FindAdminByNameOrPhone(tt.args.ctx, tt.args.nameOrPhone) + if (err != nil) != tt.wantErr { + t.Errorf("AdminUseCaseImpl.FindAdminByNameOrPhone() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(len(got), tt.want) { + t.Errorf("AdminUseCaseImpl.FindAdminByNameOrPhone() = %v, want %v", len(got), tt.want) + } + }) + } +} diff --git a/pkg/onboarding/usecases/agent.go b/pkg/onboarding/usecases/agent.go index d206cb8b..37068b65 100644 --- a/pkg/onboarding/usecases/agent.go +++ b/pkg/onboarding/usecases/agent.go @@ -1,13 +1,12 @@ package usecases import ( - "bytes" "context" "fmt" - "html/template" "log" "time" + "github.com/google/uuid" "github.com/savannahghi/onboarding/pkg/onboarding/application/dto" "github.com/savannahghi/onboarding/pkg/onboarding/application/exceptions" "github.com/savannahghi/onboarding/pkg/onboarding/application/extension" @@ -20,8 +19,7 @@ import ( ) const ( - agentWelcomeMessage = "We look forward to working with you." - agentWelcomeEmailSubject = "Successfully registered as an agent" + agentWelcomeMessage = " We look forward to working with you." ) // AgentUseCase represent the business logic required for management of agents @@ -42,6 +40,7 @@ type AgentUseCaseImpl struct { engagement engagement.ServiceEngagement baseExt extension.BaseExtension pin UserPINUseCases + role RoleUseCase } // NewAgentUseCases returns a new a onboarding usecase @@ -50,6 +49,7 @@ func NewAgentUseCases( eng engagement.ServiceEngagement, ext extension.BaseExtension, pin UserPINUseCases, + role RoleUseCase, ) AgentUseCase { return &AgentUseCaseImpl{ @@ -57,6 +57,7 @@ func NewAgentUseCases( engagement: eng, baseExt: ext, pin: pin, + role: role, } } @@ -76,6 +77,10 @@ func (a *AgentUseCaseImpl) checkPreconditions() { if a.pin == nil { log.Panicf("nil pin usecase in agent usecase implementation") } + + if a.role == nil { + log.Panicf("nil roles usecase in agent usecase implementation") + } } // RegisterAgent creates a new Agent in bewell @@ -87,19 +92,25 @@ func (a *AgentUseCaseImpl) RegisterAgent( ctx, span := tracer.Start(ctx, "RegisterAgent") defer span.End() - msisdn, err := a.baseExt.NormalizeMSISDN(input.PhoneNumber) + // Check logged in user has permissions to register agent + p, err := a.baseExt.GetLoggedInUser(ctx) if err != nil { utils.RecordSpanError(span, err) - return nil, exceptions.NormalizeMSISDNError(err) + return nil, err } - // Check logged in user has permissions/role of employee - p, err := a.baseExt.GetLoggedInUser(ctx) + allowed, err := a.repo.CheckIfUserHasPermission(ctx, p.UID, profileutils.CanRegisterAgent) if err != nil { utils.RecordSpanError(span, err) return nil, err } + if !allowed { + err = fmt.Errorf("error, user do not have required permissions") + utils.RecordSpanError(span, err) + return nil, err + } + // Get Logged In user profile usp, err := a.repo.GetUserProfileByUID(ctx, p.UID, false) if err != nil { @@ -107,9 +118,15 @@ func (a *AgentUseCaseImpl) RegisterAgent( return nil, err } + phoneNumber, err := a.baseExt.NormalizeMSISDN(input.PhoneNumber) + if err != nil { + utils.RecordSpanError(span, err) + return nil, exceptions.NormalizeMSISDNError(err) + } + timestamp := time.Now().In(pubsubtools.TimeLocation) - agentProfile := profileutils.UserProfile{ + userProfile := profileutils.UserProfile{ PrimaryEmailAddress: &input.Email, UserBioData: profileutils.BioData{ FirstName: &input.FirstName, @@ -125,7 +142,7 @@ func (a *AgentUseCaseImpl) RegisterAgent( } // create a user profile in bewell - profile, err := a.repo.CreateDetailedUserProfile(ctx, *msisdn, agentProfile) + profile, err := a.repo.CreateDetailedUserProfile(ctx, *phoneNumber, userProfile) if err != nil { // wrapped error utils.RecordSpanError(span, err) @@ -138,7 +155,7 @@ func (a *AgentUseCaseImpl) RegisterAgent( return nil, exceptions.InternalServerError(err) } - sup := profileutils.Supplier{ + supplierProfile := profileutils.Supplier{ IsOrganizationVerified: true, SladeCode: SavannahSladeCode, KYCSubmitted: true, @@ -146,7 +163,19 @@ func (a *AgentUseCaseImpl) RegisterAgent( OrganizationName: SavannahOrgName, } - _, err = a.repo.CreateDetailedSupplierProfile(ctx, profile.ID, sup) + if _, err := a.repo.CreateDetailedSupplierProfile(ctx, profile.ID, supplierProfile); err != nil { + utils.RecordSpanError(span, err) + return nil, exceptions.InternalServerError(err) + } + + agentProfile := domain.AgentProfile{ + ID: uuid.New().String(), + ProfileID: profile.ID, + AgentType: domain.CompanyAgent, + } + + err = a.repo.CreateAgentProfile(ctx, agentProfile) + if err != nil { utils.RecordSpanError(span, err) return nil, exceptions.InternalServerError(err) @@ -175,49 +204,15 @@ func (a *AgentUseCaseImpl) RegisterAgent( return nil, err } - if err := a.notifyNewAgent(ctx, input.Email, input.PhoneNumber, *profile.UserBioData.FirstName, otp); err != nil { - utils.RecordSpanError(span, err) - return nil, fmt.Errorf("unable to send agent registration notifications: %w", err) - } - - return profile, nil -} - -func (a *AgentUseCaseImpl) notifyNewAgent( - ctx context.Context, - email, phoneNumber, firstName, tempPIN string, -) error { - type pin struct { - Name string - Pin string - } - - message := fmt.Sprintf(domain.WelcomeMessage, firstName, tempPIN) - message += " " + agentWelcomeMessage + //send this pin to user + message := fmt.Sprintf(domain.WelcomeMessage, input.FirstName, otp) + message += agentWelcomeMessage - if err := a.engagement.SendSMS(ctx, []string{phoneNumber}, message); err != nil { - return fmt.Errorf("unable to send agent registration message: %w", err) + if err := a.engagement.SendSMS(ctx, []string{*phoneNumber}, message); err != nil { + return nil, fmt.Errorf("unable to send agent registration message: %w", err) } - if email != "" { - t := template.Must(template.New("agentApprovalEmail").Parse(utils.AgentApprovalEmail)) - - buf := new(bytes.Buffer) - - err := t.Execute(buf, pin{firstName, tempPIN}) - if err != nil { - log.Fatalf("error while generating agent approval email template: %s", err) - } - - text := buf.String() - - if err := a.engagement.SendMail(ctx, email, text, agentWelcomeEmailSubject); err != nil { - return fmt.Errorf("unable to send agent registration email: %w", err) - } - - } - - return nil + return profile, nil } // ActivateAgent activates/unsuspend the agent profile @@ -259,15 +254,30 @@ func (a *AgentUseCaseImpl) DeactivateAgent( return false, exceptions.InternalServerError(err) } - if agent.Role != profileutils.RoleTypeAgent { - return false, exceptions.InternalServerError(fmt.Errorf("this user is not an agent")) + // role ID input introduces a breaking change to the api + // deactivation involes removing the assigned role + if len(input.RoleIDs) != 0 { + for _, roleID := range input.RoleIDs { + status, err := a.role.RevokeRole(ctx, agent.ID, roleID, input.Reason) + if err != nil { + utils.RecordSpanError(span, err) + return false, exceptions.InternalServerError(err) + } + + if !status { + err := fmt.Errorf("role not removed") + return false, exceptions.InternalServerError(err) + + } + } + } else { // initial implementation involved account suspension which affected the user both on consumer and pro + err = a.repo.UpdateSuspended(ctx, agent.ID, true) + if err != nil { + utils.RecordSpanError(span, err) + return false, err + } } - err = a.repo.UpdateSuspended(ctx, agent.ID, true) - if err != nil { - utils.RecordSpanError(span, err) - return false, err - } return true, nil } @@ -294,6 +304,33 @@ func (a *AgentUseCaseImpl) FetchAgents(ctx context.Context) ([]*dto.Agent, error return nil, err } + roles, err := a.repo.GetRolesByIDs(ctx, profile.Roles) + if err != nil { + utils.RecordSpanError(span, err) + // the error is wrapped already. No need to wrap it again + return nil, err + } + + // role output + ro := []dto.RoleOutput{} + for _, role := range *roles { + perms, err := role.Permissions(ctx) + if err != nil { + utils.RecordSpanError(span, err) + return nil, err + } + o := dto.RoleOutput{ + ID: role.ID, + Name: role.Name, + Description: role.Description, + Active: role.Active, + Scopes: role.Scopes, + Permissions: perms, + } + + ro = append(ro, o) + } + agent := &dto.Agent{ ID: profile.ID, PhotoUploadID: profile.PhotoUploadID, @@ -305,6 +342,7 @@ func (a *AgentUseCaseImpl) FetchAgents(ctx context.Context) ([]*dto.Agent, error TermsAccepted: profile.TermsAccepted, Suspended: profile.Suspended, ResendPIN: pin.IsOTP, + Roles: ro, } agents = append(agents, agent) diff --git a/pkg/onboarding/usecases/agent_unit_test.go b/pkg/onboarding/usecases/agent_unit_test.go index 7d0611ac..d3f3ced8 100644 --- a/pkg/onboarding/usecases/agent_unit_test.go +++ b/pkg/onboarding/usecases/agent_unit_test.go @@ -27,6 +27,8 @@ func TestAgentUseCaseImpl_RegisterAgent(t *testing.T) { } // agent 47 + UID := "c9d62c7e-93e5-44a6-b503-6fc159c1782f" + id := "c9d62c7e-93e5-44a6-b503-6fc159c1782f" fName := "Tobias" lName := "Rieper" dob := scalarutils.Date{ @@ -54,26 +56,16 @@ func TestAgentUseCaseImpl_RegisterAgent(t *testing.T) { wantErr bool }{ { - name: "valid:register_new_agent", + name: "sad: unable to get logged in user", args: args{ ctx: ctx, input: agent, }, - want: &profileutils.UserProfile{ - ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", - VerifiedUIDS: []string{"f4f39af7-5b64-4c2f-91bd-42b3af315a4e"}, - UserBioData: profileutils.BioData{ - FirstName: &fName, - LastName: &lName, - Gender: enumutils.GenderMale, - DateOfBirth: &dob, - }, - Role: profileutils.RoleTypeAgent, - }, - wantErr: false, + want: nil, + wantErr: true, }, { - name: "invalid:cannot_create_user_profile", + name: "sad: unable to check user permissions", args: args{ ctx: ctx, input: agent, @@ -82,7 +74,7 @@ func TestAgentUseCaseImpl_RegisterAgent(t *testing.T) { wantErr: true, }, { - name: "invalid:cannot_create_customer_profile", + name: "sad: user do not have required permissions", args: args{ ctx: ctx, input: agent, @@ -91,7 +83,7 @@ func TestAgentUseCaseImpl_RegisterAgent(t *testing.T) { wantErr: true, }, { - name: "invalid:cannot_create_supplier_profile", + name: "sad: unable to get user profile", args: args{ ctx: ctx, input: agent, @@ -100,7 +92,7 @@ func TestAgentUseCaseImpl_RegisterAgent(t *testing.T) { wantErr: true, }, { - name: "invalid:cannot_set_communication_settings", + name: "sad: unable to normalize phonenumber", args: args{ ctx: ctx, input: agent, @@ -109,7 +101,7 @@ func TestAgentUseCaseImpl_RegisterAgent(t *testing.T) { wantErr: true, }, { - name: "invalid:cannot_notify_new_agent_sms", + name: "sad: unable to create detailed user profile", args: args{ ctx: ctx, input: agent, @@ -118,7 +110,7 @@ func TestAgentUseCaseImpl_RegisterAgent(t *testing.T) { wantErr: true, }, { - name: "invalid:cannot_notify_new_agent_email", + name: "sad: unable to create customer profile", args: args{ ctx: ctx, input: agent, @@ -127,7 +119,7 @@ func TestAgentUseCaseImpl_RegisterAgent(t *testing.T) { wantErr: true, }, { - name: "invalid:get_logged_in_user_error", + name: "sad: unable to create supplier profile", args: args{ ctx: ctx, input: agent, @@ -136,7 +128,7 @@ func TestAgentUseCaseImpl_RegisterAgent(t *testing.T) { wantErr: true, }, { - name: "invalid:get_profile_by_uid_error", + name: "sad: unable to create agent profile", args: args{ ctx: ctx, input: agent, @@ -145,7 +137,7 @@ func TestAgentUseCaseImpl_RegisterAgent(t *testing.T) { wantErr: true, }, { - name: "invalid:invalid_logged_in_user_role", + name: "sad: unable to create communication settings", args: args{ ctx: ctx, input: agent, @@ -154,7 +146,7 @@ func TestAgentUseCaseImpl_RegisterAgent(t *testing.T) { wantErr: true, }, { - name: "invalid:normalizing_phonenumber_failed", + name: "sad: unable to create temporary pin", args: args{ ctx: ctx, input: agent, @@ -163,7 +155,7 @@ func TestAgentUseCaseImpl_RegisterAgent(t *testing.T) { wantErr: true, }, { - name: "invalid:cannot_set_agent_temporary_pin", + name: "sad: unable to notify user", args: args{ ctx: ctx, input: agent, @@ -171,611 +163,318 @@ func TestAgentUseCaseImpl_RegisterAgent(t *testing.T) { want: nil, wantErr: true, }, + { + name: "happy: registered new agent", + args: args{ + ctx: ctx, + input: agent, + }, + want: &profileutils.UserProfile{ + ID: id, + UserBioData: profileutils.BioData{ + FirstName: &fName, + LastName: &lName, + }, + }, + wantErr: false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - - if tt.name == "invalid:normalizing_phonenumber_failed" { - fakeBaseExt.NormalizeMSISDNFn = func(msisdn string) (*string, error) { - return nil, fmt.Errorf("cannot normalize the mobile number") + if tt.name == "sad: unable to get logged in user" { + fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { + return nil, fmt.Errorf("error unable to get logged in user profile") } } - if tt.name == "invalid:get_logged_in_user_error" { - fakeBaseExt.NormalizeMSISDNFn = func(msisdn string) (*string, error) { - phone := "+254777886622" - return &phone, nil - } - + if tt.name == "sad: unable to check user permissions" { fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { - return nil, fmt.Errorf("cannot get logged in user") + return &dto.UserInfo{UID: UID}, nil + } + fakeRepo.CheckIfUserHasPermissionFn = func(ctx context.Context, UID string, requiredPermission profileutils.Permission) (bool, error) { + return false, fmt.Errorf("error unable to check user permissions") } } - if tt.name == "invalid:get_profile_by_uid_error" { - fakeBaseExt.NormalizeMSISDNFn = func(msisdn string) (*string, error) { - phone := "+254777886622" - return &phone, nil + if tt.name == "sad: user do not have required permissions" { + fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { + return &dto.UserInfo{UID: UID}, nil + } + fakeRepo.CheckIfUserHasPermissionFn = func(ctx context.Context, UID string, requiredPermission profileutils.Permission) (bool, error) { + return false, nil } + } + if tt.name == "sad: unable to get user profile" { fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { - return &dto.UserInfo{ - UID: "f4f39af7-5b64-4c2f-91bd-42b3af315a4e", - }, nil + return &dto.UserInfo{UID: UID}, nil + } + fakeRepo.CheckIfUserHasPermissionFn = func(ctx context.Context, UID string, requiredPermission profileutils.Permission) (bool, error) { + return true, nil } fakeRepo.GetUserProfileByUIDFn = func(ctx context.Context, uid string, suspended bool) (*profileutils.UserProfile, error) { - return nil, fmt.Errorf("failed to get user bu UID") + return nil, fmt.Errorf("error unable to get user profile") } } - if tt.name == "invalid:invalid_logged_in_user_role" { + if tt.name == "sad: unable to normalize phonenumber" { + fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { + return &dto.UserInfo{UID: UID}, nil + } + fakeRepo.CheckIfUserHasPermissionFn = func(ctx context.Context, UID string, requiredPermission profileutils.Permission) (bool, error) { + return true, nil + } + fakeRepo.GetUserProfileByUIDFn = func(ctx context.Context, uid string, suspended bool) (*profileutils.UserProfile, error) { + return &profileutils.UserProfile{ID: id}, nil + } fakeBaseExt.NormalizeMSISDNFn = func(msisdn string) (*string, error) { - phone := "+254777886622" - return &phone, nil + return nil, fmt.Errorf("error unable to normalize phone number") } + } + if tt.name == "sad: unable to create detailed user profile" { fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { - return &dto.UserInfo{ - UID: "f4f39af7-5b64-4c2f-91bd-42b3af315a4e", - }, nil + return &dto.UserInfo{UID: UID}, nil + } + fakeRepo.CheckIfUserHasPermissionFn = func(ctx context.Context, UID string, requiredPermission profileutils.Permission) (bool, error) { + return true, nil } fakeRepo.GetUserProfileByUIDFn = func(ctx context.Context, uid string, suspended bool) (*profileutils.UserProfile, error) { - return &profileutils.UserProfile{ - ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", - VerifiedUIDS: []string{"f4f39af7-5b64-4c2f-91bd-42b3af315a4e"}, - UserBioData: profileutils.BioData{ - FirstName: &fName, - LastName: &lName, - Gender: enumutils.GenderMale, - DateOfBirth: &dob, - }, - Permissions: profileutils.DefaultAgentPermissions, - }, fmt.Errorf("user do not have required permissions") + return &profileutils.UserProfile{ID: id}, nil } - } - - if tt.name == "valid:register_new_agent" { fakeBaseExt.NormalizeMSISDNFn = func(msisdn string) (*string, error) { - phone := "+254777886622" - return &phone, nil + phoneNumber := interserviceclient.TestUserPhoneNumber + return &phoneNumber, nil + } + fakeRepo.CreateDetailedUserProfileFn = func(ctx context.Context, phoneNumber string, profile profileutils.UserProfile) (*profileutils.UserProfile, error) { + return nil, fmt.Errorf("error unable to create user profile") } + } + if tt.name == "sad: unable to create customer profile" { fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { - return &dto.UserInfo{ - UID: "f4f39af7-5b64-4c2f-91bd-42b3af315a4e", - }, nil + return &dto.UserInfo{UID: UID}, nil + } + fakeRepo.CheckIfUserHasPermissionFn = func(ctx context.Context, UID string, requiredPermission profileutils.Permission) (bool, error) { + return true, nil } fakeRepo.GetUserProfileByUIDFn = func(ctx context.Context, uid string, suspended bool) (*profileutils.UserProfile, error) { - return &profileutils.UserProfile{ - ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", - VerifiedUIDS: []string{"f4f39af7-5b64-4c2f-91bd-42b3af315a4e"}, - UserBioData: profileutils.BioData{ - FirstName: &fName, - LastName: &lName, - Gender: enumutils.GenderMale, - }, - Permissions: profileutils.DefaultEmployeePermissions, - }, nil + return &profileutils.UserProfile{ID: id}, nil + } + fakeBaseExt.NormalizeMSISDNFn = func(msisdn string) (*string, error) { + phoneNumber := interserviceclient.TestUserPhoneNumber + return &phoneNumber, nil } - fakeRepo.CreateDetailedUserProfileFn = func(ctx context.Context, phoneNumber string, profile profileutils.UserProfile) (*profileutils.UserProfile, error) { - return &profileutils.UserProfile{ - ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", - VerifiedUIDS: []string{"f4f39af7-5b64-4c2f-91bd-42b3af315a4e"}, - UserBioData: profileutils.BioData{ - FirstName: &fName, - LastName: &lName, - Gender: enumutils.GenderMale, - DateOfBirth: &dob, - }, - Role: profileutils.RoleTypeAgent, - }, nil + return &profileutils.UserProfile{ID: id, UserBioData: profileutils.BioData{FirstName: &fName, LastName: &lName}}, nil } - fakeRepo.CreateEmptyCustomerProfileFn = func(ctx context.Context, profileID string) (*profileutils.Customer, error) { - prID := "c9d62c7e-93e5-44a6-b503-6fc159c1782f" - return &profileutils.Customer{ - ID: "5e6e41f4-846b-4ba5-ae3f-a92cc7a997ba", - ProfileID: &prID, - }, nil + return nil, fmt.Errorf("error unable to create customer profile") } + } - fakeRepo.CreateDetailedSupplierProfileFn = func(ctx context.Context, profileID string, supplier profileutils.Supplier) (*profileutils.Supplier, error) { - return &profileutils.Supplier{}, nil + if tt.name == "sad: unable to create supplier profile" { + fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { + return &dto.UserInfo{UID: UID}, nil } - - fakeRepo.SetUserCommunicationsSettingsFn = func(ctx context.Context, profileID string, allowWhatsApp *bool, allowTextSms *bool, allowPush *bool, allowEmail *bool) (*profileutils.UserCommunicationsSetting, error) { - return &profileutils.UserCommunicationsSetting{ - ID: "4711a5e4-a211-4e2b-b40b-b1160049b984", - ProfileID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", - }, nil + fakeRepo.CheckIfUserHasPermissionFn = func(ctx context.Context, UID string, requiredPermission profileutils.Permission) (bool, error) { + return true, nil } - - fakePinExt.GenerateTempPINFn = func(ctx context.Context) (string, error) { - return "1234", nil + fakeRepo.GetUserProfileByUIDFn = func(ctx context.Context, uid string, suspended bool) (*profileutils.UserProfile, error) { + return &profileutils.UserProfile{ID: id}, nil } - - fakePinExt.EncryptPINFn = func(rawPwd string, options *extension.Options) (string, string) { - return "salt", "passw" + fakeBaseExt.NormalizeMSISDNFn = func(msisdn string) (*string, error) { + phoneNumber := interserviceclient.TestUserPhoneNumber + return &phoneNumber, nil } - - fakeRepo.SavePINFn = func(ctx context.Context, pin *domain.PIN) (bool, error) { - return true, nil + fakeRepo.CreateDetailedUserProfileFn = func(ctx context.Context, phoneNumber string, profile profileutils.UserProfile) (*profileutils.UserProfile, error) { + return &profileutils.UserProfile{ID: id, UserBioData: profileutils.BioData{FirstName: &fName, LastName: &lName}}, nil } - - fakeEngagementSvs.SendSMSFn = func(ctx context.Context, phoneNumbers []string, message string) error { - return nil + fakeRepo.CreateEmptyCustomerProfileFn = func(ctx context.Context, profileID string) (*profileutils.Customer, error) { + return &profileutils.Customer{}, nil } - - fakeEngagementSvs.SendMailFn = func(ctx context.Context, email string, message string, subject string) error { - return nil + fakeRepo.CreateDetailedSupplierProfileFn = func(ctx context.Context, profileID string, supplier profileutils.Supplier) (*profileutils.Supplier, error) { + return nil, fmt.Errorf("error unable to create supplier profile") } } - if tt.name == "invalid:cannot_notify_new_agent_sms" { - fakeBaseExt.NormalizeMSISDNFn = func(msisdn string) (*string, error) { - phone := "+254777886622" - return &phone, nil - } + if tt.name == "sad: unable to create agent profile" { fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { - return &dto.UserInfo{ - UID: "f4f39af7-5b64-4c2f-91bd-42b3af315a4e", - }, nil + return &dto.UserInfo{UID: UID}, nil + } + fakeRepo.CheckIfUserHasPermissionFn = func(ctx context.Context, UID string, requiredPermission profileutils.Permission) (bool, error) { + return true, nil } fakeRepo.GetUserProfileByUIDFn = func(ctx context.Context, uid string, suspended bool) (*profileutils.UserProfile, error) { - return &profileutils.UserProfile{ - ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", - VerifiedUIDS: []string{"f4f39af7-5b64-4c2f-91bd-42b3af315a4e"}, - UserBioData: profileutils.BioData{ - FirstName: &fName, - LastName: &lName, - Gender: enumutils.GenderMale, - }, - Permissions: profileutils.DefaultEmployeePermissions, - }, nil + return &profileutils.UserProfile{ID: id}, nil + } + fakeBaseExt.NormalizeMSISDNFn = func(msisdn string) (*string, error) { + phoneNumber := interserviceclient.TestUserPhoneNumber + return &phoneNumber, nil } - fakeRepo.CreateDetailedUserProfileFn = func(ctx context.Context, phoneNumber string, profile profileutils.UserProfile) (*profileutils.UserProfile, error) { - return &profileutils.UserProfile{ - ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", - VerifiedUIDS: []string{"f4f39af7-5b64-4c2f-91bd-42b3af315a4e"}, - UserBioData: profileutils.BioData{ - FirstName: &fName, - LastName: &lName, - Gender: enumutils.GenderMale, - DateOfBirth: &dob, - }, - Role: profileutils.RoleTypeAgent, - }, nil + return &profileutils.UserProfile{ID: id, UserBioData: profileutils.BioData{FirstName: &fName, LastName: &lName}}, nil } - fakeRepo.CreateEmptyCustomerProfileFn = func(ctx context.Context, profileID string) (*profileutils.Customer, error) { - prID := "c9d62c7e-93e5-44a6-b503-6fc159c1782f" - return &profileutils.Customer{ - ID: "5e6e41f4-846b-4ba5-ae3f-a92cc7a997ba", - ProfileID: &prID, - }, nil + return &profileutils.Customer{}, nil } - fakeRepo.CreateDetailedSupplierProfileFn = func(ctx context.Context, profileID string, supplier profileutils.Supplier) (*profileutils.Supplier, error) { - prID := "c9d62c7e-93e5-44a6-b503-6fc159c1782f" - return &profileutils.Supplier{ - ID: "5e6e41f4-846b-4ba5-ae3f-a92cc7a997ba", - ProfileID: &prID, - }, nil - } - - fakeRepo.SetUserCommunicationsSettingsFn = func(ctx context.Context, profileID string, allowWhatsApp *bool, allowTextSms *bool, allowPush *bool, allowEmail *bool) (*profileutils.UserCommunicationsSetting, error) { - return &profileutils.UserCommunicationsSetting{ - ID: "4711a5e4-a211-4e2b-b40b-b1160049b984", - ProfileID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", - }, nil + return &profileutils.Supplier{}, nil } - - fakePinExt.GenerateTempPINFn = func(ctx context.Context) (string, error) { - return "1234", nil + fakeRepo.CreateAgentProfileFn = func(ctx context.Context, agentProfile domain.AgentProfile) error { + return fmt.Errorf("error unable to create agent profile") } + } - fakePinExt.EncryptPINFn = func(rawPwd string, options *extension.Options) (string, string) { - return "salt", "passw" + if tt.name == "sad: unable to create communication settings" { + fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { + return &dto.UserInfo{UID: UID}, nil } - - fakeRepo.SavePINFn = func(ctx context.Context, pin *domain.PIN) (bool, error) { + fakeRepo.CheckIfUserHasPermissionFn = func(ctx context.Context, UID string, requiredPermission profileutils.Permission) (bool, error) { return true, nil } - - fakeEngagementSvs.SendSMSFn = func(ctx context.Context, phoneNumbers []string, message string) error { - return fmt.Errorf("cannot send notification sms") + fakeRepo.GetUserProfileByUIDFn = func(ctx context.Context, uid string, suspended bool) (*profileutils.UserProfile, error) { + return &profileutils.UserProfile{ID: id}, nil } - } - - if tt.name == "invalid:cannot_set_agent_temporary_pin" { fakeBaseExt.NormalizeMSISDNFn = func(msisdn string) (*string, error) { - phone := "+254777886622" - return &phone, nil + phoneNumber := interserviceclient.TestUserPhoneNumber + return &phoneNumber, nil } - fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { - return &dto.UserInfo{ - UID: "f4f39af7-5b64-4c2f-91bd-42b3af315a4e", - }, nil - } - fakeRepo.GetUserProfileByUIDFn = func(ctx context.Context, uid string, suspended bool) (*profileutils.UserProfile, error) { - return &profileutils.UserProfile{ - ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", - VerifiedUIDS: []string{"f4f39af7-5b64-4c2f-91bd-42b3af315a4e"}, - UserBioData: profileutils.BioData{ - FirstName: &fName, - LastName: &lName, - Gender: enumutils.GenderMale, - }, - Role: profileutils.RoleTypeEmployee, - }, nil - } - fakeRepo.CreateDetailedUserProfileFn = func(ctx context.Context, phoneNumber string, profile profileutils.UserProfile) (*profileutils.UserProfile, error) { - return &profileutils.UserProfile{ - ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", - VerifiedUIDS: []string{"f4f39af7-5b64-4c2f-91bd-42b3af315a4e"}, - UserBioData: profileutils.BioData{ - FirstName: &fName, - LastName: &lName, - Gender: enumutils.GenderMale, - }, - Role: profileutils.RoleTypeAgent, - }, nil + return &profileutils.UserProfile{ID: id, UserBioData: profileutils.BioData{FirstName: &fName, LastName: &lName}}, nil } - fakeRepo.CreateEmptyCustomerProfileFn = func(ctx context.Context, profileID string) (*profileutils.Customer, error) { - prID := "c9d62c7e-93e5-44a6-b503-6fc159c1782f" - return &profileutils.Customer{ - ID: "5e6e41f4-846b-4ba5-ae3f-a92cc7a997ba", - ProfileID: &prID, - }, nil + return &profileutils.Customer{}, nil } - fakeRepo.CreateDetailedSupplierProfileFn = func(ctx context.Context, profileID string, supplier profileutils.Supplier) (*profileutils.Supplier, error) { - prID := "c9d62c7e-93e5-44a6-b503-6fc159c1782f" - return &profileutils.Supplier{ - ID: "5e6e41f4-846b-4ba5-ae3f-a92cc7a997ba", - ProfileID: &prID, - }, nil + return &profileutils.Supplier{}, nil } - - fakeRepo.SetUserCommunicationsSettingsFn = func(ctx context.Context, profileID string, allowWhatsApp *bool, allowTextSms *bool, allowPush *bool, allowEmail *bool) (*profileutils.UserCommunicationsSetting, error) { - return &profileutils.UserCommunicationsSetting{ - ID: "4711a5e4-a211-4e2b-b40b-b1160049b984", - ProfileID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", - }, nil + fakeRepo.CreateAgentProfileFn = func(ctx context.Context, agentProfile domain.AgentProfile) error { + return nil } - - fakePinExt.GenerateTempPINFn = func(ctx context.Context) (string, error) { - return "1234", fmt.Errorf("cannot generate temporary PIN") + fakeRepo.SetUserCommunicationsSettingsFn = func(ctx context.Context, profileID string, allowWhatsApp, allowTextSms, allowPush, allowEmail *bool) (*profileutils.UserCommunicationsSetting, error) { + return nil, fmt.Errorf("error unable to create communication settings") } - } - if tt.name == "invalid:cannot_set_agent_temporary_pin" { - fakeBaseExt.NormalizeMSISDNFn = func(msisdn string) (*string, error) { - phone := "+254777886622" - return &phone, nil - } + if tt.name == "sad: unable to create temporary pin" { fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { - return &dto.UserInfo{ - UID: "f4f39af7-5b64-4c2f-91bd-42b3af315a4e", - }, nil + return &dto.UserInfo{UID: UID}, nil + } + fakeRepo.CheckIfUserHasPermissionFn = func(ctx context.Context, UID string, requiredPermission profileutils.Permission) (bool, error) { + return true, nil } fakeRepo.GetUserProfileByUIDFn = func(ctx context.Context, uid string, suspended bool) (*profileutils.UserProfile, error) { - return &profileutils.UserProfile{ - ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", - VerifiedUIDS: []string{"f4f39af7-5b64-4c2f-91bd-42b3af315a4e"}, - UserBioData: profileutils.BioData{ - FirstName: &fName, - LastName: &lName, - Gender: enumutils.GenderMale, - }, - Permissions: profileutils.DefaultEmployeePermissions, - }, nil + return &profileutils.UserProfile{ID: id}, nil + } + fakeBaseExt.NormalizeMSISDNFn = func(msisdn string) (*string, error) { + phoneNumber := interserviceclient.TestUserPhoneNumber + return &phoneNumber, nil } - fakeRepo.CreateDetailedUserProfileFn = func(ctx context.Context, phoneNumber string, profile profileutils.UserProfile) (*profileutils.UserProfile, error) { - return &profileutils.UserProfile{ - ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", - VerifiedUIDS: []string{"f4f39af7-5b64-4c2f-91bd-42b3af315a4e"}, - UserBioData: profileutils.BioData{ - FirstName: &fName, - LastName: &lName, - Gender: enumutils.GenderMale, - DateOfBirth: &dob, - }, - Role: profileutils.RoleTypeAgent, - }, nil + return &profileutils.UserProfile{ID: id, UserBioData: profileutils.BioData{FirstName: &fName, LastName: &lName}}, nil } - fakeRepo.CreateEmptyCustomerProfileFn = func(ctx context.Context, profileID string) (*profileutils.Customer, error) { - prID := "c9d62c7e-93e5-44a6-b503-6fc159c1782f" - return &profileutils.Customer{ - ID: "5e6e41f4-846b-4ba5-ae3f-a92cc7a997ba", - ProfileID: &prID, - }, nil + return &profileutils.Customer{}, nil } - fakeRepo.CreateDetailedSupplierProfileFn = func(ctx context.Context, profileID string, supplier profileutils.Supplier) (*profileutils.Supplier, error) { - prID := "c9d62c7e-93e5-44a6-b503-6fc159c1782f" - return &profileutils.Supplier{ - ID: "5e6e41f4-846b-4ba5-ae3f-a92cc7a997ba", - ProfileID: &prID, - }, nil + return &profileutils.Supplier{}, nil } - - fakeRepo.SetUserCommunicationsSettingsFn = func(ctx context.Context, profileID string, allowWhatsApp *bool, allowTextSms *bool, allowPush *bool, allowEmail *bool) (*profileutils.UserCommunicationsSetting, error) { - return &profileutils.UserCommunicationsSetting{ - ID: "4711a5e4-a211-4e2b-b40b-b1160049b984", - ProfileID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", - }, nil + fakeRepo.CreateAgentProfileFn = func(ctx context.Context, agentProfile domain.AgentProfile) error { + return nil + } + fakeRepo.SetUserCommunicationsSettingsFn = func(ctx context.Context, profileID string, allowWhatsApp, allowTextSms, allowPush, allowEmail *bool) (*profileutils.UserCommunicationsSetting, error) { + return &profileutils.UserCommunicationsSetting{}, nil } fakePinExt.GenerateTempPINFn = func(ctx context.Context) (string, error) { - return "1234", fmt.Errorf("cannot generate temporary PIN") + return "nil", fmt.Errorf("error, unable to generate user pin") } - } - if tt.name == "invalid:cannot_notify_new_agent_email" { - fakeBaseExt.NormalizeMSISDNFn = func(msisdn string) (*string, error) { - phone := "+254777886622" - return &phone, nil - } - + if tt.name == "sad: unable to notify user" { fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { - return &dto.UserInfo{ - UID: "f4f39af7-5b64-4c2f-91bd-42b3af315a4e", - }, nil + return &dto.UserInfo{UID: UID}, nil + } + fakeRepo.CheckIfUserHasPermissionFn = func(ctx context.Context, UID string, requiredPermission profileutils.Permission) (bool, error) { + return true, nil } fakeRepo.GetUserProfileByUIDFn = func(ctx context.Context, uid string, suspended bool) (*profileutils.UserProfile, error) { - return &profileutils.UserProfile{ - ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", - VerifiedUIDS: []string{"f4f39af7-5b64-4c2f-91bd-42b3af315a4e"}, - UserBioData: profileutils.BioData{ - FirstName: &fName, - LastName: &lName, - Gender: enumutils.GenderMale, - }, - Permissions: profileutils.DefaultEmployeePermissions, - }, nil + return &profileutils.UserProfile{ID: id}, nil + } + fakeBaseExt.NormalizeMSISDNFn = func(msisdn string) (*string, error) { + phoneNumber := interserviceclient.TestUserPhoneNumber + return &phoneNumber, nil } fakeRepo.CreateDetailedUserProfileFn = func(ctx context.Context, phoneNumber string, profile profileutils.UserProfile) (*profileutils.UserProfile, error) { - return &profileutils.UserProfile{ - ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", - VerifiedUIDS: []string{"f4f39af7-5b64-4c2f-91bd-42b3af315a4e"}, - UserBioData: profileutils.BioData{ - FirstName: &fName, - LastName: &lName, - Gender: enumutils.GenderMale, - DateOfBirth: &dob, - }, - Role: profileutils.RoleTypeAgent, - }, nil + return &profileutils.UserProfile{ID: id, UserBioData: profileutils.BioData{FirstName: &fName, LastName: &lName}}, nil } - fakeRepo.CreateEmptyCustomerProfileFn = func(ctx context.Context, profileID string) (*profileutils.Customer, error) { - prID := "c9d62c7e-93e5-44a6-b503-6fc159c1782f" - return &profileutils.Customer{ - ID: "5e6e41f4-846b-4ba5-ae3f-a92cc7a997ba", - ProfileID: &prID, - }, nil + return &profileutils.Customer{}, nil } - fakeRepo.CreateDetailedSupplierProfileFn = func(ctx context.Context, profileID string, supplier profileutils.Supplier) (*profileutils.Supplier, error) { - prID := "c9d62c7e-93e5-44a6-b503-6fc159c1782f" - return &profileutils.Supplier{ - ID: "5e6e41f4-846b-4ba5-ae3f-a92cc7a997ba", - ProfileID: &prID, - }, nil + return &profileutils.Supplier{}, nil } - - fakeRepo.SetUserCommunicationsSettingsFn = func(ctx context.Context, profileID string, allowWhatsApp *bool, allowTextSms *bool, allowPush *bool, allowEmail *bool) (*profileutils.UserCommunicationsSetting, error) { - return &profileutils.UserCommunicationsSetting{ - ID: "4711a5e4-a211-4e2b-b40b-b1160049b984", - ProfileID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", - }, nil + fakeRepo.CreateAgentProfileFn = func(ctx context.Context, agentProfile domain.AgentProfile) error { + return nil + } + fakeRepo.SetUserCommunicationsSettingsFn = func(ctx context.Context, profileID string, allowWhatsApp, allowTextSms, allowPush, allowEmail *bool) (*profileutils.UserCommunicationsSetting, error) { + return &profileutils.UserCommunicationsSetting{}, nil } - fakePinExt.GenerateTempPINFn = func(ctx context.Context) (string, error) { - return "1234", nil + return "123", nil } - fakePinExt.EncryptPINFn = func(rawPwd string, options *extension.Options) (string, string) { - return "salt", "passw" + return "pin", "sha" } - fakeRepo.SavePINFn = func(ctx context.Context, pin *domain.PIN) (bool, error) { return true, nil } - fakeEngagementSvs.SendSMSFn = func(ctx context.Context, phoneNumbers []string, message string) error { - return nil - } - - fakeEngagementSvs.SendMailFn = func(ctx context.Context, email string, message string, subject string) error { - return fmt.Errorf("cannot send notification email") + return fmt.Errorf("error unable to notify user") } } - if tt.name == "invalid:cannot_set_communication_settings" { - fakeBaseExt.NormalizeMSISDNFn = func(msisdn string) (*string, error) { - phone := "+254777886622" - return &phone, nil - } - + if tt.name == "happy: registered new agent" { fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { - return &dto.UserInfo{ - UID: "f4f39af7-5b64-4c2f-91bd-42b3af315a4e", - }, nil + return &dto.UserInfo{UID: UID}, nil } - fakeRepo.GetUserProfileByUIDFn = func(ctx context.Context, uid string, suspended bool) (*profileutils.UserProfile, error) { - return &profileutils.UserProfile{ - ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", - VerifiedUIDS: []string{"f4f39af7-5b64-4c2f-91bd-42b3af315a4e"}, - UserBioData: profileutils.BioData{ - FirstName: &fName, - LastName: &lName, - Gender: enumutils.GenderMale, - }, - Permissions: profileutils.DefaultEmployeePermissions, - }, nil - } - fakeRepo.CreateDetailedUserProfileFn = func(ctx context.Context, phoneNumber string, profile profileutils.UserProfile) (*profileutils.UserProfile, error) { - return &profileutils.UserProfile{ - ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", - VerifiedUIDS: []string{"f4f39af7-5b64-4c2f-91bd-42b3af315a4e"}, - UserBioData: profileutils.BioData{ - FirstName: &fName, - LastName: &lName, - Gender: enumutils.GenderMale, - DateOfBirth: &dob, - }, - Role: profileutils.RoleTypeAgent, - }, nil - } - - fakeRepo.CreateEmptyCustomerProfileFn = func(ctx context.Context, profileID string) (*profileutils.Customer, error) { - prID := "c9d62c7e-93e5-44a6-b503-6fc159c1782f" - return &profileutils.Customer{ - ID: "5e6e41f4-846b-4ba5-ae3f-a92cc7a997ba", - ProfileID: &prID, - }, nil - } - - fakeRepo.CreateDetailedSupplierProfileFn = func(ctx context.Context, profileID string, supplier profileutils.Supplier) (*profileutils.Supplier, error) { - prID := "c9d62c7e-93e5-44a6-b503-6fc159c1782f" - return &profileutils.Supplier{ - ID: "5e6e41f4-846b-4ba5-ae3f-a92cc7a997ba", - ProfileID: &prID, - }, nil + fakeRepo.CheckIfUserHasPermissionFn = func(ctx context.Context, UID string, requiredPermission profileutils.Permission) (bool, error) { + return true, nil } - - fakeRepo.SetUserCommunicationsSettingsFn = func(ctx context.Context, profileID string, allowWhatsApp *bool, allowTextSms *bool, allowPush *bool, allowEmail *bool) (*profileutils.UserCommunicationsSetting, error) { - return nil, fmt.Errorf("") + fakeRepo.GetUserProfileByUIDFn = func(ctx context.Context, uid string, suspended bool) (*profileutils.UserProfile, error) { + return &profileutils.UserProfile{ID: id}, nil } - } - - if tt.name == "invalid:cannot_create_supplier_profile" { fakeBaseExt.NormalizeMSISDNFn = func(msisdn string) (*string, error) { - phone := "+254777886622" - return &phone, nil - } - - fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { - return &dto.UserInfo{ - UID: "f4f39af7-5b64-4c2f-91bd-42b3af315a4e", - }, nil - } - fakeRepo.GetUserProfileByUIDFn = func(ctx context.Context, uid string, suspended bool) (*profileutils.UserProfile, error) { - return &profileutils.UserProfile{ - ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", - VerifiedUIDS: []string{"f4f39af7-5b64-4c2f-91bd-42b3af315a4e"}, - UserBioData: profileutils.BioData{ - FirstName: &fName, - LastName: &lName, - Gender: enumutils.GenderMale, - }, - Permissions: profileutils.DefaultEmployeePermissions, - }, nil + phoneNumber := interserviceclient.TestUserPhoneNumber + return &phoneNumber, nil } fakeRepo.CreateDetailedUserProfileFn = func(ctx context.Context, phoneNumber string, profile profileutils.UserProfile) (*profileutils.UserProfile, error) { - return &profileutils.UserProfile{ - ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", - VerifiedUIDS: []string{"f4f39af7-5b64-4c2f-91bd-42b3af315a4e"}, - UserBioData: profileutils.BioData{ - FirstName: &fName, - LastName: &lName, - Gender: enumutils.GenderMale, - DateOfBirth: &dob, - }, - Role: profileutils.RoleTypeAgent, - }, nil + return &profileutils.UserProfile{ID: id, UserBioData: profileutils.BioData{FirstName: &fName, LastName: &lName}}, nil } - fakeRepo.CreateEmptyCustomerProfileFn = func(ctx context.Context, profileID string) (*profileutils.Customer, error) { - prID := "c9d62c7e-93e5-44a6-b503-6fc159c1782f" - return &profileutils.Customer{ - ID: "5e6e41f4-846b-4ba5-ae3f-a92cc7a997ba", - ProfileID: &prID, - }, nil + return &profileutils.Customer{}, nil } - fakeRepo.CreateDetailedSupplierProfileFn = func(ctx context.Context, profileID string, supplier profileutils.Supplier) (*profileutils.Supplier, error) { - return nil, fmt.Errorf("cannot create supplier profile") - } - } - - if tt.name == "invalid:cannot_create_customer_profile" { - - fakeBaseExt.NormalizeMSISDNFn = func(msisdn string) (*string, error) { - phone := "+254777886622" - return &phone, nil - } - fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { - return &dto.UserInfo{ - UID: "f4f39af7-5b64-4c2f-91bd-42b3af315a4e", - }, nil - } - fakeRepo.GetUserProfileByUIDFn = func(ctx context.Context, uid string, suspended bool) (*profileutils.UserProfile, error) { - return &profileutils.UserProfile{ - ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", - VerifiedUIDS: []string{"f4f39af7-5b64-4c2f-91bd-42b3af315a4e"}, - UserBioData: profileutils.BioData{ - FirstName: &fName, - LastName: &lName, - Gender: enumutils.GenderMale, - }, - Permissions: profileutils.DefaultEmployeePermissions, - }, nil + return &profileutils.Supplier{}, nil } - fakeRepo.CreateDetailedUserProfileFn = func(ctx context.Context, phoneNumber string, profile profileutils.UserProfile) (*profileutils.UserProfile, error) { - return &profileutils.UserProfile{ - ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", - VerifiedUIDS: []string{"f4f39af7-5b64-4c2f-91bd-42b3af315a4e"}, - UserBioData: profileutils.BioData{ - FirstName: &fName, - LastName: &lName, - Gender: enumutils.GenderMale, - DateOfBirth: &dob, - }, - Role: profileutils.RoleTypeAgent, - }, nil + fakeRepo.CreateAgentProfileFn = func(ctx context.Context, agentProfile domain.AgentProfile) error { + return nil } - - fakeRepo.CreateEmptyCustomerProfileFn = func(ctx context.Context, profileID string) (*profileutils.Customer, error) { - return nil, fmt.Errorf("cannot create customer profile") + fakeRepo.SetUserCommunicationsSettingsFn = func(ctx context.Context, profileID string, allowWhatsApp, allowTextSms, allowPush, allowEmail *bool) (*profileutils.UserCommunicationsSetting, error) { + return &profileutils.UserCommunicationsSetting{}, nil } - } - - if tt.name == "invalid:cannot_create_user_profile" { - fakeBaseExt.NormalizeMSISDNFn = func(msisdn string) (*string, error) { - phone := "+254777886622" - return &phone, nil + fakePinExt.GenerateTempPINFn = func(ctx context.Context) (string, error) { + return "123", nil } - - fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { - return &dto.UserInfo{ - UID: "f4f39af7-5b64-4c2f-91bd-42b3af315a4e", - }, nil + fakePinExt.EncryptPINFn = func(rawPwd string, options *extension.Options) (string, string) { + return "pin", "sha" } - fakeRepo.GetUserProfileByUIDFn = func(ctx context.Context, uid string, suspended bool) (*profileutils.UserProfile, error) { - return &profileutils.UserProfile{ - ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", - VerifiedUIDS: []string{"f4f39af7-5b64-4c2f-91bd-42b3af315a4e"}, - UserBioData: profileutils.BioData{ - FirstName: &fName, - LastName: &lName, - Gender: enumutils.GenderMale, - }, - Permissions: profileutils.DefaultEmployeePermissions, - }, nil + fakeRepo.SavePINFn = func(ctx context.Context, pin *domain.PIN) (bool, error) { + return true, nil } - fakeRepo.CreateDetailedUserProfileFn = func(ctx context.Context, phoneNumber string, profile profileutils.UserProfile) (*profileutils.UserProfile, error) { - return nil, fmt.Errorf("cannot create user profile") + fakeEngagementSvs.SendSMSFn = func(ctx context.Context, phoneNumbers []string, message string) error { + return nil } } @@ -976,14 +675,6 @@ func TestAgentUseCaseImpl_DeactivateAgent(t *testing.T) { input dto.ProfileSuspensionInput } - inputData := args{ - ctx: ctx, - input: dto.ProfileSuspensionInput{ - ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", - Reason: "", - }, - } - tests := []struct { name string args args @@ -991,32 +682,79 @@ func TestAgentUseCaseImpl_DeactivateAgent(t *testing.T) { wantErr bool }{ { - name: "invalid:failed_to_get_loggedin_user", - args: inputData, + name: "invalid:failed_to_get_loggedin_user", + args: args{ + ctx: ctx, + input: dto.ProfileSuspensionInput{ + ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", + RoleIDs: []string{"17e6ea18-7147-4bdb-ad0b-d9ce03a8c0ac"}, + Reason: "test reason", + }, + }, want: false, wantErr: true, }, { - name: "invalid:loggedin_user_does_not_have_employee_role", - args: inputData, + name: "invalid:loggedin_user_does_not_have_employee_role", + args: args{ + ctx: ctx, + input: dto.ProfileSuspensionInput{ + ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", + RoleIDs: []string{"17e6ea18-7147-4bdb-ad0b-d9ce03a8c0ac"}, + Reason: "test reason", + }, + }, want: false, wantErr: true, }, { - name: "invalid:error_getting_agent_profile", - args: inputData, + name: "invalid:error_getting_agent_profile", + args: args{ + ctx: ctx, + input: dto.ProfileSuspensionInput{ + ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", + RoleIDs: []string{"17e6ea18-7147-4bdb-ad0b-d9ce03a8c0ac"}, + Reason: "test reason", + }, + }, want: false, wantErr: true, }, { - name: "invalid:failed_to_activate_account", - args: inputData, + name: "invalid:failed_to_activate_account", + args: args{ + ctx: ctx, + input: dto.ProfileSuspensionInput{ + ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", + RoleIDs: []string{"17e6ea18-7147-4bdb-ad0b-d9ce03a8c0ac"}, + Reason: "test reason", + }, + }, want: false, wantErr: true, }, { - name: "valid:success_deactivated_agent", - args: inputData, + name: "valid:success_deactivated_agent", + args: args{ + ctx: ctx, + input: dto.ProfileSuspensionInput{ + ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", + RoleIDs: []string{"17e6ea18-7147-4bdb-ad0b-d9ce03a8c0ac"}, + Reason: "test reason", + }, + }, + want: true, + wantErr: false, + }, + { + name: "valid:success_deactivated_agent_old_implementation", + args: args{ + ctx: ctx, + input: dto.ProfileSuspensionInput{ + ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", + Reason: "test reason", + }, + }, want: true, wantErr: false, }, @@ -1047,6 +785,10 @@ func TestAgentUseCaseImpl_DeactivateAgent(t *testing.T) { return &phone, nil } + fakeRepo.CheckIfUserHasPermissionFn = func(ctx context.Context, UID string, requiredPermission profileutils.Permission) (bool, error) { + return false, nil + } + fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { return &dto.UserInfo{ UID: "f4f39af7-5b64-4c2f-91bd-42b3af315a4e", @@ -1101,12 +843,15 @@ func TestAgentUseCaseImpl_DeactivateAgent(t *testing.T) { Role: profileutils.RoleTypeEmployee, }, nil } - fakeRepo.UpdateSuspendedFn = func(ctx context.Context, id string, status bool) error { - return fmt.Errorf("failed to unsuspend/activate agent account") + fakeRepo.GetUserProfileByUIDFn = func(ctx context.Context, uid string, suspended bool) (*profileutils.UserProfile, error) { + return nil, fmt.Errorf("cannot remove role from user") } } if tt.name == "valid:success_deactivated_agent" { + fakeRepo.CheckIfUserHasPermissionFn = func(ctx context.Context, UID string, requiredPermission profileutils.Permission) (bool, error) { + return true, nil + } fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { return &dto.UserInfo{ UID: "f4f39af7-5b64-4c2f-91bd-42b3af315a4e", @@ -1123,10 +868,85 @@ func TestAgentUseCaseImpl_DeactivateAgent(t *testing.T) { return &profileutils.UserProfile{ ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", VerifiedUIDS: []string{"f4f39af7-5b64-4c2f-91bd-42b3af315a4e"}, - Role: profileutils.RoleTypeAgent, + Roles: []string{"17e6ea18-7147-4bdb-ad0b-d9ce03a8c0ac"}, + }, nil + } + + fakeRepo.GetRoleByIDFn = func(ctx context.Context, roleID string) (*profileutils.Role, error) { + return &profileutils.Role{ + ID: "17e6ea18-7147-4bdb-ad0b-d9ce03a8c0ac", + Scopes: []string{profileutils.CanAssignRole.Scope}, + }, nil + } + + fakeRepo.GetUserProfileByUIDFn = func(ctx context.Context, uid string, suspended bool) (*profileutils.UserProfile, error) { + return &profileutils.UserProfile{ + ID: "", + Roles: []string{ + "17e6ea18-7147-4bdb-ad0b-d9ce03a8c0ac", + "56e5e987-2f02-4455-9dde-ae15162d8bce", + }, + }, nil + } + + fakeRepo.UpdateUserRoleIDsFn = func(ctx context.Context, id string, roleIDs []string) error { + return nil + } + + fakeRepo.SaveRoleRevocationFn = func(ctx context.Context, userID string, revocation dto.RoleRevocationInput) error { + return nil + } + } + + if tt.name == "valid:success_deactivated_agent_old_implementation" { + fakeRepo.CheckIfUserHasPermissionFn = func(ctx context.Context, UID string, requiredPermission profileutils.Permission) (bool, error) { + return true, nil + } + fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { + return &dto.UserInfo{ + UID: "f4f39af7-5b64-4c2f-91bd-42b3af315a4e", + }, nil + } + fakeRepo.GetUserProfileByUIDFn = func(ctx context.Context, uid string, suspended bool) (*profileutils.UserProfile, error) { + return &profileutils.UserProfile{ + ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", + VerifiedUIDS: []string{"f4f39af7-5b64-4c2f-91bd-42b3af315a4e"}, + Permissions: profileutils.DefaultEmployeePermissions, + }, nil + } + fakeRepo.GetUserProfileByIDFn = func(ctx context.Context, id string, suspended bool) (*profileutils.UserProfile, error) { + return &profileutils.UserProfile{ + ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", + VerifiedUIDS: []string{"f4f39af7-5b64-4c2f-91bd-42b3af315a4e"}, + Roles: []string{"17e6ea18-7147-4bdb-ad0b-d9ce03a8c0ac"}, }, nil } + fakeRepo.GetRoleByIDFn = func(ctx context.Context, roleID string) (*profileutils.Role, error) { + return &profileutils.Role{ + ID: "17e6ea18-7147-4bdb-ad0b-d9ce03a8c0ac", + Scopes: []string{profileutils.CanAssignRole.Scope}, + }, nil + } + + fakeRepo.GetUserProfileByUIDFn = func(ctx context.Context, uid string, suspended bool) (*profileutils.UserProfile, error) { + return &profileutils.UserProfile{ + ID: "", + Roles: []string{ + "17e6ea18-7147-4bdb-ad0b-d9ce03a8c0ac", + "56e5e987-2f02-4455-9dde-ae15162d8bce", + }, + }, nil + } + + fakeRepo.UpdateUserRoleIDsFn = func(ctx context.Context, id string, roleIDs []string) error { + return nil + } + + fakeRepo.SaveRoleRevocationFn = func(ctx context.Context, userID string, revocation dto.RoleRevocationInput) error { + return nil + } + fakeRepo.UpdateSuspendedFn = func(ctx context.Context, id string, status bool) error { return nil } @@ -1181,12 +1001,14 @@ func TestAgentUseCaseImpl_FetchAgents(t *testing.T) { PrimaryPhone: interserviceclient.TestUserPhoneNumber, PrimaryEmailAddress: &email, ResendPIN: true, + Roles: []dto.RoleOutput{}, }, { ID: "f4f39af7-5b64-4c2f-91bd-42b3af315a4e", PrimaryPhone: interserviceclient.TestUserPhoneNumber, PrimaryEmailAddress: &email, ResendPIN: true, + Roles: []dto.RoleOutput{}, }, }, wantErr: false, @@ -1220,14 +1042,12 @@ func TestAgentUseCaseImpl_FetchAgents(t *testing.T) { PrimaryPhone: &p, PrimaryEmailAddress: &e, VerifiedUIDS: []string{"f4f39af7-5b64-4c2f-91bd-42b3af315a4e"}, - Role: profileutils.RoleTypeAgent, }, { ID: "f4f39af7-5b64-4c2f-91bd-42b3af315a4e", PrimaryPhone: &p, PrimaryEmailAddress: &e, VerifiedUIDS: []string{"c9d62c7e-93e5-44a6-b503-6fc159c1782f"}, - Role: profileutils.RoleTypeAgent, }, } return s, nil @@ -1236,7 +1056,13 @@ func TestAgentUseCaseImpl_FetchAgents(t *testing.T) { fakeRepo.GetPINByProfileIDFn = func(ctx context.Context, ProfileID string) (*domain.PIN, error) { return &domain.PIN{IsOTP: true}, nil } + + fakeRepo.GetRolesByIDsFn = func(ctx context.Context, roleIDs []string) (*[]profileutils.Role, error) { + roles := []profileutils.Role{} + return &roles, nil + } } + if tt.name == "success:_empty_list_of_user_agents" { fakeRepo.ListUserProfilesFn = func(ctx context.Context, role profileutils.RoleType) ([]*profileutils.UserProfile, error) { return []*profileutils.UserProfile{}, nil diff --git a/pkg/onboarding/usecases/login.go b/pkg/onboarding/usecases/login.go index 8f8c29ad..4102204d 100644 --- a/pkg/onboarding/usecases/login.go +++ b/pkg/onboarding/usecases/login.go @@ -3,6 +3,7 @@ package usecases import ( "context" "fmt" + "strings" "github.com/savannahghi/feedlib" "github.com/savannahghi/onboarding/pkg/onboarding/application/exceptions" @@ -119,7 +120,11 @@ func (l *LoginUseCasesImpl) LoginByPhone( // get navigation actions roles, err := l.onboardingRepository.GetRolesByIDs(ctx, profile.Roles) if err != nil { - return nil, err + if strings.Contains(err.Error(), "role not found") { + roles = nil + } else { + return nil, err + } } navActions, err := utils.GetUserNavigationActions(ctx, *profile, *roles) diff --git a/pkg/onboarding/usecases/login_test.go b/pkg/onboarding/usecases/login_test.go index e45e9c05..4866aad1 100644 --- a/pkg/onboarding/usecases/login_test.go +++ b/pkg/onboarding/usecases/login_test.go @@ -105,6 +105,7 @@ func TestMain(m *testing.M) { r.GetProfileNudgesCollectionName(), r.GetSMSCollectionName(), r.GetUSSDDataCollectionName(), + r.GetRolesCollectionName(), } for _, collection := range collections { ref := fsc.Collection(collection) @@ -491,10 +492,10 @@ func InitializeFakeOnboardingInteractor() (*interactor.Interactor, error) { sms := usecases.NewSMSUsecase(r, ext) role := usecases.NewRoleUseCases(r, ext) admin := usecases.NewAdminUseCases(r, engagementSvc, ext, userpin) - agent := usecases.NewAgentUseCases(r, engagementSvc, ext, userpin) + agent := usecases.NewAgentUseCases(r, engagementSvc, ext, userpin, role) i, err := interactor.NewOnboardingInteractor( - r, profile, su, supplier, login, + profile, su, supplier, login, survey, userpin, erpSvc, chargemasterSvc, engagementSvc, messagingSvc, nhif, ps, sms, aitUssd, agent, admin, ediSvc, adminSrv, crmExt, diff --git a/pkg/onboarding/usecases/profile.go b/pkg/onboarding/usecases/profile.go index 48beb65b..1639e2a2 100644 --- a/pkg/onboarding/usecases/profile.go +++ b/pkg/onboarding/usecases/profile.go @@ -272,6 +272,8 @@ func (p *ProfileUseCaseImpl) UpdatePrimaryPhoneNumber( ctx, span := tracer.Start(ctx, "UpdatePrimaryPhoneNumber") defer span.End() + logrus.Println(phone) + var profile *profileutils.UserProfile phoneNumber, err := p.baseExt.NormalizeMSISDN(phone) @@ -996,6 +998,8 @@ func (p *ProfileUseCaseImpl) GetUserProfileByPhoneOrEmail(ctx context.Context, p ctx, span := tracer.Start(ctx, "GetUserProfileByPhoneOrEmail") defer span.End() + logrus.Println("getting user profile") + return p.onboardingRepository.GetUserProfileByPhoneOrEmail(ctx, payload) } diff --git a/pkg/onboarding/usecases/roles.go b/pkg/onboarding/usecases/roles.go index 4dca3256..46d4076d 100644 --- a/pkg/onboarding/usecases/roles.go +++ b/pkg/onboarding/usecases/roles.go @@ -25,6 +25,8 @@ type RoleUseCase interface { GetAllPermissions(ctx context.Context) ([]*profileutils.Permission, error) + GetRoleByName(ctx context.Context, name string) (*dto.RoleOutput, error) + // AddPermissionsToRole adds new scopes to a role AddPermissionsToRole( ctx context.Context, @@ -38,13 +40,19 @@ type RoleUseCase interface { ) (*dto.RoleOutput, error) // UpdateRolePermissions replaces the scopes in a role with new updated scopes - UpdateRolePermissions(ctx context.Context, input dto.RolePermissionInput) (*dto.RoleOutput, error) + UpdateRolePermissions( + ctx context.Context, + input dto.RolePermissionInput, + ) (*dto.RoleOutput, error) // AssignRole assigns a role to a user AssignRole(ctx context.Context, userID string, roleID string) (bool, error) + // AssignMultipleRoles assigns multiple roles to a user + AssignMultipleRoles(ctx context.Context, userID string, roleIDs []string) (bool, error) + // RevokeRole removes a role from a user - RevokeRole(ctx context.Context, userID string, roleID string) (bool, error) + RevokeRole(ctx context.Context, userID, roleID, reason string) (bool, error) // ActivateRole marks a role as active ActivateRole(ctx context.Context, roleID string) (*dto.RoleOutput, error) @@ -54,7 +62,22 @@ type RoleUseCase interface { // Check permission checks whether a logged in user with the given UID // is authorized to perform the action specified in the permission - CheckPermission(ctx context.Context, uid string, permission profileutils.Permission) (bool, error) + CheckPermission( + ctx context.Context, + uid string, + permission profileutils.Permission, + ) (bool, error) + + // CreateUnauthorizedRole creates a role without performing user authorization + // This usecase is useful for creating the initial role in a new environment + // and creating the test role used for running integration and acceptance tests + CreateUnauthorizedRole(ctx context.Context, input dto.RoleInput) (*dto.RoleOutput, error) + + // UnauthorizedDeleteRole creates a role without performing user authorization + // This usecase is useful for cleaning up and removing the test role(s) used for running integration and acceptance tests + UnauthorizedDeleteRole(ctx context.Context, roleID string) (bool, error) + + GetRolesByIDs(ctx context.Context, roleIDs []string) ([]*dto.RoleOutput, error) } // RoleUseCaseImpl represents usecase implementation object @@ -130,6 +153,54 @@ func (r *RoleUseCaseImpl) CreateRole( return output, nil } +// CreateUnauthorizedRole creates a role without performing user authorization +// +// This usecase is useful for creating the initial role in a new environment +// and creating the test role used for running integration and acceptance tests +// It doesn't check for the users permission +func (r *RoleUseCaseImpl) CreateUnauthorizedRole( + ctx context.Context, + input dto.RoleInput, +) (*dto.RoleOutput, error) { + ctx, span := tracer.Start(ctx, "CreateUnauthorizedRole") + defer span.End() + + user, err := r.baseExt.GetLoggedInUser(ctx) + if err != nil { + utils.RecordSpanError(span, err) + return nil, err + } + + userProfile, err := r.repo.GetUserProfileByUID(ctx, user.UID, false) + if err != nil { + utils.RecordSpanError(span, err) + return nil, err + } + + role, err := r.repo.CreateRole(ctx, userProfile.ID, input) + if err != nil { + utils.RecordSpanError(span, err) + return nil, err + } + + perms, err := role.Permissions(ctx) + if err != nil { + utils.RecordSpanError(span, err) + return nil, err + } + + output := &dto.RoleOutput{ + ID: role.ID, + Name: role.Name, + Description: role.Description, + Active: role.Active, + Scopes: role.Scopes, + Permissions: perms, + } + + return output, nil +} + //GetAllRoles returns a list of all created roles func (r *RoleUseCaseImpl) GetAllRoles(ctx context.Context) ([]*dto.RoleOutput, error) { ctx, span := tracer.Start(ctx, "GetAllRoles") @@ -166,6 +237,13 @@ func (r *RoleUseCaseImpl) GetAllRoles(ctx context.Context) ([]*dto.RoleOutput, e utils.RecordSpanError(span, err) return nil, err } + + users, err := r.repo.GetUserProfilesByRoleID(ctx, role.ID) + if err != nil { + utils.RecordSpanError(span, err) + return nil, err + } + output := &dto.RoleOutput{ ID: role.ID, Name: role.Name, @@ -173,6 +251,7 @@ func (r *RoleUseCaseImpl) GetAllRoles(ctx context.Context) ([]*dto.RoleOutput, e Active: role.Active, Scopes: role.Scopes, Permissions: perms, + Users: users, } roleOutput = append(roleOutput, output) } @@ -266,6 +345,31 @@ func (r *RoleUseCaseImpl) DeleteRole(ctx context.Context, roleID string) (bool, return success, nil } +// UnauthorizedDeleteRole creates a role without performing user authorization +// +// This usecase is useful for cleaning up and removing the test role(s) used for running integration and acceptance tests +func (r *RoleUseCaseImpl) UnauthorizedDeleteRole(ctx context.Context, roleID string) (bool, error) { + ctx, span := tracer.Start(ctx, "UnauthorizedDeleteRole") + defer span.End() + + role, err := r.repo.GetRoleByID(ctx, roleID) + if err != nil { + utils.RecordSpanError(span, err) + return false, err + } + + if !strings.Contains(strings.ToLower(role.Name), "test") { + return false, fmt.Errorf("only test roles can be removed") + } + + success, err := r.repo.DeleteRole(ctx, roleID) + if err != nil { + utils.RecordSpanError(span, err) + return false, err + } + return success, nil +} + //GetAllPermissions returns a list of all permissions declared in the system func (r *RoleUseCaseImpl) GetAllPermissions( ctx context.Context, @@ -488,6 +592,7 @@ func (r *RoleUseCaseImpl) RevokeRole( ctx context.Context, userID string, roleID string, + reason string, ) (bool, error) { ctx, span := tracer.Start(ctx, "RevokeRole") defer span.End() @@ -550,11 +655,30 @@ func (r *RoleUseCaseImpl) RevokeRole( return false, err } + // revocation input + log := dto.RoleRevocationInput{ + ProfileID: profile.ID, + RoleID: role.ID, + Reason: reason, + } + // logged in user profile + p, err := r.repo.GetUserProfileByUID(ctx, user.UID, false) + if err != nil { + return false, err + } + err = r.repo.SaveRoleRevocation(ctx, p.ID, log) + if err != nil { + return false, err + } + return true, nil } // ActivateRole marks a deactivated role as active and usable -func (r *RoleUseCaseImpl) ActivateRole(ctx context.Context, roleID string) (*dto.RoleOutput, error) { +func (r *RoleUseCaseImpl) ActivateRole( + ctx context.Context, + roleID string, +) (*dto.RoleOutput, error) { ctx, span := tracer.Start(ctx, "ActivateRole") defer span.End() @@ -617,7 +741,10 @@ func (r *RoleUseCaseImpl) ActivateRole(ctx context.Context, roleID string) (*dto } // DeactivateRole marks a role as inactive and cannot be used -func (r *RoleUseCaseImpl) DeactivateRole(ctx context.Context, roleID string) (*dto.RoleOutput, error) { +func (r *RoleUseCaseImpl) DeactivateRole( + ctx context.Context, + roleID string, +) (*dto.RoleOutput, error) { ctx, span := tracer.Start(ctx, "DeactivateRole") defer span.End() @@ -680,7 +807,10 @@ func (r *RoleUseCaseImpl) DeactivateRole(ctx context.Context, roleID string) (*d } // UpdateRolePermissions replaces the scopes in a role with new updated scopes -func (r *RoleUseCaseImpl) UpdateRolePermissions(ctx context.Context, input dto.RolePermissionInput) (*dto.RoleOutput, error) { +func (r *RoleUseCaseImpl) UpdateRolePermissions( + ctx context.Context, + input dto.RolePermissionInput, +) (*dto.RoleOutput, error) { ctx, span := tracer.Start(ctx, "UpdateRolePermissions") defer span.End() @@ -745,9 +875,126 @@ func (r *RoleUseCaseImpl) UpdateRolePermissions(ctx context.Context, input dto.R // CheckPermission checks whether a logged in user with the given UID // is authorized to perform the action specified in the permission -func (r *RoleUseCaseImpl) CheckPermission(ctx context.Context, uid string, permission profileutils.Permission) (bool, error) { +func (r *RoleUseCaseImpl) CheckPermission( + ctx context.Context, + uid string, + permission profileutils.Permission, +) (bool, error) { ctx, span := tracer.Start(ctx, "CheckPermission") defer span.End() return r.repo.CheckIfUserHasPermission(ctx, uid, permission) } + +// GetRoleByName retrieves a role with the matching name +// Each role has a unique name i.e no duplicate names +func (r *RoleUseCaseImpl) GetRoleByName(ctx context.Context, name string) (*dto.RoleOutput, error) { + ctx, span := tracer.Start(ctx, "GetRoleByName") + defer span.End() + + user, err := r.baseExt.GetLoggedInUser(ctx) + if err != nil { + utils.RecordSpanError(span, err) + return nil, err + } + + // Check logged in user has the right permissions + allowed, err := r.repo.CheckIfUserHasPermission(ctx, user.UID, profileutils.CanViewRole) + if err != nil { + utils.RecordSpanError(span, err) + return nil, err + } + if !allowed { + return nil, exceptions.RoleNotValid( + fmt.Errorf("error: logged in user does not have permission to view role"), + ) + } + + role, err := r.repo.GetRoleByName(ctx, name) + if err != nil { + return nil, err + } + + // get permissions + perms, err := role.Permissions(ctx) + if err != nil { + utils.RecordSpanError(span, err) + return nil, err + } + + output := &dto.RoleOutput{ + ID: role.ID, + Name: role.Name, + Description: role.Description, + Active: role.Active, + Scopes: role.Scopes, + Permissions: perms, + } + + return output, nil +} + +// GetRolesByIDs returns the details of roles given the IDs +func (r RoleUseCaseImpl) GetRolesByIDs(ctx context.Context, roleIDs []string) ([]*dto.RoleOutput, error) { + ctx, span := tracer.Start(ctx, "GetRolesByIDs") + defer span.End() + + roles, err := r.repo.GetRolesByIDs(ctx, roleIDs) + if err != nil { + return nil, err + } + + roleOutput := []*dto.RoleOutput{} + for _, role := range *roles { + perms, err := role.Permissions(ctx) + if err != nil { + utils.RecordSpanError(span, err) + return nil, err + } + + output := &dto.RoleOutput{ + ID: role.ID, + Name: role.Name, + Description: role.Description, + Active: role.Active, + Scopes: role.Scopes, + Permissions: perms, + } + roleOutput = append(roleOutput, output) + } + + return roleOutput, nil +} + +// AssignMultipleRoles assigns multiple roles to a user +func (r RoleUseCaseImpl) AssignMultipleRoles(ctx context.Context, userID string, roleIDs []string) (bool, error) { + ctx, span := tracer.Start(ctx, "AssignMultipleRoles") + defer span.End() + + user, err := r.baseExt.GetLoggedInUser(ctx) + if err != nil { + utils.RecordSpanError(span, err) + return false, err + } + + // Check logged in user has the right permissions + allowed, err := r.CheckPermission(ctx, user.UID, profileutils.CanAssignRole) + if err != nil { + utils.RecordSpanError(span, err) + return false, err + } + if !allowed { + return false, exceptions.RoleNotValid( + fmt.Errorf("error: logged in user does not have permission to view role"), + ) + } + + for _, roleID := range roleIDs { + _, err := r.AssignRole(ctx, userID, roleID) + if err != nil { + return false, err + } + } + + return true, nil +} diff --git a/pkg/onboarding/usecases/roles_unit_test.go b/pkg/onboarding/usecases/roles_unit_test.go index f2506e70..f4fe67d9 100644 --- a/pkg/onboarding/usecases/roles_unit_test.go +++ b/pkg/onboarding/usecases/roles_unit_test.go @@ -225,6 +225,7 @@ func TestRoleUseCaseImpl_GetAllRoles(t *testing.T) { ID: "c9d62c7e-93e5-44a6-b503-6fc159c1782f", Scopes: []string{"role.create"}, Permissions: rolePerms, + Users: []*profileutils.UserProfile{}, }, } @@ -320,6 +321,10 @@ func TestRoleUseCaseImpl_GetAllRoles(t *testing.T) { }, }, nil } + fakeRepo.GetUserProfilesByRoleIDFn = func(ctx context.Context, role string) ([]*profileutils.UserProfile, error) { + + return []*profileutils.UserProfile{}, nil + } } got, err := i.Role.GetAllRoles(tt.args.ctx) if (err != nil) != tt.wantErr { @@ -460,6 +465,9 @@ func TestRoleUseCaseImpl_FindRoleByName(t *testing.T) { Scopes: []string{"role.create"}, }}, nil } + fakeRepo.GetUserProfilesByRoleIDFn = func(ctx context.Context, role string) ([]*profileutils.UserProfile, error) { + return []*profileutils.UserProfile{}, nil + } } got, err := i.Role.FindRoleByName(tt.args.ctx, tt.args.roleName) if (err != nil) != tt.wantErr { @@ -590,6 +598,95 @@ func TestRoleUseCaseImpl_DeleteRole(t *testing.T) { } } +func TestRoleUseCaseImpl_UnauthorizedDeleteRole(t *testing.T) { + ctx := context.Background() + i, err := InitializeFakeOnboardingInteractor() + if err != nil { + t.Errorf("failed to fake initialize onboarding interactor: %v", + err, + ) + return + } + + type args struct { + ctx context.Context + roleID string + } + tests := []struct { + name string + args args + want bool + wantErr bool + }{ + { + name: "happy: remove test role", + args: args{ + ctx: ctx, + roleID: uuid.NewString(), + }, + want: true, + wantErr: false, + }, + { + name: "sad: remove non-test role", + args: args{ + ctx: ctx, + roleID: uuid.NewString(), + }, + want: false, + wantErr: true, + }, + { + name: "sad: remove role error", + args: args{ + ctx: ctx, + roleID: uuid.NewString(), + }, + want: false, + wantErr: true, + }, + } + for _, tt := range tests { + if tt.name == "happy: remove test role" { + fakeRepo.GetRoleByIDFn = func(ctx context.Context, roleID string) (*profileutils.Role, error) { + return &profileutils.Role{ID: uuid.NewString(), Name: "Happy Test Role"}, nil + } + fakeRepo.DeleteRoleFn = func(ctx context.Context, roleID string) (bool, error) { + return true, nil + } + } + + if tt.name == "sad: remove non-test role" { + fakeRepo.GetRoleByIDFn = func(ctx context.Context, roleID string) (*profileutils.Role, error) { + return &profileutils.Role{ID: uuid.NewString(), Name: "Crucial Role"}, nil + } + fakeRepo.DeleteRoleFn = func(ctx context.Context, roleID string) (bool, error) { + return true, nil + } + } + + if tt.name == "sad: remove role error" { + fakeRepo.GetRoleByIDFn = func(ctx context.Context, roleID string) (*profileutils.Role, error) { + return &profileutils.Role{ID: uuid.NewString(), Name: "Happy Test Role"}, nil + } + fakeRepo.DeleteRoleFn = func(ctx context.Context, roleID string) (bool, error) { + return true, fmt.Errorf("cannot remove role") + } + } + + t.Run(tt.name, func(t *testing.T) { + got, err := i.Role.UnauthorizedDeleteRole(tt.args.ctx, tt.args.roleID) + if (err != nil) != tt.wantErr { + t.Errorf("RoleUseCaseImpl.UnauthorizedDeleteRole() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("RoleUseCaseImpl.UnauthorizedDeleteRole() = %v, want %v", got, tt.want) + } + }) + } +} + func TestRoleUseCaseImpl_GetAllPermissions(t *testing.T) { ctx := context.Background() @@ -1338,6 +1435,7 @@ func TestRoleUseCaseImpl_RevokeRole(t *testing.T) { ctx context.Context userID string roleID string + reason string } tests := []struct { name string @@ -1411,6 +1509,7 @@ func TestRoleUseCaseImpl_RevokeRole(t *testing.T) { ctx: ctx, userID: uuid.NewString(), roleID: "17e6ea18-7147-4bdb-ad0b-d9ce03a8c0ac", + reason: "no longer working for us", }, want: true, wantErr: false, @@ -1558,9 +1657,23 @@ func TestRoleUseCaseImpl_RevokeRole(t *testing.T) { fakeRepo.UpdateUserRoleIDsFn = func(ctx context.Context, id string, roleIDs []string) error { return nil } + + fakeRepo.GetUserProfileByUIDFn = func(ctx context.Context, uid string, suspended bool) (*profileutils.UserProfile, error) { + return &profileutils.UserProfile{ + ID: "", + Roles: []string{ + "17e6ea18-7147-4bdb-ad0b-d9ce03a8c0ac", + "56e5e987-2f02-4455-9dde-ae15162d8bce", + }, + }, nil + } + + fakeRepo.SaveRoleRevocationFn = func(ctx context.Context, userID string, revocation dto.RoleRevocationInput) error { + return nil + } } - got, err := i.Role.RevokeRole(tt.args.ctx, tt.args.userID, tt.args.roleID) + got, err := i.Role.RevokeRole(tt.args.ctx, tt.args.userID, tt.args.roleID, tt.args.reason) if (err != nil) != tt.wantErr { t.Errorf("RoleUseCaseImpl.RevokeRole() error = %v, wantErr %v", err, tt.wantErr) return @@ -2145,3 +2258,390 @@ func TestRoleUseCaseImpl_UpdateRolePermissions(t *testing.T) { }) } } + +func TestRoleUseCaseImpl_CreateUnauthorizedRole(t *testing.T) { + ctx := context.Background() + i, err := InitializeFakeOnboardingInteractor() + + if err != nil { + t.Errorf("failed to fake initialize onboarding interactor: %v", + err, + ) + return + } + + input := dto.RoleInput{ + Name: "Agents", + } + + allPerms, err := profileutils.AllPermissions(ctx) + if err != nil { + t.Error("error did not get all permissions") + return + } + + perms := []profileutils.Permission{} + for _, perm := range allPerms { + if perm.Scope == "role.edit" { + perm.Allowed = true + } + perms = append(perms, perm) + } + expectedOutput := &dto.RoleOutput{ + Scopes: []string{"role.edit"}, + Permissions: perms, + } + + type args struct { + ctx context.Context + input dto.RoleInput + } + + tests := []struct { + name string + args args + want *dto.RoleOutput + wantErr bool + }{ + { + name: "sad: unable to get logged in user", + args: args{ + ctx: ctx, + input: input, + }, + want: nil, + wantErr: true, + }, + { + name: "sad: unable to get user's profile", + args: args{ + ctx: ctx, + input: input, + }, + want: nil, + wantErr: true, + }, + { + name: "sad: unable to create role in database", + args: args{ + ctx: ctx, + input: input, + }, + want: nil, + wantErr: true, + }, + { + name: "happy:created role", + args: args{ + ctx: ctx, + input: input, + }, + want: expectedOutput, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + if tt.name == "sad: unable to get logged in user" { + fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { + return nil, fmt.Errorf("unable to get logged in user") + } + } + + if tt.name == "sad: unable to get user's profile" { + fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { + return &dto.UserInfo{}, nil + } + fakeRepo.GetUserProfileByUIDFn = func(ctx context.Context, uid string, suspended bool) (*profileutils.UserProfile, error) { + return nil, fmt.Errorf("error unable to get user profile") + } + } + + if tt.name == "sad: unable to create role in database" { + fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { + return &dto.UserInfo{}, nil + } + fakeRepo.GetUserProfileByUIDFn = func(ctx context.Context, uid string, suspended bool) (*profileutils.UserProfile, error) { + return &profileutils.UserProfile{}, nil + } + fakeRepo.CreateRoleFn = func(ctx context.Context, profileID string, role dto.RoleInput) (*profileutils.Role, error) { + return nil, fmt.Errorf("error un able to create role in db") + } + } + + if tt.name == "happy:created role" { + fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { + return &dto.UserInfo{}, nil + } + fakeRepo.GetUserProfileByUIDFn = func(ctx context.Context, uid string, suspended bool) (*profileutils.UserProfile, error) { + return &profileutils.UserProfile{}, nil + } + fakeRepo.CreateRoleFn = func(ctx context.Context, profileID string, role dto.RoleInput) (*profileutils.Role, error) { + return &profileutils.Role{ + Scopes: []string{"role.edit"}, + }, nil + } + } + + got, err := i.Role.CreateUnauthorizedRole(tt.args.ctx, tt.args.input) + if (err != nil) != tt.wantErr { + t.Errorf("RoleUseCaseImpl.CreateRole() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("RoleUseCaseImpl.CreateRole() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestRoleUseCaseImpl_AssignMultipleRoles(t *testing.T) { + ctx := context.Background() + + i, err := InitializeFakeOnboardingInteractor() + if err != nil { + t.Errorf("failed to fake initialize onboarding interactor: %v", + err, + ) + return + } + + type args struct { + ctx context.Context + userID string + roleIDs []string + } + tests := []struct { + name string + args args + want bool + wantErr bool + }{ + { + name: "fail: cannot get logged in user", + args: args{ + ctx: ctx, + userID: uuid.NewString(), + roleIDs: []string{uuid.NewString()}, + }, + want: false, + wantErr: true, + }, + { + name: "fail: user doesn't have the permission", + args: args{ + ctx: ctx, + userID: uuid.NewString(), + roleIDs: []string{uuid.NewString()}, + }, + want: false, + wantErr: true, + }, + { + name: "fail: role ID doesn't exist", + args: args{ + ctx: ctx, + userID: uuid.NewString(), + roleIDs: []string{"invalid id"}, + }, + want: false, + wantErr: true, + }, + { + name: "fail: cannot retrieve user profile", + args: args{ + ctx: ctx, + userID: uuid.NewString(), + roleIDs: []string{uuid.NewString()}, + }, + want: false, + wantErr: true, + }, + { + name: "fail: role already exists", + args: args{ + ctx: ctx, + userID: uuid.NewString(), + roleIDs: []string{"0637333d-74b0-473d-95bd-0a03b1ae5e06"}, + }, + want: false, + wantErr: true, + }, + { + name: "fail: error updating user profile role", + args: args{ + ctx: ctx, + userID: uuid.NewString(), + roleIDs: []string{"17e6ea18-7147-4bdb-ad0b-d9ce03a8c0ac"}, + }, + want: false, + wantErr: true, + }, + { + name: "success: add a new role to user", + args: args{ + ctx: ctx, + userID: uuid.NewString(), + roleIDs: []string{"17e6ea18-7147-4bdb-ad0b-d9ce03a8c0ac"}, + }, + want: true, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + if tt.name == "fail: cannot get logged in user" { + fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { + return nil, fmt.Errorf("cannot get logged in user") + } + + //remove + fakeRepo.GetRoleByIDFn = func(ctx context.Context, roleID string) (*profileutils.Role, error) { + return nil, fmt.Errorf("cannot get role ny id") + } + } + + if tt.name == "fail: user doesn't have the permission" { + fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { + return &dto.UserInfo{UID: ""}, nil + } + + fakeRepo.CheckIfUserHasPermissionFn = func(ctx context.Context, UID string, requiredPermission profileutils.Permission) (bool, error) { + return false, nil + } + + fakeRepo.GetRoleByIDFn = func(ctx context.Context, roleID string) (*profileutils.Role, error) { + return &profileutils.Role{ + ID: "", + Scopes: []string{profileutils.CanRegisterAgent.Scope}, + }, nil + } + + //remove + fakeRepo.GetRoleByIDFn = func(ctx context.Context, roleID string) (*profileutils.Role, error) { + return nil, fmt.Errorf("cannot get role ny id") + } + } + + if tt.name == "fail: role ID doesn't exist" { + fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { + return &dto.UserInfo{UID: ""}, nil + } + + fakeRepo.CheckIfUserHasPermissionFn = func(ctx context.Context, UID string, requiredPermission profileutils.Permission) (bool, error) { + return true, nil + } + + fakeRepo.GetRoleByIDFn = func(ctx context.Context, roleID string) (*profileutils.Role, error) { + return nil, fmt.Errorf("cannot get role ny id") + } + } + + if tt.name == "fail: cannot retrieve user profile" { + fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { + return &dto.UserInfo{UID: ""}, nil + } + + fakeRepo.CheckIfUserHasPermissionFn = func(ctx context.Context, UID string, requiredPermission profileutils.Permission) (bool, error) { + return true, nil + } + + fakeRepo.GetRoleByIDFn = func(ctx context.Context, roleID string) (*profileutils.Role, error) { + return &profileutils.Role{ + ID: "", + Scopes: []string{profileutils.CanAssignRole.Scope}, + }, nil + } + + fakeRepo.GetUserProfileByIDFn = func(ctx context.Context, id string, suspended bool) (*profileutils.UserProfile, error) { + return nil, fmt.Errorf("no user profile") + } + } + + if tt.name == "fail: role already exists" { + fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { + return &dto.UserInfo{UID: ""}, nil + } + + fakeRepo.CheckIfUserHasPermissionFn = func(ctx context.Context, UID string, requiredPermission profileutils.Permission) (bool, error) { + return true, nil + } + + fakeRepo.GetRoleByIDFn = func(ctx context.Context, roleID string) (*profileutils.Role, error) { + return &profileutils.Role{ + ID: "0637333d-74b0-473d-95bd-0a03b1ae5e06", + Scopes: []string{profileutils.CanAssignRole.Scope}, + }, nil + } + + fakeRepo.GetUserProfileByIDFn = func(ctx context.Context, id string, suspended bool) (*profileutils.UserProfile, error) { + return &profileutils.UserProfile{ + ID: "", + Roles: []string{"0637333d-74b0-473d-95bd-0a03b1ae5e06"}, + }, nil + } + } + + if tt.name == "fail: error updating user profile role" { + fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { + return &dto.UserInfo{UID: ""}, nil + } + + fakeRepo.CheckIfUserHasPermissionFn = func(ctx context.Context, UID string, requiredPermission profileutils.Permission) (bool, error) { + return true, nil + } + + fakeRepo.GetRoleByIDFn = func(ctx context.Context, roleID string) (*profileutils.Role, error) { + return &profileutils.Role{ + ID: "", + Scopes: []string{profileutils.CanAssignRole.Scope}, + }, nil + } + + fakeRepo.GetUserProfileByIDFn = func(ctx context.Context, id string, suspended bool) (*profileutils.UserProfile, error) { + return &profileutils.UserProfile{ID: ""}, nil + } + + fakeRepo.UpdateUserRoleIDsFn = func(ctx context.Context, id string, roleIDs []string) error { + return fmt.Errorf("cannot update role ids") + } + } + + if tt.name == "success: add a new role to user" { + fakeBaseExt.GetLoggedInUserFn = func(ctx context.Context) (*dto.UserInfo, error) { + return &dto.UserInfo{UID: ""}, nil + } + + fakeRepo.CheckIfUserHasPermissionFn = func(ctx context.Context, UID string, requiredPermission profileutils.Permission) (bool, error) { + return true, nil + } + + fakeRepo.GetRoleByIDFn = func(ctx context.Context, roleID string) (*profileutils.Role, error) { + return &profileutils.Role{ + ID: "", + Scopes: []string{profileutils.CanAssignRole.Scope}, + }, nil + } + + fakeRepo.GetUserProfileByIDFn = func(ctx context.Context, id string, suspended bool) (*profileutils.UserProfile, error) { + return &profileutils.UserProfile{ID: ""}, nil + } + + fakeRepo.UpdateUserRoleIDsFn = func(ctx context.Context, id string, roleIDs []string) error { + return nil + } + } + + got, err := i.Role.AssignMultipleRoles(tt.args.ctx, tt.args.userID, tt.args.roleIDs) + if (err != nil) != tt.wantErr { + t.Errorf("RoleUseCaseImpl.AssignMultipleRoles() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("RoleUseCaseImpl.AssignMultipleRoles() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/onboarding/usecases/signup.go b/pkg/onboarding/usecases/signup.go index 4b53dc28..f99c7a4f 100644 --- a/pkg/onboarding/usecases/signup.go +++ b/pkg/onboarding/usecases/signup.go @@ -13,11 +13,13 @@ import ( "github.com/savannahghi/onboarding/pkg/onboarding/application/exceptions" "github.com/savannahghi/onboarding/pkg/onboarding/application/extension" "github.com/savannahghi/onboarding/pkg/onboarding/application/utils" + profileDomain "github.com/savannahghi/onboarding/pkg/onboarding/domain" "github.com/savannahghi/onboarding/pkg/onboarding/infrastructure/services/edi" "github.com/savannahghi/onboarding/pkg/onboarding/infrastructure/services/engagement" pubsubmessaging "github.com/savannahghi/onboarding/pkg/onboarding/infrastructure/services/pubsub" "github.com/savannahghi/onboarding/pkg/onboarding/repository" "github.com/savannahghi/profileutils" + "github.com/savannahghi/pubsubtools" "github.com/savannahghi/scalarutils" "github.com/sirupsen/logrus" "gitlab.slade360emr.com/go/commontools/crm/pkg/domain" @@ -67,6 +69,8 @@ type SignUpUseCases interface { SetPhoneAsPrimary(ctx context.Context, phone, otp string) (bool, error) RemoveUserByPhoneNumber(ctx context.Context, phone string) error + + RegisterUser(ctx context.Context, input dto.RegisterUserInput) (*dto.RegisteredUserResponse, error) } // SignUpUseCasesImpl represents usecase implementation object @@ -79,6 +83,7 @@ type SignUpUseCasesImpl struct { engagement engagement.ServiceEngagement pubsub pubsubmessaging.ServicePubSub edi edi.ServiceEdi + repo repository.OnboardingRepository } // NewSignUpUseCases returns a new a onboarding usecase @@ -101,6 +106,7 @@ func NewSignUpUseCases( engagement: eng, pubsub: pubsub, edi: edi, + repo: r, } } @@ -168,14 +174,14 @@ func (s *SignUpUseCasesImpl) CreateUserByPhone( } // get or create user via their phone number - user, err := s.onboardingRepository.GetOrCreatePhoneNumberUser(ctx, *userData.PhoneNumber) + user, err := s.repo.GetOrCreatePhoneNumberUser(ctx, *userData.PhoneNumber) if err != nil { utils.RecordSpanError(span, err) return nil, err } // create a user profile - profile, err := s.onboardingRepository.CreateUserProfile( + profile, err := s.repo.CreateUserProfile( ctx, *userData.PhoneNumber, user.UID, @@ -185,7 +191,7 @@ func (s *SignUpUseCasesImpl) CreateUserByPhone( return nil, exceptions.InternalServerError(err) } // generate auth credentials - auth, err := s.onboardingRepository.GenerateAuthCredentials( + auth, err := s.repo.GenerateAuthCredentials( ctx, *userData.PhoneNumber, profile, @@ -207,20 +213,20 @@ func (s *SignUpUseCasesImpl) CreateUserByPhone( var supplier *profileutils.Supplier var customer *profileutils.Customer - supplier, err = s.onboardingRepository.CreateEmptySupplierProfile(ctx, profile.ID) + supplier, err = s.repo.CreateEmptySupplierProfile(ctx, profile.ID) if err != nil { utils.RecordSpanError(span, err) return nil, exceptions.InternalServerError(err) } - customer, err = s.onboardingRepository.CreateEmptyCustomerProfile(ctx, profile.ID) + customer, err = s.repo.CreateEmptyCustomerProfile(ctx, profile.ID) if err != nil { utils.RecordSpanError(span, err) return nil, exceptions.InternalServerError(err) } // set the user default communications settings defaultCommunicationSetting := true - comms, err := s.onboardingRepository.SetUserCommunicationsSettings( + comms, err := s.repo.SetUserCommunicationsSettings( ctx, profile.ID, &defaultCommunicationSetting, @@ -248,7 +254,7 @@ func (s *SignUpUseCasesImpl) CreateUserByPhone( } // get navigation actions - roles, err := s.onboardingRepository.GetRolesByIDs(ctx, profile.Roles) + roles, err := s.repo.GetRolesByIDs(ctx, profile.Roles) if err != nil { return nil, err } @@ -441,7 +447,7 @@ func (s *SignUpUseCasesImpl) GetUserRecoveryPhoneNumbers( return nil, exceptions.NormalizeMSISDNError(err) } - pr, err := s.onboardingRepository.GetUserProfileByPhoneNumber(ctx, *phoneNumber, false) + pr, err := s.repo.GetUserProfileByPhoneNumber(ctx, *phoneNumber, false) if err != nil { utils.RecordSpanError(span, err) // this is a wrapped error. No need to wrap it again @@ -498,5 +504,102 @@ func (s *SignUpUseCasesImpl) RemoveUserByPhoneNumber(ctx context.Context, phone utils.RecordSpanError(span, err) return exceptions.NormalizeMSISDNError(err) } - return s.onboardingRepository.PurgeUserByPhoneNumber(ctx, *phoneNumber) + return s.repo.PurgeUserByPhoneNumber(ctx, *phoneNumber) +} + +// RegisterUser creates a new userprofile +func (s *SignUpUseCasesImpl) RegisterUser(ctx context.Context, input dto.RegisterUserInput) (*dto.RegisteredUserResponse, error) { + ctx, span := tracer.Start(ctx, "RegisterUser") + defer span.End() + + uid, err := s.baseExt.GetLoggedInUserUID(ctx) + if err != nil { + utils.RecordSpanError(span, err) + return nil, exceptions.UserNotFoundError(err) + } + + // create a user profile + //make createdByID optional only if the profile of the creating user is found + profile, err := s.repo.GetUserProfileByUID(ctx, uid, false) + var profileID string + if err == nil { + profileID = profile.ID + } + + phoneNumber, err := s.baseExt.NormalizeMSISDN(*input.PhoneNumber) + if err != nil { + utils.RecordSpanError(span, err) + return nil, exceptions.NormalizeMSISDNError(err) + } + + timestamp := time.Now().In(pubsubtools.TimeLocation) + + userProfile := profileutils.UserProfile{ + PrimaryEmailAddress: input.Email, + UserBioData: profileutils.BioData{ + FirstName: input.FirstName, + LastName: input.LastName, + Gender: enumutils.Gender(*input.Gender), + DateOfBirth: input.DateOfBirth, + }, + CreatedByID: &profileID, + Created: ×tamp, + Roles: input.RoleIDs, + } + + createdProfile, err := s.repo.CreateDetailedUserProfile(ctx, *phoneNumber, userProfile) + if err != nil { + utils.RecordSpanError(span, err) + return nil, err + } + + _, err = s.repo.CreateEmptySupplierProfile(ctx, createdProfile.ID) + if err != nil { + return nil, err + } + + _, err = s.repo.CreateEmptyCustomerProfile(ctx, createdProfile.ID) + if err != nil { + return nil, err + } + + // set the user default communications settings + defaultCommunicationSetting := true + _, err = s.repo.SetUserCommunicationsSettings( + ctx, + createdProfile.ID, + &defaultCommunicationSetting, + &defaultCommunicationSetting, + &defaultCommunicationSetting, + &defaultCommunicationSetting, + ) + if err != nil { + utils.RecordSpanError(span, err) + return nil, err + } + + otp, err := s.pinUsecase.SetUserTempPIN(ctx, createdProfile.ID) + if err != nil { + utils.RecordSpanError(span, err) + return nil, err + } + + message := input.WelcomeMessage + if message == nil { + message = &profileDomain.WelcomeMessage + } + + formartedMessage := fmt.Sprintf(*message, *input.FirstName, otp) + + if err := s.engagement.SendSMS(ctx, []string{*phoneNumber}, formartedMessage); err != nil { + return nil, fmt.Errorf("unable to send consumer registration message: %w", err) + } + + response := dto.RegisteredUserResponse{ + ID: createdProfile.ID, + DisplayName: *createdProfile.UserBioData.FirstName, + PhoneNumber: *createdProfile.PrimaryPhone, + } + + return &response, nil } diff --git a/pkg/onboarding/usecases/signup_unit_test.go b/pkg/onboarding/usecases/signup_unit_test.go index b27e17b9..63407ada 100644 --- a/pkg/onboarding/usecases/signup_unit_test.go +++ b/pkg/onboarding/usecases/signup_unit_test.go @@ -9,6 +9,7 @@ import ( "github.com/google/uuid" "github.com/savannahghi/enumutils" "github.com/savannahghi/feedlib" + "github.com/savannahghi/interserviceclient" "github.com/savannahghi/onboarding/pkg/onboarding/application/dto" "github.com/savannahghi/onboarding/pkg/onboarding/application/extension" "github.com/savannahghi/onboarding/pkg/onboarding/domain" @@ -1847,3 +1848,368 @@ func TestSignUpUseCasesImpl_UpdateUserProfile(t *testing.T) { }) } } + +func TestSignUpUseCasesImpl_RegisterUser(t *testing.T) { + ctx := context.Background() + + i, err := InitializeFakeOnboardingInteractor() + if err != nil { + t.Errorf("failed to fake initialize onboarding interactor: %v", + err, + ) + return + } + + phoneNumber := interserviceclient.TestUserPhoneNumber + fName := "Test" + lName := "Test" + email := "test@email.com" + gender := "male" + input := dto.RegisterUserInput{ + PhoneNumber: &phoneNumber, + FirstName: &fName, + LastName: &lName, + Email: &email, + Gender: (*enumutils.Gender)(&gender), + } + + type args struct { + ctx context.Context + input dto.RegisterUserInput + } + tests := []struct { + name string + args args + want *profileutils.UserProfile + wantErr bool + }{ + { + name: "sad: unable to get logged in user", + args: args{ + ctx: ctx, + input: input, + }, + want: nil, + wantErr: true, + }, + { + name: "sad: unable to normalize phonenumber", + args: args{ + ctx: ctx, + input: input, + }, + want: nil, + wantErr: true, + }, + { + name: "sad: unable to create user profile", + args: args{ + ctx: ctx, + input: input, + }, + want: nil, + wantErr: true, + }, + { + name: "sad: unable to create supplier profile", + args: args{ + ctx: ctx, + input: input, + }, + want: nil, + wantErr: true, + }, + { + name: "sad: unable to create customer profile", + args: args{ + ctx: ctx, + input: input, + }, + want: nil, + wantErr: true, + }, + { + name: "sad: unable to create communication settings", + args: args{ + ctx: ctx, + input: input, + }, + want: nil, + wantErr: true, + }, + { + name: "sad: unable to create otp", + args: args{ + ctx: ctx, + input: input, + }, + want: nil, + wantErr: true, + }, + { + name: "sad: unable to send otp sms", + args: args{ + ctx: ctx, + input: input, + }, + want: nil, + wantErr: true, + }, + { + name: "happy: registered consumer", + args: args{ + ctx: ctx, + input: input, + }, + want: &profileutils.UserProfile{}, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.name == "sad: unable to get logged in user" { + fakeBaseExt.GetLoggedInUserUIDFn = func(ctx context.Context) (string, error) { + return "", fmt.Errorf("unable to get logged in user") + } + } + + if tt.name == "sad: unable to normalize phonenumber" { + fakeBaseExt.GetLoggedInUserUIDFn = func(ctx context.Context) (string, error) { + return uuid.NewString(), nil + } + fakeRepo.GetUserProfileByUIDFn = func(ctx context.Context, uid string, suspended bool) (*profileutils.UserProfile, error) { + return &profileutils.UserProfile{ID: uuid.NewString()}, nil + } + fakeBaseExt.NormalizeMSISDNFn = func(msisdn string) (*string, error) { + return nil, fmt.Errorf("unable to normalize phone number") + } + } + + if tt.name == "sad: unable to create user profile" { + fakeBaseExt.GetLoggedInUserUIDFn = func(ctx context.Context) (string, error) { + return uuid.NewString(), nil + } + fakeRepo.GetUserProfileByUIDFn = func(ctx context.Context, uid string, suspended bool) (*profileutils.UserProfile, error) { + return &profileutils.UserProfile{ID: uuid.NewString()}, nil + } + fakeBaseExt.NormalizeMSISDNFn = func(msisdn string) (*string, error) { + return &phoneNumber, nil + } + fakeRepo.CreateDetailedUserProfileFn = func(ctx context.Context, phoneNumber string, profile profileutils.UserProfile) (*profileutils.UserProfile, error) { + return nil, fmt.Errorf("unable to create user profile") + } + } + if tt.name == "sad: unable to create supplier profile" { + fakeBaseExt.GetLoggedInUserUIDFn = func(ctx context.Context) (string, error) { + return uuid.NewString(), nil + } + fakeRepo.GetUserProfileByUIDFn = func(ctx context.Context, uid string, suspended bool) (*profileutils.UserProfile, error) { + return &profileutils.UserProfile{ID: uuid.NewString()}, nil + } + fakeBaseExt.NormalizeMSISDNFn = func(msisdn string) (*string, error) { + return &phoneNumber, nil + } + fakeRepo.CreateDetailedUserProfileFn = func(ctx context.Context, phoneNumber string, profile profileutils.UserProfile) (*profileutils.UserProfile, error) { + return &profileutils.UserProfile{ + ID: uuid.NewString(), + PrimaryPhone: &phoneNumber, + }, nil + } + fakeRepo.CreateEmptySupplierProfileFn = func(ctx context.Context, profileID string) (*profileutils.Supplier, error) { + return nil, fmt.Errorf("unable to create supplier profile") + } + } + if tt.name == "sad: unable to create customer profile" { + fakeBaseExt.GetLoggedInUserUIDFn = func(ctx context.Context) (string, error) { + return uuid.NewString(), nil + } + fakeRepo.GetUserProfileByUIDFn = func(ctx context.Context, uid string, suspended bool) (*profileutils.UserProfile, error) { + return &profileutils.UserProfile{ID: uuid.NewString()}, nil + } + fakeBaseExt.NormalizeMSISDNFn = func(msisdn string) (*string, error) { + return &phoneNumber, nil + } + fakeRepo.CreateDetailedUserProfileFn = func(ctx context.Context, phoneNumber string, profile profileutils.UserProfile) (*profileutils.UserProfile, error) { + return &profileutils.UserProfile{ + ID: uuid.NewString(), + PrimaryPhone: &phoneNumber, + }, nil + } + fakeRepo.CreateEmptySupplierProfileFn = func(ctx context.Context, profileID string) (*profileutils.Supplier, error) { + return &profileutils.Supplier{}, nil + } + fakeRepo.CreateEmptyCustomerProfileFn = func(ctx context.Context, profileID string) (*profileutils.Customer, error) { + return nil, fmt.Errorf("unable to create customer profile") + } + } + if tt.name == "sad: unable to create communication settings" { + fakeBaseExt.GetLoggedInUserUIDFn = func(ctx context.Context) (string, error) { + return uuid.NewString(), nil + } + fakeRepo.GetUserProfileByUIDFn = func(ctx context.Context, uid string, suspended bool) (*profileutils.UserProfile, error) { + return &profileutils.UserProfile{ID: uuid.NewString()}, nil + } + fakeBaseExt.NormalizeMSISDNFn = func(msisdn string) (*string, error) { + return &phoneNumber, nil + } + fakeRepo.CreateDetailedUserProfileFn = func(ctx context.Context, phoneNumber string, profile profileutils.UserProfile) (*profileutils.UserProfile, error) { + return &profileutils.UserProfile{ + ID: uuid.NewString(), + PrimaryPhone: &phoneNumber, + }, nil + } + fakeRepo.CreateEmptySupplierProfileFn = func(ctx context.Context, profileID string) (*profileutils.Supplier, error) { + return &profileutils.Supplier{}, nil + } + fakeRepo.CreateEmptyCustomerProfileFn = func(ctx context.Context, profileID string) (*profileutils.Customer, error) { + return &profileutils.Customer{}, nil + } + fakeRepo.SetUserCommunicationsSettingsFn = func(ctx context.Context, profileID string, allowWhatsApp, allowTextSms, allowPush, allowEmail *bool) (*profileutils.UserCommunicationsSetting, error) { + return nil, fmt.Errorf("unable to create communication settings") + } + } + + if tt.name == "sad: unable to create otp" { + fakeBaseExt.GetLoggedInUserUIDFn = func(ctx context.Context) (string, error) { + return uuid.NewString(), nil + } + fakeRepo.GetUserProfileByUIDFn = func(ctx context.Context, uid string, suspended bool) (*profileutils.UserProfile, error) { + return &profileutils.UserProfile{ID: uuid.NewString()}, nil + } + fakeBaseExt.NormalizeMSISDNFn = func(msisdn string) (*string, error) { + return &phoneNumber, nil + } + fakeRepo.CreateDetailedUserProfileFn = func(ctx context.Context, phoneNumber string, profile profileutils.UserProfile) (*profileutils.UserProfile, error) { + return &profileutils.UserProfile{ + ID: uuid.NewString(), + PrimaryPhone: &phoneNumber, + }, nil + } + fakeRepo.CreateEmptySupplierProfileFn = func(ctx context.Context, profileID string) (*profileutils.Supplier, error) { + return &profileutils.Supplier{ + ID: "f4f39af7-5b64-4c2f-91bd-42b3af315a4e", + }, nil + } + fakeRepo.CreateEmptyCustomerProfileFn = func(ctx context.Context, profileID string) (*profileutils.Customer, error) { + return &profileutils.Customer{ + ID: "f4f39af7-5b64-4c2f-91bd-42b3af315a4e", + }, nil + } + fakePubSub.NotifyCreateContactFn = func(ctx context.Context, contact crmDomain.CRMContact) error { + return nil + } + fakeRepo.CreateEmptySupplierProfileFn = func(ctx context.Context, profileID string) (*profileutils.Supplier, error) { + return &profileutils.Supplier{}, nil + } + fakeRepo.CreateEmptyCustomerProfileFn = func(ctx context.Context, profileID string) (*profileutils.Customer, error) { + return &profileutils.Customer{}, nil + } + fakeRepo.SetUserCommunicationsSettingsFn = func(ctx context.Context, profileID string, allowWhatsApp, allowTextSms, allowPush, allowEmail *bool) (*profileutils.UserCommunicationsSetting, error) { + return &profileutils.UserCommunicationsSetting{}, nil + } + fakePinExt.GenerateTempPINFn = func(ctx context.Context) (string, error) { + return "123", nil + } + fakePinExt.EncryptPINFn = func(rawPwd string, options *extension.Options) (string, string) { + return "pin", "sha" + } + fakeRepo.SavePINFn = func(ctx context.Context, pin *domain.PIN) (bool, error) { + return false, fmt.Errorf("unable to create otp") + } + } + + if tt.name == "sad: unable to send otp sms" { + fakeBaseExt.GetLoggedInUserUIDFn = func(ctx context.Context) (string, error) { + return uuid.NewString(), nil + } + fakeRepo.GetUserProfileByUIDFn = func(ctx context.Context, uid string, suspended bool) (*profileutils.UserProfile, error) { + return &profileutils.UserProfile{ID: uuid.NewString()}, nil + } + fakeBaseExt.NormalizeMSISDNFn = func(msisdn string) (*string, error) { + return &phoneNumber, nil + } + fakeRepo.CreateDetailedUserProfileFn = func(ctx context.Context, phoneNumber string, profile profileutils.UserProfile) (*profileutils.UserProfile, error) { + return &profileutils.UserProfile{ + ID: uuid.NewString(), + PrimaryPhone: &phoneNumber, + }, nil + } + fakeRepo.CreateEmptySupplierProfileFn = func(ctx context.Context, profileID string) (*profileutils.Supplier, error) { + return &profileutils.Supplier{}, nil + } + fakeRepo.CreateEmptyCustomerProfileFn = func(ctx context.Context, profileID string) (*profileutils.Customer, error) { + return &profileutils.Customer{}, nil + } + fakeRepo.SetUserCommunicationsSettingsFn = func(ctx context.Context, profileID string, allowWhatsApp, allowTextSms, allowPush, allowEmail *bool) (*profileutils.UserCommunicationsSetting, error) { + return &profileutils.UserCommunicationsSetting{}, nil + } + fakePinExt.GenerateTempPINFn = func(ctx context.Context) (string, error) { + return "123", nil + } + fakePinExt.EncryptPINFn = func(rawPwd string, options *extension.Options) (string, string) { + return "pin", "sha" + } + fakeRepo.SavePINFn = func(ctx context.Context, pin *domain.PIN) (bool, error) { + return true, nil + } + fakeEngagementSvs.SendSMSFn = func(ctx context.Context, phoneNumbers []string, message string) error { + return fmt.Errorf("unable to send otp sms") + } + } + + if tt.name == "happy: registered consumer" { + fakeBaseExt.GetLoggedInUserUIDFn = func(ctx context.Context) (string, error) { + return uuid.NewString(), nil + } + + fakeRepo.GetUserProfileByUIDFn = func(ctx context.Context, uid string, suspended bool) (*profileutils.UserProfile, error) { + return nil, fmt.Errorf("unable to get user profile") + } + fakeBaseExt.NormalizeMSISDNFn = func(msisdn string) (*string, error) { + return &phoneNumber, nil + } + fakeRepo.CreateDetailedUserProfileFn = func(ctx context.Context, phoneNumber string, profile profileutils.UserProfile) (*profileutils.UserProfile, error) { + return &profileutils.UserProfile{ + ID: uuid.NewString(), + UserBioData: profileutils.BioData{ + FirstName: &fName, + LastName: &lName, + }, + PrimaryPhone: &phoneNumber, + PrimaryEmailAddress: &email, + }, nil + } + fakeRepo.CreateEmptySupplierProfileFn = func(ctx context.Context, profileID string) (*profileutils.Supplier, error) { + return &profileutils.Supplier{}, nil + } + fakeRepo.CreateEmptyCustomerProfileFn = func(ctx context.Context, profileID string) (*profileutils.Customer, error) { + return &profileutils.Customer{}, nil + } + fakeRepo.SetUserCommunicationsSettingsFn = func(ctx context.Context, profileID string, allowWhatsApp, allowTextSms, allowPush, allowEmail *bool) (*profileutils.UserCommunicationsSetting, error) { + return &profileutils.UserCommunicationsSetting{}, nil + } + fakePinExt.GenerateTempPINFn = func(ctx context.Context) (string, error) { + return "123", nil + } + fakePinExt.EncryptPINFn = func(rawPwd string, options *extension.Options) (string, string) { + return "pin", "sha" + } + fakeRepo.SavePINFn = func(ctx context.Context, pin *domain.PIN) (bool, error) { + return true, nil + } + fakeEngagementSvs.SendSMSFn = func(ctx context.Context, phoneNumbers []string, message string) error { + return nil + } + } + + got, err := i.Signup.RegisterUser(tt.args.ctx, tt.args.input) + if (err != nil) != tt.wantErr { + t.Errorf("SignUpUseCasesImpl.RegisterUser() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !tt.wantErr && got == nil { + t.Errorf("SignUpUseCasesImpl.RegisterUser() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/onboarding/usecases/supplier_integration_test.go b/pkg/onboarding/usecases/supplier_integration_test.go index 261ec7e9..ca755ac6 100644 --- a/pkg/onboarding/usecases/supplier_integration_test.go +++ b/pkg/onboarding/usecases/supplier_integration_test.go @@ -10,7 +10,6 @@ import ( "github.com/savannahghi/firebasetools" "github.com/savannahghi/interserviceclient" "github.com/savannahghi/onboarding/pkg/onboarding/application/dto" - "github.com/savannahghi/onboarding/pkg/onboarding/application/utils" "github.com/savannahghi/onboarding/pkg/onboarding/domain" "github.com/savannahghi/onboarding/pkg/onboarding/infrastructure/database/fb" "github.com/savannahghi/onboarding/pkg/onboarding/presentation/interactor" @@ -2679,115 +2678,115 @@ func clean(newCtx context.Context, testPhoneNumber string, t *testing.T, service } } -func TestCreateCustomerAccount(t *testing.T) { - ctx, _, err := GetTestAuthenticatedContext(t) - if err != nil { - t.Errorf("failed to get test authenticated context: %v", err) - return - } - s, err := InitializeTestService(ctx) - if err != nil { - t.Errorf("unable to initialize test service") - return - } - type args struct { - ctx context.Context - name string - partnerType profileutils.PartnerType - } - tests := []struct { - name string - args args - wantErr bool - }{ - { - name: "happy:) create customer account", - args: args{ - ctx: ctx, - name: *utils.GetRandomName(), - partnerType: profileutils.PartnerTypeConsumer, - }, - wantErr: false, - }, - { - name: "sad:( wrong partner type", - args: args{ - ctx: ctx, - name: *utils.GetRandomName(), - partnerType: profileutils.PartnerTypeCoach, - }, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := s.Supplier.CreateCustomerAccount( - tt.args.ctx, - tt.args.name, - tt.args.partnerType, - ) - if (err != nil) != tt.wantErr { - t.Errorf("SupplierUseCasesImpl.CreateCustomerAccount() error = %v, wantErr %v", - err, - tt.wantErr, - ) - return - } - }) - } -} +// func TestCreateCustomerAccount(t *testing.T) { +// ctx, _, err := GetTestAuthenticatedContext(t) +// if err != nil { +// t.Errorf("failed to get test authenticated context: %v", err) +// return +// } +// s, err := InitializeTestService(ctx) +// if err != nil { +// t.Errorf("unable to initialize test service") +// return +// } +// type args struct { +// ctx context.Context +// name string +// partnerType profileutils.PartnerType +// } +// tests := []struct { +// name string +// args args +// wantErr bool +// }{ +// { +// name: "happy:) create customer account", +// args: args{ +// ctx: ctx, +// name: *utils.GetRandomName(), +// partnerType: profileutils.PartnerTypeConsumer, +// }, +// wantErr: false, +// }, +// { +// name: "sad:( wrong partner type", +// args: args{ +// ctx: ctx, +// name: *utils.GetRandomName(), +// partnerType: profileutils.PartnerTypeCoach, +// }, +// wantErr: true, +// }, +// } +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// err := s.Supplier.CreateCustomerAccount( +// tt.args.ctx, +// tt.args.name, +// tt.args.partnerType, +// ) +// if (err != nil) != tt.wantErr { +// t.Errorf("SupplierUseCasesImpl.CreateCustomerAccount() error = %v, wantErr %v", +// err, +// tt.wantErr, +// ) +// return +// } +// }) +// } +// } -func TestCreateSupplierAccount(t *testing.T) { - ctx, _, err := GetTestAuthenticatedContext(t) - if err != nil { - t.Errorf("failed to get test authenticated context: %v", err) - return - } - s, err := InitializeTestService(ctx) - if err != nil { - t.Errorf("unable to initialize test service") - return - } - type args struct { - ctx context.Context - name string - partnerType profileutils.PartnerType - } - tests := []struct { - name string - args args - want *profileutils.Supplier - wantErr bool - }{ - { - name: "happy:) create supplier account", - args: args{ - ctx: ctx, - name: *utils.GetRandomName(), - partnerType: profileutils.PartnerTypeRider, - }, - wantErr: false, - }, - { - name: "sad:( wrong partner type", - args: args{ - ctx: ctx, - name: *utils.GetRandomName(), - partnerType: profileutils.PartnerTypeConsumer, - }, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := s.Supplier.CreateSupplierAccount(tt.args.ctx, tt.args.name, tt.args.partnerType) - if (err != nil) != tt.wantErr { - t.Errorf("SupplierUseCasesImpl.CreateSupplierAccount() error = %v, wantErr %v", err, tt.wantErr) - return - } - }) - } -} +// func TestCreateSupplierAccount(t *testing.T) { +// ctx, _, err := GetTestAuthenticatedContext(t) +// if err != nil { +// t.Errorf("failed to get test authenticated context: %v", err) +// return +// } +// s, err := InitializeTestService(ctx) +// if err != nil { +// t.Errorf("unable to initialize test service") +// return +// } +// type args struct { +// ctx context.Context +// name string +// partnerType profileutils.PartnerType +// } +// tests := []struct { +// name string +// args args +// want *profileutils.Supplier +// wantErr bool +// }{ +// { +// name: "happy:) create supplier account", +// args: args{ +// ctx: ctx, +// name: *utils.GetRandomName(), +// partnerType: profileutils.PartnerTypeRider, +// }, +// wantErr: false, +// }, +// { +// name: "sad:( wrong partner type", +// args: args{ +// ctx: ctx, +// name: *utils.GetRandomName(), +// partnerType: profileutils.PartnerTypeConsumer, +// }, +// wantErr: true, +// }, +// } +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// err := s.Supplier.CreateSupplierAccount(tt.args.ctx, tt.args.name, tt.args.partnerType) +// if (err != nil) != tt.wantErr { +// t.Errorf("SupplierUseCasesImpl.CreateSupplierAccount() error = %v, wantErr %v", err, tt.wantErr) +// return +// } +// }) +// } +// } // func TestSupplierUseCasesImpl_CheckSupplierKYCSubmitted(t *testing.T) { // ctx, _, err := GetTestAuthenticatedContext(t) diff --git a/pkg/onboarding/usecases/ussd/main_integration_test.go b/pkg/onboarding/usecases/ussd/main_integration_test.go index 0c905101..14974801 100644 --- a/pkg/onboarding/usecases/ussd/main_integration_test.go +++ b/pkg/onboarding/usecases/ussd/main_integration_test.go @@ -181,13 +181,13 @@ func InitializeFakeOnboardingInteractor() (*interactor.Interactor, error) { sms := usecases.NewSMSUsecase(r, ext) role := usecases.NewRoleUseCases(r, ext) admin := usecases.NewAdminUseCases(r, engagementSvc, ext, userpin) - agent := usecases.NewAgentUseCases(r, engagementSvc, ext, userpin) + agent := usecases.NewAgentUseCases(r, engagementSvc, ext, userpin, role) aitUssd := ussd.NewUssdUsecases(r, ext, profile, userpin, su, pinExt, ps, crmExt) adminSrv := adminSrv.NewService(ext) i, err := interactor.NewOnboardingInteractor( - r, profile, su, supplier, login, + profile, su, supplier, login, survey, userpin, erpSvc, chargemasterSvc, engagementSvc, messagingSvc, nhif, ps, sms, aitUssd, agent, admin, ediSvc, adminSrv, crmExt, role, @@ -318,12 +318,12 @@ func InitializeFakeUSSDTestService() (*interactor.Interactor, error) { sms := usecases.NewSMSUsecase(r, ext) role := usecases.NewRoleUseCases(r, ext) admin := usecases.NewAdminUseCases(r, engagementSvc, ext, userpin) - agent := usecases.NewAgentUseCases(r, engagementSvc, ext, userpin) + agent := usecases.NewAgentUseCases(r, engagementSvc, ext, userpin, role) aitUssd := ussd.NewUssdUsecases(r, ext, profile, userpin, su, pinExt, ps, crmSvc) adminSrv := adminSrv.NewService(ext) i, err := interactor.NewOnboardingInteractor( - r, profile, su, supplier, login, + profile, su, supplier, login, survey, userpin, erpSvc, chargemasterSvc, engagementSvc, messagingSvc, nhif, ps, sms, aitUssd, agent, admin, ediSvc, adminSrv, crmExt, role, diff --git a/tests/config_test.go b/tests/config_test.go index bf6fb9ce..35d43dbd 100644 --- a/tests/config_test.go +++ b/tests/config_test.go @@ -30,6 +30,7 @@ import ( "github.com/savannahghi/onboarding/pkg/onboarding/infrastructure/services/chargemaster" "github.com/savannahghi/onboarding/pkg/onboarding/infrastructure/services/edi" "github.com/savannahghi/onboarding/pkg/onboarding/infrastructure/services/engagement" + "github.com/savannahghi/onboarding/pkg/onboarding/presentation/interactor" "github.com/savannahghi/profileutils" "github.com/savannahghi/serverutils" "github.com/sirupsen/logrus" @@ -39,7 +40,6 @@ import ( "github.com/savannahghi/onboarding/pkg/onboarding/infrastructure/services/messaging" pubsubmessaging "github.com/savannahghi/onboarding/pkg/onboarding/infrastructure/services/pubsub" "github.com/savannahghi/onboarding/pkg/onboarding/presentation" - "github.com/savannahghi/onboarding/pkg/onboarding/presentation/interactor" "github.com/savannahghi/onboarding/pkg/onboarding/repository" "github.com/savannahghi/onboarding/pkg/onboarding/usecases" erp "gitlab.slade360emr.com/go/commontools/accounting/pkg/usecases" @@ -55,13 +55,18 @@ const ( testChargeMasterBranchID = "94294577-6b27-4091-9802-1ce0f2ce4153" engagementService = "engagement" ediService = "edi" + testRoleName = "Test Role" + testPIN = "2030" ) /// these are set up once in TestMain and used by all the acceptance tests in // this package -var srv *http.Server -var baseURL string -var serverErr error +var ( + srv *http.Server + baseURL string + serverErr error + testInteractor *interactor.Interactor +) func mapToJSONReader(m map[string]interface{}) (io.Reader, error) { bs, err := json.Marshal(m) @@ -153,6 +158,7 @@ func InitializeTestService(ctx context.Context) (*interactor.Interactor, error) su := usecases.NewSignUpUseCases(repo, profile, userpin, supplier, ext, engage, ps, edi) nhif := usecases.NewNHIFUseCases(repo, profile, ext, engage) sms := usecases.NewSMSUsecase(repo, ext) + role := usecases.NewRoleUseCases(repo, ext) return &interactor.Interactor{ Onboarding: profile, @@ -167,6 +173,7 @@ func InitializeTestService(ctx context.Context) (*interactor.Interactor, error) NHIF: nhif, PubSub: ps, SMS: sms, + Role: role, }, nil } @@ -183,7 +190,7 @@ func composeInValidUserPayload(t *testing.T) *dto.SignUpInput { } func composeValidUserPayload(t *testing.T, phone string) (*dto.SignUpInput, error) { - pin := "2030" + pin := testPIN flavour := feedlib.FlavourPro otp, err := generateTestOTP(t, phone) if err != nil { @@ -197,7 +204,29 @@ func composeValidUserPayload(t *testing.T, phone string) (*dto.SignUpInput, erro }, nil } -func composeSMSMessageDataPayload(t *testing.T, payload *dto.AfricasTalkingMessage) *strings.Reader { +func composeValidRolePayload(t *testing.T, roleName string) *dto.RoleInput { + ctx := context.Background() + + var allScopes []string + + perms, _ := profileutils.AllPermissions(ctx) + for _, perm := range perms { + allScopes = append(allScopes, perm.Scope) + } + + role := dto.RoleInput{ + Name: roleName, + Description: "Role for running tests", + Scopes: allScopes, + } + + return &role +} + +func composeSMSMessageDataPayload( + t *testing.T, + payload *dto.AfricasTalkingMessage, +) *strings.Reader { data := url.Values{} data.Set("date", payload.Date) data.Set("from", payload.From) @@ -221,28 +250,28 @@ func composeUSSDPayload(t *testing.T, payload *dto.SessionDetails) *strings.Read } func CreateTestUserByPhone(t *testing.T, phone string) (*profileutils.UserResponse, error) { - client := http.DefaultClient validPayload, err := composeValidUserPayload(t, phone) if err != nil { return nil, fmt.Errorf("failed to compose a valid payload: %v", err) } + bs, err := json.Marshal(validPayload) if err != nil { return nil, fmt.Errorf("unable to marshal test item to JSON: %s", err) } payload := bytes.NewBuffer(bs) + url := fmt.Sprintf("%s/create_user_by_phone", baseURL) + r, err := http.NewRequest( http.MethodPost, url, payload, ) - if err != nil { return nil, fmt.Errorf("can't create new request: %v", err) } - if r == nil { return nil, fmt.Errorf("nil request") } @@ -250,6 +279,8 @@ func CreateTestUserByPhone(t *testing.T, phone string) (*profileutils.UserRespon r.Header.Add("Accept", "application/json") r.Header.Add("Content-Type", "application/json") + client := http.DefaultClient + resp, err := client.Do(r) if err != nil { return nil, fmt.Errorf("HTTP error: %v", err) @@ -258,6 +289,7 @@ func CreateTestUserByPhone(t *testing.T, phone string) (*profileutils.UserRespon // if resp.StatusCode != http.StatusCreated { // return nil, fmt.Errorf("failed to create user: expected status to be %v got %v ", http.StatusCreated, resp.StatusCode) // } + data, err := ioutil.ReadAll(resp.Body) if err != nil { log.Printf("HTTP error: %v", err) @@ -271,6 +303,46 @@ func CreateTestUserByPhone(t *testing.T, phone string) (*profileutils.UserRespon return &userResponse, nil } +func CreateTestRole(t *testing.T, roleName string) (*dto.RoleOutput, error) { + ctx := getTestAuthenticatedContext(t) + + validPayload := composeValidRolePayload(t, roleName) + + role, err := testInteractor.Role.CreateUnauthorizedRole(ctx, *validPayload) + if err != nil { + return nil, err + } + + return role, nil +} + +func AssignTestRole(t *testing.T, profileID, roleID string) (bool, error) { + ctx := getTestAuthenticatedContext(t) + + _, err := testInteractor.Role.AssignRole(ctx, profileID, roleID) + if err != nil { + return false, err + } + + return true, nil +} + +func RemoveTestRole(t *testing.T, name string) (bool, error) { + ctx := getTestAuthenticatedContext(t) + + role, err := testInteractor.Role.GetRoleByName(ctx, testRoleName) + if err != nil { + return false, err + } + + _, err = testInteractor.Role.DeleteRole(ctx, role.ID) + if err != nil { + return false, err + } + + return true, nil +} + func TestCreateTestUserByPhone(t *testing.T) { userResponse, err := CreateTestUserByPhone(t, interserviceclient.TestUserPhoneNumber) if err != nil { @@ -281,28 +353,50 @@ func TestCreateTestUserByPhone(t *testing.T) { t.Errorf("got a nil user response") return } + + role, err := CreateTestRole(t, testRoleName) + if err != nil { + t.Errorf("cannot create test role with err: %v", err) + return + } + + _, err = AssignTestRole(t, userResponse.Profile.ID, role.ID) + if err != nil { + t.Errorf("cannot assign test role with err: %v", err) + return + } + + // perform tear down; remove user + _, err = RemoveTestUserByPhone(t, interserviceclient.TestUserPhoneNumber) + if err != nil { + t.Errorf("unable to remove test user: %s", err) + } } func RemoveTestUserByPhone(t *testing.T, phone string) (bool, error) { - client := http.DefaultClient + _, err := RemoveTestRole(t, testRoleName) + if err != nil { + return false, fmt.Errorf("unable to remove test role: %s", err) + } + validPayload := &dto.PhoneNumberPayload{PhoneNumber: &phone} bs, err := json.Marshal(validPayload) if err != nil { return false, fmt.Errorf("unable to marshal test item to JSON: %s", err) } payload := bytes.NewBuffer(bs) + url := fmt.Sprintf("%s/remove_user", baseURL) + r, err := http.NewRequest( http.MethodPost, url, payload, ) - if err != nil { return false, fmt.Errorf("can't create new request: %v", err) } - if r == nil { return false, fmt.Errorf("nil request") } @@ -310,14 +404,21 @@ func RemoveTestUserByPhone(t *testing.T, phone string) (bool, error) { r.Header.Add("Accept", "application/json") r.Header.Add("Content-Type", "application/json") + client := http.DefaultClient + resp, err := client.Do(r) if err != nil { return false, fmt.Errorf("HTTP error: %v", err) } if resp.StatusCode != http.StatusOK { - return false, fmt.Errorf("failed to remove user: expected status to be %v got %v ", http.StatusCreated, resp.StatusCode) + return false, fmt.Errorf( + "failed to remove user: expected status to be %v got %v ", + http.StatusCreated, + resp.StatusCode, + ) } + return true, nil } @@ -333,6 +434,18 @@ func TestRemoveTestUserByPhone(t *testing.T) { return } + role, err := CreateTestRole(t, testRoleName) + if err != nil { + t.Errorf("cannot create test role with err: %v", err) + return + } + + _, err = AssignTestRole(t, userResponse.Profile.ID, role.ID) + if err != nil { + t.Errorf("cannot assign test role with err: %v", err) + return + } + removed, err := RemoveTestUserByPhone(t, phone) if err != nil { t.Errorf("an error occurred: %v", err) @@ -346,109 +459,118 @@ func TestRemoveTestUserByPhone(t *testing.T) { func generateTestOTP(t *testing.T, phone string) (*profileutils.OtpResponse, error) { ctx := context.Background() - s, err := InitializeTestService(ctx) - if err != nil { - return nil, fmt.Errorf("unable to initialize test service: %v", err) - } testAppID := uuid.New().String() - return s.Engagement.GenerateAndSendOTP(ctx, phone, &testAppID) + return testInteractor.Engagement.GenerateAndSendOTP(ctx, phone, &testAppID) } func setPrimaryEmailAddress(ctx context.Context, t *testing.T, emailAddress string) error { - s, err := InitializeTestService(ctx) - if err != nil { - return fmt.Errorf("unable to initialize test service: %v", err) - } - return s.Onboarding.UpdatePrimaryEmailAddress(ctx, emailAddress) + return testInteractor.Onboarding.UpdatePrimaryEmailAddress(ctx, emailAddress) } func updateBioData(ctx context.Context, t *testing.T, data profileutils.BioData) error { - s, err := InitializeTestService(ctx) + + return testInteractor.Onboarding.UpdateBioData(ctx, data) +} + +func addPartnerType( + ctx context.Context, + t *testing.T, + name *string, + partnerType profileutils.PartnerType, +) (bool, error) { + + return testInteractor.Supplier.AddPartnerType(ctx, name, &partnerType) +} + +func getTestUserCredentials(t *testing.T) (*profileutils.UserResponse, error) { + ctx := context.Background() + + phone := interserviceclient.TestUserPhoneNumber + pin := testPIN + flavour := feedlib.FlavourPro + userResponse, err := testInteractor.Login.LoginByPhone(ctx, phone, pin, flavour) if err != nil { - return fmt.Errorf("unable to initialize test service: %v", err) + return nil, fmt.Errorf("unable to get role by name: %v", err) } - return s.Onboarding.UpdateBioData(ctx, data) + return userResponse, nil } -func addPartnerType(ctx context.Context, t *testing.T, name *string, partnerType profileutils.PartnerType) (bool, error) { - s, err := InitializeTestService(ctx) +func getRoleByName(t *testing.T, name string) (*dto.RoleOutput, error) { + ctx := context.Background() + + phone := interserviceclient.TestUserPhoneNumber + pin := testPIN + flavour := feedlib.FlavourPro + userResponse, err := testInteractor.Login.LoginByPhone(ctx, phone, pin, flavour) if err != nil { - return false, fmt.Errorf("unable to initialize test service: %v", err) + return nil, fmt.Errorf("unable to get role by name: %v", err) } - return s.Supplier.AddPartnerType(ctx, name, &partnerType) -} + authCred := &auth.Token{UID: userResponse.Auth.UID} + authenticatedContext := context.WithValue( + ctx, + firebasetools.AuthTokenContextKey, + authCred, + ) -func setUpSupplier(ctx context.Context, t *testing.T, accountType profileutils.AccountType) (*profileutils.Supplier, error) { - s, err := InitializeTestService(ctx) + role, err := testInteractor.Role.GetRoleByName(authenticatedContext, name) if err != nil { - return nil, fmt.Errorf("unable to initialize test service: %v", err) + return nil, fmt.Errorf("unable to get role by name: %v", err) } - return s.Supplier.SetUpSupplier(ctx, accountType) + + return role, nil +} + +func setUpSupplier( + ctx context.Context, + t *testing.T, + accountType profileutils.AccountType, +) (*profileutils.Supplier, error) { + + return testInteractor.Supplier.SetUpSupplier(ctx, accountType) } func setUpLoggedInTestUserGraphHeaders(t *testing.T) map[string]string { // create a user and their profile phoneNumber := interserviceclient.TestUserPhoneNumber - resp, err := CreateTestUserByPhone(t, phoneNumber) + userResponse, err := CreateTestUserByPhone(t, phoneNumber) if err != nil { - log.Printf("unable to create a test user: %s", err) + t.Errorf("unable to create a test user: %s", err) return nil } - if resp.Profile.ID == "" { + if userResponse.Profile.ID == "" { t.Errorf(" user profile id should not be empty") return nil } - if len(resp.Profile.VerifiedUIDS) == 0 { + if len(userResponse.Profile.VerifiedUIDS) == 0 { t.Errorf(" user profile VerifiedUIDS should not be empty") return nil } - logrus.Infof("profile from create user : %v", resp.Profile) + logrus.Infof("profile from create user : %v", userResponse.Profile) - logrus.Infof("uid from create user : %v", resp.Auth.UID) + logrus.Infof("uid from create user : %v", userResponse.Auth.UID) - return getGraphHeaders(*resp.Auth.IDToken) -} + role, err := CreateTestRole(t, testRoleName) + if err != nil { + t.Errorf("cannot create test role with err: %v", err) + return nil + } + + _, err = AssignTestRole(t, userResponse.Profile.ID, role.ID) + if err != nil { + t.Errorf("cannot assign test role with err: %v", err) + return nil + } -// func setRoleForUserWithPhone(phoneNumber string, role profileutils.RoleType, headers map[string]string) error { -// url := fmt.Sprintf("%s/roles/add_user_role", baseURL) - -// payload := dto.RolePayload{ -// PhoneNumber: &phoneNumber, -// Role: &role, -// } -// body, err := json.Marshal(payload) -// if err != nil { -// return fmt.Errorf("unable to marshal payload to JSON: %s", err) -// } - -// request, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(body)) -// if err != nil { -// return fmt.Errorf("unable to compose request: %s", err) -// } -// if request == nil { -// return fmt.Errorf("nil request") -// } - -// for header, value := range headers { -// request.Header.Add(header, value) -// } - -// response, err := http.DefaultClient.Do(request) -// if err != nil { -// return fmt.Errorf("request error: %s", err) -// } - -// if response.StatusCode != http.StatusOK { -// return fmt.Errorf("failed to set role for user : expected status to be %v, but got %v ", http.StatusOK, response.StatusCode) -// } -// return nil -// } + headers := getGraphHeaders(*userResponse.Auth.IDToken) + + return headers +} func getGraphHeaders(idToken string) map[string]string { return req.Header{ @@ -458,6 +580,23 @@ func getGraphHeaders(idToken string) map[string]string { } } +func getTestAuthenticatedContext(t *testing.T) context.Context { + ctx := context.Background() + user, err := getTestUserCredentials(t) + if err != nil { + t.Errorf("error getting test user credentials:%v", err) + } + + authCred := &auth.Token{UID: user.Auth.UID} + authenticatedContext := context.WithValue( + ctx, + firebasetools.AuthTokenContextKey, + authCred, + ) + + return authenticatedContext +} + func TestMain(m *testing.M) { // setup os.Setenv("ENVIRONMENT", "staging") @@ -473,10 +612,19 @@ func TestMain(m *testing.M) { ) // set the globals if serverErr != nil { log.Printf("unable to start test server: %s", serverErr) + return } fsc, _ := initializeAcceptanceTestFirebaseClient(ctx) + i, err := InitializeTestService(ctx) + if err != nil { + log.Printf("unable to initialize test service: %v", err) + return + } + + testInteractor = i + purgeRecords := func() { if serverutils.MustGetEnvVar(domain.Repo) == domain.FirebaseRepository { r := fb.Repository{} @@ -495,6 +643,7 @@ func TestMain(m *testing.M) { r.GetProfileNudgesCollectionName(), r.GetSMSCollectionName(), r.GetUSSDDataCollectionName(), + r.GetRolesCollectionName(), } for _, collection := range collections { ref := fsc.Collection(collection) @@ -612,7 +761,12 @@ func TestHealthStatusCheck(t *testing.T) { } if tt.wantStatus != resp.StatusCode { - t.Errorf("expected status %d, got %d and response %s", tt.wantStatus, resp.StatusCode, string(data)) + t.Errorf( + "expected status %d, got %d and response %s", + tt.wantStatus, + resp.StatusCode, + string(data), + ) return } diff --git a/tests/login_acceptance_test.go b/tests/login_acceptance_test.go index 53e4ef49..bdda4437 100644 --- a/tests/login_acceptance_test.go +++ b/tests/login_acceptance_test.go @@ -19,7 +19,7 @@ import ( ) func composeInvalidUserPINPayload(t *testing.T) *dto.LoginPayload { - phone := interserviceclient.TestUserPhoneNumberWithPin + phone := interserviceclient.TestUserPhoneNumber pin := "" // empty pin flavour := feedlib.FlavourPro payload := &dto.LoginPayload{ @@ -68,7 +68,7 @@ func composeInvalidUserPhonePayload(t *testing.T) *dto.LoginPayload { } func composeWrongFlavourPayload(t *testing.T) *dto.LoginPayload { - phone := interserviceclient.TestUserPhoneNumberWithPin + phone := interserviceclient.TestUserPhoneNumber pin := interserviceclient.TestUserPin payload := &dto.LoginPayload{ PhoneNumber: &phone, @@ -90,6 +90,18 @@ func TestLoginInByPhone(t *testing.T) { return } + role, err := CreateTestRole(t, testRoleName) + if err != nil { + t.Errorf("cannot create test role with err: %v", err) + return + } + + _, err = AssignTestRole(t, user.Profile.ID, role.ID) + if err != nil { + t.Errorf("cannot assign test role with err: %v", err) + return + } + client := http.DefaultClient validPayload, err := composeValidUserPayload(t, phoneNumber) if err != nil { @@ -424,12 +436,25 @@ func TestLoginAsAnonymous(t *testing.T) { func TestRefreshToken(t *testing.T) { client := http.DefaultClient - phoneNumber := interserviceclient.TestUserPhoneNumberWithPin + phoneNumber := interserviceclient.TestUserPhoneNumber user, err := CreateTestUserByPhone(t, phoneNumber) if err != nil { t.Errorf("failed to create a user by phone %v", err) return } + + role, err := CreateTestRole(t, testRoleName) + if err != nil { + t.Errorf("cannot create test role with err: %v", err) + return + } + + _, err = AssignTestRole(t, user.Profile.ID, role.ID) + if err != nil { + t.Errorf("cannot assign test role with err: %v", err) + return + } + validToken := user.Auth.RefreshToken validPayload := &dto.RefreshTokenPayload{ RefreshToken: &validToken, @@ -565,23 +590,16 @@ func TestRefreshToken(t *testing.T) { } }) } -} -func TestResumeWithPin(t *testing.T) { - // create a user and their profile - phoneNumber := interserviceclient.TestUserPhoneNumber - user, err := CreateTestUserByPhone(t, phoneNumber) + // perform tear down; remove user + _, err = RemoveTestUserByPhone(t, phoneNumber) if err != nil { - t.Errorf("failed to create a user by phone %v", err) - return + t.Errorf("unable to remove test user: %s", err) } +} - idToken := user.Auth.IDToken - headers, err := CreatedUserGraphQLHeaders(idToken) - if err != nil { - t.Errorf("error in getting headers: %w", err) - return - } +func TestResumeWithPin(t *testing.T) { + headers := setUpLoggedInTestUserGraphHeaders(t) graphQLURL := fmt.Sprintf("%s/%s", baseURL, "graphql") @@ -606,7 +624,7 @@ func TestResumeWithPin(t *testing.T) { query: map[string]interface{}{ "query": graphqlMutation, "variables": map[string]interface{}{ - "pin": "2030", + "pin": testPIN, }, }, }, @@ -693,7 +711,7 @@ func TestResumeWithPin(t *testing.T) { } // perform tear down; remove user - _, err = RemoveTestUserByPhone(t, interserviceclient.TestUserPhoneNumber) + _, err := RemoveTestUserByPhone(t, interserviceclient.TestUserPhoneNumber) if err != nil { t.Errorf("unable to remove test user: %s", err) } diff --git a/tests/nhif_acceptance_test.go b/tests/nhif_acceptance_test.go index 66fd68b7..45fa20a8 100644 --- a/tests/nhif_acceptance_test.go +++ b/tests/nhif_acceptance_test.go @@ -20,19 +20,7 @@ import ( ) func TestAddNHIFDetails(t *testing.T) { - phoneNumber := interserviceclient.TestUserPhoneNumber - user, err := CreateTestUserByPhone(t, phoneNumber) - if err != nil { - t.Errorf("failed to create a user by phone %v", err) - return - } - - idToken := user.Auth.IDToken - headers, err := CreatedUserGraphQLHeaders(idToken) - if err != nil { - t.Errorf("error in getting headers: %w", err) - return - } + headers := setUpLoggedInTestUserGraphHeaders(t) graphQLURL := fmt.Sprintf("%s/%s", baseURL, "graphql") @@ -185,7 +173,7 @@ func TestAddNHIFDetails(t *testing.T) { }) } // perform tear down; remove user - _, err = RemoveTestUserByPhone(t, phoneNumber) + _, err := RemoveTestUserByPhone(t, interserviceclient.TestUserPhoneNumber) if err != nil { t.Errorf("unable to remove test user: %s", err) } @@ -193,10 +181,6 @@ func TestAddNHIFDetails(t *testing.T) { func AddTestNHIFDetails(t *testing.T, user *profileutils.UserResponse) error { ctx := context.Background() - i, err := InitializeTestService(ctx) - if err != nil { - return fmt.Errorf("an error occurred: %v", err) - } authCred := &auth.Token{UID: user.Auth.UID} authenticatedContext := context.WithValue( @@ -205,7 +189,7 @@ func AddTestNHIFDetails(t *testing.T, user *profileutils.UserResponse) error { authCred, ) - _, err = i.NHIF.AddNHIFDetails( + _, err := testInteractor.NHIF.AddNHIFDetails( authenticatedContext, dto.NHIFDetailsInput{ MembershipNumber: fmt.Sprintln(time.Now().Unix()), @@ -231,23 +215,15 @@ func TestAddTestNHIFDetails(t *testing.T) { return } - err = AddTestNHIFDetails(t, user) + role, err := CreateTestRole(t, testRoleName) if err != nil { - t.Errorf("an error occurred: %v", err) + t.Errorf("cannot create test role with err: %v", err) return } - // perform tear down; remove user - _, err = RemoveTestUserByPhone(t, phoneNumber) - if err != nil { - t.Errorf("unable to remove test user: %s", err) - } -} -func TestGetNHIFDetails(t *testing.T) { - phoneNumber := interserviceclient.TestUserPhoneNumber - user, err := CreateTestUserByPhone(t, phoneNumber) + _, err = AssignTestRole(t, user.Profile.ID, role.ID) if err != nil { - t.Errorf("failed to create a user by phone %v", err) + t.Errorf("cannot assign test role with err: %v", err) return } @@ -257,12 +233,14 @@ func TestGetNHIFDetails(t *testing.T) { return } - idToken := user.Auth.IDToken - headers, err := CreatedUserGraphQLHeaders(idToken) + // perform tear down; remove user + _, err = RemoveTestUserByPhone(t, phoneNumber) if err != nil { - t.Errorf("error in getting headers: %w", err) - return + t.Errorf("unable to remove test user: %s", err) } +} +func TestGetNHIFDetails(t *testing.T) { + headers := setUpLoggedInTestUserGraphHeaders(t) graphQLURL := fmt.Sprintf("%s/%s", baseURL, "graphql") graphqlQuery := `query NHIFDetails{ @@ -381,7 +359,7 @@ func TestGetNHIFDetails(t *testing.T) { }) } // perform tear down; remove user - _, err = RemoveTestUserByPhone(t, phoneNumber) + _, err := RemoveTestUserByPhone(t, interserviceclient.TestUserPhoneNumber) if err != nil { t.Errorf("unable to remove test user: %s", err) } diff --git a/tests/otp_acceptance_test.go b/tests/otp_acceptance_test.go index eadf19de..67a8abd6 100644 --- a/tests/otp_acceptance_test.go +++ b/tests/otp_acceptance_test.go @@ -53,6 +53,19 @@ func TestSendRetryOTP(t *testing.T) { t.Errorf("nil user found") return } + + role, err := CreateTestRole(t, testRoleName) + if err != nil { + t.Errorf("cannot create test role with err: %v", err) + return + } + + _, err = AssignTestRole(t, user.Profile.ID, role.ID) + if err != nil { + t.Errorf("cannot assign test role with err: %v", err) + return + } + validWAPayload := composeValidWARetryOTPPayload(t) bs, err := json.Marshal(validWAPayload) if err != nil { diff --git a/tests/pin_acceptance_test.go b/tests/pin_acceptance_test.go index 9e49bda2..cc7aca69 100644 --- a/tests/pin_acceptance_test.go +++ b/tests/pin_acceptance_test.go @@ -10,11 +10,9 @@ import ( "testing" "time" - "github.com/savannahghi/firebasetools" "github.com/savannahghi/interserviceclient" "github.com/savannahghi/onboarding/pkg/onboarding/application/dto" "github.com/savannahghi/onboarding/pkg/onboarding/domain" - "gitlab.slade360emr.com/go/apiclient" ) // func composeInValidPinPayload(t *testing.T) *domain.SetPINRequest { @@ -27,8 +25,8 @@ import ( func composeValidChangePinPayload(t *testing.T, otp string) *domain.ChangePINRequest { return &domain.ChangePINRequest{ - PhoneNumber: interserviceclient.TestUserPhoneNumberWithPin, - PIN: "1234", + PhoneNumber: interserviceclient.TestUserPhoneNumber, + PIN: testPIN, OTP: otp, } } @@ -36,7 +34,7 @@ func composeValidChangePinPayload(t *testing.T, otp string) *domain.ChangePINReq func composeInValidChangePinPayload(t *testing.T, otp string) *domain.ChangePINRequest { return &domain.ChangePINRequest{ PhoneNumber: "", - PIN: "1234", + PIN: testPIN, OTP: otp, } @@ -65,7 +63,7 @@ func composeInValidPinResetPayload(t *testing.T) *dto.PhoneNumberPayload { } func composeValidPinResetPayload(t *testing.T) *dto.PhoneNumberPayload { - validNumber := interserviceclient.TestUserPhoneNumberWithPin + validNumber := interserviceclient.TestUserPhoneNumber return &dto.PhoneNumberPayload{ PhoneNumber: &validNumber, } @@ -74,13 +72,25 @@ func composeValidPinResetPayload(t *testing.T) *dto.PhoneNumberPayload { func TestResetPin(t *testing.T) { client := http.DefaultClient // create a user and their profile - phoneNumber := interserviceclient.TestUserPhoneNumberWithPin - _, err := CreateTestUserByPhone(t, phoneNumber) + phoneNumber := interserviceclient.TestUserPhoneNumber + user, err := CreateTestUserByPhone(t, phoneNumber) if err != nil { t.Errorf("failed to create a user by phone %v", err) return } + role, err := CreateTestRole(t, testRoleName) + if err != nil { + t.Errorf("cannot create test role with err: %v", err) + return + } + + _, err = AssignTestRole(t, user.Profile.ID, role.ID) + if err != nil { + t.Errorf("cannot assign test role with err: %v", err) + return + } + // valid change pin payload otpResp, err := generateTestOTP(t, phoneNumber) if err != nil { @@ -203,12 +213,25 @@ func TestResetPin(t *testing.T) { func TestRequestPINReset(t *testing.T) { client := http.DefaultClient // create a user and their profile - phoneNumber := interserviceclient.TestUserPhoneNumberWithPin - _, err := CreateTestUserByPhone(t, phoneNumber) + phoneNumber := interserviceclient.TestUserPhoneNumber + user, err := CreateTestUserByPhone(t, phoneNumber) if err != nil { t.Errorf("failed to create a user by phone %v", err) return } + + role, err := CreateTestRole(t, testRoleName) + if err != nil { + t.Errorf("cannot create test role with err: %v", err) + return + } + + _, err = AssignTestRole(t, user.Profile.ID, role.ID) + if err != nil { + t.Errorf("cannot assign test role with err: %v", err) + return + } + // valid change pin payload validPayload := composeValidPinResetPayload(t) bs, err := json.Marshal(validPayload) @@ -317,21 +340,8 @@ func TestRequestPINReset(t *testing.T) { } func TestUpdateUserPIN(t *testing.T) { - // create a user and their profile - phoneNumber := interserviceclient.TestUserPhoneNumber - _, err := CreateTestUserByPhone(t, phoneNumber) - if err != nil { - t.Errorf("failed to create a user by phone %v", err) - return - } - ctx := firebasetools.GetAuthenticatedContext(t) - graphQLURL := fmt.Sprintf("%s/%s", baseURL, "graphql") - headers, err := apiclient.GetGraphQLHeaders(ctx) - if err != nil { - t.Errorf("error in getting headers: %w", err) - return - } + headers := setUpLoggedInTestUserGraphHeaders(t) graphqlMutation := ` mutation updateUserPIN($phone:String!, $pin:String!){ @@ -355,7 +365,7 @@ func TestUpdateUserPIN(t *testing.T) { "query": graphqlMutation, "variables": map[string]interface{}{ "phone": interserviceclient.TestUserPhoneNumber, - "pin": "1234", + "pin": testPIN, }, }, }, @@ -369,7 +379,7 @@ func TestUpdateUserPIN(t *testing.T) { "query": graphqlMutation, "variables": map[string]interface{}{ "phone": interserviceclient.TestUserPhoneNumberWithPin, - "pin": "1234", + "pin": testPIN, }, }, }, @@ -467,7 +477,7 @@ func TestUpdateUserPIN(t *testing.T) { }) } // perform tear down; remove user - _, err = RemoveTestUserByPhone(t, phoneNumber) + _, err := RemoveTestUserByPhone(t, interserviceclient.TestUserPhoneNumber) if err != nil { t.Errorf("unable to remove test user: %s", err) } diff --git a/tests/profile_acceptance_test.go b/tests/profile_acceptance_test.go index 8e2acf44..c79d51d8 100644 --- a/tests/profile_acceptance_test.go +++ b/tests/profile_acceptance_test.go @@ -583,6 +583,19 @@ func TestUpdateCovers(t *testing.T) { t.Errorf("failed to create a user by phone %v", err) return } + + role, err := CreateTestRole(t, testRoleName) + if err != nil { + t.Errorf("cannot create test role with err: %v", err) + return + } + + _, err = AssignTestRole(t, user.Profile.ID, role.ID) + if err != nil { + t.Errorf("cannot assign test role with err: %v", err) + return + } + if user == nil { t.Errorf("nil user found") return @@ -748,6 +761,19 @@ func TestEmailsProfileAttributes(t *testing.T) { t.Errorf("nil user found") return } + + role, err := CreateTestRole(t, testRoleName) + if err != nil { + t.Errorf("cannot create test role with err: %v", err) + return + } + + _, err = AssignTestRole(t, user.Profile.ID, role.ID) + if err != nil { + t.Errorf("cannot assign test role with err: %v", err) + return + } + uids := &dto.UIDsPayload{ UIDs: []string{user.Auth.UID}, } @@ -881,6 +907,19 @@ func TestPhoneNumbersProfileAttributes(t *testing.T) { t.Errorf("nil user found") return } + + role, err := CreateTestRole(t, testRoleName) + if err != nil { + t.Errorf("cannot create test role with err: %v", err) + return + } + + _, err = AssignTestRole(t, user.Profile.ID, role.ID) + if err != nil { + t.Errorf("cannot assign test role with err: %v", err) + return + } + uids := &dto.UIDsPayload{ UIDs: []string{user.Auth.UID}, } @@ -1014,6 +1053,19 @@ func TestTokensProfileAttributes(t *testing.T) { t.Errorf("nil user found") return } + + role, err := CreateTestRole(t, testRoleName) + if err != nil { + t.Errorf("cannot create test role with err: %v", err) + return + } + + _, err = AssignTestRole(t, user.Profile.ID, role.ID) + if err != nil { + t.Errorf("cannot assign test role with err: %v", err) + return + } + uids := &dto.UIDsPayload{ UIDs: []string{user.Auth.UID}, } diff --git a/tests/roles_acceptance_test.go b/tests/roles_acceptance_test.go new file mode 100644 index 00000000..202d726c --- /dev/null +++ b/tests/roles_acceptance_test.go @@ -0,0 +1,814 @@ +package tests + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "testing" + "time" + + "github.com/savannahghi/interserviceclient" +) + +func TestCreateRole(t *testing.T) { + graphQLURL := fmt.Sprintf("%s/%s", baseURL, "graphql") + headers := setUpLoggedInTestUserGraphHeaders(t) + + graphqlMutation := ` + mutation createRole($input: RoleInput!) { + createRole(input: $input) { + id + name + description + scopes + permissions { + scope + description + group + allowed + } + } + } + ` + + type args struct { + query map[string]interface{} + } + + tests := []struct { + name string + args args + wantStatus int + wantErr bool + }{ + { + name: "success: create a new role", + args: args{ + query: map[string]interface{}{ + "query": graphqlMutation, + "variables": map[string]interface{}{ + "input": map[string]interface{}{ + "name": "Acceptance Test Role", + "description": "Role creation Test", + "scopes": []string{"agent.view"}, + }, + }, + }, + }, + wantStatus: http.StatusOK, + wantErr: false, + }, + { + name: "fail:cannot create a new role with similar name", + args: args{ + query: map[string]interface{}{ + "query": graphqlMutation, + "variables": map[string]interface{}{ + "input": map[string]interface{}{ + "name": testRoleName, // this role name exists and is used to run tests + "description": "Role creation Test", + "scopes": []string{"agent.view"}, + }, + }, + }, + }, + wantStatus: http.StatusOK, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + body, err := mapToJSONReader(tt.args.query) + + if err != nil { + t.Errorf("unable to get GQL JSON io Reader: %s", err) + return + } + + r, err := http.NewRequest( + http.MethodPost, + graphQLURL, + body, + ) + + if err != nil { + t.Errorf("unable to compose request: %s", err) + return + } + + if r == nil { + t.Errorf("nil request") + return + } + + for k, v := range headers { + r.Header.Add(k, v) + } + client := http.Client{ + Timeout: time.Second * testHTTPClientTimeout, + } + resp, err := client.Do(r) + if err != nil { + t.Errorf("request error: %s", err) + return + } + + dataResponse, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Errorf("can't read request body: %s", err) + return + } + if dataResponse == nil { + t.Errorf("nil response data") + return + } + + data := map[string]interface{}{} + err = json.Unmarshal(dataResponse, &data) + if err != nil { + t.Errorf("bad data returned") + return + } + if tt.wantErr { + errMsg, ok := data["errors"] + if !ok { + t.Errorf("GraphQL error: %s", errMsg) + return + } + } + + if !tt.wantErr { + _, ok := data["errors"] + if ok { + t.Errorf("error not expected") + return + } + } + if tt.wantStatus != resp.StatusCode { + t.Errorf("Bad status response returned") + return + } + }) + } + + // perform tear down; remove user + _, err := RemoveTestUserByPhone(t, interserviceclient.TestUserPhoneNumber) + if err != nil { + t.Errorf("unable to remove test user: %s", err) + } +} + +func TestAddPermissionsToRole(t *testing.T) { + + graphQLURL := fmt.Sprintf("%s/%s", baseURL, "graphql") + headers := setUpLoggedInTestUserGraphHeaders(t) + + // get role id using repository + role, err := getRoleByName(t, testRoleName) + if err != nil { + t.Errorf("unable to initialize test service: %v", err) + return + } + // get user by phone number + + graphqlMutation := ` + mutation addPermission($input: RolePermissionInput!) { + addPermissionsToRole(input: $input) { + id + name + description + scopes + permissions { + scope + description + group + allowed + } + } + } + ` + + type args struct { + query map[string]interface{} + } + + tests := []struct { + name string + args args + wantStatus int + wantErr bool + }{ + { + name: "success: add permission to role", + args: args{ + query: map[string]interface{}{ + "query": graphqlMutation, + "variables": map[string]interface{}{ + "input": map[string]interface{}{ + "roleID": role.ID, + "scopes": []string{"agent.view"}, + }, + }, + }, + }, + wantStatus: http.StatusOK, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + body, err := mapToJSONReader(tt.args.query) + + if err != nil { + t.Errorf("unable to get GQL JSON io Reader: %s", err) + return + } + + r, err := http.NewRequest( + http.MethodPost, + graphQLURL, + body, + ) + + if err != nil { + t.Errorf("unable to compose request: %s", err) + return + } + + if r == nil { + t.Errorf("nil request") + return + } + + for k, v := range headers { + r.Header.Add(k, v) + } + client := http.Client{ + Timeout: time.Second * testHTTPClientTimeout, + } + resp, err := client.Do(r) + if err != nil { + t.Errorf("request error: %s", err) + return + } + + dataResponse, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Errorf("can't read request body: %s", err) + return + } + if dataResponse == nil { + t.Errorf("nil response data") + return + } + + data := map[string]interface{}{} + err = json.Unmarshal(dataResponse, &data) + if err != nil { + t.Errorf("bad data returned") + return + } + if tt.wantErr { + errMsg, ok := data["errors"] + if !ok { + t.Errorf("GraphQL error: %s", errMsg) + return + } + } + + if !tt.wantErr { + _, ok := data["errors"] + if ok { + t.Errorf("error not expected") + return + } + } + if tt.wantStatus != resp.StatusCode { + t.Errorf("Bad status response returned") + return + } + }) + } + // perform tear down; remove user + _, err = RemoveTestUserByPhone(t, interserviceclient.TestUserPhoneNumber) + if err != nil { + t.Errorf("unable to remove test user: %s", err) + } +} + +func TestAssignRole(t *testing.T) { + graphQLURL := fmt.Sprintf("%s/%s", baseURL, "graphql") + headers := setUpLoggedInTestUserGraphHeaders(t) + + creds, err := getTestUserCredentials(t) + if err != nil { + t.Errorf("cannot get logged in user credentials") + return + } + + role, err := CreateTestRole(t, "Test Assign Role") + if err != nil { + t.Errorf("cannot create a test role") + return + } + + roleID := role.ID + userID := creds.Profile.ID + + graphqlMutation := ` + mutation assignRole($userID:ID!,$roleID:ID!) { + assignRole(userID:$userID,roleID:$roleID) + } + ` + + type args struct { + query map[string]interface{} + } + + tests := []struct { + name string + args args + wantStatus int + wantErr bool + }{ + { + name: "success: assign a new role", + args: args{ + query: map[string]interface{}{ + "query": graphqlMutation, + "variables": map[string]string{ + "userID": userID, + "roleID": roleID, + }, + }, + }, + wantStatus: http.StatusOK, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + body, err := mapToJSONReader(tt.args.query) + + if err != nil { + t.Errorf("unable to get GQL JSON io Reader: %s", err) + return + } + + r, err := http.NewRequest( + http.MethodPost, + graphQLURL, + body, + ) + + if err != nil { + t.Errorf("unable to compose request: %s", err) + return + } + + if r == nil { + t.Errorf("nil request") + return + } + + for k, v := range headers { + r.Header.Add(k, v) + } + client := http.Client{ + Timeout: time.Second * testHTTPClientTimeout, + } + resp, err := client.Do(r) + if err != nil { + t.Errorf("request error: %s", err) + return + } + + dataResponse, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Errorf("can't read request body: %s", err) + return + } + if dataResponse == nil { + t.Errorf("nil response data") + return + } + + data := map[string]interface{}{} + err = json.Unmarshal(dataResponse, &data) + if err != nil { + t.Errorf("bad data returned") + return + } + if tt.wantErr { + errMsg, ok := data["errors"] + if !ok { + t.Errorf("GraphQL error: %s", errMsg) + return + } + } + + if !tt.wantErr { + _, ok := data["errors"] + if ok { + t.Errorf("error not expected") + return + } + } + if tt.wantStatus != resp.StatusCode { + t.Errorf("Bad status response returned") + return + } + }) + } + // perform tear down; remove user + _, err = RemoveTestUserByPhone(t, interserviceclient.TestUserPhoneNumber) + if err != nil { + t.Errorf("unable to remove test user: %s", err) + } +} + +func TestRevokeRole(t *testing.T) { + graphQLURL := fmt.Sprintf("%s/%s", baseURL, "graphql") + headers := setUpLoggedInTestUserGraphHeaders(t) + + creds, err := getTestUserCredentials(t) + if err != nil { + t.Errorf("cannot get logged in user credentials") + return + } + + role, err := CreateTestRole(t, "Test Revoke Role") + if err != nil { + t.Errorf("cannot create a test role") + return + } + + _, err = AssignTestRole(t, creds.Profile.ID, role.ID) + if err != nil { + t.Errorf("cannot assign a test revoke role") + return + } + + roleID := role.ID + userID := creds.Profile.ID + + graphqlMutation := ` + mutation revokeRole($userID:ID!,$roleID:ID!, $reason:String!) { + revokeRole(userID:$userID,roleID:$roleID,reason: $reason) + } + ` + + type args struct { + query map[string]interface{} + } + + tests := []struct { + name string + args args + wantStatus int + wantErr bool + }{ + { + name: "success: revoke a role", + args: args{ + query: map[string]interface{}{ + "query": graphqlMutation, + "variables": map[string]string{ + "userID": userID, + "roleID": roleID, + "reason": "test reason", + }, + }, + }, + wantStatus: http.StatusOK, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + body, err := mapToJSONReader(tt.args.query) + + if err != nil { + t.Errorf("unable to get GQL JSON io Reader: %s", err) + return + } + + r, err := http.NewRequest( + http.MethodPost, + graphQLURL, + body, + ) + + if err != nil { + t.Errorf("unable to compose request: %s", err) + return + } + + if r == nil { + t.Errorf("nil request") + return + } + + for k, v := range headers { + r.Header.Add(k, v) + } + client := http.Client{ + Timeout: time.Second * testHTTPClientTimeout, + } + resp, err := client.Do(r) + if err != nil { + t.Errorf("request error: %s", err) + return + } + + dataResponse, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Errorf("can't read request body: %s", err) + return + } + if dataResponse == nil { + t.Errorf("nil response data") + return + } + + data := map[string]interface{}{} + err = json.Unmarshal(dataResponse, &data) + if err != nil { + t.Errorf("bad data returned") + return + } + if tt.wantErr { + errMsg, ok := data["errors"] + if !ok { + t.Errorf("GraphQL error: %s", errMsg) + return + } + } + + if !tt.wantErr { + msg, ok := data["errors"] + if ok { + t.Errorf("error not expected: %s", msg) + return + } + } + if tt.wantStatus != resp.StatusCode { + t.Errorf("Bad status response returned") + return + } + }) + } + + // perform tear down; remove user + _, err = RemoveTestUserByPhone(t, interserviceclient.TestUserPhoneNumber) + if err != nil { + t.Errorf("unable to remove test user: %s", err) + } +} + +func TestGetAllPermissions(t *testing.T) { + graphQLURL := fmt.Sprintf("%s/%s", baseURL, "graphql") + headers := setUpLoggedInTestUserGraphHeaders(t) + + graphqlQuery := ` + query listPermissions { + getAllPermissions { + scope + group + description + } + } + ` + + type args struct { + query map[string]interface{} + } + + tests := []struct { + name string + args args + wantStatus int + wantErr bool + }{ + { + name: "success: list all permissions", + args: args{ + query: map[string]interface{}{ + "query": graphqlQuery, + }, + }, + wantStatus: http.StatusOK, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + body, err := mapToJSONReader(tt.args.query) + + if err != nil { + t.Errorf("unable to get GQL JSON io Reader: %s", err) + return + } + + r, err := http.NewRequest( + http.MethodPost, + graphQLURL, + body, + ) + + if err != nil { + t.Errorf("unable to compose request: %s", err) + return + } + + if r == nil { + t.Errorf("nil request") + return + } + + for k, v := range headers { + r.Header.Add(k, v) + } + client := http.Client{ + Timeout: time.Second * testHTTPClientTimeout, + } + resp, err := client.Do(r) + if err != nil { + t.Errorf("request error: %s", err) + return + } + + dataResponse, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Errorf("can't read request body: %s", err) + return + } + if dataResponse == nil { + t.Errorf("nil response data") + return + } + + data := map[string]interface{}{} + err = json.Unmarshal(dataResponse, &data) + if err != nil { + t.Errorf("bad data returned") + return + } + if tt.wantErr { + errMsg, ok := data["errors"] + if !ok { + t.Errorf("GraphQL error: %s", errMsg) + return + } + } + + if !tt.wantErr { + _, ok := data["errors"] + if ok { + t.Errorf("error not expected") + return + } + } + if tt.wantStatus != resp.StatusCode { + t.Errorf("Bad status response returned") + return + } + }) + } + // perform tear down; remove user + _, err := RemoveTestUserByPhone(t, interserviceclient.TestUserPhoneNumber) + if err != nil { + t.Errorf("unable to remove test user: %s", err) + } +} + +func TestGetAllRoles(t *testing.T) { + graphQLURL := fmt.Sprintf("%s/%s", baseURL, "graphql") + headers := setUpLoggedInTestUserGraphHeaders(t) + + graphqlQuery := ` + query listRoles { + getAllRoles { + id + name + description + active + scopes + permissions { + scope + group + description + allowed + } + } + } + ` + + type args struct { + query map[string]interface{} + } + + tests := []struct { + name string + args args + wantStatus int + wantErr bool + }{ + { + name: "success: list all permissions", + args: args{ + query: map[string]interface{}{ + "query": graphqlQuery, + }, + }, + wantStatus: http.StatusOK, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + body, err := mapToJSONReader(tt.args.query) + + if err != nil { + t.Errorf("unable to get GQL JSON io Reader: %s", err) + return + } + + r, err := http.NewRequest( + http.MethodPost, + graphQLURL, + body, + ) + + if err != nil { + t.Errorf("unable to compose request: %s", err) + return + } + + if r == nil { + t.Errorf("nil request") + return + } + + for k, v := range headers { + r.Header.Add(k, v) + } + client := http.Client{ + Timeout: time.Second * testHTTPClientTimeout, + } + resp, err := client.Do(r) + if err != nil { + t.Errorf("request error: %s", err) + return + } + + dataResponse, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Errorf("can't read request body: %s", err) + return + } + if dataResponse == nil { + t.Errorf("nil response data") + return + } + + data := map[string]interface{}{} + err = json.Unmarshal(dataResponse, &data) + if err != nil { + t.Errorf("bad data returned") + return + } + if tt.wantErr { + errMsg, ok := data["errors"] + if !ok { + t.Errorf("GraphQL error: %s", errMsg) + return + } + } + + if !tt.wantErr { + _, ok := data["errors"] + if ok { + t.Errorf("error not expected") + return + } + } + if tt.wantStatus != resp.StatusCode { + t.Errorf("Bad status response returned") + return + } + }) + } + // perform tear down; remove user + _, err := RemoveTestUserByPhone(t, interserviceclient.TestUserPhoneNumber) + if err != nil { + t.Errorf("unable to remove test user: %s", err) + } +} diff --git a/tests/signup_acceptance_test.go b/tests/signup_acceptance_test.go index 1b662414..12ba9350 100644 --- a/tests/signup_acceptance_test.go +++ b/tests/signup_acceptance_test.go @@ -2,7 +2,6 @@ package tests import ( "bytes" - "context" "encoding/json" "fmt" "io" @@ -13,7 +12,6 @@ import ( "time" "github.com/savannahghi/feedlib" - "github.com/savannahghi/firebasetools" "github.com/savannahghi/interserviceclient" "github.com/savannahghi/onboarding/pkg/onboarding/application/dto" "github.com/savannahghi/profileutils" @@ -138,6 +136,24 @@ func TestCreateUserWithPhoneNumber(t *testing.T) { }) } + + // assign role before removing + user, err := getTestUserCredentials(t) + if err != nil { + t.Errorf("error getting test user credentials:%v", err) + } + + role, err := CreateTestRole(t, testRoleName) + if err != nil { + t.Errorf("cannot create test role with err: %v", err) + return + } + + _, err = AssignTestRole(t, user.Profile.ID, role.ID) + if err != nil { + t.Errorf("cannot assign test role with err: %v", err) + return + } // perform tear down; remove user _, err = RemoveTestUserByPhone(t, phoneNumber) if err != nil { @@ -148,11 +164,24 @@ func TestCreateUserWithPhoneNumber(t *testing.T) { func TestVerifySignUpPhoneNumber(t *testing.T) { client := http.DefaultClient // create a test user - _, err := CreateTestUserByPhone(t, interserviceclient.TestUserPhoneNumber) + user, err := CreateTestUserByPhone(t, interserviceclient.TestUserPhoneNumber) if err != nil { t.Errorf("failed to create a user by phone %v", err) return } + + role, err := CreateTestRole(t, testRoleName) + if err != nil { + t.Errorf("cannot create test role with err: %v", err) + return + } + + _, err = AssignTestRole(t, user.Profile.ID, role.ID) + if err != nil { + t.Errorf("cannot assign test role with err: %v", err) + return + } + // prepare a valid payload registeredPhone := struct { PhoneNumber string @@ -289,11 +318,24 @@ func TestUserRecoveryPhoneNumbers(t *testing.T) { client := http.DefaultClient // create a test user validPhoneNumber := interserviceclient.TestUserPhoneNumber - _, err := CreateTestUserByPhone(t, validPhoneNumber) + user, err := CreateTestUserByPhone(t, validPhoneNumber) if err != nil { t.Errorf("failed to create a user by phone %v", err) return } + + role, err := CreateTestRole(t, testRoleName) + if err != nil { + t.Errorf("cannot create test role with err: %v", err) + return + } + + _, err = AssignTestRole(t, user.Profile.ID, role.ID) + if err != nil { + t.Errorf("cannot assign test role with err: %v", err) + return + } + validPayload := dto.PhoneNumberPayload{ PhoneNumber: &validPhoneNumber, } @@ -416,6 +458,7 @@ func TestRegisterPushToken(t *testing.T) { registerPushToken(token: $token) } ` + token := "QP18DqWVyuOcPG8CcDUNcEDzU3A2" // ggignore type args struct { query map[string]interface{} } @@ -432,8 +475,8 @@ func TestRegisterPushToken(t *testing.T) { query: map[string]interface{}{ "query": graphqlMutation, "variables": map[string]interface{}{ - "phone": interserviceclient.TestUserPhoneNumberWithPin, - "token": "QP18DqWVyuOcPG8CcDUNcEDzU3A2", + "phone": interserviceclient.TestUserPhoneNumber, + "token": token, }, }, }, @@ -538,33 +581,8 @@ func TestRegisterPushToken(t *testing.T) { } func TestCompleteSignup(t *testing.T) { - ctx := context.Background() - s, err := InitializeTestService(ctx) - if err != nil { - t.Errorf("unable to initialize test service: %v", err) - return - } - - phoneNumber := interserviceclient.TestUserPhoneNumber - user, err := CreateTestUserByPhone(t, phoneNumber) - if err != nil { - t.Errorf("failed to create a user by phone %v", err) - return - } - - idToken := user.Auth.IDToken - headers, err := CreatedUserGraphQLHeaders(idToken) - if err != nil { - t.Errorf("error in getting headers: %w", err) - return - } - - authToken, err := firebasetools.ValidateBearerToken(ctx, *idToken) - if err != nil { - t.Errorf("invalid token: %w", err) - return - } - authenticatedContext := context.WithValue(ctx, firebasetools.AuthTokenContextKey, authToken) + headers := setUpLoggedInTestUserGraphHeaders(t) + authenticatedContext := getTestAuthenticatedContext(t) firstName := "Be.Well" lastName := "Consumer" @@ -573,7 +591,7 @@ func TestCompleteSignup(t *testing.T) { LastName: &lastName, } // Update the user BioData - err = s.Onboarding.UpdateBioData(authenticatedContext, bioData) + err := testInteractor.Onboarding.UpdateBioData(authenticatedContext, bioData) if err != nil { t.Errorf("failed to update userprofile biodata: %v", err) diff --git a/tests/supplier_acceptance_test.go b/tests/supplier_acceptance_test.go index f6ac916b..0eadd20d 100644 --- a/tests/supplier_acceptance_test.go +++ b/tests/supplier_acceptance_test.go @@ -1,7 +1,6 @@ package tests import ( - "context" "encoding/json" "fmt" "io/ioutil" @@ -11,12 +10,10 @@ import ( "testing" "time" - "github.com/savannahghi/firebasetools" "github.com/savannahghi/interserviceclient" "github.com/savannahghi/profileutils" "github.com/savannahghi/scalarutils" "github.com/sirupsen/logrus" - "gitlab.slade360emr.com/go/apiclient" ) const ( @@ -31,41 +28,10 @@ const ( testEmail = "test@bewell.co.ke" ) -// CreatedUserGraphQLHeaders updates the authorization header with the -// bearer(ID) token of the created user during test -// TODO:(muchogo) create a reusable function in base that accepts a UID -// or modify the apiclient.GetGraphQLHeaders(ctx) extra UID argument -func CreatedUserGraphQLHeaders(idToken *string) (map[string]string, error) { - ctx := context.Background() - - authHeaderBearerToken := fmt.Sprintf("Bearer %v", *idToken) - - headers, err := apiclient.GetGraphQLHeaders(ctx) - if err != nil { - return nil, fmt.Errorf("error in getting headers: %w", err) - } - - headers["Authorization"] = authHeaderBearerToken - - return headers, nil -} func TestAddPartnerType(t *testing.T) { - // create a user and their profile - phoneNumber := interserviceclient.TestUserPhoneNumber - user, err := CreateTestUserByPhone(t, phoneNumber) - if err != nil { - t.Errorf("failed to create a user by phone %v", err) - return - } - - idToken := user.Auth.IDToken - headers, err := CreatedUserGraphQLHeaders(idToken) - if err != nil { - t.Errorf("error in getting headers: %w", err) - return - } - graphQLURL := fmt.Sprintf("%s/%s", baseURL, "graphql") + headers := setUpLoggedInTestUserGraphHeaders(t) + graphqlMutation := ` mutation addPartnerType($name:String!, $partnerType:PartnerType!){ addPartnerType(name: $name, partnerType:$partnerType) @@ -203,7 +169,7 @@ func TestAddPartnerType(t *testing.T) { } // perform tear down; remove user - _, err = RemoveTestUserByPhone(t, interserviceclient.TestUserPhoneNumber) + _, err := RemoveTestUserByPhone(t, interserviceclient.TestUserPhoneNumber) if err != nil { t.Errorf("unable to remove test user: %s", err) } @@ -211,23 +177,8 @@ func TestAddPartnerType(t *testing.T) { } func TestSetUpSupplier_acceptance(t *testing.T) { - - // create a user and their profile - phoneNumber := interserviceclient.TestUserPhoneNumber - user, err := CreateTestUserByPhone(t, phoneNumber) - if err != nil { - t.Errorf("failed to create a user by phone %v", err) - return - } - - idToken := user.Auth.IDToken - headers, err := CreatedUserGraphQLHeaders(idToken) - if err != nil { - t.Errorf("error in getting headers: %w", err) - return - } - graphQLURL := fmt.Sprintf("%s/%s", baseURL, "graphql") + headers := setUpLoggedInTestUserGraphHeaders(t) graphqlMutation := ` mutation setUpSupplier($input: AccountType!) { @@ -365,37 +316,19 @@ func TestSetUpSupplier_acceptance(t *testing.T) { } // perform tear down; remove user - _, err = RemoveTestUserByPhone(t, interserviceclient.TestUserPhoneNumber) + _, err := RemoveTestUserByPhone(t, interserviceclient.TestUserPhoneNumber) if err != nil { t.Errorf("unable to remove test user: %s", err) } } func TestSuspendSupplier_acceptance(t *testing.T) { - ctx := context.Background() - // create a user and their profile - phoneNumber := interserviceclient.TestUserPhoneNumber - user, err := CreateTestUserByPhone(t, phoneNumber) - if err != nil { - t.Errorf("failed to create a user by phone %v", err) - return - } - - idToken := user.Auth.IDToken - headers, err := CreatedUserGraphQLHeaders(idToken) - if err != nil { - t.Errorf("error in getting headers: %w", err) - return - } + graphQLURL := fmt.Sprintf("%s/%s", baseURL, "graphql") + headers := setUpLoggedInTestUserGraphHeaders(t) - authToken, err := firebasetools.ValidateBearerToken(ctx, *idToken) - if err != nil { - t.Errorf("invalid token: %w", err) - return - } - authenticatedContext := context.WithValue(ctx, firebasetools.AuthTokenContextKey, authToken) + ctx := getTestAuthenticatedContext(t) - err = setPrimaryEmailAddress(authenticatedContext, t, testEmail) + err := setPrimaryEmailAddress(ctx, t, testEmail) if err != nil { t.Errorf("failed to set primary email address: %v", err) return @@ -416,18 +349,18 @@ func TestSuspendSupplier_acceptance(t *testing.T) { partnerName := "practitioner" partnerType := profileutils.PartnerTypePractitioner - _, err = addPartnerType(authenticatedContext, t, &partnerName, partnerType) + _, err = addPartnerType(ctx, t, &partnerName, partnerType) if err != nil { t.Errorf("failed to add partnerType: %v", err) return } - account, err := setUpSupplier(authenticatedContext, t, profileutils.AccountTypeIndividual) + account, err := setUpSupplier(ctx, t, profileutils.AccountTypeIndividual) if err != nil { t.Errorf("failed to setup supplier: %v", err) return } log.Printf("the account type for this supplier is %v:", account.AccountType) - err = updateBioData(authenticatedContext, t, completeUserDetails) + err = updateBioData(ctx, t, completeUserDetails) if err != nil { t.Errorf("failed to update biodata: %v", err) return @@ -436,8 +369,6 @@ func TestSuspendSupplier_acceptance(t *testing.T) { "This email is to inform you that as a result of your actions on April 12th, 2021, you have been issued a suspension for 1 week (7 days)" ` - graphQLURL := fmt.Sprintf("%s/%s", baseURL, "graphql") - graphqlMutation := `mutation suspendSupplier($suspensionReason: String){ suspendSupplier(suspensionReason:$suspensionReason) } @@ -565,30 +496,11 @@ func TestSuspendSupplier_acceptance(t *testing.T) { } func TestSupplierEDILogin(t *testing.T) { - // create a user and their profile - phoneNumber := interserviceclient.TestUserPhoneNumber - user, err := CreateTestUserByPhone(t, phoneNumber) - if err != nil { - t.Errorf("failed to create a user by phone %v", err) - return - } - - idToken := user.Auth.IDToken - headers, err := CreatedUserGraphQLHeaders(idToken) - if err != nil { - t.Errorf("error in getting headers: %w", err) - return - } - graphQLURL := fmt.Sprintf("%s/%s", baseURL, "graphql") + headers := setUpLoggedInTestUserGraphHeaders(t) sladeCode := "1" - if err != nil { - t.Errorf("error getting headers: %w", err) - return - } - graphQLMutationPayload := ` mutation supplierEDILogin($username: String!, $password: String!, $sladeCode: String!) { supplierEDILogin(username: $username, password:$password, sladeCode: $sladeCode) { @@ -773,37 +685,19 @@ func TestSupplierEDILogin(t *testing.T) { } // perform tear down; remove user - _, err = RemoveTestUserByPhone(t, interserviceclient.TestUserPhoneNumber) + _, err := RemoveTestUserByPhone(t, interserviceclient.TestUserPhoneNumber) if err != nil { t.Errorf("unable to remove test user: %s", err) } } func TestAddIndividualPractitionerKYC(t *testing.T) { + graphQLURL := fmt.Sprintf("%s/%s", baseURL, "graphql") + headers := setUpLoggedInTestUserGraphHeaders(t) - ctx := context.Background() - phoneNumber := interserviceclient.TestUserPhoneNumber - user, err := CreateTestUserByPhone(t, phoneNumber) - log.Printf("the user is %v", user) - if err != nil { - t.Errorf("failed to create a user by phone %v", err) - return - } - idToken := user.Auth.IDToken - headers, err := CreatedUserGraphQLHeaders(idToken) - if err != nil { - t.Errorf("error in getting headers: %w", err) - return - } - - authToken, err := firebasetools.ValidateBearerToken(ctx, *idToken) - if err != nil { - t.Errorf("invalid token: %w", err) - return - } - authenticatedContext := context.WithValue(ctx, firebasetools.AuthTokenContextKey, authToken) + ctx := getTestAuthenticatedContext(t) - err = setPrimaryEmailAddress(authenticatedContext, t, testEmail) + err := setPrimaryEmailAddress(ctx, t, testEmail) if err != nil { t.Errorf("failed to set primary email address: %v", err) return @@ -824,25 +718,23 @@ func TestAddIndividualPractitionerKYC(t *testing.T) { partnerName := "practitioner" partnerType := profileutils.PartnerTypePractitioner - _, err = addPartnerType(authenticatedContext, t, &partnerName, partnerType) + _, err = addPartnerType(ctx, t, &partnerName, partnerType) if err != nil { t.Errorf("failed to add partnerType: %v", err) return } - account, err := setUpSupplier(authenticatedContext, t, profileutils.AccountTypeIndividual) + account, err := setUpSupplier(ctx, t, profileutils.AccountTypeIndividual) if err != nil { t.Errorf("failed to setup supplier: %v", err) return } log.Printf("the account type for this supplier is %v:", account.AccountType) - err = updateBioData(authenticatedContext, t, completeUserDetails) + err = updateBioData(ctx, t, completeUserDetails) if err != nil { t.Errorf("failed to update biodata: %v", err) return } - graphQLURL := fmt.Sprintf("%s/%s", baseURL, "graphql") - graphQLMutationPayload := ` mutation addIndividualPractitionerKYC($input:IndividualPractitionerInput!){ addIndividualPractitionerKYC(input:$input) { @@ -1004,29 +896,12 @@ func TestAddIndividualPractitionerKYC(t *testing.T) { } } func TestAddOrganizationProviderKYC(t *testing.T) { - ctx := context.Background() - phoneNumber := interserviceclient.TestUserPhoneNumber - user, err := CreateTestUserByPhone(t, phoneNumber) - log.Printf("the user is %v", user) - if err != nil { - t.Errorf("failed to create a user by phone %v", err) - return - } - idToken := user.Auth.IDToken - headers, err := CreatedUserGraphQLHeaders(idToken) - if err != nil { - t.Errorf("error in getting headers: %w", err) - return - } + graphQLURL := fmt.Sprintf("%s/%s", baseURL, "graphql") + headers := setUpLoggedInTestUserGraphHeaders(t) - authToken, err := firebasetools.ValidateBearerToken(ctx, *idToken) - if err != nil { - t.Errorf("invalid token: %w", err) - return - } - authenticatedContext := context.WithValue(ctx, firebasetools.AuthTokenContextKey, authToken) + ctx := getTestAuthenticatedContext(t) - err = setPrimaryEmailAddress(authenticatedContext, t, testEmail) + err := setPrimaryEmailAddress(ctx, t, testEmail) if err != nil { t.Errorf("failed to set primary email address: %v", err) return @@ -1047,24 +922,22 @@ func TestAddOrganizationProviderKYC(t *testing.T) { partnerName := "provider" partnerType := profileutils.PartnerTypeProvider - _, err = addPartnerType(authenticatedContext, t, &partnerName, partnerType) + _, err = addPartnerType(ctx, t, &partnerName, partnerType) if err != nil { t.Errorf("failed to add partnerType: %v", err) return } - _, err = setUpSupplier(authenticatedContext, t, profileutils.AccountTypeOrganisation) + _, err = setUpSupplier(ctx, t, profileutils.AccountTypeOrganisation) if err != nil { t.Errorf("failed to setup supplier: %v", err) return } - err = updateBioData(authenticatedContext, t, completeUserDetails) + err = updateBioData(ctx, t, completeUserDetails) if err != nil { t.Errorf("failed to update biodata: %v", err) return } - graphQLURL := fmt.Sprintf("%s/%s", baseURL, "graphql") - graphqlMutation := ` mutation addOrganizationProviderKYC($input:OrganizationProviderInput!){ addOrganizationProviderKYC(input:$input) { @@ -1236,29 +1109,12 @@ func TestAddOrganizationProviderKYC(t *testing.T) { } func TestAddIndividualPharmaceuticalKYC(t *testing.T) { - ctx := context.Background() - phoneNumber := interserviceclient.TestUserPhoneNumber - user, err := CreateTestUserByPhone(t, phoneNumber) - log.Printf("the user is %v", user) - if err != nil { - t.Errorf("failed to create a user by phone %v", err) - return - } - idToken := user.Auth.IDToken - headers, err := CreatedUserGraphQLHeaders(idToken) - if err != nil { - t.Errorf("error in getting headers: %w", err) - return - } + graphQLURL := fmt.Sprintf("%s/%s", baseURL, "graphql") + headers := setUpLoggedInTestUserGraphHeaders(t) - authToken, err := firebasetools.ValidateBearerToken(ctx, *idToken) - if err != nil { - t.Errorf("invalid token: %w", err) - return - } - authenticatedContext := context.WithValue(ctx, firebasetools.AuthTokenContextKey, authToken) + ctx := getTestAuthenticatedContext(t) - err = setPrimaryEmailAddress(authenticatedContext, t, testEmail) + err := setPrimaryEmailAddress(ctx, t, testEmail) if err != nil { t.Errorf("failed to set primary email address: %v", err) return @@ -1279,24 +1135,22 @@ func TestAddIndividualPharmaceuticalKYC(t *testing.T) { partnerName := "pharmaceutical" partnerType := profileutils.PartnerTypePharmaceutical - _, err = addPartnerType(authenticatedContext, t, &partnerName, partnerType) + _, err = addPartnerType(ctx, t, &partnerName, partnerType) if err != nil { t.Errorf("failed to add partnerType: %v", err) return } - _, err = setUpSupplier(authenticatedContext, t, profileutils.AccountTypeIndividual) + _, err = setUpSupplier(ctx, t, profileutils.AccountTypeIndividual) if err != nil { t.Errorf("failed to setup supplier: %v", err) return } - err = updateBioData(authenticatedContext, t, completeUserDetails) + err = updateBioData(ctx, t, completeUserDetails) if err != nil { t.Errorf("failed to update biodata: %v", err) return } - graphQLURL := fmt.Sprintf("%s/%s", baseURL, "graphql") - graphQLMutation := ` mutation addIndividualPharmaceuticalKYC($input:IndividualPharmaceuticalInput!){ addIndividualPharmaceuticalKYC(input:$input) { @@ -1456,29 +1310,12 @@ func TestAddIndividualPharmaceuticalKYC(t *testing.T) { } func TestAddOrganizationPharmaceuticalKYC(t *testing.T) { - ctx := context.Background() - phoneNumber := interserviceclient.TestUserPhoneNumber - user, err := CreateTestUserByPhone(t, phoneNumber) - log.Printf("the user is %v", user) - if err != nil { - t.Errorf("failed to create a user by phone %v", err) - return - } - idToken := user.Auth.IDToken - headers, err := CreatedUserGraphQLHeaders(idToken) - if err != nil { - t.Errorf("error in getting headers: %w", err) - return - } + graphQLURL := fmt.Sprintf("%s/%s", baseURL, "graphql") + headers := setUpLoggedInTestUserGraphHeaders(t) - authToken, err := firebasetools.ValidateBearerToken(ctx, *idToken) - if err != nil { - t.Errorf("invalid token: %w", err) - return - } - authenticatedContext := context.WithValue(ctx, firebasetools.AuthTokenContextKey, authToken) + ctx := getTestAuthenticatedContext(t) - err = setPrimaryEmailAddress(authenticatedContext, t, testEmail) + err := setPrimaryEmailAddress(ctx, t, testEmail) if err != nil { t.Errorf("failed to set primary email address: %v", err) return @@ -1499,24 +1336,22 @@ func TestAddOrganizationPharmaceuticalKYC(t *testing.T) { partnerName := "pharmaceutical" partnerType := profileutils.PartnerTypePharmaceutical - _, err = addPartnerType(authenticatedContext, t, &partnerName, partnerType) + _, err = addPartnerType(ctx, t, &partnerName, partnerType) if err != nil { t.Errorf("failed to add partnerType: %v", err) return } - _, err = setUpSupplier(authenticatedContext, t, profileutils.AccountTypeOrganisation) + _, err = setUpSupplier(ctx, t, profileutils.AccountTypeOrganisation) if err != nil { t.Errorf("failed to setup supplier: %v", err) return } - err = updateBioData(authenticatedContext, t, completeUserDetails) + err = updateBioData(ctx, t, completeUserDetails) if err != nil { t.Errorf("failed to update biodata: %v", err) return } - graphQLURL := fmt.Sprintf("%s/%s", baseURL, "graphql") - graphQLMutation := ` mutation addOrganizationPharmaceuticalKYC($input:OrganizationPharmaceuticalInput!){ addOrganizationPharmaceuticalKYC(input:$input) { @@ -1690,29 +1525,12 @@ func TestAddOrganizationPharmaceuticalKYC(t *testing.T) { } func TestAddIndividualCoachKYC(t *testing.T) { - ctx := context.Background() - phoneNumber := interserviceclient.TestUserPhoneNumber - user, err := CreateTestUserByPhone(t, phoneNumber) - log.Printf("the user is %v", user) - if err != nil { - t.Errorf("failed to create a user by phone %v", err) - return - } - idToken := user.Auth.IDToken - headers, err := CreatedUserGraphQLHeaders(idToken) - if err != nil { - t.Errorf("error in getting headers: %w", err) - return - } + graphQLURL := fmt.Sprintf("%s/%s", baseURL, "graphql") + headers := setUpLoggedInTestUserGraphHeaders(t) - authToken, err := firebasetools.ValidateBearerToken(ctx, *idToken) - if err != nil { - t.Errorf("invalid token: %w", err) - return - } - authenticatedContext := context.WithValue(ctx, firebasetools.AuthTokenContextKey, authToken) + ctx := getTestAuthenticatedContext(t) - err = setPrimaryEmailAddress(authenticatedContext, t, testEmail) + err := setPrimaryEmailAddress(ctx, t, testEmail) if err != nil { t.Errorf("failed to set primary email address: %v", err) return @@ -1733,24 +1551,22 @@ func TestAddIndividualCoachKYC(t *testing.T) { partnerName := "coach" partnerType := profileutils.PartnerTypeCoach - _, err = addPartnerType(authenticatedContext, t, &partnerName, partnerType) + _, err = addPartnerType(ctx, t, &partnerName, partnerType) if err != nil { t.Errorf("failed to add partnerType: %v", err) return } - _, err = setUpSupplier(authenticatedContext, t, profileutils.AccountTypeIndividual) + _, err = setUpSupplier(ctx, t, profileutils.AccountTypeIndividual) if err != nil { t.Errorf("failed to setup supplier: %v", err) return } - err = updateBioData(authenticatedContext, t, completeUserDetails) + err = updateBioData(ctx, t, completeUserDetails) if err != nil { t.Errorf("failed to update biodata: %v", err) return } - graphQLURL := fmt.Sprintf("%s/%s", baseURL, "graphql") - graphQLMutation := ` mutation addIndividualCoachKYC($input:IndividualCoachInput!){ addIndividualCoachKYC(input:$input) { @@ -1912,29 +1728,10 @@ func TestAddIndividualCoachKYC(t *testing.T) { } func TestAddOrganizationRiderKYC(t *testing.T) { - ctx := context.Background() - phoneNumber := interserviceclient.TestUserPhoneNumber - user, err := CreateTestUserByPhone(t, phoneNumber) - log.Printf("the user is %v", user) - if err != nil { - t.Errorf("failed to create a user by phone %v", err) - return - } - idToken := user.Auth.IDToken - headers, err := CreatedUserGraphQLHeaders(idToken) - if err != nil { - t.Errorf("error in getting headers: %w", err) - return - } + headers := setUpLoggedInTestUserGraphHeaders(t) + ctx := getTestAuthenticatedContext(t) - authToken, err := firebasetools.ValidateBearerToken(ctx, *idToken) - if err != nil { - t.Errorf("invalid token: %w", err) - return - } - authenticatedContext := context.WithValue(ctx, firebasetools.AuthTokenContextKey, authToken) - - err = setPrimaryEmailAddress(authenticatedContext, t, testEmail) + err := setPrimaryEmailAddress(ctx, t, testEmail) if err != nil { t.Errorf("failed to set primary email address: %v", err) return @@ -1955,17 +1752,17 @@ func TestAddOrganizationRiderKYC(t *testing.T) { partnerName := "rider" partnerType := profileutils.PartnerTypeRider - _, err = addPartnerType(authenticatedContext, t, &partnerName, partnerType) + _, err = addPartnerType(ctx, t, &partnerName, partnerType) if err != nil { t.Errorf("failed to add partnerType: %v", err) return } - _, err = setUpSupplier(authenticatedContext, t, profileutils.AccountTypeOrganisation) + _, err = setUpSupplier(ctx, t, profileutils.AccountTypeOrganisation) if err != nil { t.Errorf("failed to setup supplier: %v", err) return } - err = updateBioData(authenticatedContext, t, completeUserDetails) + err = updateBioData(ctx, t, completeUserDetails) if err != nil { t.Errorf("failed to update biodata: %v", err) return @@ -2136,29 +1933,10 @@ func TestAddOrganizationRiderKYC(t *testing.T) { } func TestAddIndividualRiderKYC_acceptance(t *testing.T) { - ctx := context.Background() - phoneNumber := interserviceclient.TestUserPhoneNumber - user, err := CreateTestUserByPhone(t, phoneNumber) - log.Printf("the user is %v", user) - if err != nil { - t.Errorf("failed to create a user by phone %v", err) - return - } - idToken := user.Auth.IDToken - headers, err := CreatedUserGraphQLHeaders(idToken) - if err != nil { - t.Errorf("error in getting headers: %w", err) - return - } - - authToken, err := firebasetools.ValidateBearerToken(ctx, *idToken) - if err != nil { - t.Errorf("invalid token: %w", err) - return - } - authenticatedContext := context.WithValue(ctx, firebasetools.AuthTokenContextKey, authToken) + headers := setUpLoggedInTestUserGraphHeaders(t) + ctx := getTestAuthenticatedContext(t) - err = setPrimaryEmailAddress(authenticatedContext, t, testEmail) + err := setPrimaryEmailAddress(ctx, t, testEmail) if err != nil { t.Errorf("failed to set primary email address: %v", err) return @@ -2179,17 +1957,17 @@ func TestAddIndividualRiderKYC_acceptance(t *testing.T) { partnerName := "rider" partnerType := profileutils.PartnerTypeRider - _, err = addPartnerType(authenticatedContext, t, &partnerName, partnerType) + _, err = addPartnerType(ctx, t, &partnerName, partnerType) if err != nil { t.Errorf("failed to add partnerType: %v", err) return } - _, err = setUpSupplier(authenticatedContext, t, profileutils.AccountTypeIndividual) + _, err = setUpSupplier(ctx, t, profileutils.AccountTypeIndividual) if err != nil { t.Errorf("failed to setup supplier: %v", err) return } - err = updateBioData(authenticatedContext, t, completeUserDetails) + err = updateBioData(ctx, t, completeUserDetails) if err != nil { t.Errorf("failed to update biodata: %v", err) return @@ -2355,21 +2133,7 @@ func TestAddIndividualRiderKYC_acceptance(t *testing.T) { } func TestFetchKYCProcessingRequests(t *testing.T) { - // create a user and their profile - phoneNumber := interserviceclient.TestUserPhoneNumber - user, err := CreateTestUserByPhone(t, phoneNumber) - if err != nil { - t.Errorf("failed to create a user by phone %v", err) - return - } - - idToken := user.Auth.IDToken - headers, err := CreatedUserGraphQLHeaders(idToken) - if err != nil { - t.Errorf("error in getting headers: %w", err) - return - } - + headers := setUpLoggedInTestUserGraphHeaders(t) graphQLURL := fmt.Sprintf("%s/%s", baseURL, "graphql") graphQLQuery := ` @@ -2505,27 +2269,14 @@ func TestFetchKYCProcessingRequests(t *testing.T) { } // perform tear down; remove user - _, err = RemoveTestUserByPhone(t, interserviceclient.TestUserPhoneNumber) + _, err := RemoveTestUserByPhone(t, interserviceclient.TestUserPhoneNumber) if err != nil { t.Errorf("unable to remove test user: %s", err) } } func TestFetchSupplierAllowedLocations(t *testing.T) { - // create a user and their profile - phoneNumber := interserviceclient.TestUserPhoneNumber - user, err := CreateTestUserByPhone(t, phoneNumber) - if err != nil { - t.Errorf("failed to create a user by phone %v", err) - return - } - - idToken := user.Auth.IDToken - headers, err := CreatedUserGraphQLHeaders(idToken) - if err != nil { - t.Errorf("error in getting headers: %w", err) - return - } + headers := setUpLoggedInTestUserGraphHeaders(t) graphQLURL := fmt.Sprintf("%s/%s", baseURL, "graphql") graphqlQueryPayload := ` @@ -2648,56 +2399,31 @@ func TestFetchSupplierAllowedLocations(t *testing.T) { }) } // perform tear down; remove user - _, err = RemoveTestUserByPhone(t, interserviceclient.TestUserPhoneNumber) + _, err := RemoveTestUserByPhone(t, interserviceclient.TestUserPhoneNumber) if err != nil { t.Errorf("unable to remove test user: %s", err) } } func TestSupplierSetDefaultLocation_acceptance(t *testing.T) { - ctx := context.Background() - s, err := InitializeTestService(ctx) - if err != nil { - t.Errorf("unable to initialize test service: %v", err) - return - } - - phoneNumber := interserviceclient.TestUserPhoneNumber - user, err := CreateTestUserByPhone(t, phoneNumber) - if err != nil { - t.Errorf("failed to create a user by phone %v", err) - return - } - - idToken := user.Auth.IDToken - headers, err := CreatedUserGraphQLHeaders(idToken) - if err != nil { - t.Errorf("error in getting headers: %w", err) - return - } - - authToken, err := firebasetools.ValidateBearerToken(ctx, *idToken) - if err != nil { - t.Errorf("invalid token: %w", err) - return - } - authenticatedContext := context.WithValue(ctx, firebasetools.AuthTokenContextKey, authToken) + headers := setUpLoggedInTestUserGraphHeaders(t) + ctx := getTestAuthenticatedContext(t) name := "Makmende" partnerPractitioner := profileutils.PartnerTypePractitioner - _, err = s.Supplier.AddPartnerType(authenticatedContext, &name, &partnerPractitioner) + _, err := testInteractor.Supplier.AddPartnerType(ctx, &name, &partnerPractitioner) if err != nil { t.Errorf("can't create a supplier") return } - _, err = s.Supplier.SetUpSupplier(authenticatedContext, profileutils.AccountTypeOrganisation) + _, err = testInteractor.Supplier.SetUpSupplier(ctx, profileutils.AccountTypeOrganisation) if err != nil { t.Errorf("can't set up a supplier") return } - _, err = s.Supplier.SupplierEDILogin(authenticatedContext, TestEDIPortalUsername, TestEDIPortalPassword, TestSladeCode) + _, err = testInteractor.Supplier.SupplierEDILogin(ctx, TestEDIPortalUsername, TestEDIPortalPassword, TestSladeCode) if err != nil { t.Errorf("can't perform supplier edi login: %v", err) return @@ -2848,29 +2574,10 @@ func TestSupplierSetDefaultLocation_acceptance(t *testing.T) { } func TestAddOrganizationCoachKYC(t *testing.T) { - ctx := context.Background() - phoneNumber := interserviceclient.TestUserPhoneNumber - user, err := CreateTestUserByPhone(t, phoneNumber) - log.Printf("the user is %v", user) - if err != nil { - t.Errorf("failed to create a user by phone %v", err) - return - } - idToken := user.Auth.IDToken - headers, err := CreatedUserGraphQLHeaders(idToken) - if err != nil { - t.Errorf("error in getting headers: %w", err) - return - } + headers := setUpLoggedInTestUserGraphHeaders(t) + ctx := getTestAuthenticatedContext(t) - authToken, err := firebasetools.ValidateBearerToken(ctx, *idToken) - if err != nil { - t.Errorf("invalid token: %w", err) - return - } - authenticatedContext := context.WithValue(ctx, firebasetools.AuthTokenContextKey, authToken) - - err = setPrimaryEmailAddress(authenticatedContext, t, testEmail) + err := setPrimaryEmailAddress(ctx, t, testEmail) if err != nil { t.Errorf("failed to set primary email address: %v", err) return @@ -2891,17 +2598,17 @@ func TestAddOrganizationCoachKYC(t *testing.T) { partnerName := "coach" partnerType := profileutils.PartnerTypeCoach - _, err = addPartnerType(authenticatedContext, t, &partnerName, partnerType) + _, err = addPartnerType(ctx, t, &partnerName, partnerType) if err != nil { t.Errorf("failed to add partnerType: %v", err) return } - _, err = setUpSupplier(authenticatedContext, t, profileutils.AccountTypeOrganisation) + _, err = setUpSupplier(ctx, t, profileutils.AccountTypeOrganisation) if err != nil { t.Errorf("failed to setup supplier: %v", err) return } - err = updateBioData(authenticatedContext, t, completeUserDetails) + err = updateBioData(ctx, t, completeUserDetails) if err != nil { t.Errorf("failed to update biodata: %v", err) return @@ -3078,29 +2785,10 @@ func TestAddOrganizationCoachKYC(t *testing.T) { } func TestAddIndividualNutritionKYC(t *testing.T) { - ctx := context.Background() - phoneNumber := interserviceclient.TestUserPhoneNumber - user, err := CreateTestUserByPhone(t, phoneNumber) - log.Printf("the user is %v", user) - if err != nil { - t.Errorf("failed to create a user by phone %v", err) - return - } - idToken := user.Auth.IDToken - headers, err := CreatedUserGraphQLHeaders(idToken) - if err != nil { - t.Errorf("error in getting headers: %w", err) - return - } + headers := setUpLoggedInTestUserGraphHeaders(t) + ctx := getTestAuthenticatedContext(t) - authToken, err := firebasetools.ValidateBearerToken(ctx, *idToken) - if err != nil { - t.Errorf("invalid token: %w", err) - return - } - authenticatedContext := context.WithValue(ctx, firebasetools.AuthTokenContextKey, authToken) - - err = setPrimaryEmailAddress(authenticatedContext, t, testEmail) + err := setPrimaryEmailAddress(ctx, t, testEmail) if err != nil { t.Errorf("failed to set primary email address: %v", err) return @@ -3121,17 +2809,17 @@ func TestAddIndividualNutritionKYC(t *testing.T) { partnerName := "nutrition" partnerType := profileutils.PartnerTypeNutrition - _, err = addPartnerType(authenticatedContext, t, &partnerName, partnerType) + _, err = addPartnerType(ctx, t, &partnerName, partnerType) if err != nil { t.Errorf("failed to add partnerType: %v", err) return } - _, err = setUpSupplier(authenticatedContext, t, profileutils.AccountTypeIndividual) + _, err = setUpSupplier(ctx, t, profileutils.AccountTypeIndividual) if err != nil { t.Errorf("failed to setup supplier: %v", err) return } - err = updateBioData(authenticatedContext, t, completeUserDetails) + err = updateBioData(ctx, t, completeUserDetails) if err != nil { t.Errorf("failed to update biodata: %v", err) return @@ -3297,29 +2985,10 @@ func TestAddIndividualNutritionKYC(t *testing.T) { } func TestAddOrganizationNutritionKyc(t *testing.T) { - ctx := context.Background() - phoneNumber := interserviceclient.TestUserPhoneNumber - user, err := CreateTestUserByPhone(t, phoneNumber) - log.Printf("the user is %v", user) - if err != nil { - t.Errorf("failed to create a user by phone %v", err) - return - } - idToken := user.Auth.IDToken - headers, err := CreatedUserGraphQLHeaders(idToken) - if err != nil { - t.Errorf("error in getting headers: %w", err) - return - } + headers := setUpLoggedInTestUserGraphHeaders(t) + ctx := getTestAuthenticatedContext(t) - authToken, err := firebasetools.ValidateBearerToken(ctx, *idToken) - if err != nil { - t.Errorf("invalid token: %w", err) - return - } - authenticatedContext := context.WithValue(ctx, firebasetools.AuthTokenContextKey, authToken) - - err = setPrimaryEmailAddress(authenticatedContext, t, testEmail) + err := setPrimaryEmailAddress(ctx, t, testEmail) if err != nil { t.Errorf("failed to set primary email address: %v", err) return @@ -3340,17 +3009,17 @@ func TestAddOrganizationNutritionKyc(t *testing.T) { partnerName := "nutrition" partnerType := profileutils.PartnerTypeNutrition - _, err = addPartnerType(authenticatedContext, t, &partnerName, partnerType) + _, err = addPartnerType(ctx, t, &partnerName, partnerType) if err != nil { t.Errorf("failed to add partnerType: %v", err) return } - _, err = setUpSupplier(authenticatedContext, t, profileutils.AccountTypeOrganisation) + _, err = setUpSupplier(ctx, t, profileutils.AccountTypeOrganisation) if err != nil { t.Errorf("failed to setup supplier: %v", err) return } - err = updateBioData(authenticatedContext, t, completeUserDetails) + err = updateBioData(ctx, t, completeUserDetails) if err != nil { t.Errorf("failed to update biodata: %v", err) return @@ -3485,20 +3154,7 @@ func TestAddOrganizationNutritionKyc(t *testing.T) { } func TestSetupAsExperimentParticipant(t *testing.T) { - // create a user and their profile - phoneNumber := interserviceclient.TestUserPhoneNumber - user, err := CreateTestUserByPhone(t, phoneNumber) - if err != nil { - t.Errorf("failed to create a user by phone %v", err) - return - } - - idToken := user.Auth.IDToken - headers, err := CreatedUserGraphQLHeaders(idToken) - if err != nil { - t.Errorf("error in getting headers: %w", err) - return - } + headers := setUpLoggedInTestUserGraphHeaders(t) graphQLURL := fmt.Sprintf("%s/%s", baseURL, "graphql") @@ -3610,36 +3266,17 @@ func TestSetupAsExperimentParticipant(t *testing.T) { } // perform tear down; remove user - _, err = RemoveTestUserByPhone(t, interserviceclient.TestUserPhoneNumber) + _, err := RemoveTestUserByPhone(t, interserviceclient.TestUserPhoneNumber) if err != nil { t.Errorf("unable to remove test user: %s", err) } } func TestAddOrganizationPractitionerKyc(t *testing.T) { - ctx := context.Background() - phoneNumber := interserviceclient.TestUserPhoneNumber - user, err := CreateTestUserByPhone(t, phoneNumber) - log.Printf("the user is %v", user) - if err != nil { - t.Errorf("failed to create a user by phone %v", err) - return - } - idToken := user.Auth.IDToken - headers, err := CreatedUserGraphQLHeaders(idToken) - if err != nil { - t.Errorf("error in getting headers: %w", err) - return - } - - authToken, err := firebasetools.ValidateBearerToken(ctx, *idToken) - if err != nil { - t.Errorf("invalid token: %w", err) - return - } - authenticatedContext := context.WithValue(ctx, firebasetools.AuthTokenContextKey, authToken) + headers := setUpLoggedInTestUserGraphHeaders(t) + ctx := getTestAuthenticatedContext(t) - err = setPrimaryEmailAddress(authenticatedContext, t, testEmail) + err := setPrimaryEmailAddress(ctx, t, testEmail) if err != nil { t.Errorf("failed to set primary email address: %v", err) return @@ -3660,17 +3297,17 @@ func TestAddOrganizationPractitionerKyc(t *testing.T) { partnerName := "nutrition" partnerType := profileutils.PartnerTypePractitioner - _, err = addPartnerType(authenticatedContext, t, &partnerName, partnerType) + _, err = addPartnerType(ctx, t, &partnerName, partnerType) if err != nil { t.Errorf("failed to add partnerType: %v", err) return } - _, err = setUpSupplier(authenticatedContext, t, profileutils.AccountTypeOrganisation) + _, err = setUpSupplier(ctx, t, profileutils.AccountTypeOrganisation) if err != nil { t.Errorf("failed to setup supplier: %v", err) return } - err = updateBioData(authenticatedContext, t, completeUserDetails) + err = updateBioData(ctx, t, completeUserDetails) if err != nil { t.Errorf("failed to update biodata: %v", err) return