spec_forge 0.3.2 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d8f32cf2a6adb2037851cc3408d7bcc093b3c28d061dbc0a00abcdd2de4d3b49
4
- data.tar.gz: d5aff61ab4b2b593c3412cb04e54ec4cd0af1160571105c15c7d80f980267fe9
3
+ metadata.gz: 658fd0830ce9179c1fe7ea880e2ed8196ccbc47bc8b244f6fc40c458ca900cca
4
+ data.tar.gz: 3658379f52f23ffa362ded1c50b28e052911b6602ac3a3bed02b1fd9c8ee334c
5
5
  SHA512:
6
- metadata.gz: 8f2fe0cb87f66820f2a4273e137052eade6d34d2c01594a44d7a7d56e61d6896a41a476a8730f5bdd744efffea20c5b1afb00471d43b10cae548ab0775e8e936
7
- data.tar.gz: 3ec74966a47dcf22bc16ae5e64391ede4963a32e22a62f6a2f1189f6e881747b33f74326b190dbd9580ec2ebb0f402844e8d00e3e8986c2ba5c9aae796520a74
6
+ metadata.gz: ebcdfd4e02d965049cabd1a18b7897665800dc8081149aa4554433e7083296f0121d76f4ec474b527fdd8df0da1c71170c9c10c5cff1a108f8704b7f14e7104f
7
+ data.tar.gz: ad3b84d27b9e7ab62dc902ad2a5d41a77670286aaba9a635ccebf5735061fa725e8d46daf62fd75562a35688d62b3dc595a597b9e38ec965f5273fdc27a2aabb
data/CHANGELOG.md CHANGED
@@ -13,6 +13,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
13
13
 
14
14
  ### Removed
15
15
 
16
+ ## [0.4.0] - 12025-02-22
17
+
18
+ ### Added
19
+
20
+ - Added support to run an individual file, spec, or expectation
21
+
22
+ ### Changed
23
+
24
+ - Updated `everythingrb` to 0.2
25
+ - Updated spec and factory templates
26
+ - Improved error reporting to use SpecForge's commands instead of RSpec's
27
+ - Updated `run` command's CLI documentation
28
+
29
+ ### Removed
30
+
31
+ - Removed support for ActiveSupport 7.0
32
+
16
33
  ## [0.3.2] - 12025-02-20
17
34
 
18
35
  ### Changed
@@ -71,7 +88,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
71
88
 
72
89
  - Initial commit
73
90
 
74
- [unreleased]: https://github.com/itsthedevman/spec_forge/compare/v0.3.2...HEAD
91
+ [unreleased]: https://github.com/itsthedevman/spec_forge/compare/v0.4.0...HEAD
92
+ [0.4.0]: https://github.com/itsthedevman/spec_forge/compare/v0.3.2...v0.4.0
75
93
  [0.3.2]: https://github.com/itsthedevman/spec_forge/compare/v0.3.0...v0.3.2
76
94
  [0.3.0]: https://github.com/itsthedevman/spec_forge/compare/v0.2.0...v0.3.0
77
95
  [0.2.0]: https://github.com/itsthedevman/spec_forge/compare/v0.1.0...v0.2.0
data/README.md CHANGED
@@ -41,14 +41,14 @@ Consider alternatives when you need:
41
41
  ## Roadmap
42
42
 
43
43
  Current development priorities:
44
- - [ ] Support for running individual specs
45
44
  - [ ] Array support for `json` expectations
46
45
  - [ ] Negated matchers: `matcher.not`
47
46
  - [ ] `create_list/build_list` factory strategies
48
47
  - [ ] `transform.map` support
49
- - [ ] Improved error handling
50
48
  - [ ] XML/HTML response handling
51
49
  - [ ] OpenAPI generation from tests
50
+ - [x] Support for running individual specs
51
+ - [x] Improved error handling
52
52
 
53
53
  Have a feature request? Open an issue on GitHub!
54
54
 
@@ -63,7 +63,11 @@ I'm currently looking for opportunities where I can tackle meaningful problems a
63
63
  - [Compatibility](#compatibility)
64
64
  - [Installation](#installation)
65
65
  - [Getting Started](#getting-started)
66
- - [Writing Your First Test](#writing-your-first-test)
66
+ - [Forging Your First Test](#forging-your-first-test)
67
+ - [Running Tests](#running-tests)
68
+ - [Targeting Specific Files](#targeting-specific-files)
69
+ - [Targeting Specific Specs](#targeting-specific-specs)
70
+ - [Targeting Individual Expectations](#targeting-individual-expectations)
67
71
  - [Configuration](#configuration)
68
72
  - [Basic Configuration](#basic-configuration)
69
73
  - [Framework Integration](#framework-integration)
@@ -138,7 +142,7 @@ bundle exec spec_forge init
138
142
 
139
143
  This creates the `spec_forge` directory containing factory definitions, test specifications, and global configuration.
140
144
 
141
- ## Writing Your First Test
145
+ ## Forging Your First Test
142
146
 
143
147
  Let's write a simple test to verify a user endpoint. Create a new spec file:
144
148
 
@@ -167,6 +171,63 @@ Run your tests with:
167
171
  spec_forge run
168
172
  ```
169
173
 
174
+ Since `run` is the default command, you can just use:
175
+
176
+ ```bash
177
+ spec_forge
178
+ ```
179
+
180
+ ## Running Tests
181
+
182
+ As your test suite grows, you'll want more control over which tests to run.
183
+
184
+ #### Targeting Specific Files
185
+
186
+ When working on a specific feature, run tests from a single file:
187
+
188
+ ```bash
189
+ spec_forge users # Runs all tests in specs/users.yml
190
+ ```
191
+
192
+ #### Targeting Specific Specs
193
+
194
+ Focus on a specific endpoint by running a single spec:
195
+
196
+ ```bash
197
+ spec_forge users:destroy_user # Runs all expectations in the destroy_user spec
198
+ ```
199
+
200
+ #### Targeting Individual Expectations
201
+
202
+ You can also run individual expectations within a spec. The format depends on whether the expectation has a name:
203
+
204
+ ```yaml
205
+ # specs/users.yml
206
+ destroy_user:
207
+ path: /users/:id
208
+ method: delete
209
+ expectations:
210
+ - expect: # Unnamed expectation
211
+ status: 200
212
+ - name: "Destroys a User" # Named expectation
213
+ expect:
214
+ status: 200
215
+ ```
216
+
217
+ For named expectations:
218
+ ```bash
219
+ # Format: <file>:<spec>:'<verb> <path> - <name>'
220
+ spec_forge users:destroy_user:'DELETE /users/:id - Destroys a User'
221
+ ```
222
+
223
+ For unnamed expectations:
224
+ ```bash
225
+ # Format: <file>:<spec>:'<verb> <path>'
226
+ spec_forge users:destroy_user:'DELETE /users/:id'
227
+ ```
228
+
229
+ **Note**: When targeting an unnamed expectation, SpecForge executes all matching expectations within that spec. This means if you have multiple unnamed expectations with the same verb and path, they will all run.
230
+
170
231
  ## Configuration
171
232
 
172
233
  ### Basic Configuration
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpecForge
4
+ #
5
+ # Used internally by RSpec
6
+ # This class handles formatting backtraces, hence the name ;)
7
+ #
8
+ module BacktraceFormatter
9
+ def self.formatter
10
+ @formatter ||= RSpec::Core::BacktraceFormatter.new
11
+ end
12
+
13
+ def self.backtrace_line(line)
14
+ formatter.backtrace_line(line)
15
+ end
16
+
17
+ def self.format_backtrace(backtrace, example_metadata)
18
+ backtrace = SpecForge.backtrace_cleaner.clean(backtrace)
19
+
20
+ location = example_metadata[:example_group][:location]
21
+ line_number = example_metadata[:example_group][:line_number]
22
+
23
+ # Add the yaml location to the front so it's the first thing people see
24
+ ["#{location}:#{line_number}"] + backtrace
25
+ end
26
+ end
27
+ end
@@ -38,123 +38,33 @@ module SpecForge
38
38
  private
39
39
 
40
40
  def create_new_spec(name)
41
- actions.create_file(
41
+ actions.template(
42
+ "new_spec.tt",
42
43
  SpecForge.forge.join("specs", "#{name}.yml"),
43
- generate_spec(name)
44
+ context: Proxy.new(name).call
44
45
  )
45
46
  end
46
47
 
47
48
  def create_new_factory(name)
48
- actions.create_file(
49
+ actions.template(
50
+ "new_factory.tt",
49
51
  SpecForge.forge.join("factories", "#{name}.yml"),
50
- generate_factory(name)
52
+ context: Proxy.new(name).call
51
53
  )
52
54
  end
53
55
 
54
- def generate_spec(name)
55
- plural_name = name.pluralize
56
- singular_name = name.singularize
56
+ class Proxy
57
+ attr_reader :original_name, :singular_name, :plural_name
57
58
 
58
- base_spec = {url: ""}
59
- base_constraint = {expect: {status: 200}}
60
-
61
- hash = {
62
- ##################################################
63
- "index_#{plural_name}" => base_spec.merge(
64
- url: "/#{plural_name}",
65
- expectations: [base_constraint]
66
- ),
67
- ##################################################
68
- "show_#{singular_name}" => base_spec.merge(
69
- url: "/#{plural_name}/{id}",
70
- expectations: [
71
- base_constraint.merge(expect: {status: 404}),
72
- base_constraint.deep_merge(
73
- query: {id: 1},
74
- expect: {
75
- json: {
76
- name: "kind_of.string",
77
- email: /\w+@example\.com/i
78
- }
79
- }
80
- )
81
- ]
82
- ),
83
- ##################################################
84
- "create_#{singular_name}" => base_spec.merge(
85
- url: "/#{plural_name}",
86
- method: "post",
87
- expectations: [
88
- base_constraint.merge(expect: {status: 400}),
89
- base_constraint.deep_merge(
90
- variables: {
91
- name: "faker.name.name",
92
- role: "user"
93
- },
94
- body: {name: "variables.name"},
95
- expect: {
96
- json: {name: "variables.name", role: "variables.role"}
97
- }
98
- )
99
- ]
100
- ),
101
- ##################################################
102
- "update_#{singular_name}" => base_spec.merge(
103
- url: "/#{plural_name}/{id}",
104
- method: "patch",
105
- query: {id: 1},
106
- variables: {
107
- number: {
108
- "faker.number.between" => {from: 100_000, to: 999_999}
109
- }
110
- },
111
- expectations: [
112
- base_constraint.deep_merge(
113
- body: {number: "variables.number"},
114
- expect: {
115
- json: {name: "kind_of.string", number: "kind_of.integer"}
116
- }
117
- )
118
- ]
119
- ),
120
- ##################################################
121
- "destroy_#{singular_name}" => base_spec.merge(
122
- url: "/#{plural_name}/{id}",
123
- method: "delete",
124
- query: {id: 1},
125
- expectations: [
126
- base_constraint
127
- ]
128
- )
129
- }
130
-
131
- generate_yaml(hash)
132
- end
133
-
134
- def generate_factory(name)
135
- singular_name = name.singularize
136
-
137
- hash = {
138
- singular_name => {
139
- class: singular_name.titleize,
140
- attributes: {
141
- attribute: "value"
142
- }
143
- }
144
- }
145
-
146
- generate_yaml(hash)
147
- end
148
-
149
- def generate_yaml(hash)
150
- result = hash.deep_stringify_keys.join_map("\n") do |key, value|
151
- {key => value}.to_yaml
152
- .sub!("---\n", "")
153
- .gsub("!ruby/regexp ", "")
59
+ def initialize(name)
60
+ @original_name = name
61
+ @plural_name = name.pluralize
62
+ @singular_name = name.singularize
154
63
  end
155
64
 
156
- result.delete!("\"")
157
- result
65
+ def call
66
+ binding
67
+ end
158
68
  end
159
69
  end
160
70
  end
@@ -4,13 +4,63 @@ module SpecForge
4
4
  class CLI
5
5
  class Run < Command
6
6
  command_name "run"
7
- syntax "run"
8
- summary "Runs all specs"
7
+ syntax "run [target]"
8
+
9
+ summary "Runs specs loaded from spec_forge/specs/"
10
+ description "Runs specs loaded from spec_forge/specs/. The optional target argument allows running specific files, specs, or expectations."
11
+
12
+ example "spec_forge run",
13
+ "Run all specs in spec_forge/specs/"
14
+
15
+ example "spec_forge run users",
16
+ "Run all specs in users.yml"
17
+
18
+ example "spec_forge run users:create_user",
19
+ "Run all expectations in the create_user spec"
20
+
21
+ example "spec_forge run users:create_user:\"POST /users\"",
22
+ "Run expectations matching POST /users"
23
+
24
+ example "spec_forge run users:create_user:\"POST /users - Create Admin\"",
25
+ "Run the specific expectation named \"Create Admin\""
9
26
 
10
27
  # option "-n", "--no-docs", "Do not generate OpenAPI documentation on completion"
11
28
 
12
29
  def call
13
- SpecForge.run
30
+ return SpecForge.run if arguments.blank?
31
+
32
+ # spec_forge users:show_user
33
+ filter = extract_filter(arguments.first)
34
+
35
+ # Filter and run the specs
36
+ SpecForge.run(**filter)
37
+ end
38
+
39
+ private
40
+
41
+ #
42
+ # The input can be
43
+ #
44
+ # "<file_name>" for a file
45
+ # Example: "users"
46
+ #
47
+ # "<file_name>:<spec_name>" for a single spec
48
+ # Example: "users:show_user"
49
+ #
50
+ # "<file_name:<spec_name>:'<verb> <path> - <?name>'" for a single expectation
51
+ # Example:
52
+ # "users:show_user:'GET /users/:id'"
53
+ # Example with name:
54
+ # "users:show_user:'GET /users/:id - Returns 404 due to missing user'"
55
+ #
56
+ def extract_filter(input)
57
+ # Note: Only split 3 because the expectation name can have colons in them.
58
+ file_name, spec_name, expectation_name = input.split(":", 3).map(&:strip)
59
+
60
+ # Remove the quotes
61
+ expectation_name.gsub!(/^['"]|['"]$/, "") if expectation_name.present?
62
+
63
+ {file_name:, spec_name:, expectation_name:}
14
64
  end
15
65
  end
16
66
  end
@@ -30,6 +30,9 @@ module SpecForge
30
30
  def initialize
31
31
  config = Normalizer.default_configuration
32
32
 
33
+ # Allows me to modify the error backtrace reporting within rspec
34
+ RSpec.configuration.instance_variable_set(:@backtrace_formatter, BacktraceFormatter)
35
+
33
36
  config[:base_url] = "http://localhost:3000"
34
37
  config[:factories] = Factories.new
35
38
  config[:specs] = RSpec.configuration
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpec
4
+ module Core
5
+ module Notifications
6
+ #
7
+ # I did attempt to do this without monkey patching
8
+ # Getting around the `rspec` word was making it difficult
9
+ #
10
+ class SummaryNotification
11
+ # Customizes RSpec's failure output to:
12
+ # 1. Use 'spec_forge' instead of 'rspec' for rerun commands
13
+ # 2. Remove line numbers since SpecForge uses dynamic spec generation
14
+ alias_method :og_colorized_rerun_commands, :colorized_rerun_commands
15
+
16
+ def colorized_rerun_commands(colorizer)
17
+ # Updating these at this point fixes the re-run for some failures - it depends
18
+ failed_examples.each do |example|
19
+ metadata = example.metadata[:example_group]
20
+
21
+ # I might've uncovered an inconsistency here
22
+ # When multiple specs fail, it appears that the rerun_commands will use
23
+ # :rerun_file_path from the example's metadata.
24
+ # But when a single spec is ran and fails, it's using :location.
25
+ example.metadata[:location] = metadata[:rerun_file_path]
26
+ example.metadata[:line_number] = metadata[:line_number]
27
+ end
28
+
29
+ og_colorized_rerun_commands.gsub(/rspec/i, "spec_forge")
30
+ .gsub(/\[[\d:]+\]/, "")
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ Dir[File.expand_path("core_ext/*.rb", __dir__)].sort.each do |path|
4
+ require path
5
+ end
@@ -5,32 +5,30 @@ module SpecForge
5
5
  #
6
6
  # Loads the factories from their yml files and registers them with FactoryBot
7
7
  #
8
- # @param path [String, Path] The base path where the factories directory are located
9
- #
10
- def self.load_and_register(base_path)
8
+ def self.load_and_register
11
9
  if SpecForge.configuration.factories.paths?
12
10
  FactoryBot.definition_file_paths = SpecForge.configuration.factories.paths
13
11
  end
14
12
 
15
13
  FactoryBot.find_definitions if SpecForge.configuration.factories.auto_discover?
16
14
 
17
- factories = load_from_path(base_path.join("factories", "**/*.yml"))
15
+ factories = load_from_files
18
16
  factories.each(&:register)
19
17
  end
20
18
 
21
19
  #
22
- # Loads any factories defined in the path. A single file can contain one or more factories
23
- #
24
- # @param path [String, Path] The path where the factories are located
20
+ # Loads any factories defined in the factories. A single file can contain one or more factories
25
21
  #
26
22
  # @return [Array<Factory>] An array of factories that were loaded.
27
23
  # Note: This factories have not been registered with FactoryBot.
28
24
  # See #register
29
25
  #
30
- def self.load_from_path(path)
26
+ def self.load_from_files
27
+ path = SpecForge.forge.join("factories", "**/*.yml")
28
+
31
29
  factories = []
32
30
 
33
- Dir[path].map do |file_path|
31
+ Dir[path].each do |file_path|
34
32
  hash = YAML.load_file(file_path).deep_symbolize_keys
35
33
 
36
34
  hash.each do |factory_name, factory_hash|
@@ -23,6 +23,9 @@ module SpecForge
23
23
  spec_forge.expectations.each do |expectation|
24
24
  # Define the example group
25
25
  describe(expectation.name) do
26
+ # Set up the class metadata for error reporting
27
+ runner_forge.set_group_metadata(self, spec_forge, expectation)
28
+
26
29
  constraints = expectation.constraints
27
30
 
28
31
  let!(:expected_status) { constraints.status.resolve }
@@ -31,6 +34,9 @@ module SpecForge
31
34
  before do
32
35
  # Ensure all variables are called and resolved, in case they are not referenced
33
36
  expectation.variables.resolve
37
+
38
+ # Set up the example metadata for error reporting
39
+ runner_forge.set_example_metadata(spec_forge, expectation)
34
40
  end
35
41
 
36
42
  subject(:response) { expectation.http_client.call }
@@ -54,9 +60,31 @@ module SpecForge
54
60
  end
55
61
  end
56
62
 
63
+ # @private
57
64
  def handle_debug(...)
58
65
  DebugProxy.new(...).call
59
66
  end
67
+
68
+ # @private
69
+ def set_group_metadata(context, spec, expectation)
70
+ metadata = {
71
+ file_path: spec.file_path,
72
+ absolute_file_path: spec.file_path,
73
+ line_number: spec.line_number,
74
+ location: spec.file_path,
75
+ rerun_file_path: "#{spec.file_name}:#{spec.name}:\"#{expectation.name}\""
76
+ }
77
+
78
+ context.metadata.merge!(metadata)
79
+ end
80
+
81
+ # @private
82
+ def set_example_metadata(spec, expectation)
83
+ # This is needed when an error raises in an example
84
+ metadata = {location: "#{spec.file_path}:#{spec.line_number}"}
85
+
86
+ RSpec.current_example.metadata.merge!(metadata)
87
+ end
60
88
  end
61
89
 
62
90
  ################################################################################################
@@ -5,31 +5,49 @@ require_relative "spec/expectation"
5
5
  module SpecForge
6
6
  class Spec
7
7
  #
8
- # Loads the specs from their yml files and defines them with the test runner
8
+ # Loads and defines specs with the runner. Specs can be filtered using the optional parameters
9
9
  #
10
- # @param path [String, Path] The base path where the specs directory is located
10
+ # @param file_name [String, nil] The name of the file without the extension.
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.
11
13
  #
12
- def self.load_and_define(base_path)
13
- specs = load_from_path(base_path.join("specs", "**/*.yml"))
14
+ # @return [Array<Spec>]
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
+
14
28
  specs.each(&:define)
15
29
  end
16
30
 
17
31
  #
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
32
+ # Loads any specs defined in the spec files. A single file can contain one or more specs
21
33
  #
22
34
  # @return [Array<Spec>] An array of specs that were loaded.
23
35
  #
24
- def self.load_from_path(path)
36
+ def self.load_from_files
37
+ path = SpecForge.forge.join("specs")
25
38
  specs = []
26
39
 
27
- Dir[path].map do |file_path|
28
- hash = YAML.load_file(file_path).deep_symbolize_keys
40
+ Dir[path.join("**/*.yml")].each do |file_path|
41
+ content = File.read(file_path)
42
+ hash = YAML.load(content).deep_symbolize_keys
29
43
 
30
44
  hash.each do |spec_name, spec_hash|
31
- spec_hash[:name] = spec_name
45
+ line_number = content.lines.index { |line| line.start_with?("#{spec_name}:") }
46
+
47
+ spec_hash[:name] = spec_name.to_s
32
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
33
51
 
34
52
  specs << new(**spec_hash)
35
53
  end
@@ -38,11 +56,34 @@ module SpecForge
38
56
  specs
39
57
  end
40
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
78
+
79
+ specs
80
+ end
81
+
41
82
  ############################################################################
42
83
 
43
84
  attr_predicate :debug
44
85
 
45
- attr_reader :name, :file_path, :expectations
86
+ attr_reader :name, :file_path, :file_name, :line_number, :expectations
46
87
 
47
88
  #
48
89
  # Creates a Spec based on the input
@@ -52,9 +93,11 @@ module SpecForge
52
93
  # @param **input [Hash] Any attributes related to the spec, including expectations
53
94
  # See Normalizer::Spec
54
95
  #
55
- def initialize(name:, file_path:, **input)
96
+ def initialize(name:, file_path:, file_name:, line_number:, **input)
56
97
  @name = name
57
98
  @file_path = file_path
99
+ @file_name = file_name
100
+ @line_number = line_number
58
101
 
59
102
  input = Normalizer.normalize_spec!(input)
60
103
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SpecForge
4
- VERSION = "0.3.2"
4
+ VERSION = "0.4.0"
5
5
  end
data/lib/spec_forge.rb CHANGED
@@ -17,8 +17,10 @@ require "thor"
17
17
  require "yaml"
18
18
 
19
19
  require_relative "spec_forge/attribute"
20
+ require_relative "spec_forge/backtrace_formatter"
20
21
  require_relative "spec_forge/cli"
21
22
  require_relative "spec_forge/configuration"
23
+ require_relative "spec_forge/core_ext"
22
24
  require_relative "spec_forge/error"
23
25
  require_relative "spec_forge/factory"
24
26
  require_relative "spec_forge/http"
@@ -34,15 +36,21 @@ module SpecForge
34
36
  #
35
37
  # @param path [String] The file path that contains factories and specs
36
38
  #
37
- def self.run(path = SpecForge.forge)
39
+ def self.run(file_name: nil, spec_name: nil, expectation_name: nil)
40
+ path = SpecForge.forge
41
+
42
+ # Initialize
38
43
  forge_helper = path.join("forge_helper.rb")
39
44
  require_relative forge_helper if File.exist?(forge_helper)
40
45
 
46
+ # Validate
41
47
  configuration.validate
42
48
 
43
- Factory.load_and_register(path)
44
- Spec.load_and_define(path)
49
+ # Prepare
50
+ Factory.load_and_register
51
+ Spec.load_and_define(file_name:, spec_name:, expectation_name:)
45
52
 
53
+ # Run
46
54
  Runner.run
47
55
  end
48
56
 
@@ -0,0 +1,4 @@
1
+ <%= original_name %>:
2
+ class: <%= singular_name.titleize %>
3
+ attributes:
4
+ active: true
@@ -0,0 +1,43 @@
1
+ index_<%= plural_name %>:
2
+ url: /<%= plural_name %>
3
+ expectations:
4
+ - expect:
5
+ status: 200
6
+
7
+ show_<%= singular_name %>:
8
+ url: /<%= plural_name %>/{id}
9
+ query:
10
+ id: 1
11
+ expectations:
12
+ - expect:
13
+ status: 200
14
+
15
+ create_<%= singular_name %>:
16
+ url: /<%= plural_name %>
17
+ method: post
18
+ body:
19
+ name: faker.name.name
20
+ email: faker.internet.email
21
+ expectations:
22
+ - expect:
23
+ status: 200
24
+
25
+ update_<%= singular_name %>:
26
+ url: /<%= plural_name %>/{id}
27
+ method: patch
28
+ query:
29
+ id: 1
30
+ body:
31
+ name:
32
+ expectations:
33
+ - expect:
34
+ status: 200
35
+
36
+ destroy_<%= singular_name %>:
37
+ url: /<%= plural_name %>/{id}
38
+ method: delete
39
+ query:
40
+ id: 1
41
+ expectations:
42
+ - expect:
43
+ status: 200
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spec_forge
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bryan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-02-20 00:00:00.000000000 Z
11
+ date: 2025-02-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '7.0'
19
+ version: '7.1'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '7.0'
26
+ version: '7.1'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: commander
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -44,14 +44,14 @@ dependencies:
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '0.1'
47
+ version: '0.2'
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '0.1'
54
+ version: '0.2'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: factory_bot
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -172,6 +172,7 @@ files:
172
172
  - lib/spec_forge/attribute/resolvable_hash.rb
173
173
  - lib/spec_forge/attribute/transform.rb
174
174
  - lib/spec_forge/attribute/variable.rb
175
+ - lib/spec_forge/backtrace_formatter.rb
175
176
  - lib/spec_forge/cli.rb
176
177
  - lib/spec_forge/cli/actions.rb
177
178
  - lib/spec_forge/cli/command.rb
@@ -179,6 +180,8 @@ files:
179
180
  - lib/spec_forge/cli/new.rb
180
181
  - lib/spec_forge/cli/run.rb
181
182
  - lib/spec_forge/configuration.rb
183
+ - lib/spec_forge/core_ext.rb
184
+ - lib/spec_forge/core_ext/rspec.rb
182
185
  - lib/spec_forge/error.rb
183
186
  - lib/spec_forge/factory.rb
184
187
  - lib/spec_forge/http.rb
@@ -200,6 +203,8 @@ files:
200
203
  - lib/spec_forge/type.rb
201
204
  - lib/spec_forge/version.rb
202
205
  - lib/templates/forge_helper.tt
206
+ - lib/templates/new_factory.tt
207
+ - lib/templates/new_spec.tt
203
208
  - spec_forge/factories/user.yml
204
209
  - spec_forge/forge_helper.rb
205
210
  - spec_forge/specs/users.yml