spec_forge 0.5.0 → 0.7.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.
Files changed (116) hide show
  1. checksums.yaml +4 -4
  2. data/.standard.yml +3 -3
  3. data/CHANGELOG.md +217 -2
  4. data/README.md +162 -25
  5. data/flake.lock +3 -3
  6. data/flake.nix +11 -5
  7. data/lib/spec_forge/attribute/chainable.rb +208 -20
  8. data/lib/spec_forge/attribute/factory.rb +92 -15
  9. data/lib/spec_forge/attribute/faker.rb +62 -13
  10. data/lib/spec_forge/attribute/global.rb +96 -0
  11. data/lib/spec_forge/attribute/literal.rb +15 -2
  12. data/lib/spec_forge/attribute/matcher.rb +186 -11
  13. data/lib/spec_forge/attribute/parameterized.rb +45 -12
  14. data/lib/spec_forge/attribute/regex.rb +55 -5
  15. data/lib/spec_forge/attribute/resolvable.rb +48 -5
  16. data/lib/spec_forge/attribute/resolvable_array.rb +62 -4
  17. data/lib/spec_forge/attribute/resolvable_hash.rb +62 -4
  18. data/lib/spec_forge/attribute/store.rb +65 -0
  19. data/lib/spec_forge/attribute/transform.rb +33 -5
  20. data/lib/spec_forge/attribute/variable.rb +37 -6
  21. data/lib/spec_forge/attribute.rb +166 -66
  22. data/lib/spec_forge/backtrace_formatter.rb +26 -3
  23. data/lib/spec_forge/callbacks.rb +88 -0
  24. data/lib/spec_forge/cli/actions.rb +27 -0
  25. data/lib/spec_forge/cli/command.rb +78 -24
  26. data/lib/spec_forge/cli/docs/generate.rb +72 -0
  27. data/lib/spec_forge/cli/docs.rb +92 -0
  28. data/lib/spec_forge/cli/init.rb +51 -9
  29. data/lib/spec_forge/cli/new.rb +67 -6
  30. data/lib/spec_forge/cli/run.rb +32 -4
  31. data/lib/spec_forge/cli/serve.rb +155 -0
  32. data/lib/spec_forge/cli.rb +26 -7
  33. data/lib/spec_forge/configuration.rb +96 -24
  34. data/lib/spec_forge/context/callbacks.rb +91 -0
  35. data/lib/spec_forge/context/global.rb +72 -0
  36. data/lib/spec_forge/context/store.rb +131 -0
  37. data/lib/spec_forge/context/variables.rb +91 -0
  38. data/lib/spec_forge/context.rb +36 -0
  39. data/lib/spec_forge/core_ext/array.rb +27 -0
  40. data/lib/spec_forge/core_ext/rspec.rb +22 -4
  41. data/lib/spec_forge/documentation/builder.rb +383 -0
  42. data/lib/spec_forge/documentation/document/operation.rb +47 -0
  43. data/lib/spec_forge/documentation/document/parameter.rb +22 -0
  44. data/lib/spec_forge/documentation/document/request_body.rb +24 -0
  45. data/lib/spec_forge/documentation/document/response.rb +39 -0
  46. data/lib/spec_forge/documentation/document/response_body.rb +27 -0
  47. data/lib/spec_forge/documentation/document.rb +48 -0
  48. data/lib/spec_forge/documentation/generators/base.rb +81 -0
  49. data/lib/spec_forge/documentation/generators/openapi/base.rb +100 -0
  50. data/lib/spec_forge/documentation/generators/openapi/error_formatter.rb +149 -0
  51. data/lib/spec_forge/documentation/generators/openapi/v3_0.rb +65 -0
  52. data/lib/spec_forge/documentation/generators/openapi.rb +59 -0
  53. data/lib/spec_forge/documentation/generators.rb +17 -0
  54. data/lib/spec_forge/documentation/loader/cache.rb +138 -0
  55. data/lib/spec_forge/documentation/loader.rb +159 -0
  56. data/lib/spec_forge/documentation/openapi/base.rb +33 -0
  57. data/lib/spec_forge/documentation/openapi/v3_0/example.rb +44 -0
  58. data/lib/spec_forge/documentation/openapi/v3_0/media_type.rb +42 -0
  59. data/lib/spec_forge/documentation/openapi/v3_0/operation.rb +175 -0
  60. data/lib/spec_forge/documentation/openapi/v3_0/response.rb +65 -0
  61. data/lib/spec_forge/documentation/openapi/v3_0/schema.rb +80 -0
  62. data/lib/spec_forge/documentation/openapi/v3_0/tag.rb +71 -0
  63. data/lib/spec_forge/documentation/openapi.rb +23 -0
  64. data/lib/spec_forge/documentation.rb +27 -0
  65. data/lib/spec_forge/error.rb +284 -113
  66. data/lib/spec_forge/factory.rb +35 -16
  67. data/lib/spec_forge/filter.rb +86 -0
  68. data/lib/spec_forge/forge.rb +171 -0
  69. data/lib/spec_forge/http/backend.rb +101 -29
  70. data/lib/spec_forge/http/client.rb +23 -13
  71. data/lib/spec_forge/http/request.rb +85 -62
  72. data/lib/spec_forge/http/verb.rb +79 -0
  73. data/lib/spec_forge/http.rb +105 -0
  74. data/lib/spec_forge/loader.rb +244 -0
  75. data/lib/spec_forge/matchers.rb +130 -0
  76. data/lib/spec_forge/normalizer/default.rb +51 -0
  77. data/lib/spec_forge/normalizer/definition.rb +248 -0
  78. data/lib/spec_forge/normalizer/validators.rb +99 -0
  79. data/lib/spec_forge/normalizer.rb +486 -115
  80. data/lib/spec_forge/normalizers/_shared.yml +74 -0
  81. data/lib/spec_forge/normalizers/configuration.yml +23 -0
  82. data/lib/spec_forge/normalizers/constraint.yml +8 -0
  83. data/lib/spec_forge/normalizers/expectation.yml +47 -0
  84. data/lib/spec_forge/normalizers/factory.yml +12 -0
  85. data/lib/spec_forge/normalizers/factory_reference.yml +15 -0
  86. data/lib/spec_forge/normalizers/global_context.yml +28 -0
  87. data/lib/spec_forge/normalizers/spec.yml +50 -0
  88. data/lib/spec_forge/runner/adapter.rb +183 -0
  89. data/lib/spec_forge/runner/callbacks.rb +246 -0
  90. data/lib/spec_forge/runner/debug_proxy.rb +213 -0
  91. data/lib/spec_forge/runner/listener.rb +54 -0
  92. data/lib/spec_forge/runner/metadata.rb +58 -0
  93. data/lib/spec_forge/runner/state.rb +98 -0
  94. data/lib/spec_forge/runner.rb +50 -125
  95. data/lib/spec_forge/spec/expectation/constraint.rb +100 -21
  96. data/lib/spec_forge/spec/expectation.rb +47 -51
  97. data/lib/spec_forge/spec.rb +50 -108
  98. data/lib/spec_forge/type.rb +36 -4
  99. data/lib/spec_forge/version.rb +4 -1
  100. data/lib/spec_forge.rb +168 -76
  101. data/lib/templates/openapi.yml.tt +22 -0
  102. data/lib/templates/redoc.html.tt +28 -0
  103. data/lib/templates/swagger.html.tt +59 -0
  104. metadata +109 -16
  105. data/lib/spec_forge/normalizer/configuration.rb +0 -77
  106. data/lib/spec_forge/normalizer/constraint.rb +0 -47
  107. data/lib/spec_forge/normalizer/expectation.rb +0 -86
  108. data/lib/spec_forge/normalizer/factory.rb +0 -65
  109. data/lib/spec_forge/normalizer/factory_reference.rb +0 -71
  110. data/lib/spec_forge/normalizer/spec.rb +0 -74
  111. data/spec_forge/factories/user.yml +0 -4
  112. data/spec_forge/forge_helper.rb +0 -48
  113. data/spec_forge/specs/users.yml +0 -65
  114. /data/lib/templates/{forge_helper.tt → forge_helper.rb.tt} +0 -0
  115. /data/lib/templates/{new_factory.tt → new_factory.yml.tt} +0 -0
  116. /data/lib/templates/{new_spec.tt → new_spec.yml.tt} +0 -0
@@ -0,0 +1,213 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpecForge
4
+ class Runner
5
+ #
6
+ # Creates a debugging environment during test execution.
7
+ # When a breakpoint is triggered, this provides an interface to inspect
8
+ # the current test state including the request, response, variables, and expectations.
9
+ #
10
+ # By default, this outputs a JSON representation of the current testing context,
11
+ # but it can be customized by configuring SpecForge.configuration.on_debug
12
+ # to use any Ruby debugger (like pry or debug).
13
+ #
14
+ # @example Basic usage in a spec with `debug: true`
15
+ # # In your YAML test:
16
+ # get_users:
17
+ # debug: true
18
+ # path: /users
19
+ # expectations:
20
+ # - expect:
21
+ # status: 200
22
+ #
23
+ # @example Custom debug handler in forge_helper.rb
24
+ # SpecForge.configure do |config|
25
+ # config.on_debug { binding.pry } # Requires 'pry' gem
26
+ # end
27
+ #
28
+ class DebugProxy
29
+ #
30
+ # @return [Proc] The default debugging handler that outputs JSON state information
31
+ #
32
+ def self.default
33
+ -> { puts inspect }
34
+ end
35
+
36
+ # @return [RSpec::Forge] The current Forge that is being tested
37
+ attr_reader :forge
38
+
39
+ # @return [SpecForge::Spec] The current Spec that is being tested
40
+ attr_reader :spec
41
+
42
+ # @return [SpecForge::Spec::Expectation] The current expectation that is being tested
43
+ attr_reader :expectation
44
+
45
+ # @return [RSpec::ExampleGroup] The current RSpec example group
46
+ attr_reader :example_group
47
+
48
+ # @return [RSpec::Example] The current RSpec example that is running
49
+ attr_reader :example
50
+
51
+ # @return [Integer] The expected HTTP status code
52
+ attr_reader :expected_status
53
+
54
+ # @return [Object] The expected response body structure
55
+ attr_reader :expected_json
56
+
57
+ delegate_missing_to :@example_group
58
+
59
+ #
60
+ # Creates a new DebugProxy instance
61
+ #
62
+ # @param forge [SpecForge::Forge] The forge being tested
63
+ # @param spec [SpecForge::Spec] The spec being tested
64
+ # @param expectation [SpecForge::Spec::Expectation] The expectation being tested
65
+ # @param example_group [RSpec::Core::ExampleGroup] The current example group
66
+ #
67
+ # @return [SpecForge::Runner::DebugProxy]
68
+ #
69
+ def initialize(forge, spec, expectation, example_group)
70
+ @callback = SpecForge.configuration.on_debug
71
+
72
+ @forge = forge
73
+ @spec = spec
74
+ @expectation = expectation
75
+ @example_group = example_group
76
+ @example = RSpec.current_example
77
+
78
+ constraints = expectation.constraints
79
+
80
+ @expected_status = constraints.status.resolved
81
+ @expected_json = constraints.json.resolved
82
+ end
83
+
84
+ #
85
+ # Triggers the debugging environment
86
+ #
87
+ # Displays available debugging contexts and executes the configured debug callback.
88
+ # The callback runs in the context of this proxy, giving it access to all helper methods.
89
+ #
90
+ # @return [void]
91
+ #
92
+ def call
93
+ puts <<~STRING
94
+
95
+ Debug triggered for:
96
+ > #{example.metadata[:rerun_file_path]} on line #{expectation.line_number}
97
+
98
+ Available debugging contexts:
99
+ - spec: Current spec details
100
+ - expectation: Current expectation being tested
101
+ - variables: Variables defined for this test
102
+ - global: Global context shared across tests
103
+ - store: Stored data from expectations
104
+
105
+ Request & Response:
106
+ - request: HTTP request details (method, url, headers, body)
107
+ - response: HTTP response with headers, status and body
108
+
109
+ Expectations:
110
+ - expected_status: Expected HTTP status code
111
+ - expected_json: Expected response body structure
112
+
113
+ Matchers:
114
+ - match_status: Matcher used to test status
115
+ - match_json: Matcher used to test response body
116
+
117
+ Helper objects:
118
+ - http_client: The HTTP client used for the request
119
+ - request_data: Raw request configuration data
120
+ - example_group: Current RSpec example group
121
+ - example: Current RSpec example
122
+ - forge: Current file being tested
123
+
124
+ 💡 Pro tips:
125
+ - Type 'self' or 'inspect' for a pretty-printed JSON overview
126
+ - Use 'to_h' for the hash representation
127
+ - Access the shared context with 'SpecForge.context'
128
+ STRING
129
+
130
+ instance_exec(&@callback)
131
+ end
132
+
133
+ ##########################################################################
134
+
135
+ #
136
+ # Returns a hash representation of the global context
137
+ #
138
+ # @return [Hash] The global context with resolved variables
139
+ #
140
+ def global
141
+ @global ||= SpecForge.context.global.to_h
142
+ end
143
+
144
+ #
145
+ # Returns a hash representation of the variables in the current context
146
+ #
147
+ # Includes both spec-level and expectation-level variables combined
148
+ # with values fully resolved.
149
+ #
150
+ # @return [Hash]
151
+ #
152
+ def variables
153
+ @variables ||= SpecForge.context.variables
154
+ end
155
+
156
+ #
157
+ # Returns a hash representation of the store context
158
+ #
159
+ # @return [Hash] The store context
160
+ #
161
+ def store
162
+ @store ||= SpecForge.context.store.to_h
163
+ end
164
+
165
+ ##########################################################################
166
+
167
+ #
168
+ # Returns a hash representation of the test state
169
+ #
170
+ # Includes the spec, expectation, request, response, variables and global context.
171
+ # RSpec matchers are converted to human-readable descriptions.
172
+ #
173
+ # @return [Hash]
174
+ #
175
+ def to_h
176
+ spec_hash = spec.to_h.except(:expectations)
177
+
178
+ expectation_hash = expectation.to_h
179
+ expectation_hash[:expect][:json] = matchers_to_description(expectation_hash[:expect][:json])
180
+
181
+ {
182
+ global:,
183
+ variables:,
184
+ request: request.to_h,
185
+ response: {
186
+ status: response.status,
187
+ body: response.body,
188
+ headers: response.headers
189
+ },
190
+ expectation: expectation_hash,
191
+ spec: spec_hash
192
+ }
193
+ end
194
+
195
+ #
196
+ # Returns a formatted JSON representation of the test state
197
+ #
198
+ # @return [String] Pretty-printed JSON of the test state
199
+ #
200
+ def inspect
201
+ JSON.pretty_generate(to_h)
202
+ end
203
+
204
+ private
205
+
206
+ def matchers_to_description(value)
207
+ return value unless value.is_a?(RSpec::Matchers::BuiltIn::BaseMatcher)
208
+
209
+ value.description
210
+ end
211
+ end
212
+ end
213
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpecForge
4
+ class Runner
5
+ #
6
+ # Listens for RSpec test result notifications and triggers the appropriate callbacks
7
+ #
8
+ # This singleton class receives notifications from RSpec when examples pass or fail,
9
+ # retrieves the current test context, and triggers the appropriate SpecForge callbacks.
10
+ # It acts as a bridge between RSpec's notification system and SpecForge's callback system.
11
+ #
12
+ class Listener
13
+ include Singleton
14
+
15
+ #
16
+ # Handles RSpec notifications for passing examples
17
+ #
18
+ # @param notification [RSpec::Core::Notifications::ExampleNotification]
19
+ # The notification object
20
+ #
21
+ def example_passed(notification)
22
+ trigger_callback
23
+ end
24
+
25
+ #
26
+ # Handles RSpec notifications for failing examples
27
+ #
28
+ # @param notification [RSpec::Core::Notifications::FailedExampleNotification]
29
+ # The notification object
30
+ #
31
+ def example_failed(notification)
32
+ trigger_callback
33
+ end
34
+
35
+ private
36
+
37
+ #
38
+ # Triggers the appropriate SpecForge callback with the complete context
39
+ #
40
+ # Retrieves the current example context stored during the RSpec execution, and passes
41
+ # everything to the appropriate callback.
42
+ #
43
+ # @private
44
+ #
45
+ def trigger_callback
46
+ context = Runner::State.current.to_h.slice(
47
+ :forge, :spec, :expectation, :example_group, :example
48
+ )
49
+
50
+ Runner::Callbacks.after_expectation(*context.values)
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpecForge
4
+ class Runner
5
+ #
6
+ # Manages metadata for RSpec example groups and examples
7
+ #
8
+ # This class provides methods for setting up correct metadata on RSpec groups
9
+ # and examples, which enables proper error reporting and command-line rerun
10
+ # instructions when tests fail.
11
+ #
12
+ # @example Setting metadata on an example group
13
+ # Metadata.set_for_group(spec, expectation, example_group)
14
+ #
15
+ class Metadata
16
+ class << self
17
+ #
18
+ # Updates the example group metadata for error reporting
19
+ #
20
+ # Sets the file path, line number, and location information in the
21
+ # example group's metadata. This ensures that RSpec can generate the
22
+ # proper command to rerun the failing tests.
23
+ #
24
+ # @param spec [SpecForge::Spec] The spec being tested
25
+ # @param expectation [SpecForge::Spec::Expectation] The expectation being evaluated
26
+ # @param example_group [RSpec::Core::ExampleGroup] The example group to update
27
+ #
28
+ def set_for_group(spec, expectation, example_group)
29
+ metadata = {
30
+ file_path: spec.file_path,
31
+ absolute_file_path: spec.file_path,
32
+ line_number: expectation.line_number,
33
+ location: spec.file_path,
34
+ rerun_file_path: "#{spec.file_name}:#{spec.name}:\"#{expectation.name}\""
35
+ }
36
+
37
+ example_group.metadata.merge!(metadata)
38
+ end
39
+
40
+ #
41
+ # Updates the current example's metadata for error reporting
42
+ #
43
+ # Sets location information on the currently running example.
44
+ # This helps RSpec generate more accurate error messages when
45
+ # an exception occurs during test execution.
46
+ #
47
+ # @param spec [SpecForge::Spec] The spec being tested
48
+ # @param expectation [SpecForge::Spec::Expectation] The expectation being evaluated
49
+ #
50
+ def set_for_example(spec, expectation)
51
+ metadata = {location: "#{spec.file_path}:#{expectation.line_number}"}
52
+
53
+ RSpec.current_example.metadata.merge!(metadata)
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpecForge
4
+ class Runner
5
+ #
6
+ # Maintains test execution state to prevent duplicate HTTP requests
7
+ #
8
+ # This singleton class captures and preserves references to the current test context,
9
+ # including request and response objects that would otherwise be re-evaluated
10
+ # when accessed after RSpec clears its memoized variables. It solves a specific
11
+ # issue where accessing response data in after_expectation callbacks would
12
+ # trigger duplicate HTTP requests.
13
+ #
14
+ class State < Struct.new(
15
+ :forge, :spec, :expectation, :example_group, :example, :response, :request
16
+ )
17
+ include Singleton
18
+
19
+ #
20
+ # Returns the singleton instance representing the current test state
21
+ #
22
+ # @return [State] The current state instance
23
+ #
24
+ def self.current
25
+ instance
26
+ end
27
+
28
+ #
29
+ # Updates multiple attributes of the state at once
30
+ #
31
+ # @param attributes [Hash] A hash mapping attribute names to values
32
+ #
33
+ def self.set(attributes)
34
+ attributes.each do |key, value|
35
+ instance[key] = value
36
+ end
37
+ end
38
+
39
+ #
40
+ # Persists the current state to the context store if needed
41
+ #
42
+ # Only runs if the current expectation has a store_as directive
43
+ #
44
+ def self.persist
45
+ return unless instance.expectation.store_as?
46
+
47
+ instance.persist_to_store
48
+ end
49
+
50
+ #
51
+ # Clears all state attributes
52
+ #
53
+ def self.clear
54
+ instance.clear
55
+ end
56
+
57
+ ##########################################################################
58
+
59
+ #
60
+ # Clears all attributes in the state
61
+ #
62
+ def clear
63
+ members.each { |key| self[key] = nil }
64
+ end
65
+
66
+ #
67
+ # Persists the current test execution data to the context store
68
+ #
69
+ # Handles scope determination and stores request/response data
70
+ # for later access via the store attribute
71
+ #
72
+ def persist_to_store
73
+ id = expectation.store_as
74
+ scope = :file
75
+
76
+ # Remove the file prefix if it was explicitly provided
77
+ id = id.delete_prefix("file.") if id.start_with?("file.")
78
+
79
+ # Change scope to spec if desired
80
+ if id.start_with?("spec.")
81
+ id = id.delete_prefix("spec.")
82
+ scope = :spec
83
+ end
84
+
85
+ SpecForge.context.store.set(
86
+ id,
87
+ scope:,
88
+ request: request&.to_h,
89
+ variables: SpecForge.context.variables.deep_dup,
90
+ response:,
91
+ headers: response&.headers,
92
+ status: response&.status,
93
+ body: response&.body
94
+ )
95
+ end
96
+ end
97
+ end
98
+ end
@@ -1,150 +1,75 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SpecForge
4
+ #
5
+ # Handles the execution of specs through RSpec
6
+ # Converts SpecForge specs into RSpec examples and runs them
7
+ #
4
8
  class Runner
