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.
- checksums.yaml +4 -4
- data/.standard.yml +3 -3
- data/CHANGELOG.md +217 -2
- data/README.md +162 -25
- data/flake.lock +3 -3
- data/flake.nix +11 -5
- data/lib/spec_forge/attribute/chainable.rb +208 -20
- data/lib/spec_forge/attribute/factory.rb +92 -15
- 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 +88 -0
- data/lib/spec_forge/cli/actions.rb +27 -0
- data/lib/spec_forge/cli/command.rb +78 -24
- data/lib/spec_forge/cli/docs/generate.rb +72 -0
- data/lib/spec_forge/cli/docs.rb +92 -0
- data/lib/spec_forge/cli/init.rb +51 -9
- data/lib/spec_forge/cli/new.rb +67 -6
- data/lib/spec_forge/cli/run.rb +32 -4
- data/lib/spec_forge/cli/serve.rb +155 -0
- data/lib/spec_forge/cli.rb +26 -7
- data/lib/spec_forge/configuration.rb +96 -24
- 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 +131 -0
- data/lib/spec_forge/context/variables.rb +91 -0
- data/lib/spec_forge/context.rb +36 -0
- data/lib/spec_forge/core_ext/array.rb +27 -0
- data/lib/spec_forge/core_ext/rspec.rb +22 -4
- data/lib/spec_forge/documentation/builder.rb +383 -0
- data/lib/spec_forge/documentation/document/operation.rb +47 -0
- data/lib/spec_forge/documentation/document/parameter.rb +22 -0
- data/lib/spec_forge/documentation/document/request_body.rb +24 -0
- data/lib/spec_forge/documentation/document/response.rb +39 -0
- data/lib/spec_forge/documentation/document/response_body.rb +27 -0
- data/lib/spec_forge/documentation/document.rb +48 -0
- data/lib/spec_forge/documentation/generators/base.rb +81 -0
- data/lib/spec_forge/documentation/generators/openapi/base.rb +100 -0
- data/lib/spec_forge/documentation/generators/openapi/error_formatter.rb +149 -0
- data/lib/spec_forge/documentation/generators/openapi/v3_0.rb +65 -0
- data/lib/spec_forge/documentation/generators/openapi.rb +59 -0
- data/lib/spec_forge/documentation/generators.rb +17 -0
- data/lib/spec_forge/documentation/loader/cache.rb +138 -0
- data/lib/spec_forge/documentation/loader.rb +159 -0
- data/lib/spec_forge/documentation/openapi/base.rb +33 -0
- data/lib/spec_forge/documentation/openapi/v3_0/example.rb +44 -0
- data/lib/spec_forge/documentation/openapi/v3_0/media_type.rb +42 -0
- data/lib/spec_forge/documentation/openapi/v3_0/operation.rb +175 -0
- data/lib/spec_forge/documentation/openapi/v3_0/response.rb +65 -0
- data/lib/spec_forge/documentation/openapi/v3_0/schema.rb +80 -0
- data/lib/spec_forge/documentation/openapi/v3_0/tag.rb +71 -0
- data/lib/spec_forge/documentation/openapi.rb +23 -0
- data/lib/spec_forge/documentation.rb +27 -0
- data/lib/spec_forge/error.rb +284 -113
- data/lib/spec_forge/factory.rb +35 -16
- data/lib/spec_forge/filter.rb +86 -0
- data/lib/spec_forge/forge.rb +171 -0
- data/lib/spec_forge/http/backend.rb +101 -29
- data/lib/spec_forge/http/client.rb +23 -13
- data/lib/spec_forge/http/request.rb +85 -62
- data/lib/spec_forge/http/verb.rb +79 -0
- data/lib/spec_forge/http.rb +105 -0
- data/lib/spec_forge/loader.rb +244 -0
- data/lib/spec_forge/matchers.rb +130 -0
- data/lib/spec_forge/normalizer/default.rb +51 -0
- data/lib/spec_forge/normalizer/definition.rb +248 -0
- data/lib/spec_forge/normalizer/validators.rb +99 -0
- data/lib/spec_forge/normalizer.rb +486 -115
- data/lib/spec_forge/normalizers/_shared.yml +74 -0
- data/lib/spec_forge/normalizers/configuration.yml +23 -0
- data/lib/spec_forge/normalizers/constraint.yml +8 -0
- data/lib/spec_forge/normalizers/expectation.yml +47 -0
- data/lib/spec_forge/normalizers/factory.yml +12 -0
- data/lib/spec_forge/normalizers/factory_reference.yml +15 -0
- data/lib/spec_forge/normalizers/global_context.yml +28 -0
- data/lib/spec_forge/normalizers/spec.yml +50 -0
- data/lib/spec_forge/runner/adapter.rb +183 -0
- 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 +98 -0
- data/lib/spec_forge/runner.rb +50 -125
- data/lib/spec_forge/spec/expectation/constraint.rb +100 -21
- data/lib/spec_forge/spec/expectation.rb +47 -51
- data/lib/spec_forge/spec.rb +50 -108
- data/lib/spec_forge/type.rb +36 -4
- data/lib/spec_forge/version.rb +4 -1
- data/lib/spec_forge.rb +168 -76
- data/lib/templates/openapi.yml.tt +22 -0
- data/lib/templates/redoc.html.tt +28 -0
- data/lib/templates/swagger.html.tt +59 -0
- metadata +109 -16
- data/lib/spec_forge/normalizer/configuration.rb +0 -77
- data/lib/spec_forge/normalizer/constraint.rb +0 -47
- data/lib/spec_forge/normalizer/expectation.rb +0 -86
- data/lib/spec_forge/normalizer/factory.rb +0 -65
- data/lib/spec_forge/normalizer/factory_reference.rb +0 -71
- data/lib/spec_forge/normalizer/spec.rb +0 -74
- 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/templates/{forge_helper.tt → forge_helper.rb.tt} +0 -0
- /data/lib/templates/{new_factory.tt → new_factory.yml.tt} +0 -0
- /data/lib/templates/{new_spec.tt → new_spec.yml.tt} +0 -0
@@ -4,44 +4,123 @@ 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
8
|
#
|
9
|
-
|
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
|
+
# headers: {response_header: "kind_of.string"},
|
16
|
+
# json: {name: {"matcher.eq" => "John"}}
|
17
|
+
# )
|
18
|
+
#
|
19
|
+
class Constraint < Data.define(:status, :headers, :json) # :xml, :html
|
10
20
|
#
|
11
|
-
# Creates a new
|
21
|
+
# Creates a new constraint
|
12
22
|
#
|
13
|
-
# @param status [Integer] The expected HTTP status code
|
23
|
+
# @param status [Integer, String] The expected HTTP status code, or reference to one
|
24
|
+
# @param headers [Hash] The expected headers with matchers
|
14
25
|
# @param json [Hash, Array] The expected JSON with matchers
|
15
26
|
#
|
16
|
-
|
17
|
-
|
27
|
+
# @return [Constraint] A new constraint instance
|
28
|
+
#
|
29
|
+
def initialize(status:, headers: {}, json: {})
|
30
|
+
super(
|
31
|
+
status: Attribute.from(status),
|
32
|
+
headers: Attribute.from(headers),
|
33
|
+
json: Attribute.from(json)
|
34
|
+
)
|
18
35
|
end
|
19
36
|
|
37
|
+
#
|
38
|
+
# Converts the constraint to a hash with resolved values
|
39
|
+
#
|
40
|
+
# @return [Hash] Hash representation with resolved values
|
41
|
+
#
|
20
42
|
def to_h
|
21
43
|
super.transform_values(&:resolve)
|
22
44
|
end
|
23
45
|
|
24
|
-
|
46
|
+
#
|
47
|
+
# Converts constraints to RSpec matchers for validation
|
48
|
+
#
|
49
|
+
# Transforms the defined constraints (status and JSON expectations) into
|
50
|
+
# appropriate RSpec matchers that can be used in test expectations.
|
51
|
+
# This method resolves all values and applies the appropriate matcher
|
52
|
+
# conversions to create a complete expectation structure.
|
53
|
+
#
|
54
|
+
# @return [Hash] A hash containing resolved matchers
|
55
|
+
#
|
56
|
+
# @example
|
57
|
+
# constraint = Constraint.new(status: 200, json: {name: "John"})
|
58
|
+
# matchers = constraint.as_matchers
|
59
|
+
# # => {status: eq(200), json: include("name" => eq("John"))}
|
60
|
+
#
|
61
|
+
def as_matchers
|
62
|
+
{
|
63
|
+
status: status.resolve_as_matcher,
|
64
|
+
json: resolve_json_matcher,
|
65
|
+
headers: resolve_hash_matcher(headers)
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
#
|
70
|
+
# Generates a human-readable description of what this constraint expects in the response
|
71
|
+
#
|
72
|
+
# Creates a description string for RSpec examples that clearly explains the expected
|
73
|
+
# status code and JSON structure. This makes test output more informative and helps
|
74
|
+
# developers understand what's being tested at a glance.
|
75
|
+
#
|
76
|
+
# @return [String] A human-readable description of the constraint expectations
|
77
|
+
#
|
78
|
+
# @example Status code with JSON object
|
79
|
+
# constraint.description
|
80
|
+
# # => "is expected to respond with \"200 OK\" and a JSON object that contains keys: \"id\", \"name\""
|
81
|
+
#
|
82
|
+
# @example Status code with JSON array
|
83
|
+
# constraint.description
|
84
|
+
# # => "is expected to respond with \"201 Created\" and a JSON array that contains 3 items"
|
85
|
+
#
|
86
|
+
def description
|
87
|
+
description = "is expected to respond with"
|
88
|
+
|
89
|
+
description += if status.is_a?(Attribute::Literal)
|
90
|
+
" #{HTTP.status_code_to_description(status.input).in_quotes}"
|
91
|
+
else
|
92
|
+
" the expected status code"
|
93
|
+
end
|
94
|
+
|
95
|
+
size = json.size
|
25
96
|
|
26
|
-
|
27
|
-
|
28
|
-
|
97
|
+
if Type.array?(json)
|
98
|
+
description +=
|
99
|
+
" and a JSON array that contains #{size} #{"item".pluralize(size)}"
|
100
|
+
elsif Type.hash?(json) && size > 0
|
101
|
+
keys = json.keys.join_map(", ", &:in_quotes)
|
29
102
|
|
30
|
-
|
103
|
+
description +=
|
104
|
+
" and a JSON object that contains #{"key".pluralize(size)}: #{keys}"
|
105
|
+
end
|
106
|
+
|
107
|
+
description
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
def resolve_json_matcher
|
113
|
+
case json
|
31
114
|
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)
|
115
|
+
resolve_hash_matcher(json)
|
41
116
|
else
|
42
|
-
|
117
|
+
json.resolve_as_matcher
|
43
118
|
end
|
44
119
|
end
|
120
|
+
|
121
|
+
def resolve_hash_matcher(hash)
|
122
|
+
hash.transform_values(&:resolve_as_matcher).stringify_keys
|
123
|
+
end
|
45
124
|
end
|
46
125
|
end
|
47
126
|
end
|
@@ -1,72 +1,68 @@
|
|
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(
|
19
|
+
:id, :name, :line_number,
|
20
|
+
:debug, :store_as, :documentation, :constraints
|
21
|
+
)
|
22
|
+
#
|
23
|
+
# @return [Boolean] True if debugging is enabled
|
24
|
+
#
|
8
25
|
attr_predicate :debug
|
9
26
|
|
10
|
-
|
27
|
+
#
|
28
|
+
# @return [Boolean] True if store_as is set
|
29
|
+
#
|
30
|
+
attr_predicate :store_as
|
11
31
|
|
12
32
|
#
|
13
|
-
# Creates a new
|
33
|
+
# Creates a new expectation with constraints
|
14
34
|
#
|
15
|
-
# @param
|
16
|
-
# @param name [String]
|
35
|
+
# @param id [String] Unique identifier
|
36
|
+
# @param name [String] Human-readable name
|
37
|
+
# @param line_number [Integer] Line number in source
|
38
|
+
# @param debug [Boolean] Whether to enable debugging
|
39
|
+
# @param store_as [String] Unique Context::Store identifier
|
40
|
+
# @param documentation [Boolean] Whether to include in documentation generation
|
41
|
+
# @param expect [Hash] Expected constraints
|
17
42
|
#
|
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
|
-
)
|
43
|
+
# @return [Expectation] A new expectation instance
|
44
|
+
#
|
45
|
+
def initialize(id:, name:, line_number:, debug:, store_as:, expect:, documentation:)
|
46
|
+
constraints = Constraint.new(**expect)
|
31
47
|
|
32
|
-
|
33
|
-
load_name(input)
|
48
|
+
super(id:, name:, line_number:, debug:, store_as:, documentation:, constraints:)
|
34
49
|
end
|
35
50
|
|
51
|
+
#
|
52
|
+
# Converts the expectation to a hash representation
|
53
|
+
#
|
54
|
+
# @return [Hash] Hash representation
|
55
|
+
#
|
36
56
|
def to_h
|
37
57
|
{
|
38
58
|
name:,
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
constraints: constraints.to_h
|
59
|
+
line_number:,
|
60
|
+
debug:,
|
61
|
+
expect: constraints.to_h
|
43
62
|
}
|
44
63
|
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
64
|
end
|
71
65
|
end
|
72
66
|
end
|
67
|
+
|
68
|
+
require_relative "expectation/constraint"
|
data/lib/spec_forge/spec.rb
CHANGED
@@ -1,126 +1,68 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "spec/expectation"
|
4
|
-
|
5
3
|
module SpecForge
|
6
|
-
|
7
|
-
|
8
|
-
|
4
|
+
#
|
5
|
+
# Represents a test specification in SpecForge
|
6
|
+
#
|
7
|
+
# A Spec contains one or more Expectations and defines the base configuration
|
8
|
+
# for those expectations. It maps directly to a test defined in YAML.
|
9
|
+
#
|
10
|
+
# @example YAML representation
|
11
|
+
# get_users:
|
12
|
+
# path: /users
|
13
|
+
# expectations:
|
14
|
+
# - expect:
|
15
|
+
# status: 200
|
16
|
+
#
|
17
|
+
class Spec < Data.define(
|
18
|
+
:id, :name, :file_path, :file_name, :line_number,
|
19
|
+
:debug, :documentation, :expectations
|
20
|
+
)
|
9
21
|
#
|
10
|
-
# @
|
11
|
-
# @param spec_name [String, nil] The name of the spec in a yaml file
|
12
|
-
# @param expectation_name [String, nil] The name of the expectation for a spec.
|
22
|
+
# @return [Boolean] True if debugging is enabled
|
13
23
|
#
|
14
|
-
|
15
|
-
#
|
16
|
-
def self.load_and_define(file_name: nil, spec_name: nil, expectation_name: nil)
|
17
|
-
specs = load_from_files
|
18
|
-
|
19
|
-
filter_specs(specs, file_name:, spec_name:, expectation_name:)
|
20
|
-
|
21
|
-
# Announce if we're using a filter
|
22
|
-
if file_name
|
23
|
-
filter = {file_name:, spec_name:, expectation_name:}.delete_if { |k, v| v.blank? }
|
24
|
-
filter.stringify_keys!
|
25
|
-
puts "Using filter: #{filter}"
|
26
|
-
end
|
27
|
-
|
28
|
-
specs.each(&:define)
|
29
|
-
end
|
24
|
+
attr_predicate :debug
|
30
25
|
|
31
26
|
#
|
32
|
-
#
|
27
|
+
# Creates a new spec instance
|
33
28
|
#
|
34
|
-
# @
|
29
|
+
# @param id [String] Unique identifier
|
30
|
+
# @param name [String] Human-readable name
|
31
|
+
# @param file_path [String] Absolute path to source file
|
32
|
+
# @param file_name [String] Base name of file
|
33
|
+
# @param debug [Boolean] Whether to enable debugging
|
34
|
+
# @param line_number [Integer] Line number in source
|
35
|
+
# @param documentation [Boolean] Whether to include in documentation generation
|
36
|
+
# @param expectations [Array<Hash>] Expectation configurations
|
35
37
|
#
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
hash.each do |spec_name, spec_hash|
|
45
|
-
line_number = content.lines.index { |line| line.start_with?("#{spec_name}:") }
|
46
|
-
|
47
|
-
spec_hash[:name] = spec_name.to_s
|
48
|
-
spec_hash[:file_path] = file_path
|
49
|
-
spec_hash[:file_name] = file_path.delete_prefix("#{path}/").delete_suffix(".yml")
|
50
|
-
spec_hash[:line_number] = line_number ? line_number + 1 : -1
|
51
|
-
|
52
|
-
specs << new(**spec_hash)
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
specs
|
57
|
-
end
|
58
|
-
|
59
|
-
# @private
|
60
|
-
def self.filter_specs(specs, file_name: nil, spec_name: nil, expectation_name: nil)
|
61
|
-
# Guard against invalid partial filters
|
62
|
-
if expectation_name && spec_name.blank?
|
63
|
-
raise ArgumentError, "The spec's name is required when filtering by an expectation's name"
|
64
|
-
end
|
65
|
-
|
66
|
-
if spec_name && file_name.blank?
|
67
|
-
raise ArgumentError, "The spec's filename is required when filtering by a spec's name"
|
68
|
-
end
|
69
|
-
|
70
|
-
specs.select! { |spec| spec.file_name == file_name } if file_name
|
71
|
-
specs.select! { |spec| spec.name == spec_name } if spec_name
|
72
|
-
|
73
|
-
if expectation_name
|
74
|
-
specs.each do |spec|
|
75
|
-
spec.expectations.select! { |expectation| expectation.name == expectation_name }
|
76
|
-
end
|
77
|
-
end
|
38
|
+
# @return [Spec] A new spec instance
|
39
|
+
#
|
40
|
+
def initialize(
|
41
|
+
id:, name:, file_path:, file_name:, line_number:,
|
42
|
+
debug:, documentation:, expectations:
|
43
|
+
)
|
44
|
+
expectations = expectations.map { |e| Expectation.new(**e) }
|
78
45
|
|
79
|
-
|
46
|
+
super
|
80
47
|
end
|
81
48
|
|
82
|
-
############################################################################
|
83
|
-
|
84
|
-
attr_predicate :debug
|
85
|
-
|
86
|
-
attr_reader :name, :file_path, :file_name, :line_number, :expectations
|
87
|
-
|
88
49
|
#
|
89
|
-
#
|
50
|
+
# Converts the spec to a hash representation
|
90
51
|
#
|
91
|
-
# @
|
92
|
-
# @param file_path [String] The path where this spec is defined
|
93
|
-
# @param **input [Hash] Any attributes related to the spec, including expectations
|
94
|
-
# See Normalizer::Spec
|
52
|
+
# @return [Hash] Hash representation
|
95
53
|
#
|
96
|
-
def
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
global_options = normalize_global_options(input)
|
108
|
-
|
109
|
-
@expectations =
|
110
|
-
input[:expectations].map.with_index do |expectation_input, index|
|
111
|
-
Expectation.new(expectation_input, global_options:)
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
def define
|
116
|
-
Runner.define_spec(self)
|
117
|
-
end
|
118
|
-
|
119
|
-
private
|
120
|
-
|
121
|
-
def normalize_global_options(input)
|
122
|
-
config = SpecForge.configuration.to_h.slice(:base_url, :headers, :query)
|
123
|
-
Configuration.overlay_options(config, input.except(:expectations))
|
54
|
+
def to_h
|
55
|
+
{
|
56
|
+
name:,
|
57
|
+
file_path:,
|
58
|
+
file_name:,
|
59
|
+
debug:,
|
60
|
+
line_number:,
|
61
|
+
documentation:,
|
62
|
+
expectations: expectations.map(&:to_h)
|
63
|
+
}
|
124
64
|
end
|
125
65
|
end
|
126
66
|
end
|
67
|
+
|
68
|
+
require_relative "spec/expectation"
|
data/lib/spec_forge/type.rb
CHANGED
@@ -1,24 +1,28 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module SpecForge
|
4
|
+
#
|
5
|
+
# Provides helper methods for checking types
|
6
|
+
# Useful for working with both regular objects and Attribute delegators
|
7
|
+
#
|
4
8
|
module Type
|
5
9
|
#
|
6
|
-
# Checks if the object is a Hash
|
10
|
+
# Checks if the object is a Hash or a ResolvableHash delegator
|
7
11
|
#
|
8
12
|
# @param object [Object] The object to check
|
9
13
|
#
|
10
|
-
# @return [Boolean]
|
14
|
+
# @return [Boolean] True if the object is a hash-like structure
|
11
15
|
#
|
12
16
|
def self.hash?(object)
|
13
17
|
object.is_a?(Hash) || object.is_a?(Attribute::ResolvableHash)
|
14
18
|
end
|
15
19
|
|
16
20
|
#
|
17
|
-
# Checks if the object is an Array
|
21
|
+
# Checks if the object is an Array or a ResolvableArray delegator
|
18
22
|
#
|
19
23
|
# @param object [Object] The object to check
|
20
24
|
#
|
21
|
-
# @return [Boolean]
|
25
|
+
# @return [Boolean] True if the object is an array-like structure
|
22
26
|
#
|
23
27
|
def self.array?(object)
|
24
28
|
object.is_a?(Array) || object.is_a?(Attribute::ResolvableArray)
|
@@ -28,8 +32,22 @@ end
|
|
28
32
|
|
29
33
|
#
|
30
34
|
# Represents Hash/ResolvableHash in a form that can be used in a case statement
|
35
|
+
# Allows for type switching on hash-like objects
|
36
|
+
#
|
37
|
+
# @example
|
38
|
+
# case value
|
39
|
+
# when HashLike
|
40
|
+
# # Handle hash-like objects
|
41
|
+
# end
|
31
42
|
#
|
32
43
|
class HashLike
|
44
|
+
#
|
45
|
+
# Provides custom type matching for use in case statements
|
46
|
+
#
|
47
|
+
# @param object [Object] The object to check against the type
|
48
|
+
#
|
49
|
+
# @return [Boolean] Whether the object matches the type
|
50
|
+
#
|
33
51
|
def self.===(object)
|
34
52
|
SpecForge::Type.hash?(object)
|
35
53
|
end
|
@@ -37,8 +55,22 @@ end
|
|
37
55
|
|
38
56
|
#
|
39
57
|
# Represents Array/ResolvableArray in a form that can be used in a case statement
|
58
|
+
# Allows for type switching on array-like objects
|
59
|
+
#
|
60
|
+
# @example
|
61
|
+
# case value
|
62
|
+
# when ArrayLike
|
63
|
+
# # Handle array-like objects
|
64
|
+
# end
|
40
65
|
#
|
41
66
|
class ArrayLike
|
67
|
+
#
|
68
|
+
# Provides custom type matching for use in case statements
|
69
|
+
#
|
70
|
+
# @param object [Object] The object to check against the type
|
71
|
+
#
|
72
|
+
# @return [Boolean] Whether the object matches the type
|
73
|
+
#
|
42
74
|
def self.===(object)
|
43
75
|
SpecForge::Type.array?(object)
|
44
76
|
end
|