github.com/decred/politeia@v1.4.0/politeiawww/legacy/codestats_test.go (about) 1 package legacy 2 3 import ( 4 "fmt" 5 "math/rand" 6 "testing" 7 "time" 8 9 "github.com/davecgh/go-spew/spew" 10 cms "github.com/decred/politeia/politeiawww/api/cms/v1" 11 www "github.com/decred/politeia/politeiawww/api/www/v1" 12 "github.com/decred/politeia/politeiawww/legacy/codetracker" 13 "github.com/decred/politeia/politeiawww/legacy/user" 14 "github.com/go-test/deep" 15 "github.com/google/uuid" 16 ) 17 18 const ( 19 // Some various constants to prepare mock code stats. 20 // Number of months of data to mock 21 numberOfMonths = 3 22 23 // Starting month and year for the mock data. 24 startingMonth = 11 25 startingYear = 2019 26 27 // Number of PRs per month to add to the mock data. 28 numberOfMonthPrs = 5 29 30 // Number of Reviews per month to add to the mock data. 31 numberOfMonthReviews = 3 32 ) 33 34 func TestProcessUserCodeStats(t *testing.T) { 35 p, cleanup := newTestCMSwww(t) 36 defer cleanup() 37 38 requestedUser := newCMSUser(t, p, false, true, cms.DomainTypeDeveloper, 39 cms.ContractorTypeDirect) 40 41 // mockedCodeStats currently creates mocked code stats in user db 42 mockedCodeStats := createMockedStats(requestedUser.GitHubName) 43 44 oneMonthStartDate := time.Date(startingYear, time.Month(startingMonth), 45 1, 0, 0, 0, 0, time.UTC) 46 oneMonthEndDate := time.Date(startingYear, time.Month(startingMonth+1), 47 1, 0, 0, 0, 0, time.UTC) 48 49 oneMonthExpectedReply := convertExpectedResults(mockedCodeStats, 50 oneMonthStartDate, oneMonthEndDate) 51 52 oneMonthExpectedNoEndReply := convertExpectedResults(mockedCodeStats, 53 oneMonthStartDate, oneMonthStartDate) 54 55 twoMonthStartDate := time.Date(startingYear, time.Month(startingMonth), 56 1, 0, 0, 0, 0, time.UTC) 57 twoMonthEndDate := time.Date(startingYear, time.Month(startingMonth+2), 58 1, 0, 0, 0, 0, time.UTC) 59 60 twoMonthExpectedReply := convertExpectedResults(mockedCodeStats, 61 twoMonthStartDate, twoMonthEndDate) 62 63 threeMonthStartDate := time.Date(startingYear, time.Month(startingMonth), 64 1, 0, 0, 0, 0, time.UTC) 65 threeMonthEndDate := time.Date(startingYear, 66 time.Month(startingMonth+numberOfMonths), 1, 0, 0, 0, 0, time.UTC) 67 68 threeMonthExpectedReply := convertExpectedResults(mockedCodeStats, 69 threeMonthStartDate, threeMonthEndDate) 70 71 // Create mocked code stats for testing expected response 72 ncs := user.UpdateCMSCodeStats{ 73 UserCodeStats: mockedCodeStats, 74 } 75 payload, err := user.EncodeUpdateCMSCodeStats(ncs) 76 if err != nil { 77 t.Fatalf("unable to encode update code stats payload %v", err) 78 } 79 pc := user.PluginCommand{ 80 ID: user.CMSPluginID, 81 Command: user.CmdNewCMSUserCodeStats, 82 Payload: string(payload), 83 } 84 _, err = p.db.PluginExec(pc) 85 if err != nil { 86 t.Fatalf("unable to execute update code stats payload %v", err) 87 } 88 89 randomUUID := uuid.New().String() 90 noGithubNameSet := newCMSUser(t, p, false, false, cms.DomainTypeDeveloper, 91 cms.ContractorTypeDirect) 92 differentDomain := newCMSUser(t, p, false, true, cms.DomainTypeMarketing, 93 cms.ContractorTypeDirect) 94 95 admin := newCMSUser(t, p, true, true, cms.DomainTypeDeveloper, 96 cms.ContractorTypeDirect) 97 98 nonCMSUser, _ := newUser(t, p, true, false) 99 100 var tests = []struct { 101 name string 102 params cms.UserCodeStats 103 wantError error 104 requesting *user.User 105 wantReply *cms.UserCodeStatsReply 106 }{ 107 { 108 "error no dates", 109 cms.UserCodeStats{ 110 UserID: requestedUser.ID.String(), 111 }, 112 www.UserError{ 113 ErrorCode: cms.ErrorStatusInvalidDatesRequested, 114 }, 115 &admin.User, 116 nil, 117 }, 118 { 119 "error no start date", 120 cms.UserCodeStats{ 121 UserID: requestedUser.ID.String(), 122 EndTime: 872841440, 123 }, 124 www.UserError{ 125 ErrorCode: cms.ErrorStatusInvalidDatesRequested, 126 }, 127 &admin.User, 128 nil, 129 }, 130 { 131 "error invalid dates end before start", 132 cms.UserCodeStats{ 133 UserID: requestedUser.ID.String(), 134 // Friday, August 29, 1997 8:14:00 AM 135 StartTime: 872842440, 136 // Friday, August 29, 1997 8:14:00 AM 137 EndTime: 872841440, 138 }, 139 www.UserError{ 140 ErrorCode: cms.ErrorStatusInvalidDatesRequested, 141 }, 142 &admin.User, 143 nil, 144 }, 145 { 146 "error invalid dates beyond 6 month window", 147 cms.UserCodeStats{ 148 UserID: requestedUser.ID.String(), 149 // Friday, August 29, 1997 8:14:00 AM 150 StartTime: 872842440, 151 // Thursday, September 10, 2020 2:35:47 PM 152 EndTime: 1599748547, 153 }, 154 www.UserError{ 155 ErrorCode: cms.ErrorStatusInvalidDatesRequested, 156 }, 157 &admin.User, 158 nil, 159 }, 160 { 161 "error can't find requested user id", 162 cms.UserCodeStats{ 163 UserID: randomUUID, 164 // Thursday, September 10, 2020 11:49:07 AM 165 StartTime: 1599738547, 166 // Thursday, September 10, 2020 2:35:47 PM 167 EndTime: 1599748547, 168 }, 169 www.UserError{ 170 ErrorCode: www.ErrorStatusUserNotFound, 171 }, 172 &admin.User, 173 nil, 174 }, 175 { 176 "error can't find requesting cms user", 177 cms.UserCodeStats{ 178 UserID: requestedUser.ID.String(), 179 // Thursday, September 10, 2020 11:49:07 AM 180 StartTime: 1599738547, 181 // Thursday, September 10, 2020 2:35:47 PM 182 EndTime: 1599748547, 183 }, 184 www.UserError{ 185 ErrorCode: www.ErrorStatusUserNotFound, 186 }, 187 nonCMSUser, 188 nil, 189 }, 190 { 191 "empty reply different domain non-admin", 192 cms.UserCodeStats{ 193 UserID: requestedUser.ID.String(), 194 // Thursday, September 10, 2020 11:49:07 AM 195 StartTime: 1599738547, 196 // Thursday, September 10, 2020 2:35:47 PM 197 EndTime: 1599748547, 198 }, 199 nil, 200 &differentDomain.User, 201 &cms.UserCodeStatsReply{}, 202 }, 203 { 204 "error can't find requesting cms user", 205 cms.UserCodeStats{ 206 UserID: noGithubNameSet.ID.String(), 207 // Thursday, September 10, 2020 11:49:07 AM 208 StartTime: 1599738547, 209 // Thursday, September 10, 2020 2:35:47 PM 210 EndTime: 1599748547, 211 }, 212 www.UserError{ 213 ErrorCode: cms.ErrorStatusMissingCodeStatsUsername, 214 }, 215 &admin.User, 216 nil, 217 }, 218 { 219 "success one month range", 220 cms.UserCodeStats{ 221 UserID: requestedUser.ID.String(), 222 StartTime: oneMonthStartDate.Unix(), 223 EndTime: oneMonthEndDate.Unix(), 224 }, 225 nil, 226 &admin.User, 227 oneMonthExpectedReply, 228 }, 229 { 230 "success one month range no end", 231 cms.UserCodeStats{ 232 UserID: requestedUser.ID.String(), 233 StartTime: oneMonthStartDate.Unix(), 234 }, 235 nil, 236 &admin.User, 237 oneMonthExpectedNoEndReply, 238 }, 239 { 240 "success two month range", 241 cms.UserCodeStats{ 242 UserID: requestedUser.ID.String(), 243 StartTime: twoMonthStartDate.Unix(), 244 EndTime: twoMonthEndDate.Unix(), 245 }, 246 nil, 247 &admin.User, 248 twoMonthExpectedReply, 249 }, 250 { 251 "success three month range", 252 cms.UserCodeStats{ 253 UserID: requestedUser.ID.String(), 254 StartTime: threeMonthStartDate.Unix(), 255 EndTime: threeMonthEndDate.Unix(), 256 }, 257 nil, 258 &admin.User, 259 threeMonthExpectedReply, 260 }, 261 } 262 263 for _, v := range tests { 264 t.Run(v.name, func(t *testing.T) { 265 reply, err := p.processUserCodeStats(v.params, v.requesting) 266 got := errToStr(err) 267 want := errToStr(v.wantError) 268 if got != want { 269 t.Errorf("got %v, want %v", got, want) 270 } 271 272 if err != nil { 273 return 274 } 275 276 diff := deep.Equal(reply, v.wantReply) 277 if diff != nil { 278 t.Errorf("got/want diff:\n%v", 279 spew.Sdump(diff)) 280 } 281 }) 282 } 283 } 284 285 // Creates a mocked set of code stats that will be updated to a test 286 // user's db information and an expected code stats results set is returned 287 // as well to confirm information. 288 func createMockedStats(username string) []user.CodeStats { 289 290 codeStats := make([]user.CodeStats, 0, numberOfMonths) 291 year := startingYear 292 293 for month := startingMonth; month < numberOfMonths+startingMonth; month++ { 294 mergedPRs := make([]codetracker.PullRequestInformation, 0, 295 numberOfMonthPrs) 296 updatePRs := make([]codetracker.PullRequestInformation, 0, 297 numberOfMonthPrs) 298 for i := 1; i <= numberOfMonthPrs; i++ { 299 date := time.Date(startingYear, time.Month(month), i, 0, 0, 0, 0, 300 time.UTC) 301 prNumber := i + month*10 302 url := fmt.Sprintf("http://github.com/test/%v/pull/%v", month, 303 prNumber) 304 additions := rand.Intn(100) 305 deletions := rand.Intn(100) 306 mergedPRs = append(mergedPRs, codetracker.PullRequestInformation{ 307 Repository: fmt.Sprintf("%v", month), 308 URL: url, 309 Number: prNumber, 310 Additions: int64(additions), 311 Deletions: int64(deletions), 312 Date: date.String(), 313 State: "MERGED", 314 }) 315 } 316 reviews := make([]codetracker.ReviewInformation, 0, 317 numberOfMonthReviews) 318 for i := 1; i <= numberOfMonthReviews; i++ { 319 date := time.Date(startingYear, time.Month(month), i, 0, 0, 0, 0, 320 time.UTC) 321 prNumber := i + month*10 322 url := fmt.Sprintf("http://github.com/test/%v/pull/%v", month, 323 prNumber) 324 additions := rand.Intn(100) 325 deletions := rand.Intn(100) 326 reviews = append(reviews, codetracker.ReviewInformation{ 327 Repository: fmt.Sprintf("%v", month), 328 URL: url, 329 Number: prNumber, 330 Additions: additions, 331 Deletions: deletions, 332 Date: date.String(), 333 State: "APPROVED", 334 }) 335 } 336 userInfo := &codetracker.UserInformationResult{ 337 Reviews: reviews, 338 MergedPRs: mergedPRs, 339 UpdatedPRs: updatePRs, 340 } 341 codeStats = append(codeStats, 342 convertCodeTrackerToUserCodeStats(username, year, month, 343 userInfo)...) 344 } 345 return codeStats 346 } 347 348 func convertExpectedResults(codeStats []user.CodeStats, start, end time.Time) *cms.UserCodeStatsReply { 349 reply := &cms.UserCodeStatsReply{} 350 rangeCodeStats := make([]user.CodeStats, 0, 6) 351 for !start.After(end) { 352 for _, codeStat := range codeStats { 353 if codeStat.Month == int(start.Month()) && 354 codeStat.Year == start.Year() { 355 rangeCodeStats = append(rangeCodeStats, codeStat) 356 } 357 } 358 start = time.Date(start.Year(), start.Month()+1, 359 start.Day(), start.Hour(), start.Minute(), 0, 0, 360 time.UTC) 361 } 362 reply.RepoStats = convertCodeStatsFromDatabase(rangeCodeStats) 363 364 return reply 365 }