create_tests 1.1.0 → 1.3.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 +42 -10
- data/bin/create_tests +17 -0
- data/lib/create-tests/create_helper.rb +66 -0
- data/lib/create-tests/create_test.rb +336 -74
- 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: 4ca29473a6da1f495ae3bb61fc97638c049f03a7e7093f54db1ac5a1e602b97b
|
|
4
|
+
data.tar.gz: 829f1a5085c49bc9339095d6fbb9d6f603f6badaa0691f9344e8c0f173ca58f1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 99a571060b7ab3971fff6ee6b0e8c67db21df52cc0ae00dbc57da0a26517aa94c8b63e8e606926bccb07a83c7d92e5f2a639495daf0c530f6bc6fe5b2189abfb
|
|
7
|
+
data.tar.gz: 785a8211a25f007e77b961868208081065b0f04e4d962382aa4b644acb757a6f6fc37b9c48e06b032e637906dcebac7127fabdfb0599a44e2719d65811bb7a3d
|
data/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|

|
|
9
9
|

|
|
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
|
|
|
@@ -71,7 +75,7 @@ CreateTests.from "./requests/uber.yaml.rb"
|
|
|
71
75
|
|
|
72
76
|
For each API endpoint, `create_tests` generates up to 7 test types:
|
|
73
77
|
|
|
74
|
-
1. **"has correct structure in successful response"** -- Calls the endpoint and validates the response structure using `NiceHttp.validate_response` with diff details on failure.
|
|
78
|
+
1. **"has correct structure in successful response"** -- Calls the endpoint and validates the response structure using `NiceHttp.validate_response` with diff details on failure. The expected status is the first 2xx response key, else `default`, else the first key. When `:data_default` is present, missing (or nil) keys in `:data` are filled before the call; existing example values are not overwritten.
|
|
75
79
|
|
|
76
80
|
2. **"doesn't retrieve data if not authenticated"** -- Calls with empty headers and asserts a 4xx response.
|
|
77
81
|
|
|
@@ -81,9 +85,9 @@ For each API endpoint, `create_tests` generates up to 7 test types:
|
|
|
81
85
|
|
|
82
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.
|
|
83
87
|
|
|
84
|
-
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.
|
|
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. Fields listed in `:data_read_only` are skipped.
|
|
85
89
|
|
|
86
|
-
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.
|
|
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. Otherwise `:data_default` can seed missing body fields the same way as the success example.
|
|
87
91
|
|
|
88
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.
|
|
89
93
|
|
|
@@ -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
|
|
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 + "-
|
|
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 + "-
|
|
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
|
-
|
|
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.
|
|
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 = "
|
|
71
|
-
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"
|
|
72
77
|
if cleanup
|
|
73
|
-
after_each_txt =
|
|
74
|
-
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)
|
|
75
80
|
end
|
|
76
81
|
elsif needs_target_setup && target_resource
|
|
77
|
-
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"
|
|
78
83
|
if cleanup
|
|
79
|
-
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)
|
|
80
85
|
end
|
|
81
86
|
elsif parent_resource
|
|
82
|
-
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"
|
|
83
88
|
if cleanup && target_resource
|
|
84
|
-
after_each_txt =
|
|
85
|
-
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)
|
|
86
91
|
elsif cleanup
|
|
87
|
-
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)
|
|
88
93
|
end
|
|
89
94
|
end
|
|
90
95
|
|
|
91
96
|
if cleanup == :after_each && !after_all_txt.empty?
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
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
|
|
123
|
-
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")
|
|
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
|
|
|
@@ -132,6 +149,16 @@ class CreateTests
|
|
|
132
149
|
has_data_examples = request.key?(:data_examples) &&
|
|
133
150
|
request[:data_examples].is_a?(Array) &&
|
|
134
151
|
!request[:data_examples].empty?
|
|
152
|
+
has_data_default = request.key?(:data_default) &&
|
|
153
|
+
request[:data_default].is_a?(Hash) &&
|
|
154
|
+
!request[:data_default].empty?
|
|
155
|
+
# Prefer data_examples over defaults when :data is absent
|
|
156
|
+
apply_data_default = has_data_default && (request.key?(:data) || !has_data_examples)
|
|
157
|
+
apply_defaults_txt = if apply_data_default
|
|
158
|
+
"@request[:data] ||= {}\n @request[:data_default].each do |k, v|\n @request[:data][k] = v if !@request[:data].key?(k) || @request[:data][k].nil?\n end\n "
|
|
159
|
+
else
|
|
160
|
+
""
|
|
161
|
+
end
|
|
135
162
|
# Prefer data_pattern for variation/invalid-field tests; fall back to :data
|
|
136
163
|
payload_source = if request.key?(:data_pattern)
|
|
137
164
|
":data_pattern"
|
|
@@ -139,116 +166,152 @@ class CreateTests
|
|
|
139
166
|
":data"
|
|
140
167
|
end
|
|
141
168
|
|
|
142
|
-
#
|
|
143
|
-
if request.key?(:responses) and request[:responses].size > 0
|
|
144
|
-
code = request[:responses]
|
|
145
|
-
|
|
169
|
+
# Success code: first 2xx key, else "default", else first key
|
|
170
|
+
if include_kind?(only_kinds, :success) && request.key?(:responses) and request[:responses].size > 0
|
|
171
|
+
code = success_response_code(request[:responses])
|
|
172
|
+
code_src = format_response_code(code)
|
|
173
|
+
title = example_title("has correct structure in successful response", test, trailing_space: true)
|
|
146
174
|
if !request.key?(:data) && has_data_examples
|
|
147
175
|
tests[title] = "do
|
|
148
176
|
request = @request.deep_copy
|
|
149
177
|
request[:data] = @request[:data_examples].first
|
|
150
178
|
resp = @http.#{request[:method]}(request)
|
|
151
|
-
|
|
179
|
+
#{assert_eq('resp.code', code_src, test)}\n"
|
|
152
180
|
if request[:responses][code].is_a?(Hash) and request[:responses][code].key?(:data)
|
|
153
181
|
tests[title] +="result = NiceHttp.validate_response(resp, @request.responses._#{code}.data, include_diff: true)\n"
|
|
154
|
-
tests[title] +="
|
|
182
|
+
tests[title] += "#{assert_be_true('result[:ok]', 'Structure mismatch: #{result[:diff]}', test)}\n"
|
|
155
183
|
end
|
|
156
184
|
tests[title] += "end\n"
|
|
157
185
|
else
|
|
158
186
|
tests[title] = "do
|
|
159
|
-
resp = @http.#{request[:method]}(@request)
|
|
160
|
-
|
|
187
|
+
#{apply_defaults_txt}resp = @http.#{request[:method]}(@request)
|
|
188
|
+
#{assert_eq('resp.code', code_src, test)}\n"
|
|
161
189
|
|
|
162
190
|
if request[:responses][code].is_a?(Hash) and request[:responses][code].key?(:data)
|
|
163
191
|
tests[title] +="result = NiceHttp.validate_response(resp, @request.responses._#{code}.data, include_diff: true)\n"
|
|
164
|
-
tests[title] +="
|
|
192
|
+
tests[title] += "#{assert_be_true('result[:ok]', 'Structure mismatch: #{result[:diff]}', test)}\n"
|
|
165
193
|
end
|
|
166
194
|
tests[title] += "end\n"
|
|
167
195
|
end
|
|
168
196
|
end
|
|
169
197
|
|
|
170
|
-
|
|
171
|
-
|
|
198
|
+
if include_kind?(only_kinds, :auth)
|
|
199
|
+
title = example_title("doesn\\'t retrieve data if not authenticated", test)
|
|
200
|
+
tests[title] = "do
|
|
172
201
|
http = NiceHttp.new()
|
|
173
202
|
http.headers = {}
|
|
174
203
|
resp = http.#{request[:method]}(@request)
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
204
|
+
#{assert_between('resp.code', "'400'", "'499'", test)}\n"
|
|
205
|
+
if request.key?(:responses) and (request[:responses].keys.select{|c| c.to_s.to_i>=400&&c.to_s.to_i<=499}).size>0
|
|
206
|
+
tests[title] += "#{assert_eq('NiceHash.compare_structure(@request.responses[resp.code.to_sym].data, resp.data.json)', 'true', test)}
|
|
207
|
+
#{assert_eq('resp.message', '@request.responses[resp.code.to_sym].message', test)}\n"
|
|
208
|
+
end
|
|
209
|
+
tests[title] += "end\n"
|
|
179
210
|
end
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
if params.size > 0 or keywords_required.size >0
|
|
211
|
+
|
|
212
|
+
if include_kind?(only_kinds, :required_params) && (params.size > 0 or keywords_required.size >0)
|
|
183
213
|
empty_param = ""
|
|
184
214
|
params.each do |p|
|
|
185
215
|
r = req_txt.gsub(/#{p}([),])/, '""\1')
|
|
186
216
|
empty_param += "
|
|
187
217
|
request = #{r}
|
|
188
218
|
resp = @http.#{request[:method]}(request)
|
|
189
|
-
|
|
219
|
+
#{assert_between('resp.code', "'400'", "'499'", test)}\n"
|
|
190
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
|
|
191
|
-
empty_param += "
|
|
221
|
+
empty_param += "#{assert_match('resp.message', '/\#{request.responses[resp.code.to_sym].message}/i', test)}\n"
|
|
192
222
|
end
|
|
193
223
|
end
|
|
194
|
-
if keywords_required.size > 0
|
|
224
|
+
if keywords_required.size > 0
|
|
195
225
|
r = req_txt.scan(/(.+)\(/).join
|
|
196
226
|
empty_param += "
|
|
197
227
|
[:#{keywords_required.join(', :')}].each do |kw|
|
|
198
228
|
request = #{r}(kw => '')
|
|
199
229
|
resp = @http.#{request[:method]}(request)
|
|
200
|
-
|
|
230
|
+
#{assert_between('resp.code', "'400'", "'499'", test)}\n"
|
|
201
231
|
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 += "
|
|
232
|
+
empty_param += "#{assert_match('resp.message', '/\#{request.responses[resp.code.to_sym].message}/i', test)}\n"
|
|
203
233
|
end
|
|
204
234
|
empty_param += "end\n"
|
|
205
235
|
end
|
|
206
236
|
empty_param += "end\n"
|
|
207
|
-
tests["
|
|
237
|
+
tests[example_title("returns error if required parameter empty", test, trailing_space: true)] = "do\n#{empty_param}"
|
|
208
238
|
end
|
|
209
239
|
|
|
210
|
-
if request.key?(:mock_response)
|
|
211
|
-
title = "
|
|
240
|
+
if include_kind?(only_kinds, :mock) && request.key?(:mock_response)
|
|
241
|
+
title = example_title("returns expected mock response", test, trailing_space: true)
|
|
212
242
|
tests[title] = "do
|
|
213
243
|
@http_mock = NiceHttp.new()
|
|
214
244
|
@http_mock.use_mocks = true
|
|
215
245
|
resp = @http_mock.#{request[:method]}(@request)
|
|
216
|
-
|
|
217
|
-
|
|
246
|
+
#{assert_eq('resp.code', '@request[:mock_response][:code]', test)}
|
|
247
|
+
#{assert_eq('resp.message', '@request[:mock_response][:message]', test)}\n"
|
|
218
248
|
if request[:mock_response].key?(:data)
|
|
219
|
-
tests[title] += "
|
|
249
|
+
tests[title] += "#{assert_be_true('NiceHash.compare_structure(@request[:mock_response][:data], resp.data.json, true)', nil, test)}\n"
|
|
220
250
|
end
|
|
221
251
|
tests[title] += "end\n"
|
|
222
252
|
end
|
|
223
253
|
|
|
224
|
-
if payload_source && [:post, :put, :patch].include?(request[:method])
|
|
225
|
-
title = "
|
|
254
|
+
if include_kind?(only_kinds, :variations) && payload_source && [:post, :put, :patch].include?(request[:method])
|
|
255
|
+
title = example_title("handles multiple valid data variations", test, trailing_space: true)
|
|
226
256
|
tests[title] = "do
|
|
227
257
|
@request[#{payload_source}].generate_n(5, :correct).each do |generated_data|
|
|
228
258
|
request = @request.deep_copy
|
|
229
259
|
request[:data] = generated_data
|
|
230
260
|
resp = @http.#{request[:method]}(request)
|
|
231
|
-
|
|
261
|
+
#{assert_between('resp.code.to_i', '200', '299', test)}
|
|
232
262
|
end
|
|
233
263
|
end\n"
|
|
234
264
|
end
|
|
235
265
|
|
|
236
|
-
if payload_source
|
|
237
|
-
|
|
238
|
-
|
|
266
|
+
if include_kind?(only_kinds, :invalid_fields) && payload_source
|
|
267
|
+
payload_key = payload_source == ":data_pattern" ? :data_pattern : :data
|
|
268
|
+
read_only_names = Array(request[:data_read_only]).map(&:to_s)
|
|
269
|
+
payload_for_invalid = request[payload_key]
|
|
270
|
+
if payload_for_invalid.is_a?(Hash) && !read_only_names.empty?
|
|
271
|
+
payload_for_invalid = payload_for_invalid.deep_copy
|
|
272
|
+
read_only_names.each do |name|
|
|
273
|
+
payload_for_invalid.delete(name.to_sym)
|
|
274
|
+
payload_for_invalid.delete(name)
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
unless payload_for_invalid.nil? || (payload_for_invalid.respond_to?(:empty?) && payload_for_invalid.empty?)
|
|
278
|
+
title = example_title("returns error when individual data fields are invalid", test, trailing_space: true)
|
|
279
|
+
if read_only_names.empty?
|
|
280
|
+
tests[title] = "do
|
|
239
281
|
wrong = @request[#{payload_source}].generate(:correct, errors: :min_length)
|
|
240
282
|
NiceHash.change_one_by_one([@request[#{payload_source}], :correct], wrong).each do |one_wrong|
|
|
241
283
|
request = @request.deep_copy
|
|
242
284
|
request[:data] = one_wrong
|
|
243
285
|
resp = @http.#{request[:method]}(request)
|
|
244
|
-
|
|
286
|
+
#{assert_between('resp.code.to_i', '200', '299', test, negate: true)}
|
|
245
287
|
end
|
|
246
288
|
end\n"
|
|
289
|
+
else
|
|
290
|
+
read_only_lit = request[:data_read_only].map(&:inspect).join(", ")
|
|
291
|
+
tests[title] = "do
|
|
292
|
+
payload = @request[#{payload_source}].deep_copy
|
|
293
|
+
[#{read_only_lit}].each do |f|
|
|
294
|
+
payload.delete(f)
|
|
295
|
+
payload.delete(f.to_s)
|
|
296
|
+
payload.delete(f.to_s.to_sym)
|
|
297
|
+
end
|
|
298
|
+
wrong = payload.generate(:correct, errors: :min_length)
|
|
299
|
+
NiceHash.change_one_by_one([payload, :correct], wrong).each do |one_wrong|
|
|
300
|
+
request = @request.deep_copy
|
|
301
|
+
request[:data] = one_wrong
|
|
302
|
+
resp = @http.#{request[:method]}(request)
|
|
303
|
+
#{assert_between('resp.code.to_i', '200', '299', test, negate: true)}
|
|
304
|
+
end
|
|
305
|
+
end\n"
|
|
306
|
+
end
|
|
307
|
+
end
|
|
247
308
|
end
|
|
248
309
|
|
|
249
|
-
if request.key?(:data_required) && (request.key?(:data) || has_data_examples)
|
|
310
|
+
if include_kind?(only_kinds, :required_data) && request.key?(:data_required) && (request.key?(:data) || has_data_examples || apply_data_default)
|
|
250
311
|
seed_data_line = if !request.key?(:data) && has_data_examples
|
|
251
312
|
"request[:data] = request[:data_examples].first\n "
|
|
313
|
+
elsif apply_data_default
|
|
314
|
+
"request[:data] ||= {}\n request[:data_default].each do |k, v|\n request[:data][k] = v if !request[:data].key?(k) || request[:data][k].nil?\n end\n "
|
|
252
315
|
else
|
|
253
316
|
""
|
|
254
317
|
end
|
|
@@ -258,14 +321,14 @@ class CreateTests
|
|
|
258
321
|
request = @request.deep_copy
|
|
259
322
|
#{seed_data_line}request.values_for[p] = ''
|
|
260
323
|
resp = @http.#{request[:method]}(request)
|
|
261
|
-
|
|
324
|
+
#{assert_between('resp.code', "'200'", "'299'", test, negate: true)}
|
|
262
325
|
if request.responses.key?(resp.code.to_sym)
|
|
263
|
-
|
|
326
|
+
#{assert_match('resp.message', '/\#{request.responses[resp.code.to_sym].message}/i', test)}
|
|
264
327
|
end
|
|
265
328
|
end
|
|
266
329
|
"
|
|
267
330
|
empty_param_data += "end\n"
|
|
268
|
-
tests["
|
|
331
|
+
tests[example_title("returns error if required parameter on data empty", test, with_do: true)] = empty_param_data
|
|
269
332
|
|
|
270
333
|
missing_param_data = ""
|
|
271
334
|
missing_param_data += "
|
|
@@ -273,19 +336,18 @@ class CreateTests
|
|
|
273
336
|
request = @request.deep_copy
|
|
274
337
|
#{seed_data_line}NiceHash.delete_nested(request[:data], p)
|
|
275
338
|
resp = @http.#{request[:method]}(request)
|
|
276
|
-
|
|
339
|
+
#{assert_between('resp.code', "'200'", "'299'", test, negate: true)}
|
|
277
340
|
if request.responses.key?(resp.code.to_sym)
|
|
278
|
-
|
|
341
|
+
#{assert_match('resp.message', '/\#{request.responses[resp.code.to_sym].message}/i', test)}
|
|
279
342
|
end
|
|
280
343
|
end
|
|
281
344
|
"
|
|
282
345
|
missing_param_data += "end\n"
|
|
283
|
-
tests["
|
|
346
|
+
tests[example_title("returns error if required parameter on data missing", test, with_do: true)] = missing_param_data
|
|
284
347
|
end
|
|
285
348
|
|
|
286
349
|
tests.each do |k,v|
|
|
287
|
-
unless
|
|
288
|
-
output.include?(k.gsub(' "'," '").gsub('" ',"' ").gsub("\n",'').gsub(/\s+do$/,'').gsub(/^\s*it/,''))
|
|
350
|
+
unless example_already_present?(output, k, test)
|
|
289
351
|
modified = true
|
|
290
352
|
message = " = test added #{k} for #{method_txt}"
|
|
291
353
|
@logger.info message
|
|
@@ -293,15 +355,215 @@ class CreateTests
|
|
|
293
355
|
puts message
|
|
294
356
|
output +="# Appended #{Time.now.stamp}\n"
|
|
295
357
|
end
|
|
296
|
-
|
|
358
|
+
if test == :minitest
|
|
359
|
+
# keys are "def test_foo" and bodies start with "do\n...\nend"
|
|
360
|
+
body = v.sub(/\Ado\n/, "").sub(/\n?end\n?\z/, "\n")
|
|
361
|
+
output += "#{k}\n#{body}end\n"
|
|
362
|
+
else
|
|
363
|
+
output += "#{k}#{v}"
|
|
364
|
+
end
|
|
297
365
|
end
|
|
298
366
|
end
|
|
299
|
-
if
|
|
367
|
+
if test == :minitest
|
|
368
|
+
if output.include?('defined?(')
|
|
369
|
+
output += "\nend\nend"
|
|
370
|
+
else
|
|
371
|
+
output += "\nend"
|
|
372
|
+
end
|
|
373
|
+
elsif output.include?('defined?(')
|
|
300
374
|
output += "\nend\nend"
|
|
301
375
|
else
|
|
302
376
|
output +="\nend"
|
|
303
377
|
end
|
|
304
378
|
return modified, output
|
|
305
379
|
end
|
|
380
|
+
|
|
381
|
+
private def success_response_code(responses)
|
|
382
|
+
keys = responses.keys
|
|
383
|
+
twoxx = keys.find do |k|
|
|
384
|
+
n = k.to_s
|
|
385
|
+
n.match?(/\A\d+\z/) && (200..299).include?(n.to_i)
|
|
386
|
+
end
|
|
387
|
+
return twoxx if twoxx
|
|
388
|
+
|
|
389
|
+
default_key = keys.find { |k| k.to_s == "default" }
|
|
390
|
+
return default_key if default_key
|
|
391
|
+
|
|
392
|
+
keys[0]
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
# Numeric status codes stay unquoted (eq 200); non-numeric keys use inspect.
|
|
396
|
+
private def format_response_code(code)
|
|
397
|
+
s = code.to_s
|
|
398
|
+
if s.match?(/\A\d+\z/)
|
|
399
|
+
s
|
|
400
|
+
else
|
|
401
|
+
code.inspect
|
|
402
|
+
end
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
private def normalize_only(only)
|
|
406
|
+
return nil if only.nil?
|
|
407
|
+
list = case only
|
|
408
|
+
when Symbol then [only]
|
|
409
|
+
when String then only.split(",").map { |s| s.strip.to_sym }
|
|
410
|
+
when Array then only.map { |s| s.to_s.strip.to_sym }
|
|
411
|
+
else
|
|
412
|
+
raise "** Wrong only parameter: #{only}"
|
|
413
|
+
end
|
|
414
|
+
unknown = list - TEST_KINDS
|
|
415
|
+
unless unknown.empty?
|
|
416
|
+
raise "** Wrong only parameter: unknown kind(s) #{unknown.join(', ')}. Valid: #{TEST_KINDS.join(', ')}"
|
|
417
|
+
end
|
|
418
|
+
list
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
private def include_kind?(only_kinds, kind)
|
|
422
|
+
only_kinds.nil? || only_kinds.include?(kind)
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
private def example_title(description, test, trailing_space: false, with_do: false)
|
|
426
|
+
if test == :minitest
|
|
427
|
+
"def test_#{minitest_method_name(description)}"
|
|
428
|
+
else
|
|
429
|
+
space = trailing_space ? " " : ""
|
|
430
|
+
if with_do
|
|
431
|
+
"it '#{description}' do\n"
|
|
432
|
+
else
|
|
433
|
+
"it '#{description}'#{space}"
|
|
434
|
+
end
|
|
435
|
+
end
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
private def minitest_method_name(description)
|
|
439
|
+
description.gsub("\\'", "")
|
|
440
|
+
.gsub(/[^a-zA-Z0-9]+/, "_")
|
|
441
|
+
.gsub(/\A_+|_+\z/, "")
|
|
442
|
+
.downcase
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
private def example_already_present?(output, key, test)
|
|
446
|
+
if test == :minitest
|
|
447
|
+
output.include?(key)
|
|
448
|
+
else
|
|
449
|
+
output.include?(key.gsub(" '", ' "').gsub("' ", '" ').gsub("\n", '').gsub(/\s+do$/, '').gsub(/^\s*it/, '')) ||
|
|
450
|
+
output.include?(key.gsub(' "', " '").gsub('" ', "' ").gsub("\n", '').gsub(/\s+do$/, '').gsub(/^\s*it/, ''))
|
|
451
|
+
end
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
private def assert_eq(actual, expected, test)
|
|
455
|
+
if test == :minitest
|
|
456
|
+
"assert_equal #{expected}, #{actual}"
|
|
457
|
+
else
|
|
458
|
+
"expect(#{actual}).to eq #{expected}"
|
|
459
|
+
end
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
private def assert_be_true(actual, message, test)
|
|
463
|
+
if test == :minitest
|
|
464
|
+
if message
|
|
465
|
+
"assert #{actual}, \"#{message}\""
|
|
466
|
+
else
|
|
467
|
+
"assert #{actual}"
|
|
468
|
+
end
|
|
469
|
+
else
|
|
470
|
+
if message
|
|
471
|
+
"expect(#{actual}).to be(true), \"#{message}\""
|
|
472
|
+
else
|
|
473
|
+
"expect(#{actual}).to be true"
|
|
474
|
+
end
|
|
475
|
+
end
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
private def assert_between(actual, low, high, test, negate: false)
|
|
479
|
+
if test == :minitest
|
|
480
|
+
if negate
|
|
481
|
+
"refute_includes (#{low}..#{high}), #{actual}"
|
|
482
|
+
else
|
|
483
|
+
"assert_includes (#{low}..#{high}), #{actual}"
|
|
484
|
+
end
|
|
485
|
+
else
|
|
486
|
+
if negate
|
|
487
|
+
"expect(#{actual}).not_to be_between(#{low}, #{high})"
|
|
488
|
+
else
|
|
489
|
+
"expect(#{actual}).to be_between(#{low}, #{high})"
|
|
490
|
+
end
|
|
491
|
+
end
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
private def assert_match(actual, pattern, test)
|
|
495
|
+
if test == :minitest
|
|
496
|
+
"assert_match #{pattern}, #{actual}"
|
|
497
|
+
else
|
|
498
|
+
"expect(#{actual}).to match #{pattern}"
|
|
499
|
+
end
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
private def framework_hook(kind, body, test)
|
|
503
|
+
if test == :minitest
|
|
504
|
+
# Stored as raw bodies; assembled into setup/teardown in the skeleton.
|
|
505
|
+
# Prefix with a marker so cleanup: :after_each merging can find them.
|
|
506
|
+
case kind
|
|
507
|
+
when :after_each then "TEARDOWN_EACH:#{body}"
|
|
508
|
+
when :after_all then "TEARDOWN_ALL:#{body}"
|
|
509
|
+
else body
|
|
510
|
+
end
|
|
511
|
+
else
|
|
512
|
+
case kind
|
|
513
|
+
when :after_each then "after(:each) do\n#{body}end\n"
|
|
514
|
+
when :after_all then "after(:all) do\n#{body}end\n"
|
|
515
|
+
else body
|
|
516
|
+
end
|
|
517
|
+
end
|
|
518
|
+
end
|
|
519
|
+
|
|
520
|
+
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)
|
|
521
|
+
class_name = "#{mod_name}#{camelize(method_txt.to_s)}Test"
|
|
522
|
+
|
|
523
|
+
teardown_each_body = after_each_txt.to_s.gsub(/\ATEARDOWN_EACH:/, "").gsub(/\ATEARDOWN_ALL:/, "")
|
|
524
|
+
# after_each_txt may contain both TEARDOWN_EACH and (after merge) TEARDOWN_ALL markers
|
|
525
|
+
each_parts = []
|
|
526
|
+
all_parts = []
|
|
527
|
+
[after_each_txt.to_s, after_all_txt.to_s].each do |chunk|
|
|
528
|
+
chunk.split(/(?=TEARDOWN_(?:EACH|ALL):)/).each do |part|
|
|
529
|
+
next if part.empty?
|
|
530
|
+
if part.start_with?("TEARDOWN_EACH:")
|
|
531
|
+
each_parts << part.sub(/\ATEARDOWN_EACH:/, "")
|
|
532
|
+
elsif part.start_with?("TEARDOWN_ALL:")
|
|
533
|
+
all_parts << part.sub(/\ATEARDOWN_ALL:/, "")
|
|
534
|
+
end
|
|
535
|
+
end
|
|
536
|
+
end
|
|
537
|
+
|
|
538
|
+
teardown_body = (each_parts + all_parts).join
|
|
539
|
+
|
|
540
|
+
setup_http = if is_delete && target_resource
|
|
541
|
+
"@http = NiceHttp.new()\n"
|
|
542
|
+
else
|
|
543
|
+
""
|
|
544
|
+
end
|
|
545
|
+
|
|
546
|
+
# before(:all) work at start of setup; before(:each) after that.
|
|
547
|
+
# First test creates @http for non-delete; delete recreates in the each section.
|
|
548
|
+
"
|
|
549
|
+
require 'minitest/autorun'
|
|
550
|
+
require_relative '#{settings_relative_path}'
|
|
551
|
+
if defined?(#{mod_name}) and defined?(#{mod_name}.#{method_txt})
|
|
552
|
+
class #{class_name} < Minitest::Test
|
|
553
|
+
def setup
|
|
554
|
+
@http = NiceHttp.new()
|
|
555
|
+
#{params_declaration_txt}#{suffix_txt}#{setup_before_all_txt}@request = #{req_txt}
|
|
556
|
+
@http.logger.info(\"\\n\#{'+'*50} Before All ends \#{'+'*50}\")
|
|
557
|
+
#{setup_http}#{setup_before_each_txt}@http.logger.info(\"\\n\\n\#{'='*100}\\nTest: \#{name}\\n\#{'-'*100}\")
|
|
558
|
+
end
|
|
559
|
+
def teardown
|
|
560
|
+
#{teardown_body}
|
|
561
|
+
end
|
|
562
|
+
"
|
|
563
|
+
end
|
|
564
|
+
|
|
565
|
+
private def camelize(snake)
|
|
566
|
+
snake.to_s.split("_").map(&:capitalize).join
|
|
567
|
+
end
|
|
306
568
|
end
|
|
307
|
-
end
|
|
569
|
+
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.3.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:
|