Skip to content

Commit

Permalink
[EASI-4507] Improve Local User Accounts (#2689)
Browse files Browse the repository at this point in the history
* feat: replace localGetUserInfo from helpers to use local mock dev data okta api

* feat: dev data using mock data test data

* feat: add username to Intake relation to existing systm

* fix correct dev data using system intake id as user name. Replace with Username references

* fix linting

* feat: update some snapshots

* fix: make email addresses all lower case

* fix email casing for resolver tests

* feat: update seeder to seed with the `mock.PrincipalUser`

* feat: refactor how we fetch mock user roles for cedar systems, so we have an explicit linking for Cypress tests

* feat: update snapshots
  • Loading branch information
StevenWadeOddball committed Jul 16, 2024
1 parent 456f406 commit 4f3a042
Show file tree
Hide file tree
Showing 9 changed files with 731 additions and 564 deletions.
31 changes: 23 additions & 8 deletions cmd/devdata/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,19 @@ import (

"github.com/cms-enterprise/easi-app/cmd/devdata/mock"
"github.com/cms-enterprise/easi-app/pkg/appconfig"
"github.com/cms-enterprise/easi-app/pkg/local"
"github.com/cms-enterprise/easi-app/pkg/models"
"github.com/cms-enterprise/easi-app/pkg/storage"
"github.com/cms-enterprise/easi-app/pkg/testhelpers"
"github.com/cms-enterprise/easi-app/pkg/upload"
"github.com/cms-enterprise/easi-app/pkg/usersearch"
)

type seederConfig struct {
logger *zap.Logger
store *storage.Store
s3Client *upload.S3Client
logger *zap.Logger
store *storage.Store
s3Client *upload.S3Client
UserSearchClient usersearch.Client
}

func main() {
Expand Down Expand Up @@ -60,10 +63,14 @@ func main() {
s3Client := upload.NewS3Client(s3Cfg)

ctx := mock.CtxWithLoggerAndPrincipal(logger, store, mock.PrincipalUser)

localOktaClient := local.NewOktaAPIClient()

seederConfig := &seederConfig{
logger: logger,
store: store,
s3Client: &s3Client,
logger: logger,
store: store,
s3Client: &s3Client,
UserSearchClient: localOktaClient,
}

var intake *models.SystemIntake
Expand Down Expand Up @@ -552,6 +559,7 @@ func main() {
logger,
store,
intakeID,
mock.PrincipalUser,
[]string{"12345", "67890"},
)

Expand All @@ -562,6 +570,7 @@ func main() {
logger,
store,
intakeID,
mock.PrincipalUser,
[]string{"00001", "00002"},
[]string{
"{11AB1A00-1234-5678-ABC1-1A001B00CC0A}",
Expand All @@ -575,6 +584,7 @@ func main() {
logger,
store,
intakeID,
mock.PrincipalUser,
[]string{"00003", "00004"},
[]string{
"{11AB1A00-1234-5678-ABC1-1A001B00CC0A}",
Expand All @@ -588,6 +598,7 @@ func main() {
logger,
store,
intakeID,
mock.PrincipalUser,
[]string{"00003", "00004"},
[]string{
"{11AB1A00-1234-5678-ABC1-1A001B00CC1B}",
Expand All @@ -601,6 +612,7 @@ func main() {
logger,
store,
intakeID,
mock.PrincipalUser,
[]string{"00005", "00001"},
[]string{
"{11AB1A00-1234-5678-ABC1-1A001B00CC5F}",
Expand All @@ -614,6 +626,7 @@ func main() {
logger,
store,
intakeID,
mock.PrincipalUser,
"My Cool Existing Contract/Service",
[]string{"00001"},
)
Expand All @@ -625,13 +638,14 @@ func main() {
logger,
store,
intakeID,
mock.PrincipalUser,
[]string{"12345", "67890"},
[]string{
"{11AB1A00-1234-5678-ABC1-1A001B00CC0A}",
"{11AB1A00-1234-5678-ABC1-1A001B00CC1B}",
},
)
unlinkSystemIntakeRelation(logger, store, intakeID)
unlinkSystemIntakeRelation(logger, store, intakeID, mock.PrincipalUser)

// 5. Link deactivated Systems
intakeID = uuid.MustParse("04cb8a97-3515-4071-9b80-2710834cd94c")
Expand All @@ -640,6 +654,7 @@ func main() {
logger,
store,
intakeID,
mock.PrincipalUser,
[]string{"12345", "67890"},
[]string{
"{11AB1A00-1234-5678-ABC1-1A001B00CC5F}",
Expand Down Expand Up @@ -668,7 +683,7 @@ func main() {
intake = makeSystemIntakeAndProgressToStep(
"For business case Cypress test",
&intakeID,
"E2E1",
mock.EndToEnd1User,
logger,
store,
models.SystemIntakeStepToProgressToDraftBusinessCase,
Expand Down
70 changes: 45 additions & 25 deletions cmd/devdata/mock/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,50 +8,70 @@ import (

"github.com/cms-enterprise/easi-app/pkg/appcontext"
"github.com/cms-enterprise/easi-app/pkg/authentication"
"github.com/cms-enterprise/easi-app/pkg/local"
"github.com/cms-enterprise/easi-app/pkg/models"
"github.com/cms-enterprise/easi-app/pkg/storage"
"github.com/cms-enterprise/easi-app/pkg/userhelpers"
)

// PrincipalUser is the "current user" when seeding the data
const PrincipalUser = "ABCD"
// These represent some users who have mocked okta data
const (
// PrincipalUser is the "current user" when seeding the data (Adeline Aarons)
PrincipalUser = "ABCD"

// TestUser is the "TEST user" when seeding the data (Terry Thompson)
TestUser = "TEST"

// EndToEnd1User is the username of the user for some end to end testing
EndToEnd1User = "E2E1"

// EndToEnd2User is the username of the user for some end to end testing
EndToEnd2User = "E2E2"

AllyUser = "A11Y"
GaryUser = "GRTB"
AubryUser = "ADMI"
User1User = "USR1"
User2User = "USR2"
User3User = "USR3"
User4User = "USR4"
User5User = "USR5"
TheoUser = "CJRW"
)

var UserNamesForCedarSystemRoles = []string{
PrincipalUser, TestUser, EndToEnd1User, EndToEnd2User, AllyUser, GaryUser, AubryUser, User1User, User2User, User3User, User4User, User5User, TheoUser,
// Duplicate so we don't run out of users for roles
PrincipalUser, TestUser, EndToEnd1User, EndToEnd2User, AllyUser, GaryUser, AubryUser, User1User, User2User, User3User, User4User, User5User, TheoUser,
PrincipalUser, TestUser, EndToEnd1User, EndToEnd2User, AllyUser, GaryUser, AubryUser, User1User, User2User, User3User, User4User, User5User, TheoUser}

// FetchUserInfoMock mocks the fetch user info logic
func FetchUserInfoMock(ctx context.Context, eua string) (*models.UserInfo, error) {
return &models.UserInfo{
Username: eua,
FirstName: eua,
LastName: "Doe",
DisplayName: eua + "Doe",
Email: models.EmailAddress(eua + "@example.com"),
}, nil
func FetchUserInfoMock(ctx context.Context, username string) (*models.UserInfo, error) {
localOktaClient := local.NewOktaAPIClient()
return localOktaClient.FetchUserInfo(ctx, username)
}

// FetchUserInfosMock mocks the fetch user info logic
func FetchUserInfosMock(ctx context.Context, euas []string) ([]*models.UserInfo, error) {
userInfos := make([]*models.UserInfo, 0, len(euas))
for _, eua := range euas {
userInfo, err := FetchUserInfoMock(ctx, eua)
if err != nil {
return nil, err
}
userInfos = append(userInfos, userInfo)
}
return userInfos, nil
func FetchUserInfosMock(ctx context.Context, usernames []string) ([]*models.UserInfo, error) {
localOktaClient := local.NewOktaAPIClient()
return localOktaClient.FetchUserInfos(ctx, usernames)
}

// CtxWithLoggerAndPrincipal makes a context with a mocked logger and principal
func CtxWithLoggerAndPrincipal(logger *zap.Logger, store *storage.Store, euaID string) context.Context {
if len(euaID) < 1 {
euaID = PrincipalUser
func CtxWithLoggerAndPrincipal(logger *zap.Logger, store *storage.Store, username string) context.Context {
//Future Enhancement: Consider adding this to the seederConfig, and also emb
if len(username) < 1 {
username = PrincipalUser
}
userAccount, err := userhelpers.GetOrCreateUserAccount(context.Background(), store, store, euaID, true, userhelpers.GetOktaAccountInfoWrapperFunction(userhelpers.GetUserInfoFromOktaLocal))

//Future Enhancement: consider passing the context with the seeder, and using the seeder.UserSearchClient to return mocked data instead of needing to initialize a client for each mock call
userAccount, err := userhelpers.GetOrCreateUserAccount(context.Background(), store, store, username, true, userhelpers.GetUserInfoAccountInfoWrapperFunc(FetchUserInfoMock))
if err != nil {
panic(fmt.Errorf("failed to get or create user account for mock data: %w", err))
}

princ := &authentication.EUAPrincipal{
EUAID: euaID,
EUAID: username,
JobCodeEASi: true,
JobCodeGRT: true,
UserAccount: userAccount,
Expand Down
13 changes: 8 additions & 5 deletions cmd/devdata/system_intake.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,9 +260,10 @@ func setSystemIntakeRelationNewSystem(
logger *zap.Logger,
store *storage.Store,
intakeID uuid.UUID,
username string,
contractNumbers []string,
) {
ctx := mock.CtxWithLoggerAndPrincipal(logger, store, intakeID.String())
ctx := mock.CtxWithLoggerAndPrincipal(logger, store, username)
input := &models.SetSystemIntakeRelationNewSystemInput{
SystemIntakeID: intakeID,
ContractNumbers: contractNumbers,
Expand All @@ -285,10 +286,11 @@ func setSystemIntakeRelationExistingSystem(
logger *zap.Logger,
store *storage.Store,
intakeID uuid.UUID,
username string,
contractNumbers []string,
cedarSystemIDs []string,
) {
ctx := mock.CtxWithLoggerAndPrincipal(logger, store, intakeID.String())
ctx := mock.CtxWithLoggerAndPrincipal(logger, store, username)
input := &models.SetSystemIntakeRelationExistingSystemInput{
SystemIntakeID: intakeID,
ContractNumbers: contractNumbers,
Expand Down Expand Up @@ -320,10 +322,11 @@ func setSystemIntakeRelationExistingService(
logger *zap.Logger,
store *storage.Store,
intakeID uuid.UUID,
username string,
contractName string,
contractNumbers []string,
) {
ctx := mock.CtxWithLoggerAndPrincipal(logger, store, intakeID.String())
ctx := mock.CtxWithLoggerAndPrincipal(logger, store, username)
input := &models.SetSystemIntakeRelationExistingServiceInput{
SystemIntakeID: intakeID,
ContractName: contractName,
Expand All @@ -344,8 +347,8 @@ func setSystemIntakeRelationExistingService(
}
}

func unlinkSystemIntakeRelation(logger *zap.Logger, store *storage.Store, intakeID uuid.UUID) {
ctx := mock.CtxWithLoggerAndPrincipal(logger, store, intakeID.String())
func unlinkSystemIntakeRelation(logger *zap.Logger, store *storage.Store, intakeID uuid.UUID, username string) {
ctx := mock.CtxWithLoggerAndPrincipal(logger, store, username)

// temp, manually unlink contract numbers
// see Note [EASI-4160 Disable Contract Number Linking]
Expand Down
48 changes: 26 additions & 22 deletions pkg/graph/resolvers/resolver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/cms-enterprise/easi-app/pkg/testhelpers"
"github.com/cms-enterprise/easi-app/pkg/upload"
"github.com/cms-enterprise/easi-app/pkg/userhelpers"
"github.com/cms-enterprise/easi-app/pkg/usersearch"

ld "gopkg.in/launchdarkly/go-server-sdk.v5"
)
Expand All @@ -46,11 +47,16 @@ func (s *ResolverSuite) SetupTest() {
assert.NoError(s.T(), err)

// Get the user account from the DB fresh for each test
princ := getTestPrincipal(s.testConfigs.Context, s.testConfigs.Store, s.testConfigs.UserInfo.Username)
princ := s.getTestPrincipal(s.testConfigs.Context, s.testConfigs.Store, s.testConfigs.UserInfo.Username)
s.testConfigs.Principal = princ

// get new dataloaders to clear any existing cached data
s.testConfigs.Context = s.ctxWithNewDataloaders()

// Since we are recreating the context we need to wrap all expected values on the context (like the principal)
s.testConfigs.Context = appcontext.WithLogger(s.testConfigs.Context, s.testConfigs.Logger)
s.testConfigs.Context = appcontext.WithPrincipal(s.testConfigs.Context, princ)

}

// TestResolverSuite runs the resolver test suite
Expand All @@ -69,16 +75,17 @@ func TestResolverSuite(t *testing.T) {

// TestConfigs is a struct that contains all the dependencies needed to run a test
type TestConfigs struct {
DBConfig storage.DBConfig
LDClient *ld.LDClient
S3Client *upload.S3Client
Logger *zap.Logger
UserInfo *models.UserInfo
Store *storage.Store
Principal *authentication.EUAPrincipal
Context context.Context
EmailClient *email.Client
Sender *mockSender
DBConfig storage.DBConfig
LDClient *ld.LDClient
S3Client *upload.S3Client
Logger *zap.Logger
UserInfo *models.UserInfo
Store *storage.Store
Principal *authentication.EUAPrincipal
Context context.Context
EmailClient *email.Client
Sender *mockSender
UserSearchClient usersearch.Client
}

type mockSender struct {
Expand Down Expand Up @@ -119,12 +126,11 @@ func (tc *TestConfigs) GetDefaults() {
}
tc.Store, _ = storage.NewStore(tc.DBConfig, tc.LDClient)

// create the test context
// principal is fetched between each test in SetupTest()
ctx := appcontext.WithLogger(context.Background(), tc.Logger)
ctx = appcontext.WithPrincipal(ctx, getTestPrincipal(ctx, tc.Store, tc.UserInfo.Username))
localOktaClient := local.NewOktaAPIClient()
tc.UserSearchClient = localOktaClient

tc.Context = ctx
// create the test context, note because of the data loaders, the context gets recreated before each test.
tc.Context = context.Background()

localSender := mockSender{}
tc.Sender = &localSender
Expand All @@ -148,9 +154,10 @@ func NewEmailClient(sender *mockSender) *email.Client {
return &emailClient
}

func getTestPrincipal(ctx context.Context, store *storage.Store, userName string) *authentication.EUAPrincipal {
// getTestPrincipal gets a user principal from database
func (s *ResolverSuite) getTestPrincipal(ctx context.Context, store *storage.Store, userName string) *authentication.EUAPrincipal {

userAccount, _ := userhelpers.GetOrCreateUserAccount(ctx, store, store, userName, true, userhelpers.GetOktaAccountInfoWrapperFunction(userhelpers.GetUserInfoFromOktaLocal))
userAccount, _ := userhelpers.GetOrCreateUserAccount(ctx, store, store, userName, true, userhelpers.GetUserInfoAccountInfoWrapperFunc(s.testConfigs.UserSearchClient.FetchUserInfo))

princ := &authentication.EUAPrincipal{
EUAID: userName,
Expand Down Expand Up @@ -240,17 +247,14 @@ func (s *ResolverSuite) getOrCreateUserAcct(euaUserID string) *authentication.Us
// that caching feature is great for app code, but in test code, where we often load something,
// update that thing, and load it again to confirm updates worked, caching the first version breaks that flow
func (s *ResolverSuite) ctxWithNewDataloaders() context.Context {
fetchUserInfos := func(ctx context.Context, euaUserIDs []string) ([]*models.UserInfo, error) {
return nil, nil
}

coreClient := cedarcore.NewClient(s.testConfigs.Context, "", "", "", true, true)
getCedarSystems := func(ctx context.Context) ([]*models.CedarSystem, error) {
return coreClient.GetSystemSummary(ctx)
}

buildDataloaders := func() *dataloaders.Dataloaders {
return dataloaders.NewDataloaders(s.testConfigs.Store, fetchUserInfos, getCedarSystems)
return dataloaders.NewDataloaders(s.testConfigs.Store, s.testConfigs.UserSearchClient.FetchUserInfos, getCedarSystems)
}

// Set up mocked dataloaders for the test context
Expand Down
4 changes: 3 additions & 1 deletion pkg/local/authentication_middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,9 @@ func devUserContext(ctx context.Context, authHeader string, store *storage.Store
JobCodeGRT: swag.ContainsStrings(config.JobCodes, "EASI_D_GOVTEAM"),
JobCodeTRBAdmin: swag.ContainsStrings(config.JobCodes, "EASI_TRB_ADMIN_D"),
}
userAccount, err := userhelpers.GetOrCreateUserAccount(ctx, store, store, princ.ID(), true, userhelpers.GetOktaAccountInfoWrapperFunction(userhelpers.GetUserInfoFromOktaLocal))
localOktaClient := NewOktaAPIClient()

userAccount, err := userhelpers.GetOrCreateUserAccount(ctx, store, store, princ.ID(), true, userhelpers.GetUserInfoAccountInfoWrapperFunc(localOktaClient.FetchUserInfo))
if err != nil {
return nil, err
}
Expand Down
Loading

0 comments on commit 4f3a042

Please sign in to comment.