spec_forge 0.5.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 +3 -3
- data/CHANGELOG.md +106 -1
- data/README.md +34 -22
- 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 +91 -14
- data/lib/spec_forge/attribute/faker.rb +62 -13
- 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 +186 -11
- data/lib/spec_forge/attribute/parameterized.rb +45 -12
- 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 +166 -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 -22
- 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 +22 -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 +21 -8
- 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 +27 -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 +132 -123
- data/lib/spec_forge/spec/expectation/constraint.rb +91 -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 -48
- data/spec_forge/specs/users.yml +0 -65
data/lib/spec_forge/runner.rb
CHANGED
@@ -1,62 +1,134 @@
|
|
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
|
-
#
|
11
|
+
# Defines RSpec examples for a collection of forges
|
12
|
+
# Creates the test structure that will be executed
|
13
|
+
#
|
14
|
+
# @param forges [Array<Forge>] The forges to define as RSpec examples
|
15
|
+
#
|
16
|
+
def define(forges)
|
17
|
+
forges.each do |forge|
|
18
|
+
define_forge(forge)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
#
|
23
|
+
# Runs the defined RSpec examples
|
24
|
+
# Executes the tests after they've been defined
|
8
25
|
#
|
9
26
|
def run
|
10
|
-
|
11
|
-
RSpec.configuration.instance_variable_set(:@backtrace_formatter, BacktraceFormatter)
|
27
|
+
prepare_for_run
|
12
28
|
|
13
|
-
|
14
|
-
RSpec::Core::Runner.
|
29
|
+
ARGV.clear
|
30
|
+
RSpec::Core::Runner.invoke
|
15
31
|
end
|
16
32
|
|
17
33
|
#
|
18
|
-
# Defines a
|
34
|
+
# Defines RSpec examples for a specific forge
|
35
|
+
# Creates the test structure for a single forge file
|
19
36
|
#
|
20
|
-
# @param
|
37
|
+
# @param forge [Forge] The forge to define
|
21
38
|
#
|
22
|
-
def
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
let!(:
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
39
|
+
def define_forge(forge)
|
40
|
+
# This is just like writing a normal RSpec test, except with loops ;)
|
41
|
+
RSpec.describe(forge.name) do
|
42
|
+
# Callback for the file
|
43
|
+
before(:context) { Callbacks.before_file(forge) }
|
44
|
+
after(:context) { Callbacks.after_file(forge) }
|
45
|
+
|
46
|
+
# Specs
|
47
|
+
forge.specs.each do |spec|
|
48
|
+
# Describe the spec
|
49
|
+
describe(spec.name) do
|
50
|
+
# Request data is for the spec and contains the base and overlays
|
51
|
+
let!(:request_data) { forge.request[spec.id] }
|
52
|
+
|
53
|
+
# The HTTP client for the spec
|
54
|
+
let!(:http_client) { HTTP::Client.new(**request_data[:base]) }
|
55
|
+
|
56
|
+
# Callback for the spec
|
57
|
+
before(:context) { Callbacks.before_spec(forge, spec) }
|
58
|
+
after(:context) { Callbacks.after_spec(forge, spec) }
|
59
|
+
|
60
|
+
# Expectations
|
61
|
+
spec.expectations.each do |expectation|
|
62
|
+
# Onto the actual expectation itself
|
63
|
+
describe(expectation.name) do
|
64
|
+
# Set metadata for the example group for error reporting
|
65
|
+
Metadata.set_for_group(spec, expectation, self)
|
66
|
+
|
67
|
+
# Lazily load the constraints
|
68
|
+
let(:constraints) { expectation.constraints.as_matchers }
|
69
|
+
|
70
|
+
let(:match_status) { constraints[:status] }
|
71
|
+
let(:match_json) { constraints[:json] }
|
72
|
+
let(:match_json_class) { be_kind_of(match_json.class) }
|
73
|
+
|
74
|
+
# The request for the test itself. Overlays the expectation's data if it exists
|
75
|
+
let(:request) do
|
76
|
+
request = request_data[:base]
|
77
|
+
|
78
|
+
if (overlay = request_data[:overlay][expectation.id])
|
79
|
+
request = request.deep_merge(overlay)
|
80
|
+
end
|
81
|
+
|
82
|
+
HTTP::Request.new(**request)
|
83
|
+
end
|
84
|
+
|
85
|
+
# The Faraday response
|
86
|
+
subject(:response) { http_client.call(request) }
|
87
|
+
|
88
|
+
# Callbacks for the expectation
|
89
|
+
before :each do
|
90
|
+
Callbacks.before_expectation(
|
91
|
+
forge, spec, expectation, self, RSpec.current_example
|
92
|
+
)
|
93
|
+
end
|
94
|
+
|
95
|
+
# The 'after_expectation' callback is handled by Listener due to RSpec not
|
96
|
+
# reporting the example's status until after the describe block has finished.
|
97
|
+
after :each do
|
98
|
+
# However, the downside about having the callback triggered later is that RSpec
|
99
|
+
# will have reset the memoized let variables back to nil.
|
100
|
+
# This causes an issue when an expectation goes to store the state, it will end
|
101
|
+
# up re-calling the various variables and triggering another HTTP request.
|
102
|
+
# Since the variables are still memoized in this hook, it is the perfect
|
103
|
+
# time to store the referenced to them.
|
104
|
+
State.set(response:)
|
105
|
+
end
|
106
|
+
|
107
|
+
# The test itself
|
108
|
+
it(expectation.constraints.description) do
|
109
|
+
if spec.debug? || expectation.debug?
|
110
|
+
Callbacks.on_debug(forge, spec, expectation, self)
|
111
|
+
end
|
112
|
+
|
113
|
+
# Status check
|
114
|
+
expect(response.status).to match_status
|
115
|
+
|
116
|
+
# JSON check
|
117
|
+
if match_json.present?
|
118
|
+
expect(response.body).to match_json_class
|
119
|
+
|
120
|
+
case match_json
|
121
|
+
when Hash
|
122
|
+
# Check per key for easier debugging
|
123
|
+
match_json.each do |key, matcher|
|
124
|
+
expect(response.body).to have_key(key)
|
125
|
+
expect(response.body[key]).to matcher
|
126
|
+
end
|
127
|
+
else
|
128
|
+
expect(response.body).to match_json
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
60
132
|
end
|
61
133
|
end
|
62
134
|
end
|
@@ -64,87 +136,24 @@ module SpecForge
|
|
64
136
|
end
|
65
137
|
end
|
66
138
|
|
67
|
-
|
68
|
-
def handle_debug(...)
|
69
|
-
DebugProxy.new(...).call
|
70
|
-
end
|
71
|
-
|
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
|
-
}
|
81
|
-
|
82
|
-
context.metadata.merge!(metadata)
|
83
|
-
end
|
84
|
-
|
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}"}
|
139
|
+
private
|
89
140
|
|
90
|
-
|
91
|
-
|
92
|
-
|
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)
|
135
|
-
end
|
136
|
-
|
137
|
-
def inspect
|
138
|
-
hash = expectation.to_h
|
139
|
-
|
140
|
-
hash[:response] = {
|
141
|
-
headers: response.headers,
|
142
|
-
status: response.status,
|
143
|
-
body: response.body
|
144
|
-
}
|
141
|
+
def prepare_for_run
|
142
|
+
# Allows modifying the error backtrace reporting within rspec
|
143
|
+
RSpec.configuration.instance_variable_set(:@backtrace_formatter, BacktraceFormatter)
|
145
144
|
|
146
|
-
|
145
|
+
# Listen for passed/failed events to trigger the "after_each" callback
|
146
|
+
RSpec.configuration.reporter.register_listener(
|
147
|
+
Listener.instance,
|
148
|
+
:example_passed, :example_failed
|
149
|
+
)
|
147
150
|
end
|
148
151
|
end
|
149
152
|
end
|
150
153
|
end
|
154
|
+
|
155
|
+
require_relative "runner/callbacks"
|
156
|
+
require_relative "runner/debug_proxy"
|
157
|
+
require_relative "runner/listener"
|
158
|
+
require_relative "runner/metadata"
|
159
|
+
require_relative "runner/state"
|
@@ -4,42 +4,113 @@ module SpecForge
|
|
4
4
|
class Spec
|
5
5
|
class Expectation
|
6
6
|
#
|
7
|
-
# Represents the
|
7
|
+
# Represents the expected response constraints for an expectation
|
8
|
+
#
|
9
|
+
# A Constraint defines what the API response should look like,
|
10
|
+
# including status code and body content with support for matchers.
|
11
|
+
#
|
12
|
+
# @example In code
|
13
|
+
# constraint = Constraint.new(
|
14
|
+
# status: 200,
|
15
|
+
# json: {name: "matcher.eq" => "John"}
|
16
|
+
# )
|
8
17
|
#
|
9
18
|
class Constraint < Data.define(:status, :json) # :xml, :html
|
10
19
|
#
|
11
|
-
# Creates a new
|
20
|
+
# Creates a new constraint
|
12
21
|
#
|
13
|
-
# @param status [Integer] The expected HTTP status code
|
22
|
+
# @param status [Integer, String] The expected HTTP status code, or reference to one
|
14
23
|
# @param json [Hash, Array] The expected JSON with matchers
|
15
24
|
#
|
16
|
-
|
17
|
-
|
25
|
+
# @return [Constraint] A new constraint instance
|
26
|
+
#
|
27
|
+
def initialize(status:, json: {})
|
28
|
+
super(
|
29
|
+
status: Attribute.from(status),
|
30
|
+
json: Attribute.from(json)
|
31
|
+
)
|
18
32
|
end
|
19
33
|
|
34
|
+
#
|
35
|
+
# Converts the constraint to a hash with resolved values
|
36
|
+
#
|
37
|
+
# @return [Hash] Hash representation with resolved values
|
38
|
+
#
|
20
39
|
def to_h
|
21
40
|
super.transform_values(&:resolve)
|
22
41
|
end
|
23
42
|
|
24
|
-
|
43
|
+
#
|
44
|
+
# Converts constraints to RSpec matchers for validation
|
45
|
+
#
|
46
|
+
# Transforms the defined constraints (status and JSON expectations) into
|
47
|
+
# appropriate RSpec matchers that can be used in test expectations.
|
48
|
+
# This method resolves all values and applies the appropriate matcher
|
49
|
+
# conversions to create a complete expectation structure.
|
50
|
+
#
|
51
|
+
# @return [Hash] A hash containing resolved matchers
|
52
|
+
#
|
53
|
+
# @example
|
54
|
+
# constraint = Constraint.new(status: 200, json: {name: "John"})
|
55
|
+
# matchers = constraint.as_matchers
|
56
|
+
# # => {status: eq(200), json: include("name" => eq("John"))}
|
57
|
+
#
|
58
|
+
def as_matchers
|
59
|
+
{
|
60
|
+
status: status.resolve_as_matcher,
|
61
|
+
json: resolve_json_matcher
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
65
|
+
#
|
66
|
+
# Generates a human-readable description of what this constraint expects in the response
|
67
|
+
#
|
68
|
+
# Creates a description string for RSpec examples that clearly explains the expected
|
69
|
+
# status code and JSON structure. This makes test output more informative and helps
|
70
|
+
# developers understand what's being tested at a glance.
|
71
|
+
#
|
72
|
+
# @return [String] A human-readable description of the constraint expectations
|
73
|
+
#
|
74
|
+
# @example Status code with JSON object
|
75
|
+
# constraint.description
|
76
|
+
# # => "is expected to respond with \"200 OK\" and a JSON object that contains keys: \"id\", \"name\""
|
77
|
+
#
|
78
|
+
# @example Status code with JSON array
|
79
|
+
# constraint.description
|
80
|
+
# # => "is expected to respond with \"201 Created\" and a JSON array that contains 3 items"
|
81
|
+
#
|
82
|
+
def description
|
83
|
+
description = "is expected to respond with"
|
84
|
+
|
85
|
+
description += if status.is_a?(Attribute::Literal)
|
86
|
+
" #{HTTP.status_code_to_description(status.input).in_quotes}"
|
87
|
+
else
|
88
|
+
" the expected status code"
|
89
|
+
end
|
25
90
|
|
26
|
-
|
27
|
-
|
28
|
-
|
91
|
+
size = json.size
|
92
|
+
|
93
|
+
if Type.array?(json)
|
94
|
+
description +=
|
95
|
+
" and a JSON array that contains #{size} #{"item".pluralize(size)}"
|
96
|
+
elsif Type.hash?(json) && size > 0
|
97
|
+
keys = json.keys.join_map(", ", &:in_quotes)
|
98
|
+
|
99
|
+
description +=
|
100
|
+
" and a JSON object that contains #{"key".pluralize(size)}: #{keys}"
|
101
|
+
end
|
102
|
+
|
103
|
+
description
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
29
107
|
|
30
|
-
|
108
|
+
def resolve_json_matcher
|
109
|
+
case json
|
31
110
|
when HashLike
|
32
|
-
|
33
|
-
Attribute.from("matcher.include" => value)
|
34
|
-
when ArrayLike
|
35
|
-
value = value.map { |i| convert_to_matchers(i) }
|
36
|
-
Attribute.from("matcher.contain_exactly" => value)
|
37
|
-
when Attribute::Regex
|
38
|
-
Attribute.from("matcher.match" => value)
|
39
|
-
when Attribute::Literal
|
40
|
-
Attribute.from("matcher.eq" => value)
|
111
|
+
json.transform_values(&:resolve_as_matcher).stringify_keys
|
41
112
|
else
|
42
|
-
|
113
|
+
json.resolve_as_matcher
|
43
114
|
end
|
44
115
|
end
|
45
116
|
end
|
@@ -1,72 +1,64 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "expectation/constraint"
|
4
|
-
|
5
3
|
module SpecForge
|
6
4
|
class Spec
|
7
|
-
|
5
|
+
#
|
6
|
+
# Represents a single test expectation within a spec
|
7
|
+
#
|
8
|
+
# An Expectation defines what should be tested for a specific API request,
|
9
|
+
# including the expected status code and response structure.
|
10
|
+
#
|
11
|
+
# @example YAML representation
|
12
|
+
# - name: "Get user successfully"
|
13
|
+
# expect:
|
14
|
+
# status: 200
|
15
|
+
# json:
|
16
|
+
# name: kind_of.string
|
17
|
+
#
|
18
|
+
class Expectation < Data.define(:id, :name, :line_number, :debug, :store_as, :constraints)
|
19
|
+
#
|
20
|
+
# @return [Boolean] True if debugging is enabled
|
21
|
+
#
|
8
22
|
attr_predicate :debug
|
9
23
|
|
10
|
-
|
24
|
+
#
|
25
|
+
# @return [Boolean] True if store_as is set
|
26
|
+
#
|
27
|
+
attr_predicate :store_as
|
11
28
|
|
12
29
|
#
|
13
|
-
# Creates a new
|
30
|
+
# Creates a new expectation with constraints
|
14
31
|
#
|
15
|
-
# @param
|
16
|
-
# @param name [String]
|
32
|
+
# @param id [String] Unique identifier
|
33
|
+
# @param name [String] Human-readable name
|
34
|
+
# @param line_number [Integer] Line number in source
|
35
|
+
# @param debug [Boolean] Whether to enable debugging
|
36
|
+
# @param store_as [String] Unique Context::Store identifier
|
37
|
+
# @param expect [Hash] Expected constraints
|
17
38
|
#
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
load_debug(input)
|
23
|
-
load_variables(input)
|
24
|
-
|
25
|
-
# Must be after load_variables
|
26
|
-
load_constraints(input)
|
27
|
-
|
28
|
-
@http_client = HTTP::Client.new(
|
29
|
-
variables:, **input.except(:name, :variables, :expect, :debug)
|
30
|
-
)
|
39
|
+
# @return [Expectation] A new expectation instance
|
40
|
+
#
|
41
|
+
def initialize(id:, name:, line_number:, debug:, store_as:, expect:)
|
42
|
+
constraints = Constraint.new(**expect)
|
31
43
|
|
32
|
-
|
33
|
-
load_name(input)
|
44
|
+
super(id:, name:, line_number:, debug:, store_as:, constraints:)
|
34
45
|
end
|
35
46
|
|
47
|
+
#
|
48
|
+
# Converts the expectation to a hash representation
|
49
|
+
#
|
50
|
+
# @return [Hash] Hash representation
|
51
|
+
#
|
36
52
|
def to_h
|
37
53
|
{
|
38
54
|
name:,
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
constraints: constraints.to_h
|
55
|
+
line_number:,
|
56
|
+
debug:,
|
57
|
+
expect: constraints.to_h
|
43
58
|
}
|
44
59
|
end
|
45
|
-
|
46
|
-
private
|
47
|
-
|
48
|
-
def load_name(input)
|
49
|
-
# GET /users
|
50
|
-
@name = "#{http_client.request.http_verb.upcase} #{http_client.request.url}"
|
51
|
-
|
52
|
-
# GET /users - Returns a 404
|
53
|
-
if (name = input[:name].resolve.presence)
|
54
|
-
@name += " - #{name}"
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
def load_variables(input)
|
59
|
-
@variables = Attribute.bind_variables(input[:variables], input[:variables])
|
60
|
-
end
|
61
|
-
|
62
|
-
def load_debug(input)
|
63
|
-
@debug = input[:debug].resolve
|
64
|
-
end
|
65
|
-
|
66
|
-
def load_constraints(input)
|
67
|
-
constraints = Attribute.bind_variables(input[:expect], variables)
|
68
|
-
@constraints = Constraint.new(**constraints)
|
69
|
-
end
|
70
60
|
end
|
71
61
|
end
|
72
62
|
end
|
63
|
+
|
64
|
+
require_relative "expectation/constraint"
|