spec_forge 0.4.0 → 0.6.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 +4 -4
- data/.standard.yml +4 -0
- data/CHANGELOG.md +145 -1
- data/README.md +49 -638
- data/flake.lock +3 -3
- data/flake.nix +8 -2
- data/lib/spec_forge/attribute/chainable.rb +208 -20
- data/lib/spec_forge/attribute/factory.rb +141 -12
- data/lib/spec_forge/attribute/faker.rb +64 -15
- data/lib/spec_forge/attribute/global.rb +96 -0
- data/lib/spec_forge/attribute/literal.rb +15 -2
- data/lib/spec_forge/attribute/matcher.rb +188 -13
- data/lib/spec_forge/attribute/parameterized.rb +45 -20
- data/lib/spec_forge/attribute/regex.rb +55 -5
- data/lib/spec_forge/attribute/resolvable.rb +48 -5
- data/lib/spec_forge/attribute/resolvable_array.rb +62 -4
- data/lib/spec_forge/attribute/resolvable_hash.rb +62 -4
- data/lib/spec_forge/attribute/store.rb +65 -0
- data/lib/spec_forge/attribute/transform.rb +33 -5
- data/lib/spec_forge/attribute/variable.rb +37 -6
- data/lib/spec_forge/attribute.rb +168 -66
- data/lib/spec_forge/backtrace_formatter.rb +26 -3
- data/lib/spec_forge/callbacks.rb +79 -0
- data/lib/spec_forge/cli/actions.rb +27 -0
- data/lib/spec_forge/cli/command.rb +78 -24
- data/lib/spec_forge/cli/init.rb +11 -1
- data/lib/spec_forge/cli/new.rb +54 -3
- data/lib/spec_forge/cli/run.rb +20 -0
- data/lib/spec_forge/cli.rb +16 -5
- data/lib/spec_forge/configuration.rb +94 -25
- data/lib/spec_forge/context/callbacks.rb +91 -0
- data/lib/spec_forge/context/global.rb +72 -0
- data/lib/spec_forge/context/store.rb +148 -0
- data/lib/spec_forge/context/variables.rb +91 -0
- data/lib/spec_forge/context.rb +36 -0
- data/lib/spec_forge/core_ext/rspec.rb +24 -4
- data/lib/spec_forge/error.rb +267 -113
- data/lib/spec_forge/factory.rb +33 -14
- data/lib/spec_forge/filter.rb +87 -0
- data/lib/spec_forge/forge.rb +170 -0
- data/lib/spec_forge/http/backend.rb +99 -29
- data/lib/spec_forge/http/client.rb +23 -13
- data/lib/spec_forge/http/request.rb +74 -62
- data/lib/spec_forge/http/verb.rb +79 -0
- data/lib/spec_forge/http.rb +105 -0
- data/lib/spec_forge/loader.rb +254 -0
- data/lib/spec_forge/matchers.rb +130 -0
- data/lib/spec_forge/normalizer/configuration.rb +24 -11
- data/lib/spec_forge/normalizer/constraint.rb +22 -9
- data/lib/spec_forge/normalizer/expectation.rb +31 -12
- data/lib/spec_forge/normalizer/factory.rb +24 -11
- data/lib/spec_forge/normalizer/factory_reference.rb +32 -13
- data/lib/spec_forge/normalizer/global_context.rb +88 -0
- data/lib/spec_forge/normalizer/spec.rb +39 -16
- data/lib/spec_forge/normalizer.rb +255 -41
- data/lib/spec_forge/runner/callbacks.rb +246 -0
- data/lib/spec_forge/runner/debug_proxy.rb +213 -0
- data/lib/spec_forge/runner/listener.rb +54 -0
- data/lib/spec_forge/runner/metadata.rb +58 -0
- data/lib/spec_forge/runner/state.rb +99 -0
- data/lib/spec_forge/runner.rb +133 -119
- data/lib/spec_forge/spec/expectation/constraint.rb +95 -20
- data/lib/spec_forge/spec/expectation.rb +43 -51
- data/lib/spec_forge/spec.rb +83 -96
- data/lib/spec_forge/type.rb +36 -4
- data/lib/spec_forge/version.rb +4 -1
- data/lib/spec_forge.rb +161 -76
- metadata +20 -5
- data/spec_forge/factories/user.yml +0 -4
- data/spec_forge/forge_helper.rb +0 -37
- data/spec_forge/specs/users.yml +0 -65
@@ -1,46 +1,62 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module SpecForge
|
4
|
-
|
5
|
-
|
6
|
-
|
4
|
+
#
|
5
|
+
# Configuration container for SpecForge settings
|
6
|
+
# Defines default values and validation for all configuration options
|
7
|
+
#
|
8
|
+
class Configuration < Struct.new(:base_url, :headers, :query, :factories, :on_debug)
|
9
|
+
#
|
10
|
+
# Manages factory configuration settings
|
11
|
+
# Controls auto-discovery behavior and custom factory paths
|
12
|
+
#
|
13
|
+
# @example
|
14
|
+
# config.factories.auto_discover = false
|
15
|
+
# config.factories.paths += ["lib/factories"]
|
16
|
+
#
|
7
17
|
class Factories < Struct.new(:auto_discover, :paths)
|
18
|
+
#
|
19
|
+
# Creates reader methods that return boolean values
|
20
|
+
# Allows for checking configuration with predicate methods
|
21
|
+
#
|
8
22
|
attr_predicate :auto_discover, :paths
|
9
23
|
|
24
|
+
#
|
25
|
+
# Initializes a new Factories configuration
|
26
|
+
# Sets default values for auto-discovery and paths
|
27
|
+
#
|
28
|
+
# @param auto_discover [Boolean] Whether to auto-discover factories (default: true)
|
29
|
+
# @param paths [Array<String>] Additional paths to look for factories (default: [])
|
30
|
+
#
|
31
|
+
# @return [Factories] A new factories configuration instance
|
32
|
+
#
|
10
33
|
def initialize(auto_discover: true, paths: []) = super
|
11
34
|
end
|
12
35
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
overlay_value
|
20
|
-
# If source is nil and overlay exists (but wasn't "present"), use overlay
|
21
|
-
elsif source_value.nil? && !overlay_value.nil?
|
22
|
-
overlay_value
|
23
|
-
# Otherwise keep source value
|
24
|
-
else
|
25
|
-
source_value
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
36
|
+
#
|
37
|
+
# Initializes a new Configuration with default values
|
38
|
+
# Sets up the configuration structure including factory settings and debug proxy
|
39
|
+
#
|
40
|
+
# @return [Configuration] A new configuration instance with defaults
|
41
|
+
#
|
30
42
|
def initialize
|
31
43
|
config = Normalizer.default_configuration
|
32
44
|
|
33
|
-
# Allows me to modify the error backtrace reporting within rspec
|
34
|
-
RSpec.configuration.instance_variable_set(:@backtrace_formatter, BacktraceFormatter)
|
35
|
-
|
36
45
|
config[:base_url] = "http://localhost:3000"
|
37
46
|
config[:factories] = Factories.new
|
38
|
-
config[:specs] = RSpec.configuration
|
39
47
|
config[:on_debug] = Runner::DebugProxy.default
|
40
48
|
|
41
49
|
super(**config)
|
42
50
|
end
|
43
51
|
|
52
|
+
#
|
53
|
+
# Validates the configuration and applies normalization
|
54
|
+
# Ensures all required fields have values and applies defaults when needed
|
55
|
+
#
|
56
|
+
# @return [self] Returns self for method chaining
|
57
|
+
#
|
58
|
+
# @api private
|
59
|
+
#
|
44
60
|
def validate
|
45
61
|
output = Normalizer.normalize_configuration!(to_h)
|
46
62
|
|
@@ -52,10 +68,63 @@ module SpecForge
|
|
52
68
|
self
|
53
69
|
end
|
54
70
|
|
71
|
+
#
|
72
|
+
# Recursively converts the configuration to a hash representation
|
73
|
+
#
|
74
|
+
# @return [Hash] Hash representation of the configuration
|
75
|
+
#
|
55
76
|
def to_h
|
56
|
-
hash = super
|
77
|
+
hash = super
|
57
78
|
hash[:factories] = hash[:factories].to_h
|
58
79
|
hash
|
59
80
|
end
|
81
|
+
|
82
|
+
#
|
83
|
+
# Returns the RSpec configuration object
|
84
|
+
# Provides access to RSpec's internal configuration for test customization
|
85
|
+
#
|
86
|
+
# @return [RSpec::Core::Configuration] RSpec's configuration object
|
87
|
+
#
|
88
|
+
# @example Setting formatter options
|
89
|
+
# SpecForge.configure do |config|
|
90
|
+
# config.specs.formatter = :documentation
|
91
|
+
# end
|
92
|
+
#
|
93
|
+
def specs
|
94
|
+
RSpec.configuration
|
95
|
+
end
|
96
|
+
|
97
|
+
alias_method :rspec, :specs
|
98
|
+
|
99
|
+
#
|
100
|
+
# Registers a callback for a specific test lifecycle event
|
101
|
+
# Allows custom code execution at specific points during test execution
|
102
|
+
#
|
103
|
+
# @param name [Symbol, String] The callback point to register for
|
104
|
+
# (:before_file, :after_expectation, etc.)
|
105
|
+
# @yield A block to execute when the callback is triggered
|
106
|
+
# @yieldparam context [Object] An object containing context-specific state data, depending
|
107
|
+
# on which hook the callback is triggered from.
|
108
|
+
#
|
109
|
+
# @return [Proc] The registered callback
|
110
|
+
#
|
111
|
+
# @example Registering a custom debug handler
|
112
|
+
# SpecForge.configure do |config|
|
113
|
+
# config.register_callback(:on_debug) { binding.pry }
|
114
|
+
# end
|
115
|
+
#
|
116
|
+
# @example Cleaning database after each test
|
117
|
+
# SpecForge.configure do |config|
|
118
|
+
# config.register_callback(:after_expectation) do
|
119
|
+
# DatabaseCleaner.clean
|
120
|
+
# end
|
121
|
+
# end
|
122
|
+
#
|
123
|
+
def register_callback(name, &)
|
124
|
+
Callbacks.register(name, &)
|
125
|
+
end
|
126
|
+
|
127
|
+
alias_method :define_callback, :register_callback
|
128
|
+
alias_method :callback, :register_callback
|
60
129
|
end
|
61
130
|
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SpecForge
|
4
|
+
class Context
|
5
|
+
#
|
6
|
+
# Manages user-defined callbacks grouped by lifecycle hook
|
7
|
+
#
|
8
|
+
# This class collects and organizes callbacks by their hook type
|
9
|
+
# (before_file, after_each, etc.) to support the test lifecycle.
|
10
|
+
# It ensures callbacks are properly categorized for execution.
|
11
|
+
#
|
12
|
+
# @example Creating callback groups
|
13
|
+
# callbacks = Context::Callbacks.new([
|
14
|
+
# {before_file: "setup_environment"},
|
15
|
+
# {after_each: "log_test_result"}
|
16
|
+
# ])
|
17
|
+
#
|
18
|
+
class Callbacks
|
19
|
+
#
|
20
|
+
# Creates a new callbacks collection
|
21
|
+
#
|
22
|
+
# @param callback_array [Array] Optional initial callbacks to register
|
23
|
+
#
|
24
|
+
# @return [Callbacks] A new callbacks collection
|
25
|
+
#
|
26
|
+
def initialize(callback_array = [])
|
27
|
+
set(callback_array)
|
28
|
+
end
|
29
|
+
|
30
|
+
#
|
31
|
+
# Updates the callbacks collection
|
32
|
+
#
|
33
|
+
# @param callback_array [Array] New callbacks to register
|
34
|
+
#
|
35
|
+
# @return [self]
|
36
|
+
#
|
37
|
+
def set(callback_array)
|
38
|
+
@inner = organize_callbacks_by_hook(callback_array)
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
#
|
43
|
+
# Returns the hash representation of callbacks
|
44
|
+
#
|
45
|
+
# @return [Hash] Callbacks organized by hook type
|
46
|
+
#
|
47
|
+
def to_h
|
48
|
+
@inner
|
49
|
+
end
|
50
|
+
|
51
|
+
#
|
52
|
+
# Executes all registered callbacks for a specific lifecycle hook
|
53
|
+
#
|
54
|
+
# @param hook_name [String, Symbol] The lifecycle hook (before_file, after_each, etc.)
|
55
|
+
# @param context [Hash] State data that will be converted to a structured object
|
56
|
+
# and passed to callbacks
|
57
|
+
#
|
58
|
+
def run(hook_name, context = {})
|
59
|
+
context = context.to_struct
|
60
|
+
|
61
|
+
@inner[hook_name].each do |callback_name|
|
62
|
+
SpecForge::Callbacks.run(callback_name, context)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
#
|
69
|
+
# Organizes callbacks from an array to hash structure by hook type
|
70
|
+
# Groups callbacks like before_file, after_each, etc. for easier lookup
|
71
|
+
#
|
72
|
+
# @param callback_array [Array] The array of callbacks
|
73
|
+
#
|
74
|
+
# @return [Hash] Callbacks indexed by hook type
|
75
|
+
#
|
76
|
+
# @private
|
77
|
+
#
|
78
|
+
def organize_callbacks_by_hook(callback_array)
|
79
|
+
groups = Hash.new { |h, k| h[k] = Set.new }
|
80
|
+
|
81
|
+
callback_array.each_with_object(groups) do |callbacks, groups|
|
82
|
+
callbacks.each do |hook, name|
|
83
|
+
next if name.blank?
|
84
|
+
|
85
|
+
groups[hook].add(name)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SpecForge
|
4
|
+
class Context
|
5
|
+
#
|
6
|
+
# Manages global state and variables at the spec file level.
|
7
|
+
#
|
8
|
+
# The Global class provides access to variables that are defined at the global level
|
9
|
+
# in a spec file and are accessible across all specs and expectations in a file.
|
10
|
+
# Unlike regular variables, global variables do not support overlaying - they maintain
|
11
|
+
# consistent values throughout test execution.
|
12
|
+
#
|
13
|
+
# @example Basic usage
|
14
|
+
# global = Global.new(variables: {api_version: "v2", environment: "test"})
|
15
|
+
#
|
16
|
+
# global.variables[:api_version] #=> "v2"
|
17
|
+
# global.variables[:environment] #=> "test"
|
18
|
+
#
|
19
|
+
# # Update global variables
|
20
|
+
# global.set(variables: {environment: "staging"})
|
21
|
+
# global.variables[:environment] #=> "staging"
|
22
|
+
# global.variables[:api_version] #=> nil
|
23
|
+
#
|
24
|
+
class Global
|
25
|
+
# @return [Context::Variables] The container for global variables
|
26
|
+
attr_reader :variables
|
27
|
+
|
28
|
+
# @return [Context::Callbacks] The container for callbacks
|
29
|
+
attr_reader :callbacks
|
30
|
+
|
31
|
+
#
|
32
|
+
# Creates a new Global context instance
|
33
|
+
#
|
34
|
+
# @param variables [Hash<Symbol, Object>] A hash of variable names and values
|
35
|
+
# @param callbacks [Array<Hash<Symbol, String>>] An array of callback hooks
|
36
|
+
#
|
37
|
+
# @return [Global] The new Global instance
|
38
|
+
#
|
39
|
+
def initialize(variables: {}, callbacks: [])
|
40
|
+
@variables = Variables.new(base: variables)
|
41
|
+
@callbacks = Callbacks.new(callbacks)
|
42
|
+
end
|
43
|
+
|
44
|
+
#
|
45
|
+
# Sets the global variables
|
46
|
+
#
|
47
|
+
# @param variables [Hash<Symbol, Object>] A hash of variable names and values
|
48
|
+
# @param callbacks [Array<Hash<Symbol, String>>] An array of callback hooks
|
49
|
+
#
|
50
|
+
# @return [self]
|
51
|
+
#
|
52
|
+
def set(variables: {}, callbacks: [])
|
53
|
+
@variables.set(base: variables)
|
54
|
+
@callbacks.set(callbacks)
|
55
|
+
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
#
|
60
|
+
# Returns a hash representation of the global context
|
61
|
+
#
|
62
|
+
# @return [Hash]
|
63
|
+
#
|
64
|
+
def to_h
|
65
|
+
{
|
66
|
+
variables: variables.to_h,
|
67
|
+
callbacks: callbacks.to_h
|
68
|
+
}
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SpecForge
|
4
|
+
class Context
|
5
|
+
#
|
6
|
+
# Manages storage of API responses for use in subsequent tests
|
7
|
+
#
|
8
|
+
# This class provides a mechanism to store HTTP requests and responses
|
9
|
+
# during test execution, allowing values to be referenced in later tests
|
10
|
+
# through the `store.id.body.attribute` syntax.
|
11
|
+
#
|
12
|
+
# @example Storing and retrieving a response in specs
|
13
|
+
# # In one expectation:
|
14
|
+
# store_as: user_creation
|
15
|
+
#
|
16
|
+
# # In a later test:
|
17
|
+
# query:
|
18
|
+
# id: store.user_creation.body.id
|
19
|
+
#
|
20
|
+
class Store
|
21
|
+
#
|
22
|
+
# Represents a single stored entry with request, variables, and response data
|
23
|
+
#
|
24
|
+
# Entries are immutable once created and contain a deep-frozen
|
25
|
+
# snapshot of the test state at the time of storage.
|
26
|
+
#
|
27
|
+
# @example Accessing stored entry data
|
28
|
+
# entry = store["user_creation"]
|
29
|
+
# entry.status # => 201
|
30
|
+
# entry.body.id # => 42
|
31
|
+
#
|
32
|
+
class Entry < Data.define(:scope, :request, :variables, :response)
|
33
|
+
#
|
34
|
+
# Creates a new immutable store entry
|
35
|
+
#
|
36
|
+
# @param request [Hash] The HTTP request that was executed
|
37
|
+
# @param variables [Hash] Variables from the test context
|
38
|
+
# @param response [Hash] The HTTP response received
|
39
|
+
# @param scope [Symbol] Scope of this entry, either :file or :spec
|
40
|
+
#
|
41
|
+
# @return [Entry] A new immutable entry instance
|
42
|
+
#
|
43
|
+
def initialize(request:, variables:, response:, scope: :file)
|
44
|
+
request = request.deep_freeze
|
45
|
+
variables = variables.deep_freeze
|
46
|
+
response = response.deep_freeze
|
47
|
+
|
48
|
+
super
|
49
|
+
end
|
50
|
+
|
51
|
+
#
|
52
|
+
# Shorthand accessor for the HTTP status code
|
53
|
+
#
|
54
|
+
# @return [Integer] The response status code
|
55
|
+
#
|
56
|
+
def status = response[:status]
|
57
|
+
|
58
|
+
#
|
59
|
+
# Shorthand accessor for the response body
|
60
|
+
#
|
61
|
+
# @return [Hash, Array, String] The parsed response body
|
62
|
+
#
|
63
|
+
def body = response[:body]
|
64
|
+
|
65
|
+
#
|
66
|
+
# Shorthand accessor for the response headers
|
67
|
+
#
|
68
|
+
# @return [Hash] The response headers
|
69
|
+
#
|
70
|
+
def headers = response[:headers]
|
71
|
+
|
72
|
+
#
|
73
|
+
# Returns all available methods that can be called
|
74
|
+
#
|
75
|
+
# @return [Array] The method names
|
76
|
+
#
|
77
|
+
def available_methods
|
78
|
+
members + [:status, :body, :headers]
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
#
|
83
|
+
# Creates a new empty store
|
84
|
+
#
|
85
|
+
# @return [Store] A new store instance
|
86
|
+
#
|
87
|
+
def initialize
|
88
|
+
@inner = {}
|
89
|
+
end
|
90
|
+
|
91
|
+
#
|
92
|
+
# Retrieves a stored entry by ID
|
93
|
+
#
|
94
|
+
# @param id [String, Symbol] The identifier for the stored entry
|
95
|
+
#
|
96
|
+
# @return [Entry, nil] The stored entry or nil if not found
|
97
|
+
#
|
98
|
+
def [](id)
|
99
|
+
@inner[id]
|
100
|
+
end
|
101
|
+
|
102
|
+
#
|
103
|
+
# Returns the number of entries in the store
|
104
|
+
#
|
105
|
+
# @return [Integer] The count of stored entries
|
106
|
+
#
|
107
|
+
def size
|
108
|
+
@inner.size
|
109
|
+
end
|
110
|
+
|
111
|
+
#
|
112
|
+
# Stores an entry with the specified ID
|
113
|
+
#
|
114
|
+
# @param id [String, Symbol] The identifier to store the entry under
|
115
|
+
#
|
116
|
+
# @return [self]
|
117
|
+
#
|
118
|
+
def set(id, **)
|
119
|
+
@inner[id] = Entry.new(**)
|
120
|
+
|
121
|
+
self
|
122
|
+
end
|
123
|
+
|
124
|
+
#
|
125
|
+
# Removes all entries from the store
|
126
|
+
#
|
127
|
+
def clear
|
128
|
+
@inner.clear
|
129
|
+
end
|
130
|
+
|
131
|
+
#
|
132
|
+
# Removes all spec entries from the store
|
133
|
+
#
|
134
|
+
def clear_specs
|
135
|
+
@inner.delete_if { |_k, v| v.scope == :spec }
|
136
|
+
end
|
137
|
+
|
138
|
+
#
|
139
|
+
# Returns a hash representation of store
|
140
|
+
#
|
141
|
+
# @return [Hash]
|
142
|
+
#
|
143
|
+
def to_h
|
144
|
+
@inner.transform_values(&:to_h).deep_stringify_keys
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SpecForge
|
4
|
+
class Context
|
5
|
+
#
|
6
|
+
# Manages variable resolution across different expectations in SpecForge tests.
|
7
|
+
#
|
8
|
+
# The Variables class handles two layers of variable definitions:
|
9
|
+
# - Base variables: The core set of variables defined at the spec level
|
10
|
+
# - Overlay variables: Additional variables defined at the expectation level
|
11
|
+
# that can override base variables with the same name.
|
12
|
+
#
|
13
|
+
# @example Basic usage
|
14
|
+
# variables = Variables.new(
|
15
|
+
# base: {user_id: 123, name: "Test User"},
|
16
|
+
# overlay: {
|
17
|
+
# "expectation_1": {name: "Override User"}
|
18
|
+
# }
|
19
|
+
# )
|
20
|
+
#
|
21
|
+
# variables[:user_id] #=> 123
|
22
|
+
# variables[:name] #=> "Test User"
|
23
|
+
#
|
24
|
+
# variables.use_overlay("expectation_1")
|
25
|
+
# variables[:name] #=> "Override User"
|
26
|
+
# variables[:user_id] #=> 123 (unchanged)
|
27
|
+
#
|
28
|
+
class Variables < Hash
|
29
|
+
attr_reader :base, :overlay
|
30
|
+
|
31
|
+
#
|
32
|
+
# Creates a new Variables container with base and overlay definitions
|
33
|
+
#
|
34
|
+
# @param base [Hash] The base set of variables (typically defined at spec level)
|
35
|
+
# @param overlay [Hash<String, Hash>] A hash of overlay variable sets keyed by ID
|
36
|
+
#
|
37
|
+
# @return [Variables]
|
38
|
+
#
|
39
|
+
def initialize(base: {}, overlay: {})
|
40
|
+
set(base:, overlay:)
|
41
|
+
end
|
42
|
+
|
43
|
+
#
|
44
|
+
# Sets the base and overlay variable hashes
|
45
|
+
#
|
46
|
+
# @param base [Hash] The new base variable hash
|
47
|
+
# @param overlay [Hash<String, Hash>] The new overlay variable hashes
|
48
|
+
#
|
49
|
+
# @return [self]
|
50
|
+
#
|
51
|
+
def set(base:, overlay: {})
|
52
|
+
@base = Attribute.from(base)
|
53
|
+
@overlay = overlay
|
54
|
+
|
55
|
+
resolve_into_self(@base)
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
#
|
60
|
+
# Applies a specific overlay to the base variables
|
61
|
+
# If the overlay doesn't exist or is empty, no changes are made.
|
62
|
+
#
|
63
|
+
# @param id [String] The ID of the overlay to apply
|
64
|
+
#
|
65
|
+
# @return [nil]
|
66
|
+
#
|
67
|
+
def use_overlay(id)
|
68
|
+
active = @base
|
69
|
+
|
70
|
+
if (overlay = @overlay[id]) && overlay.present?
|
71
|
+
active = active.deep_merge(overlay)
|
72
|
+
end
|
73
|
+
|
74
|
+
resolve_into_self(active)
|
75
|
+
self
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def resolve_into_self(hash)
|
81
|
+
# Start fresh
|
82
|
+
clear
|
83
|
+
|
84
|
+
# Load the resolved values into self
|
85
|
+
hash.each do |key, value|
|
86
|
+
self[key] = Attribute.from(value).resolved
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SpecForge
|
4
|
+
#
|
5
|
+
# Core data structure that maintains context during test execution
|
6
|
+
#
|
7
|
+
# Context stores and provides access to global variables, test variables, and
|
8
|
+
# shared state across specs.
|
9
|
+
# It acts as a central repository for test data during execution.
|
10
|
+
#
|
11
|
+
# @example Accessing the current context
|
12
|
+
# SpecForge.context.variables[:user_id] #=> 123
|
13
|
+
#
|
14
|
+
class Context < Data.define(:global, :store, :variables)
|
15
|
+
#
|
16
|
+
# Creates a new context with default values
|
17
|
+
#
|
18
|
+
# @param global [Hash] Global variables shared across all specs
|
19
|
+
# @param variables [Hash] Test variables specific to the current context
|
20
|
+
#
|
21
|
+
# @return [Context] A new context instance
|
22
|
+
#
|
23
|
+
def initialize(global: {}, variables: {})
|
24
|
+
super(
|
25
|
+
global: Global.new(**global),
|
26
|
+
store: Store.new,
|
27
|
+
variables: Variables.new(**variables)
|
28
|
+
)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
require_relative "context/callbacks"
|
34
|
+
require_relative "context/global"
|
35
|
+
require_relative "context/store"
|
36
|
+
require_relative "context/variables"
|
@@ -1,18 +1,38 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
return if defined?(SPEC_FORGE_INTERNAL_TESTING)
|
4
|
+
|
5
|
+
#
|
6
|
+
# RSpec's core testing framework module
|
7
|
+
# Provides the fundamental structure and functionality for RSpec tests
|
8
|
+
#
|
3
9
|
module RSpec
|
10
|
+
#
|
11
|
+
# Core implementation details and extensions for RSpec
|
12
|
+
# Contains the fundamental building blocks of the RSpec testing framework
|
13
|
+
#
|
4
14
|
module Core
|
15
|
+
#
|
16
|
+
# Handles notifications and reporting for RSpec test runs
|
17
|
+
# Manages how test results and metadata are processed and communicated
|
18
|
+
#
|
5
19
|
module Notifications
|
6
20
|
#
|
7
|
-
#
|
8
|
-
#
|
21
|
+
# A monkey patch of an internal RSpec class to allow SpecForge to replace parts of
|
22
|
+
# RSpec's reporting output in order to provide useful feedback to the user.
|
23
|
+
# This replaces "rspec" in commands with "spec_forge", removes any line numbers, and
|
24
|
+
# ensures that failures properly report the YAML file that it occurred in.
|
9
25
|
#
|
10
26
|
class SummaryNotification
|
27
|
+
#
|
28
|
+
# Create an alias to RSpec original colorized_rerun_commands so it can be called at a
|
29
|
+
# later point.
|
30
|
+
#
|
31
|
+
alias_method :og_colorized_rerun_commands, :colorized_rerun_commands
|
32
|
+
|
11
33
|
# Customizes RSpec's failure output to:
|
12
34
|
# 1. Use 'spec_forge' instead of 'rspec' for rerun commands
|
13
35
|
# 2. Remove line numbers since SpecForge uses dynamic spec generation
|
14
|
-
alias_method :og_colorized_rerun_commands, :colorized_rerun_commands
|
15
|
-
|
16
36
|
def colorized_rerun_commands(colorizer)
|
17
37
|
# Updating these at this point fixes the re-run for some failures - it depends
|
18
38
|
failed_examples.each do |example|
|