airbyte-source-github 1.5.7__py3-none-any.whl

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.
Files changed (88) hide show
  1. airbyte_source_github-1.5.7.dist-info/METADATA +144 -0
  2. airbyte_source_github-1.5.7.dist-info/RECORD +88 -0
  3. airbyte_source_github-1.5.7.dist-info/WHEEL +5 -0
  4. airbyte_source_github-1.5.7.dist-info/entry_points.txt +2 -0
  5. airbyte_source_github-1.5.7.dist-info/top_level.txt +3 -0
  6. integration_tests/__init__.py +0 -0
  7. integration_tests/abnormal_state.json +237 -0
  8. integration_tests/acceptance.py +16 -0
  9. integration_tests/configured_catalog.json +435 -0
  10. integration_tests/configured_catalog_full_refresh_test.json +415 -0
  11. integration_tests/invalid_config.json +5 -0
  12. integration_tests/sample_config.json +5 -0
  13. integration_tests/sample_state.json +137 -0
  14. source_github/__init__.py +27 -0
  15. source_github/config_migrations.py +106 -0
  16. source_github/constants.py +9 -0
  17. source_github/github_schema.py +41034 -0
  18. source_github/graphql.py +327 -0
  19. source_github/run.py +17 -0
  20. source_github/schemas/assignees.json +63 -0
  21. source_github/schemas/branches.json +48 -0
  22. source_github/schemas/collaborators.json +80 -0
  23. source_github/schemas/comments.json +104 -0
  24. source_github/schemas/commit_comment_reactions.json +4 -0
  25. source_github/schemas/commit_comments.json +53 -0
  26. source_github/schemas/commits.json +126 -0
  27. source_github/schemas/contributor_activity.json +109 -0
  28. source_github/schemas/deployments.json +77 -0
  29. source_github/schemas/events.json +63 -0
  30. source_github/schemas/issue_comment_reactions.json +4 -0
  31. source_github/schemas/issue_events.json +335 -0
  32. source_github/schemas/issue_labels.json +30 -0
  33. source_github/schemas/issue_milestones.json +61 -0
  34. source_github/schemas/issue_reactions.json +28 -0
  35. source_github/schemas/issue_timeline_events.json +1056 -0
  36. source_github/schemas/issues.json +281 -0
  37. source_github/schemas/organizations.json +197 -0
  38. source_github/schemas/project_cards.json +50 -0
  39. source_github/schemas/project_columns.json +38 -0
  40. source_github/schemas/projects.json +50 -0
  41. source_github/schemas/projects_v2.json +80 -0
  42. source_github/schemas/pull_request_comment_reactions.json +28 -0
  43. source_github/schemas/pull_request_commits.json +122 -0
  44. source_github/schemas/pull_request_stats.json +84 -0
  45. source_github/schemas/pull_requests.json +363 -0
  46. source_github/schemas/releases.json +126 -0
  47. source_github/schemas/repositories.json +313 -0
  48. source_github/schemas/review_comments.json +118 -0
  49. source_github/schemas/reviews.json +69 -0
  50. source_github/schemas/shared/events/comment.json +188 -0
  51. source_github/schemas/shared/events/commented.json +118 -0
  52. source_github/schemas/shared/events/committed.json +56 -0
  53. source_github/schemas/shared/events/cross_referenced.json +784 -0
  54. source_github/schemas/shared/events/reviewed.json +139 -0
  55. source_github/schemas/shared/reaction.json +27 -0
  56. source_github/schemas/shared/reactions.json +35 -0
  57. source_github/schemas/shared/user.json +59 -0
  58. source_github/schemas/shared/user_graphql.json +26 -0
  59. source_github/schemas/stargazers.json +19 -0
  60. source_github/schemas/tags.json +32 -0
  61. source_github/schemas/team_members.json +66 -0
  62. source_github/schemas/team_memberships.json +24 -0
  63. source_github/schemas/teams.json +50 -0
  64. source_github/schemas/users.json +63 -0
  65. source_github/schemas/workflow_jobs.json +109 -0
  66. source_github/schemas/workflow_runs.json +449 -0
  67. source_github/schemas/workflows.json +41 -0
  68. source_github/source.py +339 -0
  69. source_github/spec.json +179 -0
  70. source_github/streams.py +1678 -0
  71. source_github/utils.py +152 -0
  72. unit_tests/__init__.py +3 -0
  73. unit_tests/conftest.py +29 -0
  74. unit_tests/projects_v2_pull_requests_query.json +3 -0
  75. unit_tests/pull_request_stats_query.json +3 -0
  76. unit_tests/responses/contributor_activity_response.json +33 -0
  77. unit_tests/responses/graphql_reviews_responses.json +405 -0
  78. unit_tests/responses/issue_timeline_events.json +166 -0
  79. unit_tests/responses/issue_timeline_events_response.json +170 -0
  80. unit_tests/responses/projects_v2_response.json +45 -0
  81. unit_tests/responses/pull_request_comment_reactions.json +744 -0
  82. unit_tests/responses/pull_request_stats_response.json +317 -0
  83. unit_tests/test_migrations/test_config.json +8 -0
  84. unit_tests/test_migrations/test_new_config.json +8 -0
  85. unit_tests/test_multiple_token_authenticator.py +160 -0
  86. unit_tests/test_source.py +326 -0
  87. unit_tests/test_stream.py +1471 -0
  88. unit_tests/utils.py +78 -0
@@ -0,0 +1,326 @@
1
+ #
2
+ # Copyright (c) 2023 Airbyte, Inc., all rights reserved.
3
+ #
4
+
5
+ import logging
6
+ import os
7
+ from unittest.mock import MagicMock
8
+
9
+ import pytest
10
+ import responses
11
+ from airbyte_cdk.models import AirbyteConnectionStatus, Status
12
+ from airbyte_cdk.utils.traced_exception import AirbyteTracedException
13
+ from source_github import constants
14
+ from source_github.source import SourceGithub
15
+
16
+ from .utils import command_check
17
+
18
+
19
+ def check_source(repo_line: str) -> AirbyteConnectionStatus:
20
+ source = SourceGithub()
21
+ config = {"access_token": "test_token", "repository": repo_line}
22
+ logger_mock = MagicMock()
23
+ return source.check(logger_mock, config)
24
+
25
+
26
+ @responses.activate
27
+ @pytest.mark.parametrize(
28
+ "config, expected",
29
+ (
30
+ (
31
+ {
32
+ "start_date": "2021-08-27T00:00:46Z",
33
+ "access_token": "test_token",
34
+ "repository": "airbyte/test",
35
+ },
36
+ True,
37
+ ),
38
+ ({"access_token": "test_token", "repository": "airbyte/test"}, True),
39
+ ),
40
+ )
41
+ def test_check_start_date(config, expected, rate_limit_mock_response):
42
+ responses.add(responses.GET, "https://api.github.com/repos/airbyte/test?per_page=100", json={"full_name": "test_full_name"})
43
+ source = SourceGithub()
44
+ status, _ = source.check_connection(logger=logging.getLogger("airbyte"), config=config)
45
+ assert status == expected
46
+
47
+
48
+ @pytest.mark.parametrize(
49
+ "api_url, deployment_env, expected_message",
50
+ (
51
+ ("github.my.company.org", "CLOUD", "Please enter a full url for `API URL` field starting with `http`"),
52
+ (
53
+ "http://github.my.company.org",
54
+ "CLOUD",
55
+ "HTTP connection is insecure and is not allowed in this environment. Please use `https` instead.",
56
+ ),
57
+ ("http:/github.my.company.org", "NOT_CLOUD", "Please provide a correct API URL."),
58
+ ("https:/github.my.company.org", "CLOUD", "Please provide a correct API URL."),
59
+ ),
60
+ )
61
+ def test_connection_fail_due_to_config_error(api_url, deployment_env, expected_message):
62
+ os.environ["DEPLOYMENT_MODE"] = deployment_env
63
+ source = SourceGithub()
64
+ config = {"access_token": "test_token", "repository": "airbyte/test", "api_url": api_url}
65
+
66
+ with pytest.raises(AirbyteTracedException) as e:
67
+ source.check_connection(logging.getLogger(), config)
68
+ assert e.value.message == expected_message
69
+
70
+
71
+ @responses.activate
72
+ def test_check_connection_repos_only(rate_limit_mock_response):
73
+ responses.add("GET", "https://api.github.com/repos/airbytehq/airbyte", json={"full_name": "airbytehq/airbyte"})
74
+
75
+ status = check_source("airbytehq/airbyte airbytehq/airbyte airbytehq/airbyte")
76
+ assert not status.message
77
+ assert status.status == Status.SUCCEEDED
78
+ # Only one request since 3 repos have same name
79
+ assert len(responses.calls) == 2
80
+
81
+
82
+ @responses.activate
83
+ def test_check_connection_repos_and_org_repos(rate_limit_mock_response):
84
+ repos = [{"name": f"name {i}", "full_name": f"full name {i}", "updated_at": "2020-01-01T00:00:00Z"} for i in range(1000)]
85
+ responses.add(
86
+ "GET", "https://api.github.com/repos/airbyte/test", json={"full_name": "airbyte/test", "organization": {"login": "airbyte"}}
87
+ )
88
+ responses.add(
89
+ "GET", "https://api.github.com/repos/airbyte/test2", json={"full_name": "airbyte/test2", "organization": {"login": "airbyte"}}
90
+ )
91
+ responses.add("GET", "https://api.github.com/orgs/airbytehq/repos", json=repos)
92
+ responses.add("GET", "https://api.github.com/orgs/org/repos", json=repos)
93
+
94
+ status = check_source("airbyte/test airbyte/test2 airbytehq/* org/*")
95
+ assert not status.message
96
+ assert status.status == Status.SUCCEEDED
97
+ # Two requests for repos and two for organization
98
+ assert len(responses.calls) == 5
99
+
100
+
101
+ @responses.activate
102
+ def test_check_connection_org_only(rate_limit_mock_response):
103
+ repos = [{"name": f"name {i}", "full_name": f"full name {i}", "updated_at": "2020-01-01T00:00:00Z"} for i in range(1000)]
104
+ responses.add("GET", "https://api.github.com/orgs/airbytehq/repos", json=repos)
105
+
106
+ status = check_source("airbytehq/*")
107
+ assert not status.message
108
+ assert status.status == Status.SUCCEEDED
109
+ # One request to check organization
110
+ assert len(responses.calls) == 2
111
+
112
+
113
+ @responses.activate
114
+ def test_get_branches_data():
115
+
116
+ repository_args = {"repositories": ["airbytehq/integration-test"], "page_size_for_large_streams": 10}
117
+
118
+ source = SourceGithub()
119
+
120
+ responses.add(
121
+ "GET",
122
+ "https://api.github.com/repos/airbytehq/integration-test",
123
+ json={"full_name": "airbytehq/integration-test", "default_branch": "master"},
124
+ )
125
+
126
+ responses.add(
127
+ "GET",
128
+ "https://api.github.com/repos/airbytehq/integration-test/branches",
129
+ json=[
130
+ {"repository": "airbytehq/integration-test", "name": "feature/branch_0"},
131
+ {"repository": "airbytehq/integration-test", "name": "feature/branch_1"},
132
+ {"repository": "airbytehq/integration-test", "name": "feature/branch_2"},
133
+ {"repository": "airbytehq/integration-test", "name": "master"},
134
+ ],
135
+ )
136
+
137
+ default_branches, branches_to_pull = source._get_branches_data([], repository_args)
138
+ assert default_branches == {"airbytehq/integration-test": "master"}
139
+ assert branches_to_pull == {"airbytehq/integration-test": ["master"]}
140
+
141
+ default_branches, branches_to_pull = source._get_branches_data(
142
+ [
143
+ "airbytehq/integration-test/feature/branch_0",
144
+ "airbytehq/integration-test/feature/branch_1",
145
+ "airbytehq/integration-test/feature/branch_3",
146
+ ],
147
+ repository_args,
148
+ )
149
+
150
+ assert default_branches == {"airbytehq/integration-test": "master"}
151
+ assert len(branches_to_pull["airbytehq/integration-test"]) == 2
152
+ assert "feature/branch_0" in branches_to_pull["airbytehq/integration-test"]
153
+ assert "feature/branch_1" in branches_to_pull["airbytehq/integration-test"]
154
+
155
+
156
+ @responses.activate
157
+ def test_get_org_repositories():
158
+ responses.add(
159
+ "GET",
160
+ "https://api.github.com/repos/airbytehq/integration-test",
161
+ json={"full_name": "airbytehq/integration-test", "organization": {"login": "airbytehq"}},
162
+ )
163
+
164
+ responses.add(
165
+ "GET",
166
+ "https://api.github.com/orgs/docker/repos",
167
+ json=[
168
+ {"full_name": "docker/docker-py", "updated_at": "2020-01-01T00:00:00Z"},
169
+ {"full_name": "docker/compose", "updated_at": "2020-01-01T00:00:00Z"},
170
+ ],
171
+ )
172
+
173
+ config = {"repositories": ["airbytehq/integration-test", "docker/*"]}
174
+ source = SourceGithub()
175
+ config = source._ensure_default_values(config)
176
+ organisations, repositories = source._get_org_repositories(config, authenticator=None)
177
+
178
+ assert set(repositories) == {"airbytehq/integration-test", "docker/docker-py", "docker/compose"}
179
+ assert set(organisations) == {"airbytehq", "docker"}
180
+
181
+
182
+ @responses.activate
183
+ def test_organization_or_repo_available(monkeypatch, rate_limit_mock_response):
184
+ monkeypatch.setattr(SourceGithub, "_get_org_repositories", MagicMock(return_value=(False, False)))
185
+ source = SourceGithub()
186
+ with pytest.raises(Exception) as exc_info:
187
+ config = {"access_token": "test_token", "repository": ""}
188
+ source.streams(config=config)
189
+ assert exc_info.value.args[0] == "No streams available. Please check permissions"
190
+
191
+
192
+ def test_check_config_repository():
193
+ source = SourceGithub()
194
+ source.check = MagicMock(return_value=True)
195
+ config = {"credentials": {"access_token": "access_token"}, "start_date": "1900-01-01T00:00:00Z"}
196
+
197
+ repos_ok = [
198
+ "airbytehq/airbyte",
199
+ "airbytehq/airbyte-test",
200
+ "airbytehq/airbyte_test",
201
+ "erohmensing/thismonth.rocks",
202
+ "airbytehq/*",
203
+ "airbytehq/.",
204
+ "airbyte_hq/airbyte",
205
+ "airbytehq/123",
206
+ "airbytehq/airbytexgit",
207
+ ]
208
+
209
+ repos_fail = [
210
+ "airbytehq",
211
+ "airbytehq/",
212
+ "airbytehq/*/",
213
+ "airbytehq/airbyte.git",
214
+ "airbytehq/airbyte/",
215
+ "airbytehq/air*yte",
216
+ "airbyte*/airbyte",
217
+ "airbytehq/airbyte-test/master-branch",
218
+ "https://github.com/airbytehq/airbyte",
219
+ ]
220
+
221
+ config["repositories"] = []
222
+ with pytest.raises(AirbyteTracedException):
223
+ assert command_check(source, config)
224
+ config["repositories"] = []
225
+ with pytest.raises(AirbyteTracedException):
226
+ assert command_check(source, config)
227
+
228
+ for repos in repos_ok:
229
+ config["repositories"] = [repos]
230
+ assert command_check(source, config)
231
+
232
+ for repos in repos_fail:
233
+ config["repositories"] = [repos]
234
+ with pytest.raises(AirbyteTracedException):
235
+ assert command_check(source, config)
236
+
237
+
238
+ @responses.activate
239
+ def test_streams_no_streams_available_error(monkeypatch, rate_limit_mock_response):
240
+ monkeypatch.setattr(SourceGithub, "_get_org_repositories", MagicMock(return_value=(False, False)))
241
+ with pytest.raises(AirbyteTracedException) as e:
242
+ SourceGithub().streams(config={"access_token": "test_token", "repository": "airbytehq/airbyte-test"})
243
+ assert str(e.value) == "No streams available. Please check permissions"
244
+
245
+
246
+ @responses.activate
247
+ def test_streams_page_size(rate_limit_mock_response):
248
+ responses.get("https://api.github.com/repos/airbytehq/airbyte", json={"full_name": "airbytehq/airbyte", "default_branch": "master"})
249
+ responses.get("https://api.github.com/repos/airbytehq/airbyte/branches", json=[{"repository": "airbytehq/airbyte", "name": "master"}])
250
+
251
+ config = {
252
+ "credentials": {"access_token": "access_token"},
253
+ "repository": "airbytehq/airbyte",
254
+ "start_date": "1900-07-12T00:00:00Z",
255
+ }
256
+
257
+ source = SourceGithub()
258
+ streams = source.streams(config)
259
+ assert constants.DEFAULT_PAGE_SIZE != constants.DEFAULT_PAGE_SIZE_FOR_LARGE_STREAM
260
+
261
+ for stream in streams:
262
+ if stream.large_stream:
263
+ assert stream.page_size == constants.DEFAULT_PAGE_SIZE_FOR_LARGE_STREAM
264
+ else:
265
+ assert stream.page_size == constants.DEFAULT_PAGE_SIZE
266
+
267
+
268
+ @responses.activate
269
+ @pytest.mark.parametrize(
270
+ "config, expected",
271
+ (
272
+ (
273
+ {
274
+ "start_date": "2021-08-27T00:00:46Z",
275
+ "access_token": "test_token",
276
+ "repository": "airbyte/test",
277
+ },
278
+ 39,
279
+ ),
280
+ ({"access_token": "test_token", "repository": "airbyte/test"}, 39),
281
+ ),
282
+ )
283
+ def test_streams_config_start_date(config, expected, rate_limit_mock_response):
284
+ responses.add(responses.GET, "https://api.github.com/repos/airbyte/test?per_page=100", json={"full_name": "airbyte/test"})
285
+ responses.add(
286
+ responses.GET,
287
+ "https://api.github.com/repos/airbyte/test?per_page=100",
288
+ json={"full_name": "airbyte/test", "default_branch": "default_branch"},
289
+ )
290
+ responses.add(
291
+ responses.GET,
292
+ "https://api.github.com/repos/airbyte/test/branches?per_page=100",
293
+ json=[{"repository": "airbyte/test", "name": "name"}],
294
+ )
295
+ source = SourceGithub()
296
+ streams = source.streams(config=config)
297
+ # projects stream that uses start date
298
+ project_stream = streams[4]
299
+ assert len(streams) == expected
300
+ if config.get("start_date"):
301
+ assert project_stream._start_date == "2021-08-27T00:00:46Z"
302
+ else:
303
+ assert not project_stream._start_date
304
+
305
+
306
+ @pytest.mark.parametrize(
307
+ "error_message, expected_user_friendly_message",
308
+ [
309
+ (
310
+ "404 Client Error: Not Found for url: https://api.github.com/repos/repo_name",
311
+ 'Repo name: "repo_name" is unknown, "repository" config option should use existing full repo name <organization>/<repository>',
312
+ ),
313
+ (
314
+ "404 Client Error: Not Found for url: https://api.github.com/orgs/org_name",
315
+ 'Organization name: "org_name" is unknown, "repository" config option should be updated. Please validate your repository config.',
316
+ ),
317
+ (
318
+ "401 Client Error: Unauthorized for url",
319
+ "Github credentials have expired or changed, please review your credentials and re-authenticate or renew your access token.",
320
+ ),
321
+ ],
322
+ )
323
+ def test_user_friendly_message(error_message, expected_user_friendly_message):
324
+ source = SourceGithub()
325
+ user_friendly_error_message = source.user_friendly_error_message(error_message)
326
+ assert user_friendly_error_message == expected_user_friendly_message