11"""This module contains the tests for the contributors.py module"""
22
3+ import runpy
34import unittest
4- from unittest .mock import MagicMock , patch
5+ from unittest .mock import MagicMock , call , patch
56
7+ import contributors as contributors_module
68from contributor_stats import ContributorStats
7- from contributors import get_all_contributors , get_contributors
89
910
1011class TestContributors (unittest .TestCase ):
@@ -24,9 +25,13 @@ def test_get_contributors(self, mock_contributor_stats):
2425 mock_user .contributions_count = 100
2526 mock_repo .contributors .return_value = [mock_user ]
2627 mock_repo .full_name = "owner/repo"
28+ mock_repo .commits .return_value = iter ([object ()])
2729
28- get_contributors (mock_repo , "2022-01-01" , "2022-12-31" , "" )
30+ contributors_module . get_contributors (mock_repo , "2022-01-01" , "2022-12-31" , "" )
2931
32+ mock_repo .commits .assert_called_once_with (
33+ author = "user" , since = "2022-01-01" , until = "2022-12-31"
34+ )
3035 mock_contributor_stats .assert_called_once_with (
3136 "user" ,
3237 False ,
@@ -58,8 +63,8 @@ def test_get_all_contributors_with_organization(self, mock_get_contributors):
5863 ]
5964 ghe = ""
6065
61- result = get_all_contributors (
62- "org" , "" , "2022-01-01" , "2022-12-31" , mock_github_connection , ghe
66+ result = contributors_module . get_all_contributors (
67+ "org" , [] , "2022-01-01" , "2022-12-31" , mock_github_connection , ghe
6368 )
6469
6570 self .assertEqual (
@@ -97,7 +102,7 @@ def test_get_all_contributors_with_repository(self, mock_get_contributors):
97102 ]
98103 ghe = ""
99104
100- result = get_all_contributors (
105+ result = contributors_module . get_all_contributors (
101106 "" , ["owner/repo" ], "2022-01-01" , "2022-12-31" , mock_github_connection , ghe
102107 )
103108
@@ -133,14 +138,22 @@ def test_get_contributors_skip_users_with_no_commits(self, mock_contributor_stat
133138 mock_user2 .avatar_url = "https://avatars.githubusercontent.com/u/12345679?v=4"
134139 mock_user2 .contributions_count = 102
135140
136- mock_repo .contributors .return_value = [mock_user ]
141+ mock_repo .contributors .return_value = [mock_user , mock_user2 ]
137142 mock_repo .full_name = "owner/repo"
138- mock_repo .get_commits .side_effect = StopIteration
143+ mock_repo .commits .side_effect = [
144+ iter ([object ()]), # user has commits in range
145+ iter ([]), # user2 has no commits in range and should be skipped
146+ ]
139147 ghe = ""
140148
141- get_contributors (mock_repo , "2022-01-01" , "2022-12-31" , ghe )
149+ contributors_module . get_contributors (mock_repo , "2022-01-01" , "2022-12-31" , ghe )
142150
143- # Note that only user is returned and user2 is not returned here because there were no commits in the date range
151+ mock_repo .commits .assert_has_calls (
152+ [
153+ call (author = "user" , since = "2022-01-01" , until = "2022-12-31" ),
154+ call (author = "user2" , since = "2022-01-01" , until = "2022-12-31" ),
155+ ]
156+ )
144157 mock_contributor_stats .assert_called_once_with (
145158 "user" ,
146159 False ,
@@ -163,13 +176,13 @@ def test_get_contributors_skip_bot(self, mock_contributor_stats):
163176
164177 mock_repo .contributors .return_value = [mock_user ]
165178 mock_repo .full_name = "owner/repo"
166- mock_repo .get_commits .side_effect = StopIteration
167179 ghe = ""
168180
169- get_contributors (mock_repo , "2022-01-01" , "2022-12-31" , ghe )
181+ contributors_module . get_contributors (mock_repo , "2022-01-01" , "2022-12-31" , ghe )
170182
171- # Note that only user is returned and user2 is not returned here because there were no commits in the date range
172- mock_contributor_stats .isEmpty ()
183+ # Ensure that the bot user is skipped and ContributorStats is never instantiated
184+ mock_repo .commits .assert_not_called ()
185+ mock_contributor_stats .assert_not_called ()
173186
174187 @patch ("contributors.contributor_stats.ContributorStats" )
175188 def test_get_contributors_no_commit_end_date (self , mock_contributor_stats ):
@@ -184,12 +197,12 @@ def test_get_contributors_no_commit_end_date(self, mock_contributor_stats):
184197
185198 mock_repo .contributors .return_value = [mock_user ]
186199 mock_repo .full_name = "owner/repo"
187- mock_repo .get_commits .side_effect = StopIteration
188200 ghe = ""
189201
190- get_contributors (mock_repo , "2022-01-01" , "" , ghe )
202+ contributors_module . get_contributors (mock_repo , "2022-01-01" , "" , ghe )
191203
192204 # Note that only user is returned and user2 is not returned here because there were no commits in the date range
205+ mock_repo .commits .assert_not_called ()
193206 mock_contributor_stats .assert_called_once_with (
194207 "user" ,
195208 False ,
@@ -199,6 +212,143 @@ def test_get_contributors_no_commit_end_date(self, mock_contributor_stats):
199212 "" ,
200213 )
201214
215+ def test_get_contributors_skips_when_no_commits_in_range (self ):
216+ """Test get_contributors skips users with no commits in the date range."""
217+ mock_repo = MagicMock ()
218+ mock_user = MagicMock ()
219+ mock_user .login = "user"
220+ mock_user .avatar_url = "https://avatars.githubusercontent.com/u/12345678?v=4"
221+ mock_user .contributions_count = 100
222+ mock_repo .contributors .return_value = [mock_user ]
223+ mock_repo .full_name = "owner/repo"
224+ mock_repo .commits .return_value = iter ([])
225+
226+ result = contributors_module .get_contributors (
227+ mock_repo , "2022-01-01" , "2022-12-31" , ""
228+ )
229+
230+ self .assertEqual (result , [])
231+
232+ def test_get_contributors_handles_exception (self ):
233+ """Test get_contributors returns None when an exception is raised."""
234+
235+ class BoomIterable : # pylint: disable=too-few-public-methods
236+ """Iterable that raises an exception when iterated over."""
237+
238+ def __iter__ (self ):
239+ raise RuntimeError ("boom" )
240+
241+ mock_repo = MagicMock ()
242+ mock_repo .full_name = "owner/repo"
243+ mock_repo .contributors .return_value = BoomIterable ()
244+
245+ with patch ("builtins.print" ) as mock_print :
246+ result = contributors_module .get_contributors (
247+ mock_repo , "2022-01-01" , "2022-12-31" , ""
248+ )
249+
250+ self .assertIsNone (result )
251+ mock_print .assert_any_call (
252+ "Error getting contributors for repository: owner/repo"
253+ )
254+
255+ def test_main_runs_under_main_guard (self ):
256+ """Test running contributors as a script executes main."""
257+ mock_env = MagicMock ()
258+ mock_env .get_env_vars .return_value = (
259+ "org" ,
260+ [],
261+ 123 ,
262+ 456 ,
263+ b"key" ,
264+ False ,
265+ "" ,
266+ "" ,
267+ "2022-01-01" ,
268+ "2022-12-31" ,
269+ "true" ,
270+ False ,
271+ )
272+
273+ mock_auth = MagicMock ()
274+ mock_github = MagicMock ()
275+ mock_org = MagicMock ()
276+ mock_org .repositories .return_value = []
277+ mock_github .organization .return_value = mock_org
278+ mock_auth .auth_to_github .return_value = mock_github
279+ mock_auth .get_github_app_installation_token .return_value = "token"
280+
281+ mock_markdown = MagicMock ()
282+ mock_json_writer = MagicMock ()
283+
284+ with patch .dict (
285+ "sys.modules" ,
286+ {
287+ "env" : mock_env ,
288+ "auth" : mock_auth ,
289+ "markdown" : mock_markdown ,
290+ "json_writer" : mock_json_writer ,
291+ },
292+ clear = False ,
293+ ):
294+ runpy .run_module ("contributors" , run_name = "__main__" )
295+
296+ mock_env .get_env_vars .assert_called_once ()
297+ mock_auth .auth_to_github .assert_called_once ()
298+ mock_auth .get_github_app_installation_token .assert_called_once_with (
299+ "" , 123 , b"key" , 456
300+ )
301+ mock_markdown .write_to_markdown .assert_called_once ()
302+ mock_json_writer .write_to_json .assert_called_once ()
303+
304+ def test_main_sets_new_contributor_flag (self ):
305+ """Test main sets new_contributor when start/end dates are provided."""
306+ contributor = ContributorStats (
307+ "user1" ,
308+ False ,
309+ "https://avatars.githubusercontent.com/u/1" ,
310+ 10 ,
311+ "commit_url" ,
312+ "" ,
313+ )
314+
315+ with patch .object (
316+ contributors_module .env , "get_env_vars"
317+ ) as mock_get_env_vars , patch .object (
318+ contributors_module .auth , "auth_to_github"
319+ ) as mock_auth_to_github , patch .object (
320+ contributors_module , "get_all_contributors"
321+ ) as mock_get_all_contributors , patch .object (
322+ contributors_module .contributor_stats ,
323+ "is_new_contributor" ,
324+ return_value = True ,
325+ ) as mock_is_new , patch .object (
326+ contributors_module .markdown , "write_to_markdown"
327+ ), patch .object (
328+ contributors_module .json_writer , "write_to_json"
329+ ):
330+ mock_get_env_vars .return_value = (
331+ "org" ,
332+ [],
333+ None ,
334+ None ,
335+ b"" ,
336+ False ,
337+ "token" ,
338+ "" ,
339+ "2022-01-01" ,
340+ "2022-12-31" ,
341+ False ,
342+ False ,
343+ )
344+ mock_auth_to_github .return_value = MagicMock ()
345+ mock_get_all_contributors .side_effect = [[contributor ], []]
346+
347+ contributors_module .main ()
348+
349+ mock_is_new .assert_called_once_with ("user1" , [])
350+ self .assertTrue (contributor .new_contributor )
351+
202352
203353if __name__ == "__main__" :
204354 unittest .main ()
0 commit comments