• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

boinger / confvis / 21737000156

06 Feb 2026 02:57AM UTC coverage: 83.08% (-0.6%) from 83.716%
21737000156

push

github

boinger
refactor: reduce duplication with generic source implementations

Create generic AlertsSource and CoverageSource that encapsulate the
complete fetch flow, reducing individual sources to thin configuration
wrappers with source-specific callbacks.

Changes:
- Add githubalerts/source.go: generic GitHub alerts source with
  pagination, token resolution, and scoring
- Add coverage/source.go: generic coverage source for providers
- Simplify dependabot, codeql, coveralls, codecov to ~30 lines each
- Remove 4 client.go files (logic moved to generic sources)

Net reduction: ~660 lines (-1603/+942)

193 of 231 new or added lines in 6 files covered. (83.55%)

8 existing lines in 1 file now uncovered.

3820 of 4598 relevant lines covered (83.08%)

10.47 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.21
/internal/sources/githubalerts/source.go
1
package githubalerts
2

3
import (
4
        "context"
5
        "encoding/json"
6
        "net/url"
7
        "strconv"
8
        "time"
9

10
        "github.com/boinger/confvis/internal/confidence"
11
        "github.com/boinger/confvis/internal/sources"
12
        "github.com/boinger/confvis/internal/sources/repoparse"
13
        "github.com/boinger/confvis/internal/sources/scoring"
14
)
15

16
// SourceConfig defines configuration for a GitHub alerts source.
17
type SourceConfig struct {
18
        Name         string // Source name (e.g., "dependabot", "codeql")
19
        TokenEnvVar  string // Environment variable for source-specific token
20
        EndpointPath string // API endpoint path (e.g., "dependabot/alerts")
21
        WebURLPath   string // Web URL path (e.g., "security/dependabot")
22
}
23

24
// CountAlertsFunc extracts severity counts from raw JSON alert data.
25
type CountAlertsFunc func(data []byte) (scoring.SeverityCounts, error)
26

27
// ExtraParamsFunc adds source-specific query parameters.
28
type ExtraParamsFunc func(opts sources.Options) url.Values
29

30
// AlertsSource implements sources.Source for GitHub security alerts APIs.
31
type AlertsSource struct {
32
        config      SourceConfig
33
        countAlerts CountAlertsFunc
34
        extraParams ExtraParamsFunc
35
}
36

37
// NewSource creates a new GitHub alerts source with the given configuration.
38
// countAlerts is called to extract severity counts from the raw JSON response.
39
// extraParams is optional and can add source-specific query parameters.
40
func NewSource(cfg SourceConfig, countAlerts CountAlertsFunc, extraParams ExtraParamsFunc) *AlertsSource {
9✔
41
        return &AlertsSource{
9✔
42
                config:      cfg,
9✔
43
                countAlerts: countAlerts,
9✔
44
                extraParams: extraParams,
9✔
45
        }
9✔
46
}
9✔
47

48
// Name returns the source identifier.
49
func (s *AlertsSource) Name() string {
1✔
50
        return s.config.Name
1✔
51
}
1✔
52

53
var defaultTimeout = 30 * time.Second
54

55
// Fetch retrieves alerts from GitHub and converts them to a confidence report.
56
func (s *AlertsSource) Fetch(ctx context.Context, opts sources.Options) (*confidence.Report, error) {
8✔
57
        resolver := &sources.ConfigResolver{
8✔
58
                SourceName:     s.config.Name,
8✔
59
                TokenEnvVar:    s.config.TokenEnvVar,
8✔
60
                URLEnvVar:      "GITHUB_API_URL",
8✔
61
                TokenRequired:  false, // We handle token resolution manually for fallback
8✔
62
                URLRequired:    false, // Has default
8✔
63
                DefaultTimeout: defaultTimeout,
8✔
64
        }
8✔
65

8✔
66
        cfg, err := resolver.Resolve(opts)
8✔
67
        if err != nil {
8✔
NEW
68
                return nil, err
×
NEW
69
        }
×
70

71
        token, err := ResolveTokenWithFallback(cfg, s.config.Name, s.config.TokenEnvVar)
8✔
72
        if err != nil {
9✔
73
                return nil, err
1✔
74
        }
1✔
75

76
        owner, repo, err := repoparse.Parse(opts.Project)
7✔
77
        if err != nil {
8✔
78
                return nil, err
1✔
79
        }
1✔
80

81
        alertsConfig := Config{
6✔
82
                EndpointPath: s.config.EndpointPath,
6✔
83
                WebURLPath:   s.config.WebURLPath,
6✔
84
        }
6✔
85
        client := NewClient(cfg.URL, token, cfg.Timeout, alertsConfig)
6✔
86

6✔
87
        allAlerts, err := s.fetchAllAlerts(ctx, client, owner, repo, opts)
6✔
88
        if err != nil {
6✔
NEW
89
                return nil, err
×
NEW
90
        }
×
91

92
        counts, err := s.countAlerts(allAlerts)
6✔
93
        if err != nil {
7✔
94
                return nil, err
1✔
95
        }
1✔
96

97
        title := ResolveTitle(opts.Title, owner, repo)
5✔
98
        factors := scoring.BuildVulnFactors(counts, Penalties(), Weights(), client.AlertsURL(owner, repo))
5✔
99

5✔
100
        return scoring.BuildReport(title, s.config.Name, opts.Threshold, factors), nil
5✔
101
}
102

103
// fetchAllAlerts retrieves all open alerts with pagination.
104
func (s *AlertsSource) fetchAllAlerts(ctx context.Context, client *Client, owner, repo string, opts sources.Options) ([]byte, error) {
6✔
105
        const perPage = 100
6✔
106
        var allAlerts []json.RawMessage
6✔
107

6✔
108
        for page := 1; ; page++ {
13✔
109
                params := url.Values{
7✔
110
                        "state":    {"open"},
7✔
111
                        "per_page": {strconv.Itoa(perPage)},
7✔
112
                        "page":     {strconv.Itoa(page)},
7✔
113
                }
7✔
114

7✔
115
                // Add source-specific parameters
7✔
116
                if s.extraParams != nil {
8✔
117
                        for k, v := range s.extraParams(opts) {
2✔
118
                                params[k] = v
1✔
119
                        }
1✔
120
                }
121

122
                endpoint := client.BuildEndpoint(owner, repo, params)
7✔
123

7✔
124
                var pageAlerts []json.RawMessage
7✔
125
                if err := client.HTTP.Get(ctx, endpoint, &pageAlerts); err != nil {
7✔
NEW
126
                        return nil, err
×
NEW
127
                }
×
128

129
                allAlerts = append(allAlerts, pageAlerts...)
7✔
130

7✔
131
                if len(pageAlerts) < perPage {
13✔
132
                        break
6✔
133
                }
134
        }
135

136
        return json.Marshal(allAlerts)
6✔
137
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc