-
Notifications
You must be signed in to change notification settings - Fork 0
/
resolver_test.go
299 lines (257 loc) · 10.6 KB
/
resolver_test.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
package resolvers
import (
"context"
"testing"
"github.com/guregu/null"
"github.com/jmoiron/sqlx"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"go.uber.org/zap"
"github.com/cms-enterprise/easi-app/pkg/appconfig"
"github.com/cms-enterprise/easi-app/pkg/appcontext"
"github.com/cms-enterprise/easi-app/pkg/authentication"
cedarcore "github.com/cms-enterprise/easi-app/pkg/cedar/core"
"github.com/cms-enterprise/easi-app/pkg/dataloaders"
"github.com/cms-enterprise/easi-app/pkg/email"
"github.com/cms-enterprise/easi-app/pkg/local"
"github.com/cms-enterprise/easi-app/pkg/models"
"github.com/cms-enterprise/easi-app/pkg/sqlutils"
"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/userhelpers"
"github.com/cms-enterprise/easi-app/pkg/usersearch"
ld "gopkg.in/launchdarkly/go-server-sdk.v5"
)
// ResolverSuite is the testify suite for the resolver package
type ResolverSuite struct {
suite.Suite
*require.Assertions // included so that calls to things like ResolverSuite.NoError or ResolverSuite.Equal() use the "require" version instead of "assert"
testConfigs *TestConfigs
fetchUserInfoStub func(context.Context, string) (*models.UserInfo, error)
}
// SetupTest clears the database between each test
func (s *ResolverSuite) SetupTest() {
// We need to set the *require.Assertions here, as we need to have already called suite.Run() to ensure the
// test suite has been constructed before we call suite.Require()
s.Assertions = s.Require()
// Clean all tables before each test
err := s.testConfigs.Store.TruncateAllTablesDANGEROUS(s.testConfigs.Logger)
assert.NoError(s.T(), err)
// Get the user account from the DB fresh for each test
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)
// Clear email data between tests
s.testConfigs.Sender.Clear()
}
// TestResolverSuite runs the resolver test suite
func TestResolverSuite(t *testing.T) {
rs := new(ResolverSuite)
rs.testConfigs = GetDefaultTestConfigs()
rs.fetchUserInfoStub = func(context.Context, string) (*models.UserInfo, error) {
return &models.UserInfo{
Username: "ANON",
DisplayName: "Anonymous",
Email: models.NewEmailAddress("anon@local.fake"),
}, nil
}
suite.Run(t, rs)
}
// 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
UserSearchClient usersearch.Client
}
type mockSender struct {
toAddresses []models.EmailAddress
ccAddresses []models.EmailAddress
bccAddresses []models.EmailAddress
subject string
body string
emailWasSent bool
sentEmails []email.Email
}
func (s *mockSender) Send(ctx context.Context, emailData email.Email) error {
s.toAddresses = emailData.ToAddresses
s.ccAddresses = emailData.CcAddresses
s.bccAddresses = emailData.BccAddresses
s.subject = emailData.Subject
s.body = emailData.Body
s.emailWasSent = true
s.sentEmails = append(s.sentEmails, emailData)
return nil
}
func (s *mockSender) Clear() {
s.toAddresses = []models.EmailAddress{}
s.ccAddresses = []models.EmailAddress{}
s.bccAddresses = []models.EmailAddress{}
s.subject = ""
s.body = ""
s.emailWasSent = false
s.sentEmails = []email.Email{}
}
// GetDefaultTestConfigs returns a TestConfigs struct with all the dependencies needed to run a test
func GetDefaultTestConfigs() *TestConfigs {
tc := TestConfigs{}
tc.GetDefaults()
return &tc
}
// GetDefaults sets the dependencies for the TestConfigs struct
func (tc *TestConfigs) GetDefaults() {
tc.DBConfig = NewDBConfig()
tc.LDClient, _ = ld.MakeCustomClient("fake", ld.Config{Offline: true}, 0)
s3Client := upload.NewS3Client(newS3Config())
tc.S3Client = &s3Client
tc.Logger = zap.NewNop()
tc.UserInfo = &models.UserInfo{
DisplayName: "Test User",
Email: "testuser@test.com",
Username: "TEST",
}
tc.Store, _ = storage.NewStore(tc.DBConfig, tc.LDClient)
localOktaClient := local.NewOktaAPIClient()
tc.UserSearchClient = localOktaClient
// create the test context, note because of the data loaders, the context gets recreated before each test.
tc.Context = context.Background()
emailClient, localSender := NewEmailClient()
tc.Sender = localSender
tc.EmailClient = emailClient
}
func NewEmailClient() (*email.Client, *mockSender) {
sender := &mockSender{}
config := testhelpers.NewConfig()
emailConfig := email.Config{
GRTEmail: models.NewEmailAddress(config.GetString(appconfig.GRTEmailKey)),
ITInvestmentEmail: models.NewEmailAddress(config.GetString(appconfig.ITInvestmentEmailKey)),
TRBEmail: models.NewEmailAddress(config.GetString(appconfig.TRBEmailKey)),
EASIHelpEmail: models.NewEmailAddress(config.GetString(appconfig.EASIHelpEmailKey)),
URLHost: config.GetString(appconfig.ClientHostKey),
URLScheme: config.GetString(appconfig.ClientProtocolKey),
TemplateDirectory: config.GetString(appconfig.EmailTemplateDirectoryKey),
}
emailClient, _ := email.NewClient(emailConfig, sender)
return &emailClient, sender
}
// 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.GetUserInfoAccountInfoWrapperFunc(s.testConfigs.UserSearchClient.FetchUserInfo))
princ := &authentication.EUAPrincipal{
EUAID: userName,
JobCodeEASi: true,
JobCodeGRT: true,
UserAccount: userAccount,
}
return princ
}
// NewDBConfig returns a DBConfig struct with values from appconfig
func NewDBConfig() storage.DBConfig {
config := testhelpers.NewConfig()
return storage.DBConfig{
Host: config.GetString(appconfig.DBHostConfigKey),
Port: config.GetString(appconfig.DBPortConfigKey),
Database: config.GetString(appconfig.DBNameConfigKey),
Username: config.GetString(appconfig.DBUsernameConfigKey),
Password: config.GetString(appconfig.DBPasswordConfigKey),
SSLMode: config.GetString(appconfig.DBSSLModeConfigKey),
MaxConnections: config.GetInt(appconfig.DBMaxConnections),
}
}
func newS3Config() upload.Config {
config := testhelpers.NewConfig()
return upload.Config{
IsLocal: true,
Bucket: config.GetString(appconfig.AWSS3FileUploadBucket),
Region: config.GetString(appconfig.AWSRegion),
}
}
// utility method for creating a valid new system intake, checking for any errors
func (s *ResolverSuite) createNewIntake(ops ...func(*models.SystemIntake)) *models.SystemIntake {
newIntake, err := s.testConfigs.Store.CreateSystemIntake(s.testConfigs.Context, &models.SystemIntake{
ProjectName: null.StringFrom("TEST"),
// these fields are required by the SQL schema for the system_intakes table, and CreateSystemIntake() doesn't set them to defaults
RequestType: models.SystemIntakeRequestTypeNEW,
})
s.NoError(err)
for _, op := range ops {
op(newIntake)
}
if len(ops) > 0 {
newIntake, err = s.testConfigs.Store.UpdateSystemIntake(s.testConfigs.Context, newIntake)
s.NoError(err)
}
return newIntake
}
// utility method for creating a valid new TRB Request, checking for any errors
func (s *ResolverSuite) createNewTRBRequest(ops ...func(*models.TRBRequest)) *models.TRBRequest {
newTRBRequest, err := CreateTRBRequest(s.testConfigs.Context, models.TRBTBrainstorm, s.testConfigs.Store)
s.NoError(err)
for _, op := range ops {
op(newTRBRequest)
}
if len(ops) > 0 {
newTRBRequest, err = s.testConfigs.Store.UpdateTRBRequest(s.testConfigs.Context, newTRBRequest)
s.NoError(err)
}
return newTRBRequest
}
// utility method to get userAcct in resolver tests
func (s *ResolverSuite) getOrCreateUserAcct(euaUserID string) *authentication.UserAccount {
ctx := s.testConfigs.Context
store := s.testConfigs.Store
okta := local.NewOktaAPIClient()
userAcct, err := sqlutils.WithTransactionRet(ctx, store, func(tx *sqlx.Tx) (*authentication.UserAccount, error) {
user, err := userhelpers.GetOrCreateUserAccount(ctx, tx, store, euaUserID, false, userhelpers.GetUserInfoAccountInfoWrapperFunc(okta.FetchUserInfo))
if err != nil {
return nil, err
}
return user, nil
})
s.NoError(err)
return userAcct
}
// utility method to get userAcct in resolver tests
func (s *ResolverSuite) getOrCreateUserAccts(euaUserIDs []string) []*authentication.UserAccount {
ctx := s.testConfigs.Context
store := s.testConfigs.Store
okta := local.NewOktaAPIClient()
userAccts, err := sqlutils.WithTransactionRet(ctx, store, func(tx *sqlx.Tx) ([]*authentication.UserAccount, error) {
users, err := userhelpers.GetOrCreateUserAccounts(ctx, tx, store, euaUserIDs, false, userhelpers.GetUserInfoAccountInfosWrapperFunc(okta.FetchUserInfos))
if err != nil {
return nil, err
}
return users, nil
})
s.NoError(err)
return userAccts
}
// ctxWithNewDataloaders sets new Dataloaders on the test suite's existing context and returns that context.
// this is necessary in order to avoid the caching feature of the dataloadgen library.
// 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 {
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, s.testConfigs.UserSearchClient.FetchUserInfos, getCedarSystems)
}
// Set up mocked dataloaders for the test context
s.testConfigs.Context = dataloaders.CTXWithLoaders(s.testConfigs.Context, buildDataloaders)
return s.testConfigs.Context
}