Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ type Repo struct {
Description string
DefaultBranch string
Archived bool
Files []string // root-level file and directory names
}

// GitHubClient is the interface for all GitHub API interactions.
Expand Down Expand Up @@ -102,8 +103,20 @@ func (c *realGitHubClient) ListRepos(ctx context.Context, org string) ([]Repo, e
}

func (c *realGitHubClient) ListFiles(ctx context.Context, owner, repo string) ([]string, error) {
// TODO: implement when file-existence rules are added
return nil, nil
url := fmt.Sprintf("https://api.github.com/repos/%s/%s/contents", owner, repo)

var entries []struct {
Name string `json:"name"`
}
if err := c.doRequest(ctx, url, &entries); err != nil {
return nil, fmt.Errorf("list files for %s/%s: %w", owner, repo, err)
}

names := make([]string, len(entries))
for i, e := range entries {
names[i] = e.Name
}
return names, nil
}

func (c *realGitHubClient) CreateIssue(ctx context.Context, owner, repo, title, body string) error {
Expand Down
8 changes: 8 additions & 0 deletions client_mock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import "context"
type MockGitHubClient struct {
Repos []Repo
Err error
Files map[string][]string // repo name -> file list
FilesErr error
IssueErr error
// CreatedIssue records the last CreateIssue call for assertions.
CreatedIssue struct {
Expand All @@ -18,6 +20,12 @@ func (m *MockGitHubClient) ListRepos(ctx context.Context, org string) ([]Repo, e
}

func (m *MockGitHubClient) ListFiles(ctx context.Context, owner, repo string) ([]string, error) {
if m.FilesErr != nil {
return nil, m.FilesErr
}
if m.Files != nil {
return m.Files[repo], nil
}
return nil, nil
}

Expand Down
18 changes: 18 additions & 0 deletions rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type RuleResult struct {
func AllRules() []Rule {
return []Rule{
HasRepoDescription{},
HasGitignore{},
}
}

Expand All @@ -28,3 +29,20 @@ func (r HasRepoDescription) Name() string { return "Has repo description" }
func (r HasRepoDescription) Check(repo Repo) bool {
return strings.TrimSpace(repo.Description) != ""
}

// HasGitignore checks that a .gitignore file exists in the repo root.
type HasGitignore struct{}

func (r HasGitignore) Name() string { return "Has .gitignore" }
func (r HasGitignore) Check(repo Repo) bool {
return hasFile(repo.Files, ".gitignore")
}

func hasFile(files []string, name string) bool {
for _, f := range files {
if f == name {
return true
}
}
return false
}
24 changes: 24 additions & 0 deletions rules_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,27 @@ func TestHasRepoDescription_Fail_WhitespaceOnly(t *testing.T) {
t.Errorf("expected fail for repo with whitespace-only description")
}
}

func TestHasGitignore_Pass(t *testing.T) {
rule := HasGitignore{}

if !rule.Check(Repo{Files: []string{"README.md", ".gitignore", "main.go"}}) {
t.Error("expected pass when .gitignore exists")
}
}

func TestHasGitignore_Fail(t *testing.T) {
rule := HasGitignore{}

if rule.Check(Repo{Files: []string{"README.md", "main.go"}}) {
t.Error("expected fail when .gitignore is missing")
}
}

func TestHasGitignore_Fail_EmptyFiles(t *testing.T) {
rule := HasGitignore{}

if rule.Check(Repo{Files: nil}) {
t.Error("expected fail when file list is empty")
}
}
6 changes: 6 additions & 0 deletions scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ func Scan(ctx context.Context, client GitHubClient, org string) ([]RepoResult, e
continue
}

files, err := client.ListFiles(ctx, org, repo.Name)
if err != nil {
return nil, fmt.Errorf("list files for repo %s: %w", repo.Name, err)
}
repo.Files = files

rr := RepoResult{RepoName: repo.Name}
for _, rule := range rules {
rr.Results = append(rr.Results, RuleResult{
Expand Down
Loading