create_tests 1.0.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 +4 -4
- data/README.md +45 -10
- data/bin/create_tests +17 -0
- data/lib/create-tests/create_helper.rb +66 -0
- data/lib/create-tests/create_test.rb +315 -80
- data/lib/create-tests/from.rb +47 -12
- data/lib/create-tests/resource_chain.rb +14 -2
- data/lib/create-tests/version.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: adf779b9d966308976925449218e2f71071441bc665a7c5ecdd79c9ec25c477e
|
|
4
|
+
data.tar.gz: 49cd07ebd23c1dd406ee5db3af61c3a761615ecec746c451d059717ad8a668dd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 948a204d7595c2b5e5ddbd42e18029b870f0fd9212df3a20dd970b29416d69306332b2f31ecd57fc9b82d8a1ad3b6e15424312248a5c80d26f9ae0ca14e90f00
|
|
7
|
+
data.tar.gz: 9510e4c3f35eba997841db074bd32b8ef07712bacd88cda7bb49581deea70d1381148d833e3791c5b3c8b21a2d7259fb2fffacfae157dfd15080ad46ca9e5d9c
|
data/README.md
CHANGED
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://rubygems.org/gems/create_tests)
|
|
4
4
|
[](https://github.com/MarioRuiz/create_tests/actions/workflows/ci.yml)
|
|
5
|
+
[](https://github.com/MarioRuiz/create_tests/actions/workflows/ci.yml)
|
|
5
6
|

|
|
6
7
|

|
|
7
8
|

|
|
8
9
|

|
|
9
10
|
|
|
10
|
-
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.
|
|
11
12
|
|
|
12
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).
|
|
13
14
|
|
|
@@ -28,8 +29,8 @@ The gem uses [rufo](https://github.com/ruby-formatter/rufo) to format generated
|
|
|
28
29
|
After installation you can run using the command line executable or from Ruby code.
|
|
29
30
|
|
|
30
31
|
The execution will create:
|
|
31
|
-
- A `spec/` folder with one RSpec test file per API endpoint
|
|
32
|
-
- 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
|
|
33
34
|
- A `settings/general.rb` with NiceHttp configuration boilerplate
|
|
34
35
|
|
|
35
36
|
### Command line
|
|
@@ -43,6 +44,10 @@ Usage: create_tests [requests_file] [options]
|
|
|
43
44
|
-d, --dry_run Preview which files would be created or modified
|
|
44
45
|
--cleanup_each Generate cleanup in after(:each) instead of after(:all)
|
|
45
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
|
|
46
51
|
-v, --version Show version
|
|
47
52
|
```
|
|
48
53
|
|
|
@@ -78,11 +83,13 @@ For each API endpoint, `create_tests` generates up to 7 test types:
|
|
|
78
83
|
|
|
79
84
|
4. **"returns expected mock response"** -- For endpoints with `mock_response` data (from `open_api_import`), validates the mock response matches the expected code, message, and structure.
|
|
80
85
|
|
|
81
|
-
5. **"handles multiple valid data variations"** -- For POST/PUT/PATCH endpoints with
|
|
86
|
+
5. **"handles multiple valid data variations"** -- For POST/PUT/PATCH endpoints with a payload source, uses `generate_n(5, :correct)` from nice_hash to test 5 different valid payload variations. Prefers `:data_pattern` when present (real string_pattern / enum / range values from open_api_import); otherwise uses `:data`. Generated values are assigned to `request[:data]` before the call.
|
|
82
87
|
|
|
83
|
-
6. **"returns error when individual data fields are invalid"** -- Uses `NiceHash.change_one_by_one` to systematically test each
|
|
88
|
+
6. **"returns error when individual data fields are invalid"** -- Uses `NiceHash.change_one_by_one` on `:data_pattern` (preferred) or `:data` to systematically test each field being wrong one at a time, asserting the server rejects each one.
|
|
84
89
|
|
|
85
|
-
7. **"returns error if required parameter on data empty/missing"** -- For endpoints with `data_required`, tests both empty and missing values for each required data field.
|
|
90
|
+
7. **"returns error if required parameter on data empty/missing"** -- For endpoints with `data_required`, tests both empty and missing values for each required data field. When `:data` is absent but `:data_examples` is present (common for form uploads), the first example is used as the body for success and required-data tests.
|
|
91
|
+
|
|
92
|
+
Required keyword parameters from open_api_import `create_constants: true` (e.g. `def self.get_item(id: ID)`) get Helper stubs and are called as keywords (`id: @id`), same as positional required args.
|
|
86
93
|
|
|
87
94
|
## Parameters
|
|
88
95
|
|
|
@@ -90,7 +97,7 @@ For each API endpoint, `create_tests` generates up to 7 test types:
|
|
|
90
97
|
|
|
91
98
|
Accepts three options: `:overwrite`, `:dont_overwrite` and `:append`. Default: `:append`.
|
|
92
99
|
|
|
93
|
-
- **append**: If the test file already exists, only missing tests will be added. Existing content is preserved
|
|
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.
|
|
94
101
|
|
|
95
102
|
- **dont_overwrite**: If the test file exists, no changes will be made. If it doesn't exist, it will be created.
|
|
96
103
|
|
|
@@ -119,12 +126,40 @@ result.each { |path, content| puts "#{path}: #{content.length} bytes" }
|
|
|
119
126
|
|
|
120
127
|
### spec_dir / settings_dir
|
|
121
128
|
|
|
122
|
-
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`.
|
|
123
130
|
|
|
124
131
|
```ruby
|
|
125
132
|
CreateTests.from "./requests/uber.yaml.rb", spec_dir: "./test/spec", settings_dir: "./test/config"
|
|
126
133
|
```
|
|
127
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
|
+
|
|
128
163
|
### cleanup
|
|
129
164
|
|
|
130
165
|
Controls whether and how prerequisite setup/cleanup hooks are generated. Default: `:after_all`.
|
|
@@ -142,7 +177,7 @@ CreateTests.from "./requests/netapp.json.rb", cleanup: false # no cleanup
|
|
|
142
177
|
```ruby
|
|
143
178
|
before(:all) do
|
|
144
179
|
@http = NiceHttp.new()
|
|
145
|
-
@volume_name = @volume_name + "-
|
|
180
|
+
@volume_name = @volume_name + "-createorupda"
|
|
146
181
|
expect(Helper.setup_capacity_pools(@http, ...).state).to eq "Succeeded"
|
|
147
182
|
@request = Volumes.create_or_update(...)
|
|
148
183
|
end
|
|
@@ -157,7 +192,7 @@ end
|
|
|
157
192
|
|
|
158
193
|
```ruby
|
|
159
194
|
before(:all) do
|
|
160
|
-
@snapshot_name = @snapshot_name + "-
|
|
195
|
+
@snapshot_name = @snapshot_name + "-delete"
|
|
161
196
|
expect(Helper.setup_volumes(@http, ...).state).to eq "Succeeded"
|
|
162
197
|
@request = Snapshots.delete(...)
|
|
163
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,39 +1,58 @@
|
|
|
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
|
-
|
|
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 = []
|
|
17
23
|
keywords_required = []
|
|
24
|
+
keyreq_kwargs = {}
|
|
18
25
|
method_obj.parameters.each do |p|
|
|
19
|
-
if p[0] == :req #required
|
|
26
|
+
if p[0] == :req #required positional
|
|
20
27
|
params << "@#{p[1]}"
|
|
28
|
+
elsif p[0] == :keyreq
|
|
29
|
+
keywords_required << p[1]
|
|
30
|
+
keyreq_kwargs[p[1]] = nil
|
|
21
31
|
elsif p[0] == :key and p[1].to_s.match?(/^[a-z]/i) and Object.const_defined?(p[1].to_s.upcase)
|
|
32
|
+
# create_constants: required path/query as keyword with UPCASE constant default
|
|
22
33
|
keywords_required << p[1]
|
|
23
34
|
end
|
|
24
35
|
end
|
|
25
|
-
|
|
36
|
+
call_parts = params.dup
|
|
37
|
+
keywords_required.each { |k| call_parts << "#{k}: @#{k}" }
|
|
38
|
+
req_txt += call_parts.join(", ")
|
|
26
39
|
req_txt += ")"
|
|
27
40
|
require 'nice_hash'
|
|
28
|
-
|
|
41
|
+
# Keyword-only methods (create_constants) must not receive nil positionals;
|
|
42
|
+
# :keyreq needs an explicit keyword so Method#call succeeds.
|
|
43
|
+
request = method_obj.call(*Array.new(params.size), **keyreq_kwargs)
|
|
29
44
|
|
|
30
45
|
req_txt = "#{mod_name}.#{req_txt}"
|
|
31
46
|
params_declaration_txt = ""
|
|
32
|
-
## todo: add param in case of :append
|
|
47
|
+
## todo: add param in case of :append
|
|
33
48
|
params.each do |p|
|
|
34
49
|
params_declaration_txt << "#{p} = Helper.#{p.gsub('@','')}(@http)\n"
|
|
35
50
|
@params << p
|
|
36
51
|
end
|
|
52
|
+
keywords_required.each do |k|
|
|
53
|
+
params_declaration_txt << "@#{k} = Helper.#{k}(@http)\n"
|
|
54
|
+
@params << "@#{k}"
|
|
55
|
+
end
|
|
37
56
|
|
|
38
57
|
all_params_for_chain = params + keywords_required.map { |k| "@#{k}" }
|
|
39
58
|
resource_chain, target_resource = extract_resource_chain(request, all_params_for_chain)
|
|
@@ -45,8 +64,7 @@ class CreateTests
|
|
|
45
64
|
needs_target_setup = target_resource && [:delete, :get, :patch].include?(request[:method])
|
|
46
65
|
parent_resource = resource_chain.last
|
|
47
66
|
|
|
48
|
-
method_suffix = method_txt.to_s.
|
|
49
|
-
method_suffix = "-#{method_suffix}"
|
|
67
|
+
method_suffix = "-#{method_txt.to_s.gsub('_', '')[0, 12]}"
|
|
50
68
|
|
|
51
69
|
setup_before_all_txt = ""
|
|
52
70
|
setup_before_each_txt = ""
|
|
@@ -54,33 +72,39 @@ class CreateTests
|
|
|
54
72
|
after_all_txt = ""
|
|
55
73
|
|
|
56
74
|
if is_delete && target_resource && parent_resource
|
|
57
|
-
setup_before_all_txt = "
|
|
58
|
-
setup_before_each_txt = "
|
|
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"
|
|
59
77
|
if cleanup
|
|
60
|
-
after_each_txt =
|
|
61
|
-
after_all_txt =
|
|
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)
|
|
62
80
|
end
|
|
63
81
|
elsif needs_target_setup && target_resource
|
|
64
|
-
setup_before_all_txt = "
|
|
82
|
+
setup_before_all_txt = "#{assert_eq("Helper.setup_#{target_resource[:resource]}(@http, #{target_resource[:params_up_to].join(', ')}).state", "'Succeeded'", test)}\n"
|
|
65
83
|
if cleanup
|
|
66
|
-
after_all_txt =
|
|
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)
|
|
67
85
|
end
|
|
68
86
|
elsif parent_resource
|
|
69
|
-
setup_before_all_txt = "
|
|
87
|
+
setup_before_all_txt = "#{assert_eq("Helper.setup_#{parent_resource[:resource]}(@http, #{parent_resource[:params_up_to].join(', ')}).state", "'Succeeded'", test)}\n"
|
|
70
88
|
if cleanup && target_resource
|
|
71
|
-
after_each_txt =
|
|
72
|
-
after_all_txt =
|
|
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)
|
|
73
91
|
elsif cleanup
|
|
74
|
-
after_all_txt =
|
|
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)
|
|
75
93
|
end
|
|
76
94
|
end
|
|
77
95
|
|
|
78
96
|
if cleanup == :after_each && !after_all_txt.empty?
|
|
79
|
-
|
|
80
|
-
|
|
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
|
|
81
105
|
end
|
|
82
106
|
|
|
83
|
-
if test_txt ==""
|
|
107
|
+
if test_txt == ""
|
|
84
108
|
modified = true
|
|
85
109
|
|
|
86
110
|
suffix_txt = ""
|
|
@@ -89,12 +113,15 @@ class CreateTests
|
|
|
89
113
|
suffix_txt = "#{target_param} = #{target_param} + \"#{method_suffix}\"\n"
|
|
90
114
|
end
|
|
91
115
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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"
|
|
96
123
|
|
|
97
|
-
|
|
124
|
+
output = "
|
|
98
125
|
require_relative '#{settings_relative_path}'
|
|
99
126
|
if defined?(#{mod_name}) and defined?(#{mod_name}.#{method_txt})
|
|
100
127
|
RSpec.describe #{mod_name}, '##{method_txt}' do
|
|
@@ -104,147 +131,179 @@ class CreateTests
|
|
|
104
131
|
@http.logger.info(\"\\n\#{'+'*50} Before All ends \#{'+'*50}\")
|
|
105
132
|
end
|
|
106
133
|
#{after_all_txt}#{after_each_txt}#{before_each_block}\n"
|
|
134
|
+
end
|
|
107
135
|
else
|
|
108
136
|
output = test_txt
|
|
109
|
-
if
|
|
110
|
-
output.gsub!(/\s*end\s
|
|
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")
|
|
111
141
|
else
|
|
112
|
-
output.gsub!(/\s*end\s*\Z/,"\n")
|
|
142
|
+
output.gsub!(/\s*end\s*\Z/, "\n")
|
|
113
143
|
end
|
|
114
144
|
end
|
|
115
145
|
|
|
116
146
|
|
|
117
147
|
tests = Hash.new()
|
|
118
148
|
|
|
149
|
+
has_data_examples = request.key?(:data_examples) &&
|
|
150
|
+
request[:data_examples].is_a?(Array) &&
|
|
151
|
+
!request[:data_examples].empty?
|
|
152
|
+
# Prefer data_pattern for variation/invalid-field tests; fall back to :data
|
|
153
|
+
payload_source = if request.key?(:data_pattern)
|
|
154
|
+
":data_pattern"
|
|
155
|
+
elsif request.key?(:data)
|
|
156
|
+
":data"
|
|
157
|
+
end
|
|
158
|
+
|
|
119
159
|
# first response on responses is the one expected to be returned when success
|
|
120
|
-
if request.key?(:responses) and request[:responses].size > 0
|
|
160
|
+
if include_kind?(only_kinds, :success) && request.key?(:responses) and request[:responses].size > 0
|
|
121
161
|
code = request[:responses].keys[0]
|
|
122
|
-
title="
|
|
123
|
-
|
|
162
|
+
title = example_title("has correct structure in successful response", test, trailing_space: true)
|
|
163
|
+
if !request.key?(:data) && has_data_examples
|
|
164
|
+
tests[title] = "do
|
|
165
|
+
request = @request.deep_copy
|
|
166
|
+
request[:data] = @request[:data_examples].first
|
|
167
|
+
resp = @http.#{request[:method]}(request)
|
|
168
|
+
#{assert_eq('resp.code', code, test)}\n"
|
|
169
|
+
if request[:responses][code].is_a?(Hash) and request[:responses][code].key?(:data)
|
|
170
|
+
tests[title] +="result = NiceHttp.validate_response(resp, @request.responses._#{code}.data, include_diff: true)\n"
|
|
171
|
+
tests[title] += "#{assert_be_true('result[:ok]', 'Structure mismatch: #{result[:diff]}', test)}\n"
|
|
172
|
+
end
|
|
173
|
+
tests[title] += "end\n"
|
|
174
|
+
else
|
|
175
|
+
tests[title] = "do
|
|
124
176
|
resp = @http.#{request[:method]}(@request)
|
|
125
|
-
|
|
177
|
+
#{assert_eq('resp.code', code, test)}\n"
|
|
126
178
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
179
|
+
if request[:responses][code].is_a?(Hash) and request[:responses][code].key?(:data)
|
|
180
|
+
tests[title] +="result = NiceHttp.validate_response(resp, @request.responses._#{code}.data, include_diff: true)\n"
|
|
181
|
+
tests[title] += "#{assert_be_true('result[:ok]', 'Structure mismatch: #{result[:diff]}', test)}\n"
|
|
182
|
+
end
|
|
183
|
+
tests[title] += "end\n"
|
|
130
184
|
end
|
|
131
|
-
tests[title] += "end\n"
|
|
132
185
|
end
|
|
133
186
|
|
|
134
|
-
|
|
135
|
-
|
|
187
|
+
if include_kind?(only_kinds, :auth)
|
|
188
|
+
title = example_title("doesn\\'t retrieve data if not authenticated", test)
|
|
189
|
+
tests[title] = "do
|
|
136
190
|
http = NiceHttp.new()
|
|
137
191
|
http.headers = {}
|
|
138
192
|
resp = http.#{request[:method]}(@request)
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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"
|
|
143
199
|
end
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
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)
|
|
147
202
|
empty_param = ""
|
|
148
203
|
params.each do |p|
|
|
149
204
|
r = req_txt.gsub(/#{p}([),])/, '""\1')
|
|
150
205
|
empty_param += "
|
|
151
206
|
request = #{r}
|
|
152
207
|
resp = @http.#{request[:method]}(request)
|
|
153
|
-
|
|
208
|
+
#{assert_between('resp.code', "'400'", "'499'", test)}\n"
|
|
154
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
|
|
155
|
-
empty_param += "
|
|
210
|
+
empty_param += "#{assert_match('resp.message', '/\#{request.responses[resp.code.to_sym].message}/i', test)}\n"
|
|
156
211
|
end
|
|
157
212
|
end
|
|
158
|
-
if keywords_required.size > 0
|
|
213
|
+
if keywords_required.size > 0
|
|
159
214
|
r = req_txt.scan(/(.+)\(/).join
|
|
160
215
|
empty_param += "
|
|
161
216
|
[:#{keywords_required.join(', :')}].each do |kw|
|
|
162
217
|
request = #{r}(kw => '')
|
|
163
218
|
resp = @http.#{request[:method]}(request)
|
|
164
|
-
|
|
219
|
+
#{assert_between('resp.code', "'400'", "'499'", test)}\n"
|
|
165
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
|
|
166
|
-
empty_param += "
|
|
221
|
+
empty_param += "#{assert_match('resp.message', '/\#{request.responses[resp.code.to_sym].message}/i', test)}\n"
|
|
167
222
|
end
|
|
168
223
|
empty_param += "end\n"
|
|
169
224
|
end
|
|
170
225
|
empty_param += "end\n"
|
|
171
|
-
tests["
|
|
226
|
+
tests[example_title("returns error if required parameter empty", test, trailing_space: true)] = "do\n#{empty_param}"
|
|
172
227
|
end
|
|
173
228
|
|
|
174
|
-
if request.key?(:mock_response)
|
|
175
|
-
title = "
|
|
229
|
+
if include_kind?(only_kinds, :mock) && request.key?(:mock_response)
|
|
230
|
+
title = example_title("returns expected mock response", test, trailing_space: true)
|
|
176
231
|
tests[title] = "do
|
|
177
232
|
@http_mock = NiceHttp.new()
|
|
178
233
|
@http_mock.use_mocks = true
|
|
179
234
|
resp = @http_mock.#{request[:method]}(@request)
|
|
180
|
-
|
|
181
|
-
|
|
235
|
+
#{assert_eq('resp.code', '@request[:mock_response][:code]', test)}
|
|
236
|
+
#{assert_eq('resp.message', '@request[:mock_response][:message]', test)}\n"
|
|
182
237
|
if request[:mock_response].key?(:data)
|
|
183
|
-
tests[title] += "
|
|
238
|
+
tests[title] += "#{assert_be_true('NiceHash.compare_structure(@request[:mock_response][:data], resp.data.json, true)', nil, test)}\n"
|
|
184
239
|
end
|
|
185
240
|
tests[title] += "end\n"
|
|
186
241
|
end
|
|
187
242
|
|
|
188
|
-
if
|
|
189
|
-
title = "
|
|
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)
|
|
190
245
|
tests[title] = "do
|
|
191
|
-
@request[
|
|
246
|
+
@request[#{payload_source}].generate_n(5, :correct).each do |generated_data|
|
|
192
247
|
request = @request.deep_copy
|
|
193
248
|
request[:data] = generated_data
|
|
194
249
|
resp = @http.#{request[:method]}(request)
|
|
195
|
-
|
|
250
|
+
#{assert_between('resp.code.to_i', '200', '299', test)}
|
|
196
251
|
end
|
|
197
252
|
end\n"
|
|
198
253
|
end
|
|
199
254
|
|
|
200
|
-
if
|
|
201
|
-
title = "
|
|
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)
|
|
202
257
|
tests[title] = "do
|
|
203
|
-
wrong = @request[
|
|
204
|
-
NiceHash.change_one_by_one([@request[
|
|
258
|
+
wrong = @request[#{payload_source}].generate(:correct, errors: :min_length)
|
|
259
|
+
NiceHash.change_one_by_one([@request[#{payload_source}], :correct], wrong).each do |one_wrong|
|
|
205
260
|
request = @request.deep_copy
|
|
206
261
|
request[:data] = one_wrong
|
|
207
262
|
resp = @http.#{request[:method]}(request)
|
|
208
|
-
|
|
263
|
+
#{assert_between('resp.code.to_i', '200', '299', test, negate: true)}
|
|
209
264
|
end
|
|
210
265
|
end\n"
|
|
211
266
|
end
|
|
212
267
|
|
|
213
|
-
if request.key?(:
|
|
268
|
+
if include_kind?(only_kinds, :required_data) && request.key?(:data_required) && (request.key?(:data) || has_data_examples)
|
|
269
|
+
seed_data_line = if !request.key?(:data) && has_data_examples
|
|
270
|
+
"request[:data] = request[:data_examples].first\n "
|
|
271
|
+
else
|
|
272
|
+
""
|
|
273
|
+
end
|
|
214
274
|
empty_param_data = ""
|
|
215
275
|
empty_param_data += "
|
|
216
276
|
@request[:data_required].each do |p|
|
|
217
277
|
request = @request.deep_copy
|
|
218
|
-
request.values_for[p] = ''
|
|
278
|
+
#{seed_data_line}request.values_for[p] = ''
|
|
219
279
|
resp = @http.#{request[:method]}(request)
|
|
220
|
-
|
|
280
|
+
#{assert_between('resp.code', "'200'", "'299'", test, negate: true)}
|
|
221
281
|
if request.responses.key?(resp.code.to_sym)
|
|
222
|
-
|
|
282
|
+
#{assert_match('resp.message', '/\#{request.responses[resp.code.to_sym].message}/i', test)}
|
|
223
283
|
end
|
|
224
284
|
end
|
|
225
285
|
"
|
|
226
286
|
empty_param_data += "end\n"
|
|
227
|
-
tests["
|
|
287
|
+
tests[example_title("returns error if required parameter on data empty", test, with_do: true)] = empty_param_data
|
|
228
288
|
|
|
229
289
|
missing_param_data = ""
|
|
230
290
|
missing_param_data += "
|
|
231
291
|
@request[:data_required].each do |p|
|
|
232
292
|
request = @request.deep_copy
|
|
233
|
-
NiceHash.delete_nested(request[:data], p)
|
|
293
|
+
#{seed_data_line}NiceHash.delete_nested(request[:data], p)
|
|
234
294
|
resp = @http.#{request[:method]}(request)
|
|
235
|
-
|
|
295
|
+
#{assert_between('resp.code', "'200'", "'299'", test, negate: true)}
|
|
236
296
|
if request.responses.key?(resp.code.to_sym)
|
|
237
|
-
|
|
297
|
+
#{assert_match('resp.message', '/\#{request.responses[resp.code.to_sym].message}/i', test)}
|
|
238
298
|
end
|
|
239
299
|
end
|
|
240
300
|
"
|
|
241
301
|
missing_param_data += "end\n"
|
|
242
|
-
tests["
|
|
302
|
+
tests[example_title("returns error if required parameter on data missing", test, with_do: true)] = missing_param_data
|
|
243
303
|
end
|
|
244
304
|
|
|
245
305
|
tests.each do |k,v|
|
|
246
|
-
unless
|
|
247
|
-
output.include?(k.gsub(' "'," '").gsub('" ',"' ").gsub("\n",'').gsub(/\s+do$/,'').gsub(/^\s*it/,''))
|
|
306
|
+
unless example_already_present?(output, k, test)
|
|
248
307
|
modified = true
|
|
249
308
|
message = " = test added #{k} for #{method_txt}"
|
|
250
309
|
@logger.info message
|
|
@@ -252,15 +311,191 @@ class CreateTests
|
|
|
252
311
|
puts message
|
|
253
312
|
output +="# Appended #{Time.now.stamp}\n"
|
|
254
313
|
end
|
|
255
|
-
|
|
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
|
|
256
321
|
end
|
|
257
322
|
end
|
|
258
|
-
if
|
|
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?(')
|
|
259
330
|
output += "\nend\nend"
|
|
260
331
|
else
|
|
261
332
|
output +="\nend"
|
|
262
333
|
end
|
|
263
334
|
return modified, output
|
|
264
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
|
|
265
500
|
end
|
|
266
|
-
end
|
|
501
|
+
end
|
data/lib/create-tests/from.rb
CHANGED
|
@@ -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:
|
|
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
|
-
|
|
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
|
|
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}
|
|
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
|
|
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
|
-
|
|
77
|
-
|
|
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
|
data/lib/create-tests/version.rb
CHANGED
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.
|
|
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:
|