github.com/grafana/pyroscope@v1.18.0/pkg/frontend/vcs/commit_test.go (about) 1 package vcs 2 3 import ( 4 "context" 5 "errors" 6 "net/http" 7 "testing" 8 9 "github.com/google/go-github/v58/github" 10 "github.com/stretchr/testify/assert" 11 "github.com/stretchr/testify/mock" 12 13 vcsv1 "github.com/grafana/pyroscope/api/gen/proto/go/vcs/v1" 14 ) 15 16 type gitHubCommitGetterMock struct { 17 mock.Mock 18 } 19 20 func (m *gitHubCommitGetterMock) GetCommit(ctx context.Context, owner, repo, ref string) (*vcsv1.CommitInfo, error) { 21 args := m.Called(ctx, owner, repo, ref) 22 if args.Get(0) == nil { 23 return nil, args.Error(1) 24 } 25 26 return args.Get(0).(*vcsv1.CommitInfo), args.Error(1) 27 } 28 29 func TestGetCommits(t *testing.T) { 30 tests := []struct { 31 name string 32 refs []string 33 mockSetup func(*gitHubCommitGetterMock) 34 expectedCommits int 35 expectedErrors int 36 expectError bool 37 }{ 38 { 39 name: "All commits succeed", 40 refs: []string{"ref1", "ref2"}, 41 mockSetup: func(m *gitHubCommitGetterMock) { 42 m.On("GetCommit", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&vcsv1.CommitInfo{}, nil) 43 }, 44 expectedCommits: 2, 45 expectedErrors: 0, 46 expectError: false, 47 }, 48 { 49 name: "Partial fetch commits success", 50 refs: []string{"ref1", "ref2", "ref3"}, 51 mockSetup: func(m *gitHubCommitGetterMock) { 52 // ref1 succeeds on first try 53 m.On("GetCommit", mock.Anything, mock.Anything, mock.Anything, "ref1").Return(&vcsv1.CommitInfo{}, nil) 54 // ref2 fails on first try, succeeds with "heads/" prefix 55 m.On("GetCommit", mock.Anything, mock.Anything, mock.Anything, "ref2").Return(nil, errors.New("not found")) 56 m.On("GetCommit", mock.Anything, mock.Anything, mock.Anything, "heads/ref2").Return(&vcsv1.CommitInfo{}, nil) 57 // ref3 fails on all attempts 58 m.On("GetCommit", mock.Anything, mock.Anything, mock.Anything, "ref3").Return(nil, errors.New("not found")) 59 m.On("GetCommit", mock.Anything, mock.Anything, mock.Anything, "heads/ref3").Return(nil, errors.New("not found")) 60 m.On("GetCommit", mock.Anything, mock.Anything, mock.Anything, "tags/ref3").Return(nil, errors.New("not found")) 61 }, 62 expectedCommits: 2, 63 expectedErrors: 1, 64 expectError: false, 65 }, 66 { 67 name: "All commits fail to fetch", 68 refs: []string{"ref1", "ref2"}, 69 mockSetup: func(m *gitHubCommitGetterMock) { 70 m.On("GetCommit", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, errors.New("not found")) 71 }, 72 expectedCommits: 0, 73 expectedErrors: 2, 74 expectError: true, 75 }, 76 } 77 78 for _, tt := range tests { 79 t.Run(tt.name, func(t *testing.T) { 80 mockGetter := new(gitHubCommitGetterMock) 81 tt.mockSetup(mockGetter) 82 83 commits, failedFetches, err := getCommits(context.Background(), mockGetter, "owner", "repo", tt.refs) 84 85 assert.Len(t, commits, tt.expectedCommits) 86 assert.Len(t, failedFetches, tt.expectedErrors) 87 88 if tt.expectError { 89 assert.Error(t, err) 90 } else { 91 assert.NoError(t, err) 92 } 93 94 mockGetter.AssertExpectations(t) 95 }) 96 } 97 } 98 99 func TestTryGetCommit(t *testing.T) { 100 tests := []struct { 101 name string 102 setupMock func(*gitHubCommitGetterMock) 103 ref string 104 wantErr bool 105 }{ 106 { 107 name: "Direct commit hash", 108 setupMock: func(m *gitHubCommitGetterMock) { 109 m.On("GetCommit", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&vcsv1.CommitInfo{}, nil) 110 }, 111 ref: "abcdef", 112 wantErr: false, 113 }, 114 { 115 name: "Branch reference with heads prefix", 116 setupMock: func(m *gitHubCommitGetterMock) { 117 m.On("GetCommit", mock.Anything, mock.Anything, mock.Anything, "main").Return(nil, errors.New("not found")) 118 m.On("GetCommit", mock.Anything, mock.Anything, mock.Anything, "heads/main").Return(&vcsv1.CommitInfo{}, nil) 119 }, 120 ref: "main", 121 wantErr: false, 122 }, 123 { 124 name: "Tag reference with tags prefix", 125 setupMock: func(m *gitHubCommitGetterMock) { 126 m.On("GetCommit", mock.Anything, mock.Anything, mock.Anything, mock.Anything). 127 Return(nil, assert.AnError).Times(2) 128 m.On("GetCommit", mock.Anything, mock.Anything, mock.Anything, "tags/v1").Return(&vcsv1.CommitInfo{}, nil).Times(1) 129 }, 130 ref: "v1", 131 wantErr: false, 132 }, 133 { 134 name: "GitHub API returns not found error", 135 setupMock: func(m *gitHubCommitGetterMock) { 136 notFoundErr := &github.ErrorResponse{ 137 Response: &http.Response{StatusCode: http.StatusNotFound}, 138 } 139 m.On("GetCommit", mock.Anything, mock.Anything, mock.Anything, mock.Anything). 140 Return(nil, notFoundErr).Times(3) 141 }, 142 ref: "nonexistent", 143 wantErr: true, 144 }, 145 } 146 147 for _, tt := range tests { 148 t.Run(tt.name, func(t *testing.T) { 149 mockGetter := new(gitHubCommitGetterMock) 150 tt.setupMock(mockGetter) 151 152 commit, err := tryGetCommit(context.Background(), mockGetter, "owner", "repo", tt.ref) 153 154 if tt.wantErr { 155 assert.Error(t, err) 156 var githubErr *github.ErrorResponse 157 assert.True(t, errors.As(err, &githubErr), "Expected a GitHub error") 158 assert.Nil(t, commit) 159 } else { 160 assert.NoError(t, err) 161 assert.NotNil(t, commit) 162 } 163 164 mockGetter.AssertExpectations(t) 165 }) 166 } 167 }