spec_forge 0.1.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 (58) hide show
  1. checksums.yaml +7 -0
  2. data/.envrc +1 -0
  3. data/.rspec +1 -0
  4. data/.standard.yml +3 -0
  5. data/CHANGELOG.md +5 -0
  6. data/CODE_OF_CONDUCT.md +132 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +426 -0
  9. data/Rakefile +6 -0
  10. data/bin/spec_forge +5 -0
  11. data/flake.lock +61 -0
  12. data/flake.nix +41 -0
  13. data/lib/spec_forge/attribute/chainable.rb +86 -0
  14. data/lib/spec_forge/attribute/factory.rb +63 -0
  15. data/lib/spec_forge/attribute/faker.rb +54 -0
  16. data/lib/spec_forge/attribute/literal.rb +27 -0
  17. data/lib/spec_forge/attribute/matcher.rb +118 -0
  18. data/lib/spec_forge/attribute/parameterized.rb +76 -0
  19. data/lib/spec_forge/attribute/resolvable.rb +21 -0
  20. data/lib/spec_forge/attribute/resolvable_array.rb +24 -0
  21. data/lib/spec_forge/attribute/resolvable_hash.rb +24 -0
  22. data/lib/spec_forge/attribute/transform.rb +39 -0
  23. data/lib/spec_forge/attribute/variable.rb +36 -0
  24. data/lib/spec_forge/attribute.rb +208 -0
  25. data/lib/spec_forge/cli/actions.rb +23 -0
  26. data/lib/spec_forge/cli/command.rb +127 -0
  27. data/lib/spec_forge/cli/init.rb +29 -0
  28. data/lib/spec_forge/cli/new.rb +161 -0
  29. data/lib/spec_forge/cli/run.rb +17 -0
  30. data/lib/spec_forge/cli.rb +43 -0
  31. data/lib/spec_forge/config.rb +84 -0
  32. data/lib/spec_forge/environment.rb +71 -0
  33. data/lib/spec_forge/error.rb +150 -0
  34. data/lib/spec_forge/factory.rb +104 -0
  35. data/lib/spec_forge/http/backend.rb +106 -0
  36. data/lib/spec_forge/http/client.rb +33 -0
  37. data/lib/spec_forge/http/request.rb +93 -0
  38. data/lib/spec_forge/http/verb.rb +118 -0
  39. data/lib/spec_forge/http.rb +6 -0
  40. data/lib/spec_forge/normalizer/config.rb +104 -0
  41. data/lib/spec_forge/normalizer/constraint.rb +47 -0
  42. data/lib/spec_forge/normalizer/expectation.rb +85 -0
  43. data/lib/spec_forge/normalizer/factory.rb +65 -0
  44. data/lib/spec_forge/normalizer/factory_reference.rb +66 -0
  45. data/lib/spec_forge/normalizer/spec.rb +73 -0
  46. data/lib/spec_forge/normalizer.rb +183 -0
  47. data/lib/spec_forge/runner.rb +91 -0
  48. data/lib/spec_forge/spec/expectation/constraint.rb +52 -0
  49. data/lib/spec_forge/spec/expectation.rb +53 -0
  50. data/lib/spec_forge/spec.rb +77 -0
  51. data/lib/spec_forge/type.rb +45 -0
  52. data/lib/spec_forge/version.rb +5 -0
  53. data/lib/spec_forge.rb +90 -0
  54. data/lib/templates/config.tt +19 -0
  55. data/spec_forge/config.yml +19 -0
  56. data/spec_forge/factories/user.yml +4 -0
  57. data/spec_forge/specs/users.yml +63 -0
  58. metadata +234 -0
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpecForge
4
+ class Spec
5
+ class Expectation
6
+ #
7
+ # Represents the "expect" hash
8
+ #
9
+ class Constraint < Data.define(:status, :json) # :xml, :html
10
+ #
11
+ # Creates a new Constraint
12
+ #
13
+ # @param status [Integer] The expected HTTP status code
14
+ # @param json [Hash] The expected JSON with matchers
15
+ #
16
+ def initialize(status:, json:)
17
+ super(status:, json: normalize_hash(json))
18
+ end
19
+
20
+ def resolve
21
+ {
22
+ status: status.resolve,
23
+ json: json.resolve.deep_stringify_keys
24
+ }
25
+ end
26
+
27
+ private
28
+
29
+ def normalize_hash(hash)
30
+ hash =
31
+ hash.transform_values do |attribute|
32
+ if attribute.is_a?(Attribute::Literal)
33
+ normalize_literal(attribute.value)
34
+ else
35
+ attribute
36
+ end
37
+ end
38
+
39
+ Attribute.from(hash)
40
+ end
41
+
42
+ def normalize_literal(value)
43
+ if value.is_a?(Regexp)
44
+ Attribute.from("matcher.match" => value)
45
+ else
46
+ Attribute.from("matcher.eq" => value)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "expectation/constraint"
4
+
5
+ module SpecForge
6
+ class Spec
7
+ class Expectation
8
+ attr_reader :name, :variables, :constraints, :http_client
9
+
10
+ #
11
+ # Creates a new Expectation
12
+ #
13
+ # @param input [Hash] A hash containing the various attributes to control the expectation
14
+ # @param name [String] The name of the expectation
15
+ #
16
+ def initialize(name, input, global_options: {})
17
+ load_name(name, input)
18
+
19
+ # This allows defining spec level attributes that can be overwritten by the expectation
20
+ input = Attribute.from(overlay_options(global_options, input))
21
+
22
+ load_variables(input)
23
+
24
+ # Must be after load_variables
25
+ load_constraints(input)
26
+
27
+ # Must be last
28
+ @http_client = HTTP::Client.new(variables:, **input.except(:name, :variables, :expect))
29
+ end
30
+
31
+ private
32
+
33
+ def overlay_options(source, overlay)
34
+ # Remove any blank values to avoid overwriting anything from source
35
+ overlay = overlay.delete_if { |_k, v| v.blank? }
36
+ source.deep_merge(overlay)
37
+ end
38
+
39
+ def load_name(name, input)
40
+ @name = input[:name].presence || name
41
+ end
42
+
43
+ def load_variables(input)
44
+ @variables = Attribute.bind_variables(input[:variables], input[:variables])
45
+ end
46
+
47
+ def load_constraints(input)
48
+ constraints = Attribute.bind_variables(input[:expect], variables)
49
+ @constraints = Constraint.new(**constraints)
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "spec/expectation"
4
+
5
+ module SpecForge
6
+ class Spec
7
+ #
8
+ # Loads the specs from their yml files and runs them
9
+ #
10
+ # @param path [String, Path] The base path where the specs directory is located
11
+ #
12
+ def self.load_and_run(base_path)
13
+ specs = load_from_path(base_path.join("specs", "**/*.yml"))
14
+ specs.each(&:run)
15
+ end
16
+
17
+ #
18
+ # Loads any specs defined in the path. A single file can contain one or more specs
19
+ #
20
+ # @param path [String, Path] The path where the specs are located
21
+ #
22
+ # @return [Array<Spec>] An array of specs that were loaded.
23
+ #
24
+ def self.load_from_path(path)
25
+ specs = []
26
+
27
+ Dir[path].map do |file_path|
28
+ hash = YAML.load_file(file_path).deep_symbolize_keys
29
+
30
+ hash.each do |spec_name, spec_hash|
31
+ spec_hash[:name] = spec_name
32
+ spec_hash[:file_path] = file_path
33
+
34
+ specs << new(**spec_hash)
35
+ end
36
+ end
37
+
38
+ specs
39
+ end
40
+
41
+ ############################################################################
42
+
43
+ attr_reader :name, :file_path, :expectations
44
+
45
+ #
46
+ # Creates a Spec based on the input
47
+ #
48
+ # @param name [String] The identifier for this spec
49
+ # @param file_path [String] The path where this spec is defined
50
+ # @param **input [Hash] Any attributes related to the spec, including expectations
51
+ # See Normalizer::Spec
52
+ #
53
+ def initialize(name:, file_path:, **input)
54
+ @name = name
55
+ @file_path = file_path
56
+
57
+ input = Normalizer.normalize_spec!(input)
58
+ global_options = input.except(:expectations)
59
+
60
+ @expectations =
61
+ input[:expectations].map.with_index do |expectation_input, index|
62
+ Expectation.new(
63
+ "expectations (item #{index})",
64
+ expectation_input,
65
+ global_options:
66
+ )
67
+ end
68
+ end
69
+
70
+ #
71
+ # Runs the spec
72
+ #
73
+ def run
74
+ Runner.new(self).run
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpecForge
4
+ module Type
5
+ #
6
+ # Checks if the object is a Hash, or a ResolvableHash (delegator)
7
+ #
8
+ # @param object [Object] The object to check
9
+ #
10
+ # @return [Boolean]
11
+ #
12
+ def self.hash?(object)
13
+ object.is_a?(Hash) || object.is_a?(Attribute::ResolvableHash)
14
+ end
15
+
16
+ #
17
+ # Checks if the object is an Array, or a ResolvableArray (delegator)
18
+ #
19
+ # @param object [Object] The object to check
20
+ #
21
+ # @return [Boolean]
22
+ #
23
+ def self.array?(object)
24
+ object.is_a?(Array) || object.is_a?(Attribute::ResolvableArray)
25
+ end
26
+ end
27
+ end
28
+
29
+ #
30
+ # Represents Hash/ResolvableHash in a form that can be used in a case statement
31
+ #
32
+ class HashLike
33
+ def self.===(object)
34
+ SpecForge::Type.hash?(object)
35
+ end
36
+ end
37
+
38
+ #
39
+ # Represents Array/ResolvableArray in a form that can be used in a case statement
40
+ #
41
+ class ArrayLike
42
+ def self.===(object)
43
+ SpecForge::Type.array?(object)
44
+ end
45
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpecForge
4
+ VERSION = "0.1.0"
5
+ end
data/lib/spec_forge.rb ADDED
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "logger"
4
+ require "active_support"
5
+ require "active_support/core_ext"
6
+ require "commander"
7
+ require "everythingrb"
8
+ require "factory_bot"
9
+ require "faker"
10
+ require "faraday"
11
+ require "mime/types"
12
+ require "pathname"
13
+ require "rspec"
14
+ require "singleton"
15
+ require "thor"
16
+ require "yaml"
17
+
18
+ require_relative "spec_forge/attribute"
19
+ require_relative "spec_forge/cli"
20
+ require_relative "spec_forge/config"
21
+ require_relative "spec_forge/environment"
22
+ require_relative "spec_forge/error"
23
+ require_relative "spec_forge/factory"
24
+ require_relative "spec_forge/http"
25
+ require_relative "spec_forge/normalizer"
26
+ require_relative "spec_forge/runner"
27
+ require_relative "spec_forge/spec"
28
+ require_relative "spec_forge/type"
29
+ require_relative "spec_forge/version"
30
+
31
+ module SpecForge
32
+ #
33
+ # Loads all factories and specs located in "path", then runs all of the specs
34
+ #
35
+ # @param path [String] The file path that contains factories and specs
36
+ #
37
+ def self.run(path = SpecForge.forge)
38
+ SpecForge.environment.load
39
+
40
+ Factory.load_and_register(path)
41
+ Spec.load_and_run(path)
42
+ end
43
+
44
+ #
45
+ # Returns the directory root for the working directory
46
+ #
47
+ # @return [Pathname]
48
+ #
49
+ def self.root
50
+ @root ||= Pathname.pwd
51
+ end
52
+
53
+ #
54
+ # Returns SpecForge's working directory
55
+ #
56
+ # @return [Pathname]
57
+ #
58
+ def self.forge
59
+ @forge ||= root.join("spec_forge")
60
+ end
61
+
62
+ #
63
+ # Returns SpecForge's config
64
+ #
65
+ # @return [Config]
66
+ #
67
+ def self.config
68
+ @config ||= Config.new
69
+ end
70
+
71
+ #
72
+ # Returns a backtrace cleaner configured for SpecForge
73
+ #
74
+ # @return [ActiveSupport::BacktraceCleaner]
75
+ #
76
+ def self.backtrace_cleaner
77
+ @backtrace_cleaner ||= begin
78
+ root = "#{SpecForge.root}/"
79
+
80
+ cleaner = ActiveSupport::BacktraceCleaner.new
81
+ cleaner.add_filter { |line| line.delete_prefix(root) }
82
+ cleaner.add_silencer { |line| /rubygems|backtrace_cleaner/.match?(line) }
83
+ cleaner
84
+ end
85
+ end
86
+
87
+ def self.environment
88
+ @environment ||= Environment.new
89
+ end
90
+ end
@@ -0,0 +1,19 @@
1
+ # Sets the base URL prefix for all API requests. All test paths will be appended to this URL.
2
+ base_url: http://localhost:3000
3
+
4
+ # Configures the global authorization header and value for API requests.
5
+ authorization:
6
+ default:
7
+ header: Authorization
8
+ value: <%= default_authorization_value %>
9
+
10
+ factories:
11
+ ## Optional: Overrides default FactoryBot definition paths for discovering factories
12
+ ## By default, FactoryBot will look in "spec/factories" and "test/factories"
13
+ # paths:
14
+ # - custom/factories/path
15
+
16
+ ## Optional: Enable/disable automatic factory discovery, default: true
17
+ ## Note: Disabling auto discovery will only disable FactoryBot's auto discovery. Any factories
18
+ ## defined in "spec_forge/factories" will still be loaded.
19
+ # auto_discover: false
@@ -0,0 +1,19 @@
1
+ # Sets the base URL prefix for all API requests. All test paths will be appended to this URL.
2
+ base_url: http://localhost:3000
3
+
4
+ # Configures the global authorization header and value for API requests.
5
+ authorization:
6
+ default:
7
+ header: Authorization
8
+ value: Bearer <%= ENV.fetch("API_TOKEN", "") %>
9
+
10
+ factories:
11
+ ## Optional: Overrides default FactoryBot definition paths for discovering factories
12
+ ## By default, FactoryBot will look in "spec/factories" and "test/factories"
13
+ # paths:
14
+ # - custom/factories/path
15
+
16
+ ## Optional: Enable/disable automatic factory discovery, default: true
17
+ ## Note: Disabling auto discovery will only disable FactoryBot's auto discovery. Any factories
18
+ ## defined in "spec_forge/factories" will still be loaded.
19
+ # auto_discover: false
@@ -0,0 +1,4 @@
1
+ user:
2
+ class: User
3
+ attributes:
4
+ attribute: value
@@ -0,0 +1,63 @@
1
+ index_users:
2
+ url: /users
3
+ expectations:
4
+ - expect:
5
+ status: 200
6
+
7
+ show_user:
8
+ url: /users/{id}
9
+ expectations:
10
+ - expect:
11
+ status: 404
12
+ - expect:
13
+ status: 200
14
+ json:
15
+ name: kind_of.string
16
+ email: /\w+@example\.com/i
17
+ query:
18
+ id: 1
19
+
20
+ create_user:
21
+ url: /users
22
+ method: post
23
+ expectations:
24
+ - expect:
25
+ status: 400
26
+ - expect:
27
+ status: 200
28
+ json:
29
+ name: variables.name
30
+ role: variables.role
31
+ variables:
32
+ name: faker.name.name
33
+ role: user
34
+ body:
35
+ name: variables.name
36
+
37
+ update_user:
38
+ url: /users/{id}
39
+ method: patch
40
+ query:
41
+ id: 1
42
+ variables:
43
+ number:
44
+ faker.number.between:
45
+ from: 100000
46
+ to: 999999
47
+ expectations:
48
+ - expect:
49
+ status: 200
50
+ json:
51
+ name: kind_of.string
52
+ number: kind_of.integer
53
+ body:
54
+ number: variables.number
55
+
56
+ destroy_user:
57
+ url: /users/{id}
58
+ method: delete
59
+ query:
60
+ id: 1
61
+ expectations:
62
+ - expect:
63
+ status: 200
metadata ADDED
@@ -0,0 +1,234 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: spec_forge
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Bryan
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 1980-01-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '7.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '7.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: commander
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '5.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '5.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: everythingrb
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.1'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: factory_bot
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '6.5'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '6.5'
69
+ - !ruby/object:Gem::Dependency
70
+ name: faker
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.5'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.5'
83
+ - !ruby/object:Gem::Dependency
84
+ name: faraday
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '2.12'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '2.12'
97
+ - !ruby/object:Gem::Dependency
98
+ name: mime-types
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '3.6'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '3.6'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rspec
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '3.13'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '3.13'
125
+ - !ruby/object:Gem::Dependency
126
+ name: thor
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '1.3'
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '1.3'
139
+ description: Write API tests in YAML without sacrificing power. SpecForge combines
140
+ RSpec's matcher system, Faker's data generation, and factory patterns into a clean,
141
+ declarative syntax that eliminates boilerplate while preserving control over your
142
+ test suite.
143
+ email:
144
+ - bryan@itsthedevman.com
145
+ executables:
146
+ - spec_forge
147
+ extensions: []
148
+ extra_rdoc_files: []
149
+ files:
150
+ - ".envrc"
151
+ - ".rspec"
152
+ - ".standard.yml"
153
+ - CHANGELOG.md
154
+ - CODE_OF_CONDUCT.md
155
+ - LICENSE.txt
156
+ - README.md
157
+ - Rakefile
158
+ - bin/spec_forge
159
+ - flake.lock
160
+ - flake.nix
161
+ - lib/spec_forge.rb
162
+ - lib/spec_forge/attribute.rb
163
+ - lib/spec_forge/attribute/chainable.rb
164
+ - lib/spec_forge/attribute/factory.rb
165
+ - lib/spec_forge/attribute/faker.rb
166
+ - lib/spec_forge/attribute/literal.rb
167
+ - lib/spec_forge/attribute/matcher.rb
168
+ - lib/spec_forge/attribute/parameterized.rb
169
+ - lib/spec_forge/attribute/resolvable.rb
170
+ - lib/spec_forge/attribute/resolvable_array.rb
171
+ - lib/spec_forge/attribute/resolvable_hash.rb
172
+ - lib/spec_forge/attribute/transform.rb
173
+ - lib/spec_forge/attribute/variable.rb
174
+ - lib/spec_forge/cli.rb
175
+ - lib/spec_forge/cli/actions.rb
176
+ - lib/spec_forge/cli/command.rb
177
+ - lib/spec_forge/cli/init.rb
178
+ - lib/spec_forge/cli/new.rb
179
+ - lib/spec_forge/cli/run.rb
180
+ - lib/spec_forge/config.rb
181
+ - lib/spec_forge/environment.rb
182
+ - lib/spec_forge/error.rb
183
+ - lib/spec_forge/factory.rb
184
+ - lib/spec_forge/http.rb
185
+ - lib/spec_forge/http/backend.rb
186
+ - lib/spec_forge/http/client.rb
187
+ - lib/spec_forge/http/request.rb
188
+ - lib/spec_forge/http/verb.rb
189
+ - lib/spec_forge/normalizer.rb
190
+ - lib/spec_forge/normalizer/config.rb
191
+ - lib/spec_forge/normalizer/constraint.rb
192
+ - lib/spec_forge/normalizer/expectation.rb
193
+ - lib/spec_forge/normalizer/factory.rb
194
+ - lib/spec_forge/normalizer/factory_reference.rb
195
+ - lib/spec_forge/normalizer/spec.rb
196
+ - lib/spec_forge/runner.rb
197
+ - lib/spec_forge/spec.rb
198
+ - lib/spec_forge/spec/expectation.rb
199
+ - lib/spec_forge/spec/expectation/constraint.rb
200
+ - lib/spec_forge/type.rb
201
+ - lib/spec_forge/version.rb
202
+ - lib/templates/config.tt
203
+ - spec_forge/config.yml
204
+ - spec_forge/factories/user.yml
205
+ - spec_forge/specs/users.yml
206
+ homepage: https://github.com/itsthedevman/spec_forge
207
+ licenses:
208
+ - MIT
209
+ metadata:
210
+ source_code_uri: https://github.com/itsthedevman/spec_forge
211
+ changelog_uri: https://github.com/itsthedevman/spec_forge/blob/main/CHANGELOG.md
212
+ bug_tracker_uri: https://github.com/itsthedevman/spec_forge/issues
213
+ documentation_uri: https://github.com/itsthedevman/spec_forge#readme
214
+ rubygems_mfa_required: 'true'
215
+ post_install_message:
216
+ rdoc_options: []
217
+ require_paths:
218
+ - lib
219
+ required_ruby_version: !ruby/object:Gem::Requirement
220
+ requirements:
221
+ - - ">="
222
+ - !ruby/object:Gem::Version
223
+ version: 3.0.0
224
+ required_rubygems_version: !ruby/object:Gem::Requirement
225
+ requirements:
226
+ - - ">="
227
+ - !ruby/object:Gem::Version
228
+ version: '0'
229
+ requirements: []
230
+ rubygems_version: 3.5.22
231
+ signing_key:
232
+ specification_version: 4
233
+ summary: Write expressive API tests in YAML with the power of RSpec matchers
234
+ test_files: []