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  }