ace-support-test-helpers 0.14.4 → 0.14.6

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a629650c00f11a385af2702b706fad2a502e89d740000dfafa1fe465c0f63ffa
4
- data.tar.gz: 4ffe9e7d64e66814e667431cde88f42ebfbc8ac449c3c7832f3083745ab19f46
3
+ metadata.gz: 95c5242cb44820418bda5f2d031055e5adab6a3fd8e2cacb88810622e7776ff2
4
+ data.tar.gz: 9a55229cf434d2a61122be1dcd9595eafbb4af1c87afc8d9c79b5f6fcace82da
5
5
  SHA512:
6
- metadata.gz: ccf9bd44e4ba8dc9be6ea21df13afa27bd80baec31f295e61d4346cb670bd2e4905318e7c9b2fea092ca810e1b59a3f47080587efb6b25e1f4ac282e20b47701
7
- data.tar.gz: e3448213c72aaacd1fa25aad4e061a9e4f22c0a0de0e8a0848da110f15af0a30e72f7f49408d419bd22413f27a3dd56d2971408aebc2ffb05979887fdd74e1ee
6
+ metadata.gz: 2c189fbce23a0c3544780011c88d73d81f269232b2062f6c025d920883cb6872404c8d25f523ed60016741a700c2d5499c2faf977971ab84c716282f60b48fc2
7
+ data.tar.gz: e4e67a4240c9a183f72191f14273ad88457dd99951c593484bf9e1d62633209dc45bd5238296c8c2b82e89e50ea420070fedc0a66771eeefb0d3644c044c53a3
data/CHANGELOG.md CHANGED
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.14.6] - 2026-09-28
11
+
12
+ ## [0.14.5] - 2026-09-24
13
+
14
+ ### Fixed
15
+
16
+ - Accept bundle loader options in shared test mocks.
10
17
 
11
18
  ## [0.14.4] - 2026-09-02
12
19
 
@@ -152,7 +152,7 @@ module Ace
152
152
 
153
153
  original_method_holder[:load_file] = Ace::Bundle.method(:load_file) if Ace::Bundle.respond_to?(:load_file)
154
154
 
155
- Ace::Bundle.define_singleton_method(:load_file) do |path|
155
+ Ace::Bundle.define_singleton_method(:load_file) do |path, _options = {}|
156
156
  BundleMocks.mock_load_file_result(path)
157
157
  end
158
158
 
@@ -0,0 +1,173 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ace
4
+ module TestSupport
5
+ # Shared provider-contract parity suite.
6
+ #
7
+ # Provider packages (ace-git-github, ace-git-forgejo) include this module
8
+ # in a test class backed by a scripted fake command runner and assert that
9
+ # their implementation produces identical normalized evidence and the
10
+ # exact classified failure taxonomy. Identical assertions run against every
11
+ # provider, which proves contract parity without any network or CLI.
12
+ #
13
+ # Host test class contract:
14
+ # - #build_provider(runner) -> Ace::Git::Providers::Base wired to the runner
15
+ # - #ok_runner -> successful scripted runner
16
+ # - #version_fail_runner -> provider CLI presence probe fails
17
+ # - #auth_fail_runner -> provider CLI auth probe fails
18
+ # - #not_found_runner -> object lookup command fails as "not found"
19
+ # - #malformed_runner -> object lookup returns unparseable output
20
+ # - #unreachable_runner -> object lookup fails as unreachable/timeout
21
+ #
22
+ # Runners are callables receiving kwargs (args:, timeout:, env:) and
23
+ # returning {stdout:, stderr:, exit_code:}.
24
+ module ProviderContract
25
+ # Essential normalized evidence: fields every provider must supply.
26
+ # Provider-optional fields (url, draft, merged_at) are asserted as
27
+ # present or left unchecked where a provider cannot supply them.
28
+ EXPECTED_PR = {
29
+ server_name: "forge-server",
30
+ number: 25,
31
+ title: "Ship the provider contract",
32
+ state: :merged,
33
+ head_ref: "lab/W675-ace",
34
+ base_ref: "main",
35
+ head_sha: "fc14c43d3660ac6c133959a6dec29603413f0e8a",
36
+ author: "lab-builder"
37
+ }.freeze
38
+
39
+ EXPECTED_BRANCH_PR = {
40
+ server_name: "forge-server",
41
+ number: 31,
42
+ title: "Wire status to providers",
43
+ state: :open,
44
+ head_ref: "lab/W676-ace",
45
+ base_ref: "main",
46
+ head_sha: "a1b2c3d4e5f60718293a4b5c6d7e8f9012345678",
47
+ author: "lab-builder"
48
+ }.freeze
49
+
50
+ EXPECTED_ISSUE = {
51
+ server_name: "forge-server",
52
+ number: 9,
53
+ title: "Broken diff on detached HEAD",
54
+ state: :open,
55
+ author: "lab-admin"
56
+ }.freeze
57
+
58
+ EXPECTED_CHECK = {server_name: "forge-server", name: "test-suite", state: :success, conclusion: :success}.freeze
59
+
60
+ EXPECTED_REPO = {
61
+ server_name: "forge-server",
62
+ full_name: "owner/repo",
63
+ url: "https://forge.example.com/owner/repo"
64
+ }.freeze
65
+
66
+ def test_contract_reports_available_and_authenticated
67
+ provider = build_provider(ok_runner)
68
+ assert_equal true, provider.available?
69
+ assert_equal true, provider.authenticated?
70
+ provider.check_available!
71
+ provider.check_authenticated!
72
+ end
73
+
74
+ def test_contract_pull_request_evidence_matches_normalized_shape
75
+ pr = build_provider(ok_runner).pull_request(number: 25)
76
+ assert_instance_of Ace::Git::ProviderPullRequest, pr
77
+ assert_normalized_fields(EXPECTED_PR, pr)
78
+ refute_nil pr.url
79
+ end
80
+
81
+ def test_contract_pull_request_for_branch_evidence_matches_normalized_shape
82
+ pr = build_provider(ok_runner).pull_request_for_branch(branch: "lab/W676-ace")
83
+ assert_instance_of Ace::Git::ProviderPullRequest, pr
84
+ assert_normalized_fields(EXPECTED_BRANCH_PR, pr)
85
+ end
86
+
87
+ def test_contract_pull_request_for_branch_returns_nil_without_match
88
+ assert_nil build_provider(ok_runner).pull_request_for_branch(branch: "no-such-branch")
89
+ end
90
+
91
+ def test_contract_pull_request_diff_returns_raw_text
92
+ diff = build_provider(ok_runner).pull_request_diff(number: 25)
93
+ assert_instance_of String, diff
94
+ assert_includes diff, "diff --git"
95
+ end
96
+
97
+ def test_contract_recent_pull_requests_returns_normalized_list
98
+ prs = build_provider(ok_runner).recent_pull_requests(limit: 30)
99
+ assert_instance_of Array, prs
100
+ assert_equal 2, prs.length
101
+ assert prs.all? { |pr| pr.instance_of?(Ace::Git::ProviderPullRequest) }
102
+ # Newest first (descending PR number), identical across providers
103
+ assert_equal [31, 25], prs.map(&:number)
104
+ end
105
+
106
+ def test_contract_issue_evidence_matches_normalized_shape
107
+ issue = build_provider(ok_runner).issue(number: 9)
108
+ assert_instance_of Ace::Git::ProviderIssue, issue
109
+ assert_normalized_fields(EXPECTED_ISSUE, issue)
110
+ refute_nil issue.url
111
+ end
112
+
113
+ def test_contract_checks_return_normalized_list
114
+ checks = build_provider(ok_runner).checks(ref: "fc14c43d3660ac6c133959a6dec29603413f0e8a")
115
+ assert_instance_of Array, checks
116
+ assert_equal 1, checks.length
117
+ check = checks.first
118
+ assert_instance_of Ace::Git::ProviderCheck, check
119
+ assert_equal EXPECTED_CHECK[:server_name], check.server_name
120
+ assert_equal EXPECTED_CHECK[:name], check.name
121
+ assert_equal EXPECTED_CHECK[:state], check.state
122
+ assert_equal EXPECTED_CHECK[:conclusion], check.conclusion
123
+ end
124
+
125
+ def test_contract_repository_evidence_matches_normalized_shape
126
+ repo = build_provider(ok_runner).repository
127
+ assert_instance_of Ace::Git::ProviderRepository, repo
128
+ assert_equal EXPECTED_REPO[:server_name], repo.server_name
129
+ assert_equal EXPECTED_REPO[:full_name], repo.full_name
130
+ assert_equal EXPECTED_REPO[:url], repo.url
131
+ end
132
+
133
+ def test_contract_cli_missing_failure_is_classified
134
+ provider = build_provider(version_fail_runner)
135
+ error = assert_raises(Ace::Git::ProviderCliMissingError) { provider.check_available! }
136
+ assert_match(/cli/i, error.message)
137
+ end
138
+
139
+ def test_contract_authentication_failure_is_classified
140
+ provider = build_provider(auth_fail_runner)
141
+ error = assert_raises(Ace::Git::ProviderAuthenticationError) { provider.check_authenticated! }
142
+ assert_match(/auth/i, error.message)
143
+ end
144
+
145
+ def test_contract_object_not_found_failure_is_classified
146
+ provider = build_provider(not_found_runner)
147
+ error = assert_raises(Ace::Git::ProviderObjectNotFoundError) { provider.pull_request(number: 999) }
148
+ assert_match(/999/, error.message)
149
+ end
150
+
151
+ def test_contract_malformed_output_failure_is_classified
152
+ provider = build_provider(malformed_runner)
153
+ error = assert_raises(Ace::Git::ProviderMalformedOutputError) { provider.pull_request(number: 25) }
154
+ assert_match(/malformed|parse|unrecognized/i, error.message)
155
+ end
156
+
157
+ def test_contract_unreachable_failure_is_classified
158
+ provider = build_provider(unreachable_runner)
159
+ error = assert_raises(Ace::Git::ProviderUnreachableError) { provider.pull_request(number: 25) }
160
+ assert_match(/unreachable|timed out|failed/i, error.message)
161
+ end
162
+
163
+ private
164
+
165
+ def assert_normalized_fields(expected, evidence)
166
+ expected.each do |field, value|
167
+ assert_equal value, evidence.public_send(field),
168
+ "#{field} must be normalized identically across providers"
169
+ end
170
+ end
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ace
4
+ module TestSupport
5
+ # Shared PR-lifecycle contract parity suite.
6
+ #
7
+ # Provider packages include this module and supply scripted runners per
8
+ # symbolic scenario, proving identical normalized receipts, exact-identity
9
+ # matching, expected-head enforcement, idempotent create, unknown-outcome
10
+ # classification, and capability classification across every provider —
11
+ # with no network and no provider CLI.
12
+ #
13
+ # Host test class contract:
14
+ # - #build_provider(runner) -> Ace::Git::Providers::Base on the runner
15
+ # - #lifecycle_runner(scenario) -> scripted runner for the scenario
16
+ # - #ready_supported? [Boolean] provider can mark drafts ready
17
+ # - #merge_supported? [Boolean] provider enforces head atomically
18
+ module PullRequestLifecycleContract
19
+ SERVER_URL = "https://forge.example.com/owner/repo"
20
+ HEAD_SHA = "fc14c43d3660ac6c133959a6dec29603413f0e8a"
21
+ OTHER_SHA = "0123456789abcdef0123456789abcdef01234567"
22
+
23
+ IDENTITY = {
24
+ head_repository_url: SERVER_URL,
25
+ head_ref: "feature/x",
26
+ base_repository_url: SERVER_URL,
27
+ base_ref: "main"
28
+ }.freeze
29
+
30
+ # Create kwargs: the base repository is always the resolved server.
31
+ CREATE_IDENTITY = IDENTITY.except(:base_repository_url).freeze
32
+
33
+ def test_lifecycle_find_matches_exact_identity_with_provenance
34
+ prs = build_provider(lifecycle_runner(:find_single)).find_open_pull_requests(**IDENTITY)
35
+ assert_equal [25], prs.map(&:number)
36
+ pr = prs.first
37
+ assert_equal HEAD_SHA, pr.head_sha
38
+ assert_equal "feature/x", pr.head_ref
39
+ assert_equal "main", pr.base_ref
40
+ assert_equal SERVER_URL, pr.head_repository_url
41
+ assert_equal SERVER_URL, pr.base_repository_url
42
+ end
43
+
44
+ def test_lifecycle_create_proves_head_and_reports_created
45
+ provider = build_provider(lifecycle_runner(:create_new))
46
+ receipt = provider.create_pull_request(
47
+ **CREATE_IDENTITY, expected_head: HEAD_SHA, title: "Ship it"
48
+ )
49
+ assert_instance_of Ace::Git::ProviderMutationReceipt, receipt
50
+ assert_equal :create, receipt.operation
51
+ assert_equal :created, receipt.idempotency
52
+ assert_equal HEAD_SHA, receipt.pull_request.head_sha
53
+ assert_equal SERVER_URL, receipt.pull_request.base_repository_url
54
+ assert_equal HEAD_SHA, receipt.to_h.dig(:pull_request, :head_sha)
55
+ end
56
+
57
+ def test_lifecycle_create_reconciles_to_exact_open_match
58
+ receipt = build_provider(lifecycle_runner(:create_existing)).create_pull_request(
59
+ **CREATE_IDENTITY, expected_head: HEAD_SHA, title: "Ship it"
60
+ )
61
+ assert_equal :existing, receipt.idempotency
62
+ assert_equal 25, receipt.pull_request.number
63
+ end
64
+
65
+ def test_lifecycle_create_rejects_stale_expected_head_without_mutation
66
+ provider = build_provider(lifecycle_runner(:create_existing))
67
+ error = assert_raises(Ace::Git::ProviderExpectedHeadConflictError) do
68
+ provider.create_pull_request(
69
+ **CREATE_IDENTITY, expected_head: OTHER_SHA, title: "Ship it"
70
+ )
71
+ end
72
+ assert_match(/head changed|does not match/i, error.message)
73
+ end
74
+
75
+ def test_lifecycle_create_conflicts_on_multiple_exact_matches
76
+ provider = build_provider(lifecycle_runner(:find_multi))
77
+ error = assert_raises(Ace::Git::ProviderConflictingMatchesError) do
78
+ provider.create_pull_request(
79
+ **CREATE_IDENTITY, expected_head: HEAD_SHA, title: "Ship it"
80
+ )
81
+ end
82
+ assert_match(/match/i, error.message)
83
+ end
84
+
85
+ def test_lifecycle_create_reports_unknown_outcome_with_reconciliation_identity
86
+ provider = build_provider(lifecycle_runner(:create_unknown))
87
+ error = assert_raises(Ace::Git::ProviderUnknownOutcomeError) do
88
+ provider.create_pull_request(
89
+ **CREATE_IDENTITY, expected_head: HEAD_SHA, title: "Ship it"
90
+ )
91
+ end
92
+ assert_match(/reconcile by exact identity/i, error.message)
93
+ assert_match(%r{owner/repo}, error.message)
94
+ end
95
+
96
+ def test_lifecycle_update_enforces_expected_head
97
+ provider = build_provider(lifecycle_runner(:update_stale))
98
+ error = assert_raises(Ace::Git::ProviderExpectedHeadConflictError) do
99
+ provider.update_pull_request(number: 25, expected_head: OTHER_SHA, title: "New title")
100
+ end
101
+ assert_match(/head changed/i, error.message)
102
+
103
+ receipt = build_provider(lifecycle_runner(:update_ok))
104
+ .update_pull_request(number: 25, expected_head: HEAD_SHA, title: "New title")
105
+ assert_equal :update, receipt.operation
106
+ assert_equal HEAD_SHA, receipt.pull_request.head_sha
107
+ end
108
+
109
+ def test_lifecycle_ready_classifies_capability
110
+ provider = build_provider(lifecycle_runner(ready_supported? ? :ready_ok : :ready_unsupported))
111
+ if ready_supported?
112
+ receipt = provider.ready_pull_request(number: 25, expected_head: HEAD_SHA)
113
+ assert_equal :ready, receipt.operation
114
+ else
115
+ error = assert_raises(Ace::Git::ProviderUnsupportedCapabilityError) do
116
+ provider.ready_pull_request(number: 25, expected_head: HEAD_SHA)
117
+ end
118
+ assert_match(/ready|capab|support/i, error.message)
119
+ end
120
+ end
121
+
122
+ def test_lifecycle_merge_enforces_expected_head_atomically
123
+ provider = build_provider(lifecycle_runner(merge_supported? ? :merge_ok : :merge_unsupported))
124
+ if merge_supported?
125
+ receipt = provider.merge_pull_request(number: 25, expected_head: HEAD_SHA, method: :squash)
126
+ assert_equal :merge, receipt.operation
127
+ assert_equal HEAD_SHA, receipt.pull_request.head_sha
128
+
129
+ error = assert_raises(Ace::Git::ProviderExpectedHeadConflictError) do
130
+ build_provider(lifecycle_runner(:merge_stale))
131
+ .merge_pull_request(number: 25, expected_head: OTHER_SHA, method: :squash)
132
+ end
133
+ assert_match(/head|match/i, error.message)
134
+ else
135
+ error = assert_raises(Ace::Git::ProviderUnsupportedCapabilityError) do
136
+ provider.merge_pull_request(number: 25, expected_head: HEAD_SHA, method: :squash)
137
+ end
138
+ assert_match(/atomic|expected[- ]head|capab|support/i, error.message)
139
+ end
140
+ end
141
+
142
+ def test_lifecycle_merge_rejects_unknown_methods
143
+ provider = build_provider(lifecycle_runner(:find_single))
144
+ assert_raises(ArgumentError) do
145
+ provider.merge_pull_request(number: 25, expected_head: HEAD_SHA, method: :fast_forward)
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Ace
4
4
  module TestSupport
5
- VERSION = "0.14.4"
5
+ VERSION = "0.14.6"
6
6
  end
7
7
  end
@@ -21,6 +21,8 @@ require_relative "test_support/fixtures/git_mocks" # Shared git mocks
21
21
  require_relative "test_support/fixtures/http_mocks" # Shared HTTP mocks for LLM testing
22
22
  require_relative "test_support/fixtures/prompt_helpers" # Shared prompt stubbing helpers
23
23
  require_relative "test_support/fixtures/test_runner_mocks" # Shared test runner mocks
24
+ require_relative "test_support/provider_contract" # Provider contract parity suite for forge provider gems
25
+ require_relative "test_support/pull_request_lifecycle_contract" # PR lifecycle contract parity suite
24
26
 
25
27
  # Configure Minitest reporters by default
26
28
  Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ace-support-test-helpers
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.14.4
4
+ version: 0.14.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michal Czyz
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-09-05 00:00:00.000000000 Z
10
+ date: 2026-09-29 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: minitest
@@ -62,6 +62,8 @@ files:
62
62
  - lib/ace/test_support/fixtures/prompt_helpers.rb
63
63
  - lib/ace/test_support/fixtures/test_runner_mocks.rb
64
64
  - lib/ace/test_support/performance_helpers.rb
65
+ - lib/ace/test_support/provider_contract.rb
66
+ - lib/ace/test_support/pull_request_lifecycle_contract.rb
65
67
  - lib/ace/test_support/sandbox_package_copy.rb
66
68
  - lib/ace/test_support/subprocess_runner.rb
67
69
  - lib/ace/test_support/test_environment.rb