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  }