5
9
  class << self
6
10
  #
7
- # Runs any specs
11
+ # Prepares forge objects for test execution
8
12
  #
9
- def run
10
- # Allows me to modify the error backtrace reporting within rspec
11
- RSpec.configuration.instance_variable_set(:@backtrace_formatter, BacktraceFormatter)
12
-
13
- RSpec::Core::Runner.disable_autorun!
14
- RSpec::Core::Runner.run([], $stderr, $stdout)
15
- end
16
-
13
+ # Loads the forge helper, registers factories, loads specs from files,
14
+ # applies filtering, and returns ready-to-run forge objects.
17
15
  #
18
- # Defines a spec with RSpec
16
+ # @param file_name [String, nil] Optional file name filter
17
+ # @param spec_name [String, nil] Optional spec name filter
18
+ # @param expectation_name [String, nil] Optional expectation name filter
19
19
  #
20
- # @param spec_forge [Spec] The spec to define
20
+ # @return [Array<Forge>] Array of prepared forge objects
21
21
  #
22
- def define_spec(spec_forge)
23
- runner_forge = self
24
-
25
- RSpec.describe(spec_forge.name) do
26
- spec_forge.expectations.each do |expectation|
27
- # Define the example group
28
- describe(expectation.name) do
29
- # Set up the class metadata for error reporting
30
- runner_forge.set_group_metadata(self, spec_forge, expectation)
31
-
32
- constraints = expectation.constraints
33
-
34
- let!(:expected_status) { constraints.status.resolve }
35
- let!(:expected_json) { constraints.json.resolve }
36
- let!(:expected_json_class) { expected_json&.expected.class }
37
-
38
- before do
39
- # Ensure all variables are called and resolved, in case they are not referenced
40
- expectation.variables.resolve
41
-
42
- # Set up the example metadata for error reporting
43
- runner_forge.set_example_metadata(spec_forge, expectation)
44
- end
45
-
46
- subject(:response) { expectation.http_client.call }
22
+ def prepare(file_name: nil, spec_name: nil, expectation_name: nil)
23
+ load_forge_helper
47
24
 
