github2gerrit 1.3.2__tar.gz → 1.3.3__tar.gz

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 (134) hide show
  1. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/PKG-INFO +1 -1
  2. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_dependency_supersession.py +68 -41
  3. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_gitutils_helpers.py +73 -27
  4. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/.editorconfig +0 -0
  5. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/.gitignore +0 -0
  6. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/.gitlint +0 -0
  7. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/.markdownlint.yaml +0 -0
  8. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/.pre-commit-config.yaml +0 -0
  9. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/.readthedocs.yml +0 -0
  10. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/.yamllint +0 -0
  11. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/LICENSE +0 -0
  12. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/LICENSES/Apache-2.0.txt +0 -0
  13. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/README.md +0 -0
  14. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/REUSE.toml +0 -0
  15. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/SECURITY.md +0 -0
  16. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/action.yaml +0 -0
  17. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/docs/COMMIT_RULES.md +0 -0
  18. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/docs/COMPOSITE_ACTION_TESTING.md +0 -0
  19. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/docs/PR_UPDATE_IMPLEMENTATION.md +0 -0
  20. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/docs/RELEASE-v0.2.0.md +0 -0
  21. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/docs/github2gerrit_token_permissions_classic.png +0 -0
  22. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/pyproject.toml +0 -0
  23. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/sitecustomize.py +0 -0
  24. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/src/github2gerrit/__init__.py +0 -0
  25. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/src/github2gerrit/cli.py +0 -0
  26. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/src/github2gerrit/commit_normalization.py +0 -0
  27. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/src/github2gerrit/commit_rules.py +0 -0
  28. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/src/github2gerrit/config.py +0 -0
  29. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/src/github2gerrit/constants.py +0 -0
  30. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/src/github2gerrit/core.py +0 -0
  31. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/src/github2gerrit/duplicate_detection.py +0 -0
  32. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/src/github2gerrit/error_codes.py +0 -0
  33. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/src/github2gerrit/external_api.py +0 -0
  34. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/src/github2gerrit/gerrit_pr_closer.py +0 -0
  35. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/src/github2gerrit/gerrit_query.py +0 -0
  36. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/src/github2gerrit/gerrit_rest.py +0 -0
  37. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/src/github2gerrit/gerrit_ssh.py +0 -0
  38. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/src/github2gerrit/gerrit_urls.py +0 -0
  39. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/src/github2gerrit/github_api.py +0 -0
  40. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/src/github2gerrit/gitreview.py +0 -0
  41. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/src/github2gerrit/gitutils.py +0 -0
  42. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/src/github2gerrit/mapping_comment.py +0 -0
  43. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/src/github2gerrit/models.py +0 -0
  44. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/src/github2gerrit/netrc.py +0 -0
  45. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/src/github2gerrit/orchestrator/__init__.py +0 -0
  46. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/src/github2gerrit/orchestrator/reconciliation.py +0 -0
  47. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/src/github2gerrit/pr_commands.py +0 -0
  48. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/src/github2gerrit/pr_content_filter.py +0 -0
  49. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/src/github2gerrit/reconcile_matcher.py +0 -0
  50. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/src/github2gerrit/rich_display.py +0 -0
  51. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/src/github2gerrit/rich_logging.py +0 -0
  52. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/src/github2gerrit/similarity.py +0 -0
  53. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/src/github2gerrit/ssh_agent_setup.py +0 -0
  54. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/src/github2gerrit/ssh_common.py +0 -0
  55. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/src/github2gerrit/ssh_config_parser.py +0 -0
  56. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/src/github2gerrit/ssh_discovery.py +0 -0
  57. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/src/github2gerrit/trailers.py +0 -0
  58. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/src/github2gerrit/utils.py +0 -0
  59. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/conftest.py +0 -0
  60. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/fixtures/__init__.py +0 -0
  61. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/fixtures/make_repo.py +0 -0
  62. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/fixtures/ssh_config_samples.py +0 -0
  63. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_action_environment_mapping.py +0 -0
  64. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_action_outputs.py +0 -0
  65. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_action_pr_number_handling.py +0 -0
  66. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_action_step_validation.py +0 -0
  67. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_automation_only.py +0 -0
  68. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_change_id_deduplication.py +0 -0
  69. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_clean_squash_title.py +0 -0
  70. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_cli.py +0 -0
  71. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_cli_helpers.py +0 -0
  72. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_cli_netrc_options.py +0 -0
  73. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_cli_outputs_file.py +0 -0
  74. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_cli_url_and_dryrun.py +0 -0
  75. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_commit_normalization.py +0 -0
  76. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_commit_rules.py +0 -0
  77. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_composite_action_coverage.py +0 -0
  78. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_config_and_reviewers.py +0 -0
  79. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_config_helpers.py +0 -0
  80. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_core_close_pr_policy.py +0 -0
  81. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_core_config_and_errors.py +0 -0
  82. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_core_gerrit_backref_comment.py +0 -0
  83. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_core_gerrit_push_errors.py +0 -0
  84. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_core_gerrit_rest_results.py +0 -0
  85. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_core_integration_fixture_repo.py +0 -0
  86. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_core_prepare_commits.py +0 -0
  87. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_core_shallow_clone.py +0 -0
  88. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_core_ssh_setup.py +0 -0
  89. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_core_ssrf_protection.py +0 -0
  90. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_dns_validation_and_no_gerrit.py +0 -0
  91. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_duplicate_detection.py +0 -0
  92. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_email_case_normalization.py +0 -0
  93. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_error_codes.py +0 -0
  94. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_external_api_framework.py +0 -0
  95. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_force_flag_cli.py +0 -0
  96. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_gerrit_change_id_footer.py +0 -0
  97. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_gerrit_change_status_checks.py +0 -0
  98. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_gerrit_pr_closer.py +0 -0
  99. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_gerrit_rest_client.py +0 -0
  100. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_gerrit_ssh_abandon.py +0 -0
  101. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_gerrit_urls.py +0 -0
  102. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_gerrit_urls_more.py +0 -0
  103. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_ghe_and_gitreview_args.py +0 -0
  104. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_github_api_error_handling.py +0 -0
  105. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_github_api_helpers.py +0 -0
  106. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_github_api_retry_and_helpers.py +0 -0
  107. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_gitreview.py +0 -0
  108. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_issue_157_regressions.py +0 -0
  109. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_mapping_comment_additional.py +0 -0
  110. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_mapping_comment_digest_and_backref.py +0 -0
  111. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_metadata_and_reconciliation.py +0 -0
  112. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_metadata_trailer_separation_bug.py +0 -0
  113. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_misc_small_coverage.py +0 -0
  114. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_netrc.py +0 -0
  115. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_orphan_rest_side_effects.py +0 -0
  116. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_pr_commands.py +0 -0
  117. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_pr_content_filter.py +0 -0
  118. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_pr_content_filter_integration.py +0 -0
  119. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_pr_update_detection.py +0 -0
  120. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_reconciliation_extracted_module.py +0 -0
  121. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_reconciliation_plan_and_orphans.py +0 -0
  122. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_reconciliation_scenarios.py +0 -0
  123. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_ssh_agent.py +0 -0
  124. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_ssh_agent_ownership.py +0 -0
  125. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_ssh_artifact_prevention.py +0 -0
  126. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_ssh_common.py +0 -0
  127. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_ssh_discovery.py +0 -0
  128. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_ssh_discovery_dry_run.py +0 -0
  129. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_trailers_additional.py +0 -0
  130. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_url_parser.py +0 -0
  131. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/test_utils.py +0 -0
  132. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/unit/test_config_integration.py +0 -0
  133. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/tests/unit/test_ssh_config_parser.py +0 -0
  134. {github2gerrit-1.3.2 → github2gerrit-1.3.3}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: github2gerrit
3
- Version: 1.3.2
3
+ Version: 1.3.3
4
4
  Summary: Submit a GitHub pull request to a Gerrit repository.
5
5
  Project-URL: Homepage, https://github.com/lfreleng-actions/github2gerrit-action
6
6
  Project-URL: Repository, https://github.com/lfreleng-actions/github2gerrit-action
@@ -17,6 +17,7 @@ Covers two mechanisms:
17
17
 
18
18
  from __future__ import annotations
19
19
 
20
+ import hashlib
20
21
  from dataclasses import dataclass
21
22
  from typing import Any
22
23
  from unittest.mock import MagicMock
@@ -31,6 +32,7 @@ from github2gerrit.gerrit_query import query_open_changes_by_project
31
32
  from github2gerrit.gitreview import GerritInfo
32
33
  from github2gerrit.models import GitHubContext
33
34
  from github2gerrit.similarity import extract_dependency_package_from_subject
35
+ from github2gerrit.utils import reset_warning_once
34
36
 
35
37
 
36
38
  # -------------------------------------------------------------------
@@ -38,6 +40,46 @@ from github2gerrit.similarity import extract_dependency_package_from_subject
38
40
  # -------------------------------------------------------------------
39
41
 
40
42
 
43
+ # Dependency fixture values reused across the supersession tests.
44
+ # Centralising them documents which data is significant to the logic under
45
+ # test (the *package name* must match for a change to be superseded) versus
46
+ # which is arbitrary (the exact version strings).
47
+ _DEP_PACKAGE = "requests"
48
+ _DEP_VERSION_OLD = "2.31.0"
49
+ _DEP_VERSION_NEW = "2.32.0"
50
+ _DEP_VERSION_INTERMEDIATE = "2.31.5"
51
+ _DEP_BUMP_TITLE = (
52
+ f"Bump {_DEP_PACKAGE} from {_DEP_VERSION_OLD} to {_DEP_VERSION_NEW}"
53
+ )
54
+
55
+
56
+ @pytest.fixture(autouse=True)
57
+ def _reset_warning_once_state() -> None:
58
+ """Reset the one-time warning cache before each test.
59
+
60
+ Several tests assert warn-once behaviour (for example the truncation
61
+ warning emitted by the anonymous fallback), which relies on process-wide
62
+ global state. Resetting it automatically keeps every test isolated so
63
+ individual tests no longer need to call ``reset_warning_once`` by hand.
64
+ """
65
+ reset_warning_once()
66
+
67
+
68
+ def _make_change_id(seed: str) -> str:
69
+ """Return a valid-looking Gerrit Change-Id derived from ``seed``.
70
+
71
+ A real Gerrit Change-Id is the letter ``I`` followed by 40 hexadecimal
72
+ characters (a SHA-1 digest). Tests previously used mnemonic but invalid
73
+ values such as ``Iexist00...`` that contain non-hex characters. This
74
+ helper instead derives a truncated SHA-256 digest from a human-readable
75
+ seed: the exact hash algorithm is unimportant, it only needs to match
76
+ the ``I`` + 40-hex-character format while keeping failures easy to
77
+ trace.
78
+ """
79
+ digest = hashlib.sha256(seed.encode("utf-8")).hexdigest()[:40]
80
+ return f"I{digest}"
81
+
82
+
41
83
  def _gerrit_change(
42
84
  *,
43
85
  change_id: str = "Iaaaa000000000000000000000000000000000000",
@@ -202,10 +244,6 @@ class TestQueryOpenChangesByProject:
202
244
  to drive the anonymous fallback, the helper must short-circuit and
203
245
  never issue the request, returning an empty list.
204
246
  """
205
- from github2gerrit.utils import reset_warning_once
206
-
207
- reset_warning_once()
208
-
209
247
  fake_client = MagicMock()
210
248
  fake_client.is_authenticated = False
211
249
 
@@ -232,10 +270,6 @@ class TestQueryOpenChangesByProject:
232
270
  The query must NOT use owner:self, and only changes whose
233
271
  GitHub-PR trailer targets the current repository are returned.
234
272
  """
235
- from github2gerrit.utils import reset_warning_once
236
-
237
- reset_warning_once()
238
-
239
273
  ours = _raw_change(
240
274
  100,
241
275
  "chore: bump requests from 2.31.0 to 2.32.0",
@@ -262,10 +296,6 @@ class TestQueryOpenChangesByProject:
262
296
 
263
297
  def test_anonymous_fallback_repo_match_is_case_insensitive(self) -> None:
264
298
  """Repo trailer matching ignores case differences."""
265
- from github2gerrit.utils import reset_warning_once
266
-
267
- reset_warning_once()
268
-
269
299
  ours = _raw_change(
270
300
  200,
271
301
  "chore: bump requests from 2.31.0 to 2.32.0",
@@ -283,10 +313,6 @@ class TestQueryOpenChangesByProject:
283
313
 
284
314
  def test_anonymous_fallback_matches_ghes_host(self) -> None:
285
315
  """The host is ignored, so GitHub Enterprise URLs still match."""
286
- from github2gerrit.utils import reset_warning_once
287
-
288
- reset_warning_once()
289
-
290
316
  ours = _raw_change(
291
317
  210,
292
318
  "chore: bump requests from 2.31.0 to 2.32.0",
@@ -310,10 +336,6 @@ class TestQueryOpenChangesByProject:
310
336
  repo in its query string, a non-URL value, and a relative path
311
337
  (e.g. ``/org/repo/pull/1``) are all correctly excluded.
312
338
  """
313
- from github2gerrit.utils import reset_warning_once
314
-
315
- reset_warning_once()
316
-
317
339
  other = _raw_change(
318
340
  220,
319
341
  "chore: bump requests from 2.31.0 to 2.32.0",
@@ -346,10 +368,6 @@ class TestQueryOpenChangesByProject:
346
368
  GitHub2Gerrit changes (which carry the GitHub-PR trailer) so busy
347
369
  projects do not truncate before the relevant changes are seen.
348
370
  """
349
- from github2gerrit.utils import reset_warning_once
350
-
351
- reset_warning_once()
352
-
353
371
  fake_client = MagicMock()
354
372
  fake_client.is_authenticated = False
355
373
  fake_client.get.return_value = []
@@ -369,10 +387,6 @@ class TestQueryOpenChangesByProject:
369
387
  """Hitting the result cap emits a one-time truncation warning."""
370
388
  import logging
371
389
 
372
- from github2gerrit.utils import reset_warning_once
373
-
374
- reset_warning_once()
375
-
376
390
  c1 = _raw_change(
377
391
  300,
378
392
  "chore: bump x from 1 to 2",
@@ -406,9 +420,6 @@ class TestQueryOpenChangesByProject:
406
420
  self, monkeypatch: pytest.MonkeyPatch
407
421
  ) -> None:
408
422
  """Disabling the fallback flag skips even when repo is known."""
409
- from github2gerrit.utils import reset_warning_once
410
-
411
- reset_warning_once()
412
423
  monkeypatch.setenv("G2G_ANON_SUPERSEDE_FALLBACK", "false")
413
424
 
414
425
  fake_client = MagicMock()
@@ -422,10 +433,6 @@ class TestQueryOpenChangesByProject:
422
433
 
423
434
  def test_anonymous_fallback_whitespace_repo_skips(self) -> None:
424
435
  """A blank/whitespace-only repository must not trigger a query."""
425
- from github2gerrit.utils import reset_warning_once
426
-
427
- reset_warning_once()
428
-
429
436
  fake_client = MagicMock()
430
437
  fake_client.is_authenticated = False
431
438
 
@@ -446,7 +453,16 @@ class TestQueryOpenChangesByProject:
446
453
  new=lambda *_a, **_kw: "",
447
454
  )
448
455
  class TestAbandonSupersededDependencyChanges:
449
- """Tests for the post-push abandon sweep."""
456
+ """Tests for the post-push abandon sweep.
457
+
458
+ The class-level ``@patch`` stubs
459
+ ``gerrit_urls._discover_base_path_for_host`` to return an empty base
460
+ path for *every* test method below, so URL construction does not attempt
461
+ host discovery (a network call). The patch is intentionally applied at
462
+ the class level because all methods exercise URL building; it is not
463
+ injected as a method argument because the stubbed value is never
464
+ inspected by the tests.
465
+ """
450
466
 
451
467
  @patch("github2gerrit.gerrit_rest.build_client_for_host")
452
468
  @patch("github2gerrit.gerrit_query.query_open_changes_by_project")
@@ -789,7 +805,7 @@ class TestStrategy5Integration:
789
805
  # Set up GitHub mocks — strategies 1-4 should all fail
790
806
  mock_get_pull.return_value = _FakePullRequest(
791
807
  number=42,
792
- title="Bump requests from 2.31.0 to 2.32.0",
808
+ title=_DEP_BUMP_TITLE,
793
809
  user=_FakeGitHubUser(),
794
810
  )
795
811
  mock_get_repo.return_value = MagicMock()
@@ -803,10 +819,14 @@ class TestStrategy5Integration:
803
819
  mock_gerrit_client.return_value = fake_gerrit
804
820
 
805
821
  # Strategy 5 — return an open change bumping the same package
822
+ existing_change_id = _make_change_id("strategy5-existing")
806
823
  existing_change = _gerrit_change(
807
- change_id="Iexist00000000000000000000000000000000000",
824
+ change_id=existing_change_id,
808
825
  number="999",
809
- subject="chore: bump requests from 2.31.0 to 2.31.5",
826
+ subject=(
827
+ f"chore: bump {_DEP_PACKAGE} from "
828
+ f"{_DEP_VERSION_OLD} to {_DEP_VERSION_INTERMEDIATE}"
829
+ ),
810
830
  )
811
831
  mock_query_open.return_value = [existing_change]
812
832
 
@@ -832,7 +852,7 @@ class TestStrategy5Integration:
832
852
  )
833
853
 
834
854
  result = orch._find_existing_change_for_pr(gh, gerrit)
835
- assert result == ["Iexist00000000000000000000000000000000000"]
855
+ assert result == [existing_change_id]
836
856
 
837
857
  @patch("github2gerrit.core.build_client")
838
858
  @patch("github2gerrit.core.get_repo_from_env")
@@ -956,7 +976,14 @@ class TestStrategy5Integration:
956
976
  new=lambda *_a, **_kw: "",
957
977
  )
958
978
  class TestRealWorldScenarios:
959
- """Scenarios taken directly from the issue report."""
979
+ """Scenarios taken directly from the issue report.
980
+
981
+ As with :class:`TestAbandonSupersededDependencyChanges`, the class-level
982
+ ``@patch`` stubs ``gerrit_urls._discover_base_path_for_host`` for every
983
+ method so URL construction avoids host discovery. The stub uses ``new=``
984
+ rather than a mock argument because its return value is fixed and never
985
+ asserted on.
986
+ """
960
987
 
961
988
  @patch("github2gerrit.gerrit_rest.build_client_for_host")
962
989
  @patch("github2gerrit.gerrit_query.query_open_changes_by_project")
@@ -16,19 +16,40 @@ from github2gerrit.gitutils import run_cmd
16
16
  from github2gerrit.gitutils import run_cmd_with_retries
17
17
 
18
18
 
19
+ # Environment variables that pytest, pytest-cov, and pytest-xdist inject so
20
+ # that coverage measurement and test metadata propagate into subprocesses.
21
+ # When a test spawns the current Python interpreter via ``run_cmd`` we clear
22
+ # these so the child does not start its own coverage run (which can emit
23
+ # warnings or write stray data files) and so its output stays deterministic
24
+ # regardless of how the outer pytest session was invoked.
25
+ #
26
+ # COV_CORE_SOURCE/CONFIG/DATAFILE pytest-cov subprocess coverage hooks
27
+ # COVERAGE_PROCESS_START triggers coverage startup in children
28
+ # COVERAGE_FILE / COVERAGE_RCFILE coverage data / config file locations
29
+ # PYTEST_ADDOPTS extra pytest args inherited by children
30
+ # PYTEST_CURRENT_TEST current test id (set per test by pytest)
31
+ # PYTEST_XDIST_WORKER xdist worker id when running in parallel
32
+ _COVERAGE_ENV_KEYS = (
33
+ "COV_CORE_SOURCE",
34
+ "COV_CORE_CONFIG",
35
+ "COV_CORE_DATAFILE",
36
+ "COVERAGE_PROCESS_START",
37
+ "COVERAGE_FILE",
38
+ "COVERAGE_RCFILE",
39
+ "PYTEST_ADDOPTS",
40
+ "PYTEST_CURRENT_TEST",
41
+ "PYTEST_XDIST_WORKER",
42
+ )
43
+
44
+
19
45
  def _no_cov_env() -> dict[str, str]:
20
- keys = [
21
- "COV_CORE_SOURCE",
22
- "COV_CORE_CONFIG",
23
- "COV_CORE_DATAFILE",
24
- "COVERAGE_PROCESS_START",
25
- "COVERAGE_FILE",
26
- "COVERAGE_RCFILE",
27
- "PYTEST_ADDOPTS",
28
- "PYTEST_CURRENT_TEST",
29
- "PYTEST_XDIST_WORKER",
30
- ]
31
- return dict.fromkeys(keys, "")
46
+ """Return an env mapping that disables coverage in subprocesses.
47
+
48
+ Every key in :data:`_COVERAGE_ENV_KEYS` is mapped to an empty string so
49
+ a spawned child process inherits a clean, coverage-free environment. See
50
+ that constant for the rationale behind each cleared variable.
51
+ """
52
+ return dict.fromkeys(_COVERAGE_ENV_KEYS, "")
32
53
 
33
54
 
34
55
  def test_mask_text_replaces_tokens_and_ignores_empty() -> None:
@@ -93,24 +114,31 @@ def test_run_cmd_with_retries_retries_on_transient_error_then_succeeds(
93
114
 
94
115
  monkeypatch.setattr("time.sleep", _fake_sleep)
95
116
 
96
- # Script: on first run create a marker and exit non-zero with a transient
97
- # error in stderr.
98
- # On subsequent runs (marker exists) print success and exit 0.
117
+ # The helper script creates a marker file and fails with a transient
118
+ # error on its first run, then succeeds once the marker exists. Writing
119
+ # the behaviour to a real script file (taking the marker path as an
120
+ # argument) keeps the test readable and avoids embedding Python source
121
+ # inside an f-string.
99
122
  marker = tmp_path / "attempted"
100
-
101
- code = f"""
102
- import os, sys, pathlib
103
- p = pathlib.Path({str(marker)!r})
104
- if not p.exists():
105
- p.write_text("1", encoding="utf-8")
106
- print("could not resolve host: example.com", file=sys.stderr)
107
- sys.exit(128)
108
- else:
109
- print("ok")
110
- """
123
+ script = tmp_path / "transient_then_succeed.py"
124
+ script.write_text(
125
+ "import pathlib\n"
126
+ "import sys\n"
127
+ "\n"
128
+ "marker = pathlib.Path(sys.argv[1])\n"
129
+ "if not marker.exists():\n"
130
+ ' marker.write_text("1", encoding="utf-8")\n'
131
+ ' print("could not resolve host: example.com", file=sys.stderr)\n'
132
+ " sys.exit(128)\n"
133
+ "else:\n"
134
+ ' print("ok")\n',
135
+ encoding="utf-8",
136
+ )
111
137
 
112
138
  res = run_cmd_with_retries(
113
- [sys.executable, "-c", code], cwd=tmp_path, env=_no_cov_env()
139
+ [sys.executable, str(script), str(marker)],
140
+ cwd=tmp_path,
141
+ env=_no_cov_env(),
114
142
  )
115
143
  # Should succeed after retry, marker must exist, and we should have slept at
116
144
  # least once
@@ -264,6 +292,24 @@ def test_git_last_commit_trailers_parsing_edge_cases(
264
292
  ]
265
293
 
266
294
 
295
+ def test_git_last_commit_trailers_returns_empty_on_git_error(
296
+ monkeypatch: pytest.MonkeyPatch,
297
+ ) -> None:
298
+ """A failing ``git show`` (e.g. fresh repo) yields an empty mapping."""
299
+ from github2gerrit.gitutils import GitError
300
+ from github2gerrit.gitutils import git_last_commit_trailers
301
+
302
+ def _raise_git_error(rev: str, **_kw: object) -> str:
303
+ raise GitError("git show HEAD failed")
304
+
305
+ monkeypatch.setattr("github2gerrit.gitutils.git_show", _raise_git_error)
306
+
307
+ # The implementation catches GitError and returns an empty dict, both
308
+ # without and with a key filter.
309
+ assert git_last_commit_trailers() == {}
310
+ assert git_last_commit_trailers(keys=["Change-Id"]) == {}
311
+
312
+
267
313
  def test_git_quiet_suppresses_failure_logging(
268
314
  caplog: pytest.LogCaptureFixture, monkeypatch: pytest.MonkeyPatch
269
315
  ) -> None:
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes