diff --git a/scanner/github.go b/scanner/github.go index 6fcec42..c4c32c3 100644 --- a/scanner/github.go +++ b/scanner/github.go @@ -191,25 +191,24 @@ Retry: return fmt.Errorf("[%s]: failed to get publiccode.yml: %w", *repo.FullName, err) } - if file.DownloadURL == nil { + // GetContents returns no file content and no error when the path is a directory. + if file == nil || file.DownloadURL == nil { return fmt.Errorf("[%s]: failed to get publiccode.yml: not a regular file?", *repo.FullName) } - if file != nil { - canonicalURL, err := url.Parse(*repo.CloneURL) - if err != nil { - return fmt.Errorf("failed to get canonical repo URL for %s: %w", url.String(), err) - } + canonicalURL, err := url.Parse(*repo.CloneURL) + if err != nil { + return fmt.Errorf("failed to get canonical repo URL for %s: %w", url.String(), err) + } - repositories <- common.Repository{ - Name: *repo.FullName, - FileRawURL: *file.DownloadURL, - URL: url, - CanonicalURL: *canonicalURL, - GitBranch: *repo.DefaultBranch, - Publisher: publisher, - Headers: make(map[string]string), - } + repositories <- common.Repository{ + Name: *repo.FullName, + FileRawURL: *file.DownloadURL, + URL: url, + CanonicalURL: *canonicalURL, + GitBranch: *repo.DefaultBranch, + Publisher: publisher, + Headers: make(map[string]string), } return nil diff --git a/scanner/github_test.go b/scanner/github_test.go index f6a0f36..bbf7f5d 100644 --- a/scanner/github_test.go +++ b/scanner/github_test.go @@ -85,3 +85,76 @@ func TestGitHubScannerScanKeepsNotFoundContentError(t *testing.T) { assert.ErrorIs(t, err, ErrPubliccodeNotFound) } + +func githubContentsResponse(body string) *http.Response { + return &http.Response{ + StatusCode: http.StatusOK, + Header: make(http.Header), + Body: io.NopCloser(strings.NewReader(body)), + } +} + +func TestGitHubScannerScanRejectsPubliccodeDirectory(t *testing.T) { + scanner := newGitHubScannerForTransport(roundTripperFunc(func(request *http.Request) (*http.Response, error) { + switch request.URL.Path { + case "/repos/org/repository": + return githubRepositoryResponse(), nil + case "/repos/org/repository/contents/publiccode.yml": + return githubContentsResponse(`[{ + "type": "file", + "name": "README.md", + "path": "publiccode.yml/README.md", + "download_url": "https://raw.githubusercontent.com/org/repository/main/publiccode.yml/README.md" + }]`), nil + default: + t.Fatalf("unexpected request path %q", request.URL.Path) + return nil, nil + } + })) + + repositoryURL, err := url.Parse("https://github.com/org/repository") + require.NoError(t, err) + + repositories := make(chan common.Repository, 1) + err = scanner.Scan(*repositoryURL, common.Publisher{}, repositories) + + require.Error(t, err) + assert.NotErrorIs(t, err, ErrPubliccodeNotFound) + assert.Empty(t, repositories) +} + +func TestGitHubScannerScanSendsRegularPubliccodeFile(t *testing.T) { + const downloadURL = "https://raw.githubusercontent.com/org/repository/main/publiccode.yml" + + scanner := newGitHubScannerForTransport(roundTripperFunc(func(request *http.Request) (*http.Response, error) { + switch request.URL.Path { + case "/repos/org/repository": + return githubRepositoryResponse(), nil + case "/repos/org/repository/contents/publiccode.yml": + return githubContentsResponse(`{ + "type": "file", + "name": "publiccode.yml", + "path": "publiccode.yml", + "download_url": "` + downloadURL + `" + }`), nil + default: + t.Fatalf("unexpected request path %q", request.URL.Path) + return nil, nil + } + })) + + repositoryURL, err := url.Parse("https://github.com/org/repository") + require.NoError(t, err) + + repositories := make(chan common.Repository, 1) + err = scanner.Scan(*repositoryURL, common.Publisher{}, repositories) + + require.NoError(t, err) + require.Len(t, repositories, 1) + + repository := <-repositories + assert.Equal(t, "org/repository", repository.Name) + assert.Equal(t, downloadURL, repository.FileRawURL) + assert.Equal(t, "https://github.com/org/repository.git", repository.CanonicalURL.String()) + assert.Equal(t, "main", repository.GitBranch) +}