oss-stats 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3 @@
1
+ module OssStats
2
+ VERSION = '0.0.1'.freeze
3
+ end
data/oss-stats.gemspec ADDED
@@ -0,0 +1,39 @@
1
+ require_relative 'lib/oss_stats/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = 'oss-stats'
5
+ spec.version = OssStats::VERSION
6
+ spec.summary = 'Suite of tools for reporting health of F/OSS communities'
7
+ spec.authors = ['Phil Dibowitz']
8
+ spec.email = ['phil@ipom.com']
9
+ spec.license = 'Apache-2.0'
10
+ spec.homepage = 'https://github.com/jaymzh/oss-stats'
11
+ spec.required_ruby_version = '>= 3.2'
12
+ docs = %w{
13
+ README.md
14
+ LICENSE
15
+ Gemfile
16
+ oss-stats.gemspec
17
+ CONTRIBUTING.md
18
+ CHANGELOG.md
19
+ } + Dir.glob('examples/*') | Dir.glob('docs/*')
20
+ spec.extra_rdoc_files = docs
21
+ spec.executables += Dir.glob('bin/*').map { |x| File.basename(x) }
22
+ spec.files =
23
+ Dir.glob('lib/oss_stats/*.rb') +
24
+ Dir.glob('lib/oss_stats/config/*.rb') +
25
+ Dir.glob('bin/*') +
26
+ Dir.glob('extras/*') +
27
+ Dir.glob('spec/*') +
28
+ Dir.glob('initialization_data/*') +
29
+ Dir.glob('initialization_data/github_workflow/s*')
30
+ %w{sqlite3 octokit mixlib-log mixlib-config gruff deep_merge}.each do |dep|
31
+ spec.add_dependency dep
32
+ end
33
+ spec.metadata = {
34
+ 'rubygems_mfa_required' => 'true',
35
+ 'bug_tracker_uri' => 'https://github.com/jaymzh/oss-stats/issues',
36
+ 'homepage_uri' => 'https://github.com/jaymzh/oss-stats',
37
+ 'source_code_uri' => 'https://github.com/jaymzh/oss-stats',
38
+ }
39
+ end
@@ -0,0 +1,171 @@
1
+ require_relative '../lib/oss_stats/buildkite_client'
2
+ require_relative '../lib/oss_stats/log'
3
+ require 'date'
4
+
5
+ RSpec.describe OssStats::BuildkiteClient do
6
+ let(:token) { 'test-token' }
7
+ let(:organization_slug) { 'test-org' }
8
+ let(:client) { described_class.new(token) }
9
+
10
+ describe '#get_pipeline_builds' do
11
+ let(:pipeline_slug) { 'test-pipeline' }
12
+ let(:pull_request_id) { '123' }
13
+ let(:from_date) { Date.new(2024, 1, 1) }
14
+ let(:to_date) { Date.new(2024, 1, 31) }
15
+
16
+ context 'when the API returns builds' do
17
+ let(:mock_api_response) do
18
+ {
19
+ 'data' => {
20
+ 'pipeline' => {
21
+ 'builds' => {
22
+ 'edges' => [
23
+ {
24
+ 'node' => {
25
+ 'state' => 'PASSED',
26
+ },
27
+ },
28
+ {
29
+ 'node' => {
30
+ 'state' => 'FAILED',
31
+ },
32
+ },
33
+ ],
34
+ 'pageInfo' => {
35
+ 'hasNextPage' => false,
36
+ 'endCursor' => nil,
37
+ },
38
+ },
39
+ },
40
+ },
41
+ }
42
+ end
43
+
44
+ before do
45
+ # Mock the execute_graphql_query method
46
+ allow(client).to receive(:execute_graphql_query)
47
+ .and_return(mock_api_response)
48
+ end
49
+
50
+ it 'returns a list of builds with their job statuses' do
51
+ builds = client.get_pipeline_builds(
52
+ organization_slug, pipeline_slug, from_date, to_date
53
+ )
54
+
55
+ expect(builds.size).to eq(2)
56
+ expect(builds[0]['node']['state']).to eq('PASSED')
57
+ expect(builds[1]['node']['state']).to eq('FAILED')
58
+ end
59
+ end
60
+
61
+ context 'when the API response is paginated' do
62
+ let(:mock_api_response_page1) do
63
+ {
64
+ 'data' => {
65
+ 'pipeline' => {
66
+ 'builds' => {
67
+ 'edges' => [
68
+ { 'node' => { 'state' => 'PASSED' } },
69
+ ],
70
+ 'pageInfo' => {
71
+ 'hasNextPage' => true, 'endCursor' => 'cursor1'
72
+ },
73
+ },
74
+ },
75
+ },
76
+ }
77
+ end
78
+ let(:mock_api_response_page2) do
79
+ {
80
+ 'data' => {
81
+ 'pipeline' => {
82
+ 'builds' => {
83
+ 'edges' => [
84
+ { 'node' => { 'state' => 'FAILED' } },
85
+ ],
86
+ 'pageInfo' => { 'hasNextPage' => false, 'endCursor' => nil },
87
+ },
88
+ },
89
+ },
90
+ }
91
+ end
92
+
93
+ before do
94
+ # Mock execute_graphql_query to return page 1 then page 2
95
+ allow(client).to receive(:execute_graphql_query)
96
+ .and_return(mock_api_response_page1, mock_api_response_page2)
97
+ end
98
+
99
+ it 'fetches all builds across pages' do
100
+ builds = client.get_pipeline_builds(
101
+ organization_slug, pipeline_slug, from_date, to_date
102
+ )
103
+ expect(builds.size).to eq(2)
104
+ expect(builds[0]['node']['state']).to eq('PASSED')
105
+ expect(builds[1]['node']['state']).to eq('FAILED')
106
+ end
107
+
108
+ it 'makes two API calls' do
109
+ expect(client).to receive(:execute_graphql_query).twice
110
+ client.get_pipeline_builds(
111
+ organization_slug, pipeline_slug, from_date, to_date
112
+ )
113
+ end
114
+ end
115
+
116
+ context 'when the API call fails' do
117
+ before do
118
+ allow(client).to receive(:execute_graphql_query)
119
+ .and_raise(StandardError.new('API Error'))
120
+ allow(OssStats::Log).to receive(:error)
121
+ end
122
+
123
+ it 'returns an empty array' do
124
+ builds = client.get_pipeline_builds(
125
+ organization_slug, pipeline_slug, from_date, to_date
126
+ )
127
+ expect(builds).to be_empty
128
+ end
129
+
130
+ it 'logs the error' do
131
+ expected_log_message =
132
+ %r{Error in get_pipeline_builds for test-org/test-pipeline}
133
+ expect(OssStats::Log).to receive_message_chain(:error)
134
+ .with(expected_log_message)
135
+ client.get_pipeline_builds(
136
+ organization_slug, pipeline_slug, from_date, to_date
137
+ )
138
+ end
139
+ end
140
+
141
+ context 'when the API returns no builds' do
142
+ let(:mock_api_response) do
143
+ {
144
+ 'data' => {
145
+ 'pipeline' => {
146
+ 'builds' => {
147
+ 'edges' => [],
148
+ 'pageInfo' => {
149
+ 'hasNextPage' => false,
150
+ 'endCursor' => nil,
151
+ },
152
+ },
153
+ },
154
+ },
155
+ }
156
+ end
157
+
158
+ before do
159
+ allow(client).to receive(:execute_graphql_query)
160
+ .and_return(mock_api_response)
161
+ end
162
+
163
+ it 'returns an empty array' do
164
+ builds = client.get_pipeline_builds(
165
+ organization_slug, pipeline_slug, from_date, to_date
166
+ )
167
+ expect(builds).to be_empty
168
+ end
169
+ end
170
+ end
171
+ end