48
- it do
49
- if spec_forge.debug? || expectation.debug?
50
- runner_forge.handle_debug(expectation, self)
51
- end
25
+ # Load factories
26
+ Factory.load_and_register
52
27
 
53
- # Status check
54
- expect(response.status).to eq(expected_status)
28
+ # Load the specs from their files and create forges from them
29
+ forges = Loader.load_from_files.map { |f| Forge.new(*f) }
55
30
 
56
- # JSON check
57
- if expected_json
58
- expect(response.body).to be_kind_of(expected_json_class)
59
- expect(response.body).to expected_json
60
- end
61
- end
62
- end
63
- end
64
- end
65
- end
66
-
67
- # @private
68
- def handle_debug(...)
69
- DebugProxy.new(...).call
70
- end
31
+ # Filter out the specs and expectations
32
+ forges = Filter.apply(forges, file_name:, spec_name:, expectation_name:)
71
33
 
72
- # @private
73
- def set_group_metadata(context, spec, expectation)
74
- metadata = {
75
- file_path: spec.file_path,
76
- absolute_file_path: spec.file_path,
77
- line_number: spec.line_number,
78
- location: spec.file_path,
79
- rerun_file_path: "#{spec.file_name}:#{spec.name}:\"#{expectation.name}\""
80
- }
34
+ # Tell the user that we filtered if we did
35
+ Filter.announce(forges, file_name:, spec_name:, expectation_name:)
81
36
 
82
- context.metadata.merge!(metadata)
37
+ forges
83
38
  end
84
39
 
85
- # @private
86
- def set_example_metadata(spec, expectation)
87
- # This is needed when an error raises in an example
88
- metadata = {location: "#{spec.file_path}:#{spec.line_number}"}
89
-
90
- RSpec.current_example.metadata.merge!(metadata)
91
- end
92
- end
93
-
94
- ################################################################################################
95
-
96
- class DebugProxy
97
- def self.default
98
- -> { puts inspect }
99
- end
100
-
101
- attr_reader :expectation, :variables, :expected_status, :expected_json, :request, :response
102
-
103
- def initialize(expectation, spec_context)
104
- @callback = SpecForge.configuration.on_debug
105
-
106
- @expected_status = spec_context.expected_status
107
- @expected_json = spec_context.expected_json
108
-
109
- @request = expectation.http_client.request
110
- @response = spec_context.response
111
-
112
- @variables = expectation.variables
113
- @expectation = expectation
114
- end
115
-
116
- def call
117
- puts <<~STRING
118
-
119
- Debug triggered for: #{expectation.name}
120
-
121
- Available methods:
122
- - expectation: Full expectation context
123
- - variables: Current variable definitions
124
- - expected_status: Expected HTTP status code (#{expected_status})
125
- - expected_json: Expected response body
126
- - expected_json_class: Expected response body class
127
- - request: HTTP request details (method, url, headers, body)
128
- - response: HTTP response
129
-
130
- Tip: Type 'self' for a JSON overview of the current state
131
- Individual methods return full object details for advanced debugging
132
- STRING
133
-
134
- instance_exec(&@callback)
40
+ #
41
+ # Runs the prepared forges through RSpec
42
+ #
43
+ # Sets up the RSpec adapter and executes all tests, with optional
44
+ # exit behavior for CLI usage.
45
+ #
46
+ # @param forges [Array<Forge>] The forge objects to run
47
+ # @param exit_on_finish [Boolean] Whether to exit the process when complete
48
+ # @param exit_on_failure [Boolean] Whether to exit the process if any test fails
49
+ #
50
+ # @return [Integer, nil] Exit status if exit_on_finish is false
51
+ #
52
+ def run(forges, **)
53
+ Adapter.setup(forges)
54
+ Adapter.run(**)
135
55
  end
136
56
 
137
- def inspect
138
- hash = expectation.to_h
57
+ private
139
58
 
140
- hash[:response] = {
141
- headers: response.headers,
142
- status: response.status,
143
- body: response.body
144
- }
59
+ def load_forge_helper
60
+ forge_helper = SpecForge.forge_path.join("forge_helper.rb")
61
+ require_relative forge_helper if File.exist?(forge_helper)
145
62
 
146
- JSON.pretty_generate(hash)
63
+ # Validate in case anything was changed
64
+ SpecForge.configuration.validate
147
65
  end
148
66
  end
149
67
  end
150
68
  end
69
+
70
+ require_relative "runner/adapter"
71
+ require_relative "runner/callbacks"
72
+ require_relative "runner/debug_proxy"
73
+ require_relative "runner/listener"
74
+ require_relative "runner/metadata"
75
+ require_relative "runner/state"