create_tests 1.1.0 → 1.2.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: 754317e21adff9283ea8d3424b46ee09de1f5a53d9e875b9388014e261f6d561
4
- data.tar.gz: e70d64dddd1f4312c67149e7ca3761e1d5038332252e9c8a191427e53627c3b9
3
+ metadata.gz: adf779b9d966308976925449218e2f71071441bc665a7c5ecdd79c9ec25c477e
4
+ data.tar.gz: 49cd07ebd23c1dd406ee5db3af61c3a761615ecec746c451d059717ad8a668dd
5
5
  SHA512:
6
- metadata.gz: 10f9687590b335bd26ab6bc039d9ad05eed6ca780608363c934db2a929f11ea229e267d95498935ac9755c4eb6bb4eec437b985d114efe747870e7371fc50dd5
7
- data.tar.gz: '048048f6f02865499ee314d55841e9e3259fad8c9edcb74c6fb3937e508eb2ccbdebe9b49c6870fe1e8984a466410aa61f0ea7a6b03ef2dfbbe1f1eeabadd752'
6
+ metadata.gz: 948a204d7595c2b5e5ddbd42e18029b870f0fd9212df3a20dd970b29416d69306332b2f31ecd57fc9b82d8a1ad3b6e15424312248a5c80d26f9ae0ca14e90f00
7
+ data.tar.gz: 9510e4c3f35eba997841db074bd32b8ef07712bacd88cda7bb49581deea70d1381148d833e3791c5b3c8b21a2d7259fb2fffacfae157dfd15080ad46ca9e5d9c
data/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
  ![GitHub last commit](https://img.shields.io/github/last-commit/MarioRuiz/create_tests)
9
9
  ![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/MarioRuiz/create_tests)
10
10
 
11
- Create RSpec tests automatically from Request Hash files. Perfect to be used with the result from importing a Swagger / OpenAPI file using the [open_api_import](https://github.com/MarioRuiz/open_api_import) gem.
11
+ Create RSpec or Minitest tests automatically from Request Hash files. Perfect to be used with the result from importing a Swagger / OpenAPI file using the [open_api_import](https://github.com/MarioRuiz/open_api_import) gem.
12
12
 
13
13
  We strongly recommend using [nice_http](https://github.com/MarioRuiz/nice_http) for your tests together with [nice_hash](https://github.com/MarioRuiz/nice_hash) and [string_pattern](https://github.com/MarioRuiz/string_pattern).
14
14
 
@@ -29,8 +29,8 @@ The gem uses [rufo](https://github.com/ruby-formatter/rufo) to format generated
29
29
  After installation you can run using the command line executable or from Ruby code.
30
30
 
31
31
  The execution will create:
32
- - A `spec/` folder with one RSpec test file per API endpoint
33
- - A `spec/helper.rb` with stub methods for required parameters
32
+ - A `spec/` folder with one RSpec test file per API endpoint (or `test/` with `*_test.rb` when using Minitest)
33
+ - A `spec/helper.rb` (or `test/helper.rb`) with stub methods for required parameters
34
34
  - A `settings/general.rb` with NiceHttp configuration boilerplate
35
35
 
36
36
  ### Command line
@@ -44,6 +44,10 @@ Usage: create_tests [requests_file] [options]
44
44
  -d, --dry_run Preview which files would be created or modified
45
45
  --cleanup_each Generate cleanup in after(:each) instead of after(:all)
46
46
  --no_cleanup Do not generate setup/cleanup hooks
47
+ --spec_dir DIR Output directory for test files (default: ./spec, or ./test with --minitest)
48
+ --settings_dir DIR Output directory for settings (default: ./settings)
49
+ --only KIND Generate only these test kinds (repeatable or comma-separated)
50
+ --minitest Generate Minitest tests instead of RSpec
47
51
  -v, --version Show version
48
52
  ```
49
53
 
@@ -93,7 +97,7 @@ Required keyword parameters from open_api_import `create_constants: true` (e.g.
93
97
 
94
98
  Accepts three options: `:overwrite`, `:dont_overwrite` and `:append`. Default: `:append`.
95
99
 
96
- - **append**: If the test file already exists, only missing tests will be added. Existing content is preserved, including `helper/setup.rb` and `helper/cleanup.rb`. If the file doesn't exist, it will be created with all tests.
100
+ - **append**: If the test file already exists, only missing tests will be added. Existing content is preserved. For `helper/setup.rb` and `helper/cleanup.rb`, only missing `setup_*` / `cleanup_*` methods are inserted; existing method bodies are left alone. If every method already exists, the file is not rewritten. If the file doesn't exist, it will be created with all tests.
97
101
 
98
102
  - **dont_overwrite**: If the test file exists, no changes will be made. If it doesn't exist, it will be created.
99
103
 
@@ -122,12 +126,40 @@ result.each { |path, content| puts "#{path}: #{content.length} bytes" }
122
126
 
123
127
  ### spec_dir / settings_dir
124
128
 
125
- Custom output directories (default: `./spec` and `./settings`).
129
+ Custom output directories (default: `./spec` and `./settings`). With `test: :minitest`, the default test directory is `./test` unless you pass an explicit `spec_dir`.
126
130
 
127
131
  ```ruby
128
132
  CreateTests.from "./requests/uber.yaml.rb", spec_dir: "./test/spec", settings_dir: "./test/config"
129
133
  ```
130
134
 
135
+ ### only
136
+
137
+ Limit which example kinds are generated. Default `nil` means all kinds. Accepts a symbol, an array, or a comma-separated string. Unknown kinds raise.
138
+
139
+ Kinds: `success`, `auth`, `required_params`, `mock`, `variations`, `invalid_fields`, `required_data`.
140
+
141
+ ```ruby
142
+ CreateTests.from "./requests/uber.yaml.rb", only: [:success, :auth]
143
+ CreateTests.from "./requests/uber.yaml.rb", only: "success,mock"
144
+ ```
145
+
146
+ ```bash
147
+ create_tests ./requests/uber.yaml.rb --only success --only auth
148
+ create_tests ./requests/uber.yaml.rb --only success,auth
149
+ ```
150
+
151
+ ### test / Minitest
152
+
153
+ Default is `:rspec`. Pass `test: :minitest` (or `--minitest`) to generate Minitest files (`*_test.rb`) with `setup` / `teardown` instead of RSpec hooks, and `assert_*` / `refute_*` instead of `expect`.
154
+
155
+ ```ruby
156
+ CreateTests.from "./requests/uber.yaml.rb", test: :minitest
157
+ ```
158
+
159
+ ```bash
160
+ create_tests ./requests/uber.yaml.rb --minitest
161
+ ```
162
+
131
163
  ### cleanup
132
164
 
133
165
  Controls whether and how prerequisite setup/cleanup hooks are generated. Default: `:after_all`.
@@ -145,7 +177,7 @@ CreateTests.from "./requests/netapp.json.rb", cleanup: false # no cleanup
145
177
  ```ruby
146
178
  before(:all) do
147
179
  @http = NiceHttp.new()
148
- @volume_name = @volume_name + "-cre"
180
+ @volume_name = @volume_name + "-createorupda"
149
181
  expect(Helper.setup_capacity_pools(@http, ...).state).to eq "Succeeded"
150
182
  @request = Volumes.create_or_update(...)
151
183
  end
@@ -160,7 +192,7 @@ end
160
192
 
161
193
  ```ruby
162
194
  before(:all) do
163
- @snapshot_name = @snapshot_name + "-del"
195
+ @snapshot_name = @snapshot_name + "-delete"
164
196
  expect(Helper.setup_volumes(@http, ...).state).to eq "Succeeded"
165
197
  @request = Snapshots.delete(...)
166
198
  end
data/bin/create_tests CHANGED
@@ -37,6 +37,23 @@ optparse = OptionParser.new do |opts|
37
37
  options[:cleanup] = false
38
38
  end
39
39
 
40
+ opts.on("--spec_dir DIR", "Output directory for generated test files (default: ./spec, or ./test with --minitest)") do |dir|
41
+ options[:spec_dir] = dir
42
+ end
43
+
44
+ opts.on("--settings_dir DIR", "Output directory for generated settings files (default: ./settings)") do |dir|
45
+ options[:settings_dir] = dir
46
+ end
47
+
48
+ opts.on("--only KIND", "Generate only these test kinds (repeatable or comma-separated). Kinds: success, auth, required_params, mock, variations, invalid_fields, required_data") do |kind|
49
+ options[:only] ||= []
50
+ options[:only].concat(kind.split(",").map { |s| s.strip.to_sym })
51
+ end
52
+
53
+ opts.on("--minitest", "Generate Minitest tests instead of RSpec") do
54
+ options[:test] = :minitest
55
+ end
56
+
40
57
  opts.on("-v", "--version", "Show version") do
41
58
  puts "create_tests #{CreateTests::VERSION}"
42
59
  exit
@@ -125,5 +125,71 @@ class CreateTests
125
125
  end
126
126
  best
127
127
  end
128
+
129
+ # Append only missing setup_/cleanup_ methods from generated content into an
130
+ # existing file. Existing method bodies are left untouched.
131
+ # @return [Array(String, Boolean)] merged content and whether anything was added
132
+ private def append_missing_chain_methods(existing_txt, generated_txt)
133
+ method_blocks = extract_chain_method_blocks(generated_txt)
134
+ missing = method_blocks.reject { |blk| existing_txt.include?("def self.#{blk[:name]}(") }
135
+ return [existing_txt, false] if missing.empty?
136
+
137
+ output = existing_txt.dup
138
+ insert = missing.map { |blk| blk[:text] }.join
139
+
140
+ if output.match?(/\bmodule\s+Helper\b/)
141
+ if output.match?(/\s*end\s*\Z/)
142
+ output.sub!(/\s*end\s*\Z/, "\n#{insert}\nend\n")
143
+ else
144
+ output << "\n#{insert}"
145
+ end
146
+ else
147
+ # Preserve leading require (e.g. ostruct) from generated content when wrapping.
148
+ require_line = generated_txt[/^require[^\n]*\n/]
149
+ prefix = require_line && !output.include?(require_line.strip) ? require_line : ""
150
+ output = "#{prefix}module Helper\n#{output.chomp}\n#{insert}\nend\n"
151
+ end
152
+
153
+ [output, true]
154
+ end
155
+
156
+ private def extract_chain_method_blocks(generated_txt)
157
+ blocks = []
158
+ lines = generated_txt.lines
159
+ i = 0
160
+ while i < lines.size
161
+ if lines[i] =~ /\A#/
162
+ comment_start = i
163
+ i += 1 while i < lines.size && lines[i] =~ /\A#/
164
+ if i < lines.size && lines[i] =~ /\Adef self\.(setup_\w+|cleanup_\w+)\(/
165
+ name = $1
166
+ start = comment_start
167
+ i += 1
168
+ depth = 1
169
+ while i < lines.size && depth > 0
170
+ depth += 1 if lines[i] =~ /\A\s*def\b/
171
+ depth -= 1 if lines[i] =~ /\Aend\b/
172
+ i += 1
173
+ end
174
+ blocks << { name: name, text: lines[start...i].join }
175
+ next
176
+ end
177
+ elsif lines[i] =~ /\Adef self\.(setup_\w+|cleanup_\w+)\(/
178
+ name = $1
179
+ start = i
180
+ i += 1
181
+ depth = 1
182
+ while i < lines.size && depth > 0
183
+ depth += 1 if lines[i] =~ /\A\s*def\b/
184
+ depth -= 1 if lines[i] =~ /\Aend\b/
185
+ i += 1
186
+ end
187
+ blocks << { name: name, text: lines[start...i].join }
188
+ next
189
+ end
190
+ i += 1
191
+ end
192
+ blocks
193
+ end
128
194
  end
129
195
  end
@@ -1,16 +1,22 @@
1
1
 
2
2
  class CreateTests
3
3
 
4
+ TEST_KINDS = %i[
5
+ success auth required_params mock variations invalid_fields required_data
6
+ ].freeze
7
+
4
8
  class << self
5
9
 
6
10
  # Return true or false and the test source code
7
- private def create_test(module_txt, method_txt, method_obj, test_txt, settings_relative_path: '../../settings/general', cleanup: :after_all)
11
+ private def create_test(module_txt, method_txt, method_obj, test_txt, settings_relative_path: '../../settings/general', cleanup: :after_all, test: :rspec, only: nil)
8
12
  unless cleanup == false || [:after_all, :after_each].include?(cleanup)
9
13
  message = "** Wrong cleanup parameter: #{cleanup}"
10
14
  raise message
11
15
  end
12
16
 
13
- modified = false
17
+ only_kinds = normalize_only(only)
18
+
19
+ modified = false
14
20
  mod_name = module_txt.scan(/::(\w+)$/).join
15
21
  req_txt = "#{method_txt}("
16
22
  params = []
@@ -38,7 +44,7 @@ class CreateTests
38
44
 
39
45
  req_txt = "#{mod_name}.#{req_txt}"
40
46
  params_declaration_txt = ""
41
- ## todo: add param in case of :append
47
+ ## todo: add param in case of :append
42
48
  params.each do |p|
43
49
  params_declaration_txt << "#{p} = Helper.#{p.gsub('@','')}(@http)\n"
44
50
  @params << p
@@ -58,8 +64,7 @@ class CreateTests
58
64
  needs_target_setup = target_resource && [:delete, :get, :patch].include?(request[:method])
59
65
  parent_resource = resource_chain.last
60
66
 
61
- method_suffix = method_txt.to_s.split("_").map { |w| w[0..2] }.join[0..2]
62
- method_suffix = "-#{method_suffix}"
67
+ method_suffix = "-#{method_txt.to_s.gsub('_', '')[0, 12]}"
63
68
 
64
69
  setup_before_all_txt = ""
65
70
  setup_before_each_txt = ""
@@ -67,33 +72,39 @@ class CreateTests
67
72
  after_all_txt = ""
68
73
 
69
74
  if is_delete && target_resource && parent_resource
70
- setup_before_all_txt = "expect(Helper.setup_#{parent_resource[:resource]}(@http, #{parent_resource[:params_up_to].join(', ')}).state).to eq 'Succeeded'\n"
71
- setup_before_each_txt = "expect(Helper.setup_#{target_resource[:resource]}(@http, #{target_resource[:params_up_to].join(', ')}).state).to eq 'Succeeded'\n"
75
+ setup_before_all_txt = "#{assert_eq("Helper.setup_#{parent_resource[:resource]}(@http, #{parent_resource[:params_up_to].join(', ')}).state", "'Succeeded'", test)}\n"
76
+ setup_before_each_txt = "#{assert_eq("Helper.setup_#{target_resource[:resource]}(@http, #{target_resource[:params_up_to].join(', ')}).state", "'Succeeded'", test)}\n"
72
77
  if cleanup
73
- after_each_txt = "after(:each) do\nHelper.cleanup_#{target_resource[:resource]}(@http, #{target_resource[:params_up_to].join(', ')})\nend\n"
74
- after_all_txt = "after(:all) do\nunless defined?(DONT_DELETE) && DONT_DELETE\nHelper.cleanup_#{parent_resource[:resource]}(@http, #{parent_resource[:params_up_to].join(', ')})\nend\nend\n"
78
+ after_each_txt = framework_hook(:after_each, "Helper.cleanup_#{target_resource[:resource]}(@http, #{target_resource[:params_up_to].join(', ')})\n", test)
79
+ after_all_txt = framework_hook(:after_all, "unless defined?(DONT_DELETE) && DONT_DELETE\nHelper.cleanup_#{parent_resource[:resource]}(@http, #{parent_resource[:params_up_to].join(', ')})\nend\n", test)
75
80
  end
76
81
  elsif needs_target_setup && target_resource
77
- setup_before_all_txt = "expect(Helper.setup_#{target_resource[:resource]}(@http, #{target_resource[:params_up_to].join(', ')}).state).to eq 'Succeeded'\n"
82
+ setup_before_all_txt = "#{assert_eq("Helper.setup_#{target_resource[:resource]}(@http, #{target_resource[:params_up_to].join(', ')}).state", "'Succeeded'", test)}\n"
78
83
  if cleanup
79
- after_all_txt = "after(:all) do\nunless defined?(DONT_DELETE) && DONT_DELETE\nHelper.cleanup_#{target_resource[:resource]}(@http, #{target_resource[:params_up_to].join(', ')})\nend\nend\n"
84
+ after_all_txt = framework_hook(:after_all, "unless defined?(DONT_DELETE) && DONT_DELETE\nHelper.cleanup_#{target_resource[:resource]}(@http, #{target_resource[:params_up_to].join(', ')})\nend\n", test)
80
85
  end
81
86
  elsif parent_resource
82
- setup_before_all_txt = "expect(Helper.setup_#{parent_resource[:resource]}(@http, #{parent_resource[:params_up_to].join(', ')}).state).to eq 'Succeeded'\n"
87
+ setup_before_all_txt = "#{assert_eq("Helper.setup_#{parent_resource[:resource]}(@http, #{parent_resource[:params_up_to].join(', ')}).state", "'Succeeded'", test)}\n"
83
88
  if cleanup && target_resource
84
- after_each_txt = "after(:each) do\nHelper.cleanup_#{target_resource[:resource]}(@http, #{target_resource[:params_up_to].join(', ')})\nend\n"
85
- after_all_txt = "after(:all) do\nunless defined?(DONT_DELETE) && DONT_DELETE\nHelper.cleanup_#{parent_resource[:resource]}(@http, #{parent_resource[:params_up_to].join(', ')})\nend\nend\n"
89
+ after_each_txt = framework_hook(:after_each, "Helper.cleanup_#{target_resource[:resource]}(@http, #{target_resource[:params_up_to].join(', ')})\n", test)
90
+ after_all_txt = framework_hook(:after_all, "unless defined?(DONT_DELETE) && DONT_DELETE\nHelper.cleanup_#{parent_resource[:resource]}(@http, #{parent_resource[:params_up_to].join(', ')})\nend\n", test)
86
91
  elsif cleanup
87
- after_all_txt = "after(:all) do\nunless defined?(DONT_DELETE) && DONT_DELETE\nHelper.cleanup_#{parent_resource[:resource]}(@http, #{parent_resource[:params_up_to].join(', ')})\nend\nend\n"
92
+ after_all_txt = framework_hook(:after_all, "unless defined?(DONT_DELETE) && DONT_DELETE\nHelper.cleanup_#{parent_resource[:resource]}(@http, #{parent_resource[:params_up_to].join(', ')})\nend\n", test)
88
93
  end
89
94
  end
90
95
 
91
96
  if cleanup == :after_each && !after_all_txt.empty?
92
- after_each_txt += after_all_txt.sub("after(:all)", "after(:each)")
93
- after_all_txt = ""
97
+ if test == :minitest
98
+ # Minitest folds after(:all) into teardown; after_each mode just uses teardown body once.
99
+ after_each_txt += after_all_txt
100
+ after_all_txt = ""
101
+ else
102
+ after_each_txt += after_all_txt.sub("after(:all)", "after(:each)")
103
+ after_all_txt = ""
104
+ end
94
105
  end
95
106
 
96
- if test_txt ==""
107
+ if test_txt == ""
97
108
  modified = true
98
109
 
99
110
  suffix_txt = ""
@@ -102,12 +113,15 @@ class CreateTests
102
113
  suffix_txt = "#{target_param} = #{target_param} + \"#{method_suffix}\"\n"
103
114
  end
104
115
 
105
- before_each_block = "before(:each) do |example|\n"
106
- before_each_block << "@http = NiceHttp.new()\n" if is_delete && target_resource
107
- before_each_block << setup_before_each_txt
108
- before_each_block << "@http.logger.info(\"\\n\\n\#{'='*100}\\nTest: \#{example.description}\\n\#{'-'*100}\")\nend\n"
116
+ if test == :minitest
117
+ output = build_minitest_skeleton(mod_name, method_txt, settings_relative_path, params_declaration_txt, suffix_txt, setup_before_all_txt, setup_before_each_txt, after_each_txt, after_all_txt, req_txt, is_delete, target_resource)
118
+ else
119
+ before_each_block = "before(:each) do |example|\n"
120
+ before_each_block << "@http = NiceHttp.new()\n" if is_delete && target_resource
121
+ before_each_block << setup_before_each_txt
122
+ before_each_block << "@http.logger.info(\"\\n\\n\#{'='*100}\\nTest: \#{example.description}\\n\#{'-'*100}\")\nend\n"
109
123
 
110
- output = "
124
+ output = "
111
125
  require_relative '#{settings_relative_path}'
112
126
  if defined?(#{mod_name}) and defined?(#{mod_name}.#{method_txt})
113
127
  RSpec.describe #{mod_name}, '##{method_txt}' do
@@ -117,12 +131,15 @@ class CreateTests
117
131
  @http.logger.info(\"\\n\#{'+'*50} Before All ends \#{'+'*50}\")
118
132
  end
119
133
  #{after_all_txt}#{after_each_txt}#{before_each_block}\n"
134
+ end
120
135
  else
121
136
  output = test_txt
122
- if output.match?(/\s*end\s*end\s*end\s*\Z/)
123
- output.gsub!(/\s*end\s*end\s*\Z/,"\n")
137
+ if test == :minitest
138
+ output.gsub!(/\s*end\s*\Z/, "\n")
139
+ elsif output.match?(/\s*end\s*end\s*end\s*\Z/)
140
+ output.gsub!(/\s*end\s*end\s*\Z/, "\n")
124
141
  else
125
- output.gsub!(/\s*end\s*\Z/,"\n")
142
+ output.gsub!(/\s*end\s*\Z/, "\n")
126
143
  end
127
144
  end
128
145
 
@@ -140,113 +157,115 @@ class CreateTests
140
157
  end
141
158
 
142
159
  # first response on responses is the one expected to be returned when success
143
- if request.key?(:responses) and request[:responses].size > 0
160
+ if include_kind?(only_kinds, :success) && request.key?(:responses) and request[:responses].size > 0
144
161
  code = request[:responses].keys[0]
145
- title="it 'has correct structure in successful response' "
162
+ title = example_title("has correct structure in successful response", test, trailing_space: true)
146
163
  if !request.key?(:data) && has_data_examples
147
164
  tests[title] = "do
148
165
  request = @request.deep_copy
149
166
  request[:data] = @request[:data_examples].first
150
167
  resp = @http.#{request[:method]}(request)
151
- expect(resp.code).to eq #{code}\n"
168
+ #{assert_eq('resp.code', code, test)}\n"
152
169
  if request[:responses][code].is_a?(Hash) and request[:responses][code].key?(:data)
153
170
  tests[title] +="result = NiceHttp.validate_response(resp, @request.responses._#{code}.data, include_diff: true)\n"
154
- tests[title] +="expect(result[:ok]).to be(true), \"Structure mismatch: \#{result[:diff]}\"\n"
171
+ tests[title] += "#{assert_be_true('result[:ok]', 'Structure mismatch: #{result[:diff]}', test)}\n"
155
172
  end
156
173
  tests[title] += "end\n"
157
174
  else
158
175
  tests[title] = "do
159
176
  resp = @http.#{request[:method]}(@request)
160
- expect(resp.code).to eq #{code}\n"
177
+ #{assert_eq('resp.code', code, test)}\n"
161
178
 
162
179
  if request[:responses][code].is_a?(Hash) and request[:responses][code].key?(:data)
163
180
  tests[title] +="result = NiceHttp.validate_response(resp, @request.responses._#{code}.data, include_diff: true)\n"
164
- tests[title] +="expect(result[:ok]).to be(true), \"Structure mismatch: \#{result[:diff]}\"\n"
181
+ tests[title] += "#{assert_be_true('result[:ok]', 'Structure mismatch: #{result[:diff]}', test)}\n"
165
182
  end
166
183
  tests[title] += "end\n"
167
184
  end
168
185
  end
169
186
 
170
- title = "it 'doesn\\'t retrieve data if not authenticated'"
171
- tests[title] = "do
187
+ if include_kind?(only_kinds, :auth)
188
+ title = example_title("doesn\\'t retrieve data if not authenticated", test)
189
+ tests[title] = "do
172
190
  http = NiceHttp.new()
173
191
  http.headers = {}
174
192
  resp = http.#{request[:method]}(@request)
175
- expect(resp.code).to be_between('400', '499')\n"
176
- if request.key?(:responses) and (request[:responses].keys.select{|c| c.to_s.to_i>=400&&c.to_s.to_i<=499}).size>0
177
- tests[title] += "expect(NiceHash.compare_structure(@request.responses[resp.code.to_sym].data, resp.data.json)).to eq true
178
- expect(resp.message).to eq @request.responses[resp.code.to_sym].message\n"
193
+ #{assert_between('resp.code', "'400'", "'499'", test)}\n"
194
+ if request.key?(:responses) and (request[:responses].keys.select{|c| c.to_s.to_i>=400&&c.to_s.to_i<=499}).size>0
195
+ tests[title] += "#{assert_eq('NiceHash.compare_structure(@request.responses[resp.code.to_sym].data, resp.data.json)', 'true', test)}
196
+ #{assert_eq('resp.message', '@request.responses[resp.code.to_sym].message', test)}\n"
197
+ end
198
+ tests[title] += "end\n"
179
199
  end
180
- tests[title] += "end\n"
181
-
182
- if params.size > 0 or keywords_required.size >0
200
+
201
+ if include_kind?(only_kinds, :required_params) && (params.size > 0 or keywords_required.size >0)
183
202
  empty_param = ""
184
203
  params.each do |p|
185
204
  r = req_txt.gsub(/#{p}([),])/, '""\1')
186
205
  empty_param += "
187
206
  request = #{r}
188
207
  resp = @http.#{request[:method]}(request)
189
- expect(resp.code).to be_between('400', '499')\n"
208
+ #{assert_between('resp.code', "'400'", "'499'", test)}\n"
190
209
  if request.key?(:responses) and (request[:responses].keys.select{|c| c.to_s.to_i>=400&&c.to_s.to_i<=499}).size>0
191
- empty_param += "expect(resp.message).to match /\#{request.responses[resp.code.to_sym].message}/i\n"
210
+ empty_param += "#{assert_match('resp.message', '/\#{request.responses[resp.code.to_sym].message}/i', test)}\n"
192
211
  end
193
212
  end
194
- if keywords_required.size > 0
213
+ if keywords_required.size > 0
195
214
  r = req_txt.scan(/(.+)\(/).join
196
215
  empty_param += "
197
216
  [:#{keywords_required.join(', :')}].each do |kw|
198
217
  request = #{r}(kw => '')
199
218
  resp = @http.#{request[:method]}(request)
200
- expect(resp.code).to be_between('400', '499')\n"
219
+ #{assert_between('resp.code', "'400'", "'499'", test)}\n"
201
220
  if request.key?(:responses) and (request[:responses].keys.select{|c| c.to_s.to_i>=400&&c.to_s.to_i<=499}).size>0
202
- empty_param += "expect(resp.message).to match /\#{request.responses[resp.code.to_sym].message}/i\n"
221
+ empty_param += "#{assert_match('resp.message', '/\#{request.responses[resp.code.to_sym].message}/i', test)}\n"
203
222
  end
204
223
  empty_param += "end\n"
205
224
  end
206
225
  empty_param += "end\n"
207
- tests["it 'returns error if required parameter empty' "] = "do\n#{empty_param}"
226
+ tests[example_title("returns error if required parameter empty", test, trailing_space: true)] = "do\n#{empty_param}"
208
227
  end
209
228
 
210
- if request.key?(:mock_response)
211
- title = "it 'returns expected mock response' "
229
+ if include_kind?(only_kinds, :mock) && request.key?(:mock_response)
230
+ title = example_title("returns expected mock response", test, trailing_space: true)
212
231
  tests[title] = "do
213
232
  @http_mock = NiceHttp.new()
214
233
  @http_mock.use_mocks = true
215
234
  resp = @http_mock.#{request[:method]}(@request)
216
- expect(resp.code).to eq @request[:mock_response][:code]
217
- expect(resp.message).to eq @request[:mock_response][:message]\n"
235
+ #{assert_eq('resp.code', '@request[:mock_response][:code]', test)}
236
+ #{assert_eq('resp.message', '@request[:mock_response][:message]', test)}\n"
218
237
  if request[:mock_response].key?(:data)
219
- tests[title] += "expect(NiceHash.compare_structure(@request[:mock_response][:data], resp.data.json, true)).to be true\n"
238
+ tests[title] += "#{assert_be_true('NiceHash.compare_structure(@request[:mock_response][:data], resp.data.json, true)', nil, test)}\n"
220
239
  end
221
240
  tests[title] += "end\n"
222
241
  end
223
242
 
224
- if payload_source && [:post, :put, :patch].include?(request[:method])
225
- title = "it 'handles multiple valid data variations' "
243
+ if include_kind?(only_kinds, :variations) && payload_source && [:post, :put, :patch].include?(request[:method])
244
+ title = example_title("handles multiple valid data variations", test, trailing_space: true)
226
245
  tests[title] = "do
227
246
  @request[#{payload_source}].generate_n(5, :correct).each do |generated_data|
228
247
  request = @request.deep_copy
229
248
  request[:data] = generated_data
230
249
  resp = @http.#{request[:method]}(request)
231
- expect(resp.code.to_i).to be_between(200, 299)
250
+ #{assert_between('resp.code.to_i', '200', '299', test)}
232
251
  end
233
252
  end\n"
234
253
  end
235
254
 
236
- if payload_source
237
- title = "it 'returns error when individual data fields are invalid' "
255
+ if include_kind?(only_kinds, :invalid_fields) && payload_source
256
+ title = example_title("returns error when individual data fields are invalid", test, trailing_space: true)
238
257
  tests[title] = "do
239
258
  wrong = @request[#{payload_source}].generate(:correct, errors: :min_length)
240
259
  NiceHash.change_one_by_one([@request[#{payload_source}], :correct], wrong).each do |one_wrong|
241
260
  request = @request.deep_copy
242
261
  request[:data] = one_wrong
243
262
  resp = @http.#{request[:method]}(request)
244
- expect(resp.code.to_i).not_to be_between(200, 299)
263
+ #{assert_between('resp.code.to_i', '200', '299', test, negate: true)}
245
264
  end
246
265
  end\n"
247
266
  end
248
267
 
249
- if request.key?(:data_required) && (request.key?(:data) || has_data_examples)
268
+ if include_kind?(only_kinds, :required_data) && request.key?(:data_required) && (request.key?(:data) || has_data_examples)
250
269
  seed_data_line = if !request.key?(:data) && has_data_examples
251
270
  "request[:data] = request[:data_examples].first\n "
252
271
  else
@@ -258,14 +277,14 @@ class CreateTests
258
277
  request = @request.deep_copy
259
278
  #{seed_data_line}request.values_for[p] = ''
260
279
  resp = @http.#{request[:method]}(request)
261
- expect(resp.code).not_to be_between('200', '299')
280
+ #{assert_between('resp.code', "'200'", "'299'", test, negate: true)}
262
281
  if request.responses.key?(resp.code.to_sym)
263
- expect(resp.message).to match /\#{request.responses[resp.code.to_sym].message}/i
282
+ #{assert_match('resp.message', '/\#{request.responses[resp.code.to_sym].message}/i', test)}
264
283
  end
265
284
  end
266
285
  "
267
286
  empty_param_data += "end\n"
268
- tests["it 'returns error if required parameter on data empty' do\n"] = empty_param_data
287
+ tests[example_title("returns error if required parameter on data empty", test, with_do: true)] = empty_param_data
269
288
 
270
289
  missing_param_data = ""
271
290
  missing_param_data += "
@@ -273,19 +292,18 @@ class CreateTests
273
292
  request = @request.deep_copy
274
293
  #{seed_data_line}NiceHash.delete_nested(request[:data], p)
275
294
  resp = @http.#{request[:method]}(request)
276
- expect(resp.code).not_to be_between('200', '299')
295
+ #{assert_between('resp.code', "'200'", "'299'", test, negate: true)}
277
296
  if request.responses.key?(resp.code.to_sym)
278
- expect(resp.message).to match /\#{request.responses[resp.code.to_sym].message}/i
297
+ #{assert_match('resp.message', '/\#{request.responses[resp.code.to_sym].message}/i', test)}
279
298
  end
280
299
  end
281
300
  "
282
301
  missing_param_data += "end\n"
283
- tests["it 'returns error if required parameter on data missing' do\n"] = missing_param_data
302
+ tests[example_title("returns error if required parameter on data missing", test, with_do: true)] = missing_param_data
284
303
  end
285
304
 
286
305
  tests.each do |k,v|
287
- unless output.include?(k.gsub(" '",' "').gsub("' ",'" ').gsub("\n",'').gsub(/\s+do$/,'').gsub(/^\s*it/,'')) or
288
- output.include?(k.gsub(' "'," '").gsub('" ',"' ").gsub("\n",'').gsub(/\s+do$/,'').gsub(/^\s*it/,''))
306
+ unless example_already_present?(output, k, test)
289
307
  modified = true
290
308
  message = " = test added #{k} for #{method_txt}"
291
309
  @logger.info message
@@ -293,15 +311,191 @@ class CreateTests
293
311
  puts message
294
312
  output +="# Appended #{Time.now.stamp}\n"
295
313
  end
296
- output += "#{k}#{v}"
314
+ if test == :minitest
315
+ # keys are "def test_foo" and bodies start with "do\n...\nend"
316
+ body = v.sub(/\Ado\n/, "").sub(/\n?end\n?\z/, "\n")
317
+ output += "#{k}\n#{body}end\n"
318
+ else
319
+ output += "#{k}#{v}"
320
+ end
297
321
  end
298
322
  end
299
- if output.include?('defined?(')
323
+ if test == :minitest
324
+ if output.include?('defined?(')
325
+ output += "\nend\nend"
326
+ else
327
+ output += "\nend"
328
+ end
329
+ elsif output.include?('defined?(')
300
330
  output += "\nend\nend"
301
331
  else
302
332
  output +="\nend"
303
333
  end
304
334
  return modified, output
305
335
  end
336
+
337
+ private def normalize_only(only)
338
+ return nil if only.nil?
339
+ list = case only
340
+ when Symbol then [only]
341
+ when String then only.split(",").map { |s| s.strip.to_sym }
342
+ when Array then only.map { |s| s.to_s.strip.to_sym }
343
+ else
344
+ raise "** Wrong only parameter: #{only}"
345
+ end
346
+ unknown = list - TEST_KINDS
347
+ unless unknown.empty?
348
+ raise "** Wrong only parameter: unknown kind(s) #{unknown.join(', ')}. Valid: #{TEST_KINDS.join(', ')}"
349
+ end
350
+ list
351
+ end
352
+
353
+ private def include_kind?(only_kinds, kind)
354
+ only_kinds.nil? || only_kinds.include?(kind)
355
+ end
356
+
357
+ private def example_title(description, test, trailing_space: false, with_do: false)
358
+ if test == :minitest
359
+ "def test_#{minitest_method_name(description)}"
360
+ else
361
+ space = trailing_space ? " " : ""
362
+ if with_do
363
+ "it '#{description}' do\n"
364
+ else
365
+ "it '#{description}'#{space}"
366
+ end
367
+ end
368
+ end
369
+
370
+ private def minitest_method_name(description)
371
+ description.gsub("\\'", "")
372
+ .gsub(/[^a-zA-Z0-9]+/, "_")
373
+ .gsub(/\A_+|_+\z/, "")
374
+ .downcase
375
+ end
376
+
377
+ private def example_already_present?(output, key, test)
378
+ if test == :minitest
379
+ output.include?(key)
380
+ else
381
+ output.include?(key.gsub(" '", ' "').gsub("' ", '" ').gsub("\n", '').gsub(/\s+do$/, '').gsub(/^\s*it/, '')) ||
382
+ output.include?(key.gsub(' "', " '").gsub('" ', "' ").gsub("\n", '').gsub(/\s+do$/, '').gsub(/^\s*it/, ''))
383
+ end
384
+ end
385
+
386
+ private def assert_eq(actual, expected, test)
387
+ if test == :minitest
388
+ "assert_equal #{expected}, #{actual}"
389
+ else
390
+ "expect(#{actual}).to eq #{expected}"
391
+ end
392
+ end
393
+
394
+ private def assert_be_true(actual, message, test)
395
+ if test == :minitest
396
+ if message
397
+ "assert #{actual}, \"#{message}\""
398
+ else
399
+ "assert #{actual}"
400
+ end
401
+ else
402
+ if message
403
+ "expect(#{actual}).to be(true), \"#{message}\""
404
+ else
405
+ "expect(#{actual}).to be true"
406
+ end
407
+ end
408
+ end
409
+
410
+ private def assert_between(actual, low, high, test, negate: false)
411
+ if test == :minitest
412
+ if negate
413
+ "refute_includes (#{low}..#{high}), #{actual}"
414
+ else
415
+ "assert_includes (#{low}..#{high}), #{actual}"
416
+ end
417
+ else
418
+ if negate
419
+ "expect(#{actual}).not_to be_between(#{low}, #{high})"
420
+ else
421
+ "expect(#{actual}).to be_between(#{low}, #{high})"
422
+ end
423
+ end
424
+ end
425
+
426
+ private def assert_match(actual, pattern, test)
427
+ if test == :minitest
428
+ "assert_match #{pattern}, #{actual}"
429
+ else
430
+ "expect(#{actual}).to match #{pattern}"
431
+ end
432
+ end
433
+
434
+ private def framework_hook(kind, body, test)
435
+ if test == :minitest
436
+ # Stored as raw bodies; assembled into setup/teardown in the skeleton.
437
+ # Prefix with a marker so cleanup: :after_each merging can find them.
438
+ case kind
439
+ when :after_each then "TEARDOWN_EACH:#{body}"
440
+ when :after_all then "TEARDOWN_ALL:#{body}"
441
+ else body
442
+ end
443
+ else
444
+ case kind
445
+ when :after_each then "after(:each) do\n#{body}end\n"
446
+ when :after_all then "after(:all) do\n#{body}end\n"
447
+ else body
448
+ end
449
+ end
450
+ end
451
+
452
+ private def build_minitest_skeleton(mod_name, method_txt, settings_relative_path, params_declaration_txt, suffix_txt, setup_before_all_txt, setup_before_each_txt, after_each_txt, after_all_txt, req_txt, is_delete, target_resource)
453
+ class_name = "#{mod_name}#{camelize(method_txt.to_s)}Test"
454
+
455
+ teardown_each_body = after_each_txt.to_s.gsub(/\ATEARDOWN_EACH:/, "").gsub(/\ATEARDOWN_ALL:/, "")
456
+ # after_each_txt may contain both TEARDOWN_EACH and (after merge) TEARDOWN_ALL markers
457
+ each_parts = []
458
+ all_parts = []
459
+ [after_each_txt.to_s, after_all_txt.to_s].each do |chunk|
460
+ chunk.split(/(?=TEARDOWN_(?:EACH|ALL):)/).each do |part|
461
+ next if part.empty?
462
+ if part.start_with?("TEARDOWN_EACH:")
463
+ each_parts << part.sub(/\ATEARDOWN_EACH:/, "")
464
+ elsif part.start_with?("TEARDOWN_ALL:")
465
+ all_parts << part.sub(/\ATEARDOWN_ALL:/, "")
466
+ end
467
+ end
468
+ end
469
+
470
+ teardown_body = (each_parts + all_parts).join
471
+
472
+ setup_http = if is_delete && target_resource
473
+ "@http = NiceHttp.new()\n"
474
+ else
475
+ ""
476
+ end
477
+
478
+ # before(:all) work at start of setup; before(:each) after that.
479
+ # First test creates @http for non-delete; delete recreates in the each section.
480
+ "
481
+ require 'minitest/autorun'
482
+ require_relative '#{settings_relative_path}'
483
+ if defined?(#{mod_name}) and defined?(#{mod_name}.#{method_txt})
484
+ class #{class_name} < Minitest::Test
485
+ def setup
486
+ @http = NiceHttp.new()
487
+ #{params_declaration_txt}#{suffix_txt}#{setup_before_all_txt}@request = #{req_txt}
488
+ @http.logger.info(\"\\n\#{'+'*50} Before All ends \#{'+'*50}\")
489
+ #{setup_http}#{setup_before_each_txt}@http.logger.info(\"\\n\\n\#{'='*100}\\nTest: \#{name}\\n\#{'-'*100}\")
490
+ end
491
+ def teardown
492
+ #{teardown_body}
493
+ end
494
+ "
495
+ end
496
+
497
+ private def camelize(snake)
498
+ snake.to_s.split("_").map(&:capitalize).join
499
+ end
306
500
  end
307
- end
501
+ end
@@ -6,18 +6,19 @@ class CreateTests
6
6
  # More info about Request Hashes: https://github.com/MarioRuiz/Request-Hash
7
7
  # @param requests_file [String]. Path and file name. Could be absolute or relative to project root folder.
8
8
  # @param type [Symbol]. (default :request_hash). The kind of content that requests_file contains.
9
- # @param test [Symbol]. (default :rspec). What kind of tests we want to create
9
+ # @param test [Symbol]. (default :rspec). What kind of tests we want to create (:rspec or :minitest).
10
10
  # @param mode [Symbol]. (default :append). :overwrite, :append, :dont_overwrite. How we want to create the tests.
11
11
  # :overwrite, it will overwrite the test, settings, helper... files in case they exist so you will loose your original source code.
12
12
  # :dont_overwrite, it will create only the files that don't exist previously
13
13
  # :append, it will append or create the tests or helpers that corrently don't exist on every file, but it won't modify any other code.
14
14
  # @param dry_run [Boolean]. (default false). Preview which files would be created or modified without writing anything.
15
15
  # @param return_data [Boolean]. (default false). Return a Hash with generated file paths as keys and content as values, without writing files.
16
- # @param spec_dir [String]. (default './spec'). Output directory for generated test files.
16
+ # @param spec_dir [String]. (default './spec' for RSpec, './test' for Minitest). Output directory for generated test files.
17
17
  # @param settings_dir [String]. (default './settings'). Output directory for generated settings files.
18
18
  # @param cleanup [Symbol, false]. (default :after_all). :after_all, :after_each, or false. Controls cleanup hook generation.
19
+ # @param only [Array, Symbol, String, nil]. (default nil = all). Limit which example kinds are generated.
19
20
  ##############################################################################
20
- def self.from(requests_file, type: :request_hash, test: :rspec, mode: :append, dry_run: false, return_data: false, spec_dir: './spec', settings_dir: './settings', cleanup: :after_all)
21
+ def self.from(requests_file, type: :request_hash, test: :rspec, mode: :append, dry_run: false, return_data: false, spec_dir: nil, settings_dir: './settings', cleanup: :after_all, only: nil)
21
22
  no_write = dry_run || return_data
22
23
  @generated_data = {} if return_data
23
24
 
@@ -50,12 +51,17 @@ class CreateTests
50
51
  raise message
51
52
  end
52
53
 
53
- unless [:rspec].include?(test)
54
+ unless [:rspec, :minitest].include?(test)
54
55
  message = "** Wrong test parameter: #{test}"
55
56
  @logger.fatal message
56
57
  raise message
57
58
  end
58
59
 
60
+ # Default output dir depends on framework; an explicit spec_dir is always honored.
61
+ if spec_dir.nil?
62
+ spec_dir = test == :minitest ? './test' : './spec'
63
+ end
64
+
59
65
  unless [:overwrite, :dont_overwrite, :append].include?(mode)
60
66
  message = "** Wrong mode parameter: #{mode}"
61
67
  @logger.fatal message
@@ -68,12 +74,16 @@ class CreateTests
68
74
  raise message
69
75
  end
70
76
 
77
+ # Validate only early so bad values fail before generation.
78
+ normalize_only(only)
79
+
71
80
  if mode == :overwrite
72
81
  message = "** Pay attention, if any of the files exist, will be overwritten"
73
82
  @logger.warn message
74
83
  warn message
75
84
  elsif mode == :append
76
- if Dir["#{spec_dir}/*/**_spec.rb"].size > 0
85
+ existing_tests = Dir["#{spec_dir}/*/**_spec.rb"] + Dir["#{spec_dir}/*/**_test.rb"]
86
+ if existing_tests.size > 0
77
87
  message = "** Pay attention, if any of the test files exist or the help file exist only will be added the tests, methods that are missing."
78
88
  @logger.warn message
79
89
  warn message
@@ -94,7 +104,7 @@ class CreateTests
94
104
  helper_from_settings = helper_path.relative_path_from(resolved_settings_dir).to_s
95
105
  requests_from_settings = resolve_pathname(requests_file).relative_path_from(resolved_settings_dir).to_s
96
106
 
97
- Dir.mkdir spec_dir unless no_write or test != :rspec or Dir.exist?(spec_dir)
107
+ Dir.mkdir spec_dir unless no_write or Dir.exist?(spec_dir)
98
108
 
99
109
  add_settings = true
100
110
  settings_file = "#{settings_dir}/general.rb"
@@ -164,6 +174,8 @@ class CreateTests
164
174
  puts message unless no_write
165
175
  end
166
176
 
177
+ test_suffix = test == :minitest ? "_test.rb" : "_spec.rb"
178
+
167
179
  modules.each do |mod_txt|
168
180
  mod_name = mod_txt.scan(/::(\w+)$/).join
169
181
  folder = "#{spec_dir}/#{mod_name}"
@@ -174,7 +186,7 @@ class CreateTests
174
186
  mod_obj = Object.const_get(mod_txt)
175
187
  mod_methods_txt = mod_obj.methods(false)
176
188
  mod_methods_txt.each do |method_txt|
177
- test_file = "#{folder}/#{method_txt}_spec.rb"
189
+ test_file = "#{folder}/#{method_txt}#{test_suffix}"
178
190
  if File.exist?(test_file) and mode==:dont_overwrite
179
191
  message = "** The file #{test_file} already exists so no content will be added to it.\n"
180
192
  message += " Remove the test file to be able to be generated by create_tests or set mode: :overwrite, or mode: :append"
@@ -187,7 +199,7 @@ class CreateTests
187
199
  test_txt = ''
188
200
  end
189
201
  settings_from_test = settings_path.relative_path_from(resolve_pathname(folder)).to_s
190
- modified, txt = create_test(mod_txt, method_txt, mod_obj.method(method_txt), test_txt, settings_relative_path: settings_from_test, cleanup: cleanup)
202
+ modified, txt = create_test(mod_txt, method_txt, mod_obj.method(method_txt), test_txt, settings_relative_path: settings_from_test, cleanup: cleanup, test: test, only: only)
191
203
  if no_write
192
204
  @generated_data[test_file] = txt if return_data
193
205
  else
@@ -198,7 +210,7 @@ class CreateTests
198
210
  message = "- Test #{no_write ? 'would be created' : 'created'}: #{test_file}"
199
211
  elsif modified
200
212
  message = "- Test #{no_write ? 'would be updated' : 'updated'}: #{test_file}"
201
- else
213
+ else
202
214
  message = "- Test without changes: #{test_file}"
203
215
  end
204
216
  @logger.info message
@@ -234,13 +246,36 @@ class CreateTests
234
246
 
235
247
  if @setup_cleanup_resources.size > 0
236
248
  write_chain_file = lambda do |path, content, label|
237
- if File.exist?(path) && mode != :overwrite
249
+ if File.exist?(path) && mode == :dont_overwrite
238
250
  message = "** The file #{path} already exists so it will be left unchanged.\n"
239
- message += " Remove the file to regenerate it or set mode: :overwrite"
251
+ message += " Remove the file to regenerate it or set mode: :overwrite or :append"
240
252
  @logger.warn message
241
253
  warn message
242
254
  return
243
255
  end
256
+
257
+ if File.exist?(path) && mode == :append
258
+ existing = File.read(path)
259
+ merged, changed = append_missing_chain_methods(existing, content)
260
+ unless changed
261
+ message = "- #{label} without changes: #{path}"
262
+ @logger.info message
263
+ return
264
+ end
265
+ content = merged
266
+ if no_write
267
+ @generated_data[path] = content if return_data
268
+ puts "- #{label} (dry run): #{path}" if dry_run
269
+ else
270
+ File.open(path, "w") { |file| file.write(content) }
271
+ system("rufo", path, out: File::NULL, err: File::NULL)
272
+ message = "- #{label} updated: #{path}"
273
+ @logger.info message
274
+ puts message
275
+ end
276
+ return
277
+ end
278
+
244
279
  if no_write
245
280
  @generated_data[path] = content if return_data
246
281
  puts "- #{label} (dry run): #{path}" if dry_run
@@ -279,4 +314,4 @@ class CreateTests
279
314
  resolved
280
315
  end
281
316
  private_class_method :resolve_pathname
282
- end
317
+ end
@@ -3,10 +3,16 @@ class CreateTests
3
3
 
4
4
  class << self
5
5
 
6
+ SKIP_CHAIN_SEGMENTS = %w[subscriptions resource_groups providers].freeze
7
+
6
8
  # Extracts the resource dependency chain from a request path.
7
9
  # Works with both placeholder paths ({param}) and interpolated paths
8
10
  # (where params are resolved, possibly to empty strings).
9
11
  #
12
+ # Segments whose snake_case names are subscriptions, resource_groups, or
13
+ # providers are omitted from the setup/cleanup chain, but their parameters
14
+ # remain in params_up_to for later resources.
15
+ #
10
16
  # @param request [Hash] Request hash containing :path and :method keys
11
17
  # @param params [Array<String>] Positional parameter names (e.g. ["@subscription_id", "@pool_name"])
12
18
  # @return [Array<Hash>, Hash|nil] Two values: prerequisite chain array and target resource hash (or nil).
@@ -73,8 +79,14 @@ class CreateTests
73
79
  { resource: resource_name, param: matching_param, params_up_to: params_up_to }
74
80
  end
75
81
 
76
- prerequisites = all_entries[0..-2]
77
- target = all_entries.last
82
+ # Drop Azure infrastructure segments from the chain; keep their params in params_up_to.
83
+ real_entries = all_entries.reject { |e| SKIP_CHAIN_SEGMENTS.include?(e[:resource]) }
84
+
85
+ return [], nil if real_entries.empty?
86
+ return [], real_entries.first if real_entries.size == 1
87
+
88
+ prerequisites = real_entries[0..-2]
89
+ target = real_entries.last
78
90
 
79
91
  return prerequisites, target
80
92
  end
@@ -1,3 +1,3 @@
1
1
  class CreateTests
2
- VERSION = "1.1.0"
2
+ VERSION = "1.2.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: create_tests
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mario Ruiz
@@ -97,7 +97,10 @@ files:
97
97
  homepage: https://github.com/MarioRuiz/create_tests
98
98
  licenses:
99
99
  - MIT
100
- metadata: {}
100
+ metadata:
101
+ changelog_uri: https://github.com/MarioRuiz/create_tests/blob/master/CHANGELOG.md
102
+ source_code_uri: https://github.com/MarioRuiz/create_tests
103
+ bug_tracker_uri: https://github.com/MarioRuiz/create_tests/issues
101
104
  post_install_message: Thanks for installing! Visit us on https://github.com/MarioRuiz/create_tests
102
105
  rdoc_options: []
103
106
  require_paths: