ace-support-test-helpers 0.13.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f6dc0530e9109d683e0b5e41530b9ff50c45aa646ecf9b289f45ad95dbe8b908
4
+ data.tar.gz: dea7a118cb5b6b8f8c06792a54c6400dd362836222dcccf00ea29ca537d5489c
5
+ SHA512:
6
+ metadata.gz: 896cecde3a9fa17f925742f5005009004e374597ea1fc4e0d26889627420987e4a98612e9095456c5c9bcee6c439eabc2726b2d01aec3ab078ff02c360fda6ec
7
+ data.tar.gz: 81227f0ce16964e340c9fb8fa07d0dc7e7294dd5da226b598d2f84fa0632aebca6b88d21ce042bd4ddc6e393953bcd3739f122539ca2e4f7e8aab6d389a612d8
data/CHANGELOG.md ADDED
@@ -0,0 +1,110 @@
1
+ # Changelog
2
+
3
+ All notable changes to ace-test-support will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.13.0] - 2026-03-23
11
+
12
+ ### Fixed
13
+ - Corrected `source_code_uri` gemspec metadata to point to package-specific path instead of repository root.
14
+
15
+ ### Technical
16
+ - Removed phantom `handbook/**/*` glob from gemspec (no handbook directory exists).
17
+
18
+ ## [0.12.6] - 2026-03-22
19
+
20
+ ### Changed
21
+ - Refreshed README structure for support-library consistency while preserving existing usage and utility documentation.
22
+ - Added the canonical `Part of ACE` footer and explicit `License` section to the package README.
23
+
24
+ ## [0.12.5] - 2026-03-18
25
+
26
+ ### Changed
27
+ - Migrated CLI namespace from `Ace::Core::CLI::*` to `Ace::Support::Cli::*` (ace-support-cli is now the canonical home for CLI infrastructure).
28
+
29
+
30
+ ## [0.12.4] - 2026-03-09
31
+
32
+ ### Fixed
33
+ - `with_temp_dir` now resets cached `Ace::Bundle` configuration alongside project-root cache state so temp-dir tests remain order-independent under full non-E2E suite execution.
34
+
35
+ ## [0.12.3] - 2026-03-04
36
+
37
+ ### Fixed
38
+ - `with_cascade_configs` now isolates HOME into a temporary directory when creating home-level config fixtures, preventing permission errors in sandboxed environments.
39
+
40
+ ## [0.12.2] - 2026-01-31
41
+
42
+ ### Fixed
43
+ - Add `respond_to?(:get)` check to `test_stub_ace_core_config_integration` skip condition
44
+
45
+ ## [0.12.1] - 2026-01-31
46
+
47
+ ### Fixed
48
+ - Improve `stub_ace_core_config` isolation with `respond_to?` guard and `define_singleton_method`
49
+
50
+ ## [0.11.1] - 2026-01-15
51
+
52
+ ### Changed
53
+ - **Context Mocks Migration**: Updated ContextMocks to use Ace::Bundle
54
+ - Renamed all `Ace::Context` references to `Ace::Bundle`
55
+ - Updated mock methods: `stub_load_file`, `stub_load_auto`, `restore_load_file`, `restore_load_auto`
56
+ - Comments now reference ace-bundle instead of ace-context
57
+ - Updated TestRunnerMocks default package from ace-context to ace-bundle
58
+
59
+ ### Technical
60
+ - Updated contract tests to check for Ace::Bundle availability
61
+
62
+ ## [0.11.0] - 2026-01-07
63
+
64
+ ### Added
65
+ - **CLI test helpers** for dry-cli based CLIs (task 179)
66
+ - `CliHelpers` module with reusable patterns for testing CLI commands
67
+ - `invoke_cli` - Invoke CLI and capture stdout/stderr/result
68
+ - `invoke_cli_stdout` - Convenience method for stdout only
69
+ - `assert_cli_success` - Assert CLI returns exit code 0
70
+ - `assert_cli_output_matches` - Assert CLI output matches pattern
71
+
72
+ ## [0.10.0] - 2026-01-03
73
+
74
+ ### Changed
75
+ - **BREAKING**: Minimum Ruby version raised to 3.3.0 (was 3.2.0)
76
+ - Standardized gemspec file patterns with deterministic Dir.glob
77
+ - Added MIT LICENSE file
78
+
79
+ ## [0.9.3] - 2025-12-27
80
+
81
+ ### Changed
82
+
83
+ - **Contract Test Enhancement**: Added guarded require for ace-git in git contract tests
84
+ - Enables integration test to exercise CommandExecutor stub with ace-git when available
85
+ - Part of ace-git-diff to ace-git migration
86
+
87
+ ## [0.9.2] - 2025-10-08
88
+
89
+ ### Changed
90
+
91
+ - **Test Structure Migration**: Migrated to flat ATOM structure
92
+ - From: `test/unit/atoms/` and `test/unit/molecules/`
93
+ - To: `test/atoms/` and `test/molecules/`
94
+ - Aligns with standardized test organization across all ACE packages
95
+ - Simplifies test discovery and maintenance
96
+
97
+ ## [0.9.1] - 2025-10-08
98
+
99
+ ### Changed
100
+ - **Test directory structure**: Reorganized tests to follow ATOM architecture
101
+ - Moved tests from flat `test/*.rb` structure to `test/unit/atoms/` and `test/unit/molecules/`
102
+ - Makes tests discoverable by ace-test-runner
103
+ - Aligns with project-wide ATOM architecture pattern (ADR-011)
104
+ - Files organized as:
105
+ - `test/unit/atoms/`: base_test_case_test.rb, test_helper_test.rb
106
+ - `test/unit/molecules/`: config_helpers_test.rb, test_environment_test.rb
107
+
108
+ ## [0.9.0] - 2025-10-05
109
+
110
+ Initial release with shared test utilities for ACE ecosystem.
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Michal Czyz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ <div align="center">
2
+ <h1> ACE - Support Test Helpers </h1>
3
+
4
+ Shared test harness and environment helpers for ACE packages.
5
+
6
+ <img src="https://raw.githubusercontent.com/cs3b/ace/main/docs/brand/AgenticCodingEnvironment.Logo.XS.jpg" alt="ACE Logo" width="480">
7
+ <br><br>
8
+
9
+ <a href="https://rubygems.org/gems/ace-support-test-helpers"><img alt="Gem Version" src="https://img.shields.io/gem/v/ace-support-test-helpers.svg" /></a>
10
+ <a href="https://www.ruby-lang.org"><img alt="Ruby" src="https://img.shields.io/badge/Ruby-3.2+-CC342D?logo=ruby" /></a>
11
+ <a href="https://opensource.org/licenses/MIT"><img alt="License: MIT" src="https://img.shields.io/badge/License-MIT-blue.svg" /></a>
12
+
13
+ </div>
14
+
15
+ > Works with: Claude Code, Codex CLI, OpenCode, Gemini CLI, pi-agent, and more.
16
+
17
+ `ace-support-test-helpers` provides reusable helpers for temporary environment setup, assertions, and integration-friendly test scaffolding so that ACE packages share consistent test patterns without duplicating boilerplate.
18
+
19
+ ## Use Cases
20
+
21
+ **Write consistent package tests** - share base test case abstractions, setup patterns, and assertions across `ace-support-*` and other ACE packages via common helper modules.
22
+
23
+ **Build integration test flows** - isolate environment variables, filesystem state, and config fixtures with reliable temporary-directory and override helpers used alongside [ace-test-runner](../ace-test-runner).
24
+
25
+ **Improve CI stability** - reduce flaky test behavior with shared deterministic helpers for directory management, configuration, and environment cleanup.
26
+
27
+ ---
28
+
29
+ Part of [ACE](https://github.com/cs3b/ace)
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "minitest/test_task"
5
+
6
+ desc "Run tests using ace-test"
7
+ task :test do
8
+ sh "ace-test"
9
+ end
10
+
11
+ desc "Run tests directly (CI mode)"
12
+ Minitest::TestTask.create(:ci)
13
+
14
+ task default: :test
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "minitest/test"
4
+
5
+ module Ace
6
+ module TestSupport
7
+ # Base test case class with common utilities for all ace-* gems
8
+ class BaseTestCase < Minitest::Test
9
+ include TestHelper
10
+ include SubprocessRunner
11
+
12
+ def fixture_path(path)
13
+ File.expand_path("fixtures/#{path}", File.dirname(caller_locations(1, 1)[0].path))
14
+ end
15
+
16
+ def setup
17
+ @original_pwd = Dir.pwd
18
+ super
19
+ # Enable test mode by default - tests needing real config use with_real_config
20
+ if defined?(Ace::Support::Config) && Ace::Support::Config.respond_to?(:test_mode=)
21
+ Ace::Support::Config.test_mode = true
22
+ end
23
+ end
24
+
25
+ def teardown
26
+ Dir.chdir(@original_pwd) if @original_pwd
27
+ super
28
+ end
29
+ end
30
+
31
+ # Alias for backward compatibility
32
+ AceTestCase = BaseTestCase
33
+ end
34
+ end
35
+
36
+ # Make AceTestCase available at top level for convenience
37
+ AceTestCase = Ace::TestSupport::AceTestCase
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ace
4
+ module TestSupport
5
+ # CLI test helpers for ace-support-cli based CLIs
6
+ #
7
+ # This module provides reusable patterns for testing CLI commands
8
+ # during the Thor to ace-support-cli migration (Task 179).
9
+ #
10
+ # @example Usage in test file
11
+ # require 'ace/test_support'
12
+ #
13
+ # class CliRoutingTest < Minitest::Test
14
+ # include Ace::TestSupport::CliHelpers
15
+ #
16
+ # def test_routes_to_version
17
+ # result = invoke_cli(MyGem::CLI, ["--version"])
18
+ # assert_match(/\d+\.\d+\.\d+/, result[:stdout])
19
+ # end
20
+ # end
21
+ module CliHelpers
22
+ # Invoke a ace-support-cli CLI and capture output
23
+ #
24
+ # This helper provides a consistent pattern for testing CLI routing
25
+ # and command execution. It wraps CLI.start with capture_io to
26
+ # capture stdout/stderr.
27
+ #
28
+ # @param cli_class [Class] The CLI module/class with a .start method
29
+ # @param args [Array<String>] Command line arguments
30
+ # @return [Hash] Result hash with :stdout, :stderr, :result keys
31
+ #
32
+ # @example Basic usage
33
+ # result = invoke_cli(Ace::Search::CLI, ["version"])
34
+ # assert_match(/\d+\.\d+/, result[:stdout])
35
+ #
36
+ # @example Testing with options
37
+ # result = invoke_cli(Ace::Search::CLI, ["search", "TODO", "--max-results", "10"])
38
+ # assert_equal 0, result[:result]
39
+ #
40
+ # @note ace-support-cli calls exit(0) for --help, so we catch SystemExit.
41
+ # Commands raise Ace::Support::Cli::Error for controlled failures
42
+ # (exception-based exit code pattern per ADR-023).
43
+ def invoke_cli(cli_class, args)
44
+ stdout, stderr = capture_io do
45
+ @_cli_result = cli_class.start(args)
46
+ rescue SystemExit => e
47
+ @_cli_result = e.status
48
+ rescue Ace::Support::Cli::Error => e
49
+ warn e.message
50
+ @_cli_result = e.exit_code
51
+ end
52
+
53
+ {
54
+ stdout: stdout,
55
+ stderr: stderr,
56
+ result: @_cli_result
57
+ }
58
+ end
59
+
60
+ # Invoke CLI and return only stdout (convenience method)
61
+ #
62
+ # @param cli_class [Class] The CLI module/class with a .start method
63
+ # @param args [Array<String>] Command line arguments
64
+ # @return [String] Standard output
65
+ #
66
+ # @example
67
+ # output = invoke_cli_stdout(Ace::Search::CLI, ["version"])
68
+ # assert_match(/\d+\.\d+/, output)
69
+ def invoke_cli_stdout(cli_class, args)
70
+ invoke_cli(cli_class, args)[:stdout]
71
+ end
72
+
73
+ # Assert CLI returns success (exit code 0)
74
+ #
75
+ # @param cli_class [Class] The CLI module/class with a .start method
76
+ # @param args [Array<String>] Command line arguments
77
+ # @param message [String, nil] Optional assertion message
78
+ #
79
+ # @example
80
+ # assert_cli_success(Ace::Search::CLI, ["version"])
81
+ def assert_cli_success(cli_class, args, message = nil)
82
+ result = invoke_cli(cli_class, args)
83
+ assert_equal 0, result[:result],
84
+ message || "Expected CLI to return 0, got #{result[:result]}. stderr: #{result[:stderr]}"
85
+ end
86
+
87
+ # Assert CLI output matches pattern
88
+ #
89
+ # @param cli_class [Class] The CLI module/class with a .start method
90
+ # @param args [Array<String>] Command line arguments
91
+ # @param pattern [Regexp, String] Pattern to match against stdout
92
+ # @param message [String, nil] Optional assertion message
93
+ #
94
+ # @example
95
+ # assert_cli_output_matches(Ace::Search::CLI, ["version"], /\d+\.\d+/)
96
+ def assert_cli_output_matches(cli_class, args, pattern, message = nil)
97
+ result = invoke_cli(cli_class, args)
98
+ assert_match pattern, result[:stdout],
99
+ message || "Expected stdout to match #{pattern.inspect}"
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,235 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+ require "fileutils"
5
+ require "tmpdir"
6
+
7
+ module Ace
8
+ module TestSupport
9
+ # Helpers for config testing across all ace-* gems
10
+ module ConfigHelpers
11
+ # Execute block with test mode enabled
12
+ #
13
+ # This helper enables Ace::Support::Config test mode for the duration of the block,
14
+ # skipping filesystem searches and returning mock config instead.
15
+ #
16
+ # @param mock_config [Hash] Mock configuration data to return (default: {})
17
+ # @yield Block to execute with test mode enabled
18
+ # @return [Object] Result of the block
19
+ #
20
+ # @example Skip config filesystem access
21
+ # with_test_config do
22
+ # config = Ace::Support::Config.create.resolve
23
+ # assert_equal({}, config.data)
24
+ # end
25
+ #
26
+ # @example Provide mock config
27
+ # with_test_config({ "key" => "value" }) do
28
+ # config = Ace::Support::Config.create.resolve
29
+ # assert_equal "value", config.get("key")
30
+ # end
31
+ #
32
+ def with_test_config(mock_config = {})
33
+ require "ace/support/config"
34
+
35
+ original_test_mode = Ace::Support::Config.test_mode
36
+ original_mock = Ace::Support::Config.default_mock
37
+
38
+ Ace::Support::Config.test_mode = true
39
+ Ace::Support::Config.default_mock = mock_config
40
+
41
+ yield
42
+ ensure
43
+ Ace::Support::Config.test_mode = original_test_mode
44
+ Ace::Support::Config.default_mock = original_mock
45
+ end
46
+
47
+ # Execute block with real config (test mode disabled)
48
+ #
49
+ # This helper temporarily disables test mode for integration tests
50
+ # that need to test actual filesystem-based config loading.
51
+ #
52
+ # @yield Block to execute with real config
53
+ # @return [Object] Result of the block
54
+ #
55
+ # @example Run integration test with real config
56
+ # with_real_config do
57
+ # with_temp_config(".git" => "", ".ace" => { "config.yml" => "key: value" }) do
58
+ # config = Ace::Support::Config.create.resolve
59
+ # assert_equal "value", config.get("key")
60
+ # end
61
+ # end
62
+ #
63
+ def with_real_config
64
+ require "ace/support/config"
65
+
66
+ original_test_mode = Ace::Support::Config.test_mode
67
+
68
+ Ace::Support::Config.test_mode = false
69
+
70
+ yield
71
+ ensure
72
+ Ace::Support::Config.test_mode = original_test_mode
73
+ end
74
+
75
+ # Execute block with temporary config file
76
+ def with_config(path, content)
77
+ FileUtils.mkdir_p(File.dirname(path))
78
+
79
+ # Handle both Hash and String content
80
+ file_content = case content
81
+ when Hash
82
+ content.to_yaml
83
+ when String
84
+ content
85
+ else
86
+ raise ArgumentError, "Content must be Hash or String"
87
+ end
88
+
89
+ File.write(path, file_content)
90
+ yield
91
+ ensure
92
+ FileUtils.rm_f(path) if path && File.exist?(path)
93
+ end
94
+
95
+ # Execute block with temporary environment variables
96
+ def with_env(vars)
97
+ old_values = {}
98
+
99
+ vars.each do |key, value|
100
+ old_values[key] = ENV[key]
101
+ ENV[key] = value
102
+ end
103
+
104
+ yield
105
+ ensure
106
+ old_values.each do |key, value|
107
+ if value.nil?
108
+ ENV.delete(key)
109
+ else
110
+ ENV[key] = value
111
+ end
112
+ end
113
+ end
114
+
115
+ # Create multi-level config setup for any ace-* gem
116
+ def with_cascade_configs(gem_name = "core", configs = {})
117
+ paths = []
118
+ old_home = ENV["HOME"]
119
+ temp_home = nil
120
+
121
+ begin
122
+ # Create project config
123
+ if configs[:project]
124
+ project_path = "./.ace/#{gem_name}/config.yml"
125
+ FileUtils.mkdir_p(File.dirname(project_path))
126
+ File.write(project_path, configs[:project].to_yaml)
127
+ paths << project_path
128
+ end
129
+
130
+ # Create home config
131
+ if configs[:home]
132
+ temp_home = Dir.mktmpdir("ace-test-home")
133
+ ENV["HOME"] = temp_home
134
+ home_path = File.join(temp_home, ".ace", gem_name, "config.yml")
135
+ FileUtils.mkdir_p(File.dirname(home_path))
136
+ File.write(home_path, configs[:home].to_yaml)
137
+ paths << home_path
138
+ end
139
+
140
+ yield
141
+ ensure
142
+ ENV["HOME"] = old_home
143
+ FileUtils.rm_rf(temp_home) if temp_home && Dir.exist?(temp_home)
144
+ paths.each { |path| FileUtils.rm_f(path) if File.exist?(path) }
145
+ end
146
+ end
147
+
148
+ # Create sample config content
149
+ def sample_config(gem_name: "core", level: "default", custom: {})
150
+ base = {
151
+ "ace" => {
152
+ "level" => level,
153
+ gem_name => {
154
+ "version" => "1.0.0",
155
+ "environment" => "test"
156
+ }
157
+ }
158
+ }
159
+
160
+ deep_merge(base, custom)
161
+ end
162
+
163
+ # Create .env file content
164
+ def sample_env_content(vars = {})
165
+ default_vars = {
166
+ "ACE_ENV" => "test",
167
+ "ACE_DEBUG" => "false"
168
+ }
169
+
170
+ default_vars.merge(vars).map do |key, value|
171
+ "#{key}=#{value}"
172
+ end.join("\n")
173
+ end
174
+
175
+ # Assert config has expected structure
176
+ def assert_config_structure(config, expected)
177
+ assert_kind_of Hash, config
178
+
179
+ expected.each do |key, value|
180
+ assert config.key?(key), "Config missing key: #{key}"
181
+
182
+ if value.is_a?(Hash)
183
+ assert_config_structure(config[key], value)
184
+ else
185
+ assert_equal value, config[key], "Config value mismatch for #{key}"
186
+ end
187
+ end
188
+ end
189
+
190
+ # Assert config cascade precedence
191
+ def assert_precedence(resolver, key_path, expected_value, source_description)
192
+ config = resolver.resolve
193
+ actual = config.get(*key_path.split("."))
194
+
195
+ assert_equal expected_value, actual,
196
+ "Expected #{key_path} to be '#{expected_value}' from #{source_description}, got '#{actual}'"
197
+ end
198
+
199
+ # Create malformed YAML content
200
+ def malformed_yaml
201
+ "ace:\n invalid: [\n unclosed"
202
+ end
203
+
204
+ # Create valid but complex YAML
205
+ def complex_yaml
206
+ {
207
+ "ace" => {
208
+ "arrays" => [1, 2, 3],
209
+ "nested" => {
210
+ "deep" => {
211
+ "value" => "found"
212
+ }
213
+ },
214
+ "symbols" => {
215
+ "key" => :symbol_value
216
+ }
217
+ }
218
+ }.to_yaml
219
+ end
220
+
221
+ private
222
+
223
+ # Deep merge helper
224
+ def deep_merge(hash1, hash2)
225
+ hash1.merge(hash2) do |_key, old_val, new_val|
226
+ if old_val.is_a?(Hash) && new_val.is_a?(Hash)
227
+ deep_merge(old_val, new_val)
228
+ else
229
+ new_val
230
+ end
231
+ end
232
+ end
233
+ end
234
+ end
235
+ end