create_tests 0.6.5 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +190 -55
- data/bin/create_tests +19 -2
- data/lib/create-tests/create_helper.rb +97 -5
- data/lib/create-tests/create_settings.rb +4 -4
- data/lib/create-tests/create_test.rb +157 -19
- data/lib/create-tests/from.rb +127 -30
- data/lib/create-tests/get_modules.rb +4 -4
- data/lib/create-tests/resource_chain.rb +88 -0
- data/lib/create-tests/version.rb +3 -0
- data/lib/create_tests.rb +2 -0
- metadata +22 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 754317e21adff9283ea8d3424b46ee09de1f5a53d9e875b9388014e261f6d561
|
|
4
|
+
data.tar.gz: e70d64dddd1f4312c67149e7ca3761e1d5038332252e9c8a191427e53627c3b9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 10f9687590b335bd26ab6bc039d9ad05eed6ca780608363c934db2a929f11ea229e267d95498935ac9755c4eb6bb4eec437b985d114efe747870e7371fc50dd5
|
|
7
|
+
data.tar.gz: '048048f6f02865499ee314d55841e9e3259fad8c9edcb74c6fb3937e508eb2ccbdebe9b49c6870fe1e8984a466410aa61f0ea7a6b03ef2dfbbe1f1eeabadd752'
|
data/README.md
CHANGED
|
@@ -1,22 +1,18 @@
|
|
|
1
1
|
# CreateTests
|
|
2
2
|
|
|
3
3
|
[](https://rubygems.org/gems/create_tests)
|
|
4
|
-
[](https://github.com/MarioRuiz/create_tests/actions/workflows/ci.yml)
|
|
5
|
+
[](https://github.com/MarioRuiz/create_tests/actions/workflows/ci.yml)
|
|
6
6
|

|
|
7
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
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
More info about the Request Hashes: https://github.com/MarioRuiz/Request-Hash
|
|
15
|
-
|
|
16
|
-
If you want to know how to import Swagger / Open API files in just a couple of seconds and transform them into Request Ruby Hashes: https://github.com/MarioRuiz/open_api_import
|
|
17
|
-
|
|
18
|
-
We strongly recommend to use nice_http gem for your tests: https://github.com/MarioRuiz/nice_http
|
|
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).
|
|
19
14
|
|
|
15
|
+
More info about Request Hashes: https://github.com/MarioRuiz/Request-Hash
|
|
20
16
|
|
|
21
17
|
## Installation
|
|
22
18
|
|
|
@@ -24,51 +20,207 @@ Install it yourself as:
|
|
|
24
20
|
|
|
25
21
|
$ gem install create_tests
|
|
26
22
|
|
|
23
|
+
**Requirements:** Ruby >= 3.0
|
|
27
24
|
|
|
28
|
-
|
|
25
|
+
The gem uses [rufo](https://github.com/ruby-formatter/rufo) to format generated files. In case you experience any trouble with it, visit: https://github.com/ruby-formatter/rufo
|
|
29
26
|
|
|
30
27
|
## Usage
|
|
31
28
|
|
|
32
|
-
After installation you can run using command line executable or
|
|
29
|
+
After installation you can run using the command line executable or from Ruby code.
|
|
33
30
|
|
|
34
|
-
The execution will create
|
|
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
|
|
34
|
+
- A `settings/general.rb` with NiceHttp configuration boilerplate
|
|
35
|
+
|
|
36
|
+
### Command line
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
Usage: create_tests [requests_file] [options]
|
|
40
|
+
|
|
41
|
+
-n, --dont_overwrite In case the test file exists it won't change anything
|
|
42
|
+
-w, --overwrite In case the test file exists it will be overwritten
|
|
43
|
+
-a, --append Only missing tests will be appended (default)
|
|
44
|
+
-d, --dry_run Preview which files would be created or modified
|
|
45
|
+
--cleanup_each Generate cleanup in after(:each) instead of after(:all)
|
|
46
|
+
--no_cleanup Do not generate setup/cleanup hooks
|
|
47
|
+
-v, --version Show version
|
|
48
|
+
```
|
|
35
49
|
|
|
36
|
-
|
|
50
|
+
Example:
|
|
37
51
|
|
|
38
|
-
|
|
52
|
+
```bash
|
|
53
|
+
create_tests ./requests/uber.yaml.rb
|
|
54
|
+
```
|
|
39
55
|
|
|
40
|
-
|
|
56
|
+
Dry run (preview without writing files):
|
|
41
57
|
|
|
42
|
-
Example:
|
|
43
58
|
```bash
|
|
44
|
-
|
|
59
|
+
create_tests ./requests/uber.yaml.rb --dry_run
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Ruby API
|
|
63
|
+
|
|
64
|
+
```ruby
|
|
65
|
+
require 'create_tests'
|
|
66
|
+
|
|
67
|
+
CreateTests.from "./requests/uber.yaml.rb"
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Generated tests
|
|
71
|
+
|
|
72
|
+
For each API endpoint, `create_tests` generates up to 7 test types:
|
|
73
|
+
|
|
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.
|
|
75
|
+
|
|
76
|
+
2. **"doesn't retrieve data if not authenticated"** -- Calls with empty headers and asserts a 4xx response.
|
|
77
|
+
|
|
78
|
+
3. **"returns error if required parameter empty"** -- For each required parameter, sends an empty string and expects a 4xx response.
|
|
79
|
+
|
|
80
|
+
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.
|
|
81
|
+
|
|
82
|
+
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
|
+
|
|
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.
|
|
85
|
+
|
|
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.
|
|
87
|
+
|
|
88
|
+
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
|
+
|
|
90
|
+
## Parameters
|
|
91
|
+
|
|
92
|
+
### mode
|
|
93
|
+
|
|
94
|
+
Accepts three options: `:overwrite`, `:dont_overwrite` and `:append`. Default: `:append`.
|
|
95
|
+
|
|
96
|
+
- **append**: If the test file already exists, only missing tests will be added. Existing content is preserved, including `helper/setup.rb` and `helper/cleanup.rb`. If the file doesn't exist, it will be created with all tests.
|
|
97
|
+
|
|
98
|
+
- **dont_overwrite**: If the test file exists, no changes will be made. If it doesn't exist, it will be created.
|
|
99
|
+
|
|
100
|
+
- **overwrite**: The test file will be recreated from scratch. All previous content will be deleted.
|
|
101
|
+
|
|
102
|
+
```ruby
|
|
103
|
+
CreateTests.from "./requests/uber.yaml.rb", mode: :overwrite
|
|
45
104
|
```
|
|
46
105
|
|
|
47
|
-
###
|
|
48
|
-
Write your ruby code on a file and in command line/bash: `ruby my_file.rb`
|
|
106
|
+
### dry_run
|
|
49
107
|
|
|
50
|
-
|
|
108
|
+
Preview which files would be created or modified without writing anything to disk.
|
|
51
109
|
|
|
52
110
|
```ruby
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
111
|
+
CreateTests.from "./requests/uber.yaml.rb", dry_run: true
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### return_data
|
|
56
115
|
|
|
116
|
+
Return the generated code as a Hash (`{ filepath => content }`) without writing files. Useful for programmatic use.
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
119
|
+
result = CreateTests.from "./requests/uber.yaml.rb", return_data: true
|
|
120
|
+
result.each { |path, content| puts "#{path}: #{content.length} bytes" }
|
|
57
121
|
```
|
|
58
122
|
|
|
123
|
+
### spec_dir / settings_dir
|
|
124
|
+
|
|
125
|
+
Custom output directories (default: `./spec` and `./settings`).
|
|
126
|
+
|
|
127
|
+
```ruby
|
|
128
|
+
CreateTests.from "./requests/uber.yaml.rb", spec_dir: "./test/spec", settings_dir: "./test/config"
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### cleanup
|
|
132
|
+
|
|
133
|
+
Controls whether and how prerequisite setup/cleanup hooks are generated. Default: `:after_all`.
|
|
134
|
+
|
|
135
|
+
When API endpoints have hierarchical paths (e.g. `/accounts/{account}/pools/{pool}/volumes/{volume}`), `create_tests` infers the dependency chain and generates production-ready test scaffolding with setup expectations, unique naming, and conditional cleanup.
|
|
136
|
+
|
|
137
|
+
```ruby
|
|
138
|
+
CreateTests.from "./requests/netapp.json.rb" # cleanup in after(:all) (default)
|
|
139
|
+
CreateTests.from "./requests/netapp.json.rb", cleanup: :after_each # move after(:all) cleanup into after(:each)
|
|
140
|
+
CreateTests.from "./requests/netapp.json.rb", cleanup: false # no cleanup generation
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**For PUT/POST (create) endpoints** -- parents are set up, test creates the target:
|
|
144
|
+
|
|
145
|
+
```ruby
|
|
146
|
+
before(:all) do
|
|
147
|
+
@http = NiceHttp.new()
|
|
148
|
+
@volume_name = @volume_name + "-cre"
|
|
149
|
+
expect(Helper.setup_capacity_pools(@http, ...).state).to eq "Succeeded"
|
|
150
|
+
@request = Volumes.create_or_update(...)
|
|
151
|
+
end
|
|
152
|
+
after(:all) do
|
|
153
|
+
unless defined?(DONT_DELETE) && DONT_DELETE
|
|
154
|
+
Helper.cleanup_capacity_pools(@http, ...)
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**For DELETE endpoints** -- target is recreated before each test (since each test deletes it):
|
|
160
|
+
|
|
161
|
+
```ruby
|
|
162
|
+
before(:all) do
|
|
163
|
+
@snapshot_name = @snapshot_name + "-del"
|
|
164
|
+
expect(Helper.setup_volumes(@http, ...).state).to eq "Succeeded"
|
|
165
|
+
@request = Snapshots.delete(...)
|
|
166
|
+
end
|
|
167
|
+
before(:each) do |example|
|
|
168
|
+
@http = NiceHttp.new()
|
|
169
|
+
expect(Helper.setup_snapshots(@http, ...).state).to eq "Succeeded"
|
|
170
|
+
end
|
|
171
|
+
after(:each) do
|
|
172
|
+
Helper.cleanup_snapshots(@http, ...)
|
|
173
|
+
end
|
|
174
|
+
after(:all) do
|
|
175
|
+
unless defined?(DONT_DELETE) && DONT_DELETE
|
|
176
|
+
Helper.cleanup_volumes(@http, ...)
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**For GET/PATCH endpoints** -- target is set up once in `before(:all)`.
|
|
182
|
+
|
|
183
|
+
Each setup method cascades to create its parents first (in `helper/setup.rb`):
|
|
184
|
+
|
|
185
|
+
```ruby
|
|
186
|
+
def self.setup_snapshots(http, ...)
|
|
187
|
+
setup_capacity_pools(http, ...)
|
|
188
|
+
# TODO: Create the snapshots resource
|
|
189
|
+
OpenStruct.new(state: "Succeeded")
|
|
190
|
+
end
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Cleanup cascades in reverse (in `helper/cleanup.rb`):
|
|
194
|
+
|
|
195
|
+
```ruby
|
|
196
|
+
def self.cleanup_snapshots(http, ...)
|
|
197
|
+
# TODO: Delete the snapshots resource
|
|
198
|
+
cleanup_capacity_pools(http, ...)
|
|
199
|
+
end
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Set `DONT_DELETE=true` in your environment to keep resources for debugging.
|
|
203
|
+
|
|
59
204
|
## Example
|
|
60
205
|
|
|
61
|
-
|
|
206
|
+
Creating tests for the Uber API using a Swagger / OpenAPI file:
|
|
62
207
|
|
|
63
|
-
1. Create a folder
|
|
208
|
+
1. Create a project folder:
|
|
64
209
|
|
|
65
|
-
|
|
210
|
+
```bash
|
|
211
|
+
mkdir create_tests_example && cd create_tests_example
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
2. Copy the Swagger file into a `requests` folder (see `./example/requests/uber.yaml` in this repo).
|
|
215
|
+
|
|
216
|
+
3. Convert the Swagger file into Request Hashes using [open_api_import](https://github.com/MarioRuiz/open_api_import):
|
|
66
217
|
|
|
67
|
-
3. First we will convert this Swagger file into Requests Hashes by running from `create_tests_example` folder:
|
|
68
218
|
```bash
|
|
69
219
|
open_api_import ./requests/uber.yaml -fT
|
|
70
220
|
```
|
|
71
|
-
|
|
221
|
+
|
|
222
|
+
Output:
|
|
223
|
+
|
|
72
224
|
```
|
|
73
225
|
** Generated files that contain the code of the requests after importing the Swagger file:
|
|
74
226
|
- requests/uber.yaml_Products.rb
|
|
@@ -78,15 +230,16 @@ Now all the Request files were created on the `requests` folder:
|
|
|
78
230
|
- requests/uber.yaml.rb
|
|
79
231
|
```
|
|
80
232
|
|
|
81
|
-
4.
|
|
233
|
+
4. Generate the tests:
|
|
234
|
+
|
|
82
235
|
```bash
|
|
83
236
|
create_tests ./requests/uber.yaml.rb
|
|
84
237
|
```
|
|
85
238
|
|
|
86
|
-
|
|
239
|
+
Output:
|
|
240
|
+
|
|
87
241
|
```
|
|
88
242
|
- Logs: ./requests/uber.yaml.rb_create_tests.log
|
|
89
|
-
** Pay attention, if any of the test files exist or the help file exist only will be added the tests, methods that are missing.
|
|
90
243
|
- Settings: ./settings/general.rb
|
|
91
244
|
- Test created: ./spec/User/profile_user_spec.rb
|
|
92
245
|
- Test created: ./spec/User/activity_user_spec.rb
|
|
@@ -96,36 +249,18 @@ create_tests ./requests/uber.yaml.rb
|
|
|
96
249
|
- Helper: ./spec/helper.rb
|
|
97
250
|
```
|
|
98
251
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
## Parameters
|
|
102
|
-
|
|
103
|
-
The parameters can be supplied alone or with other parameters. In case a parameter is not supplied then it will be used the default value.
|
|
104
|
-
|
|
105
|
-
### mode
|
|
106
|
-
|
|
107
|
-
Accepts three different options: :overwrite, :dont_overwrite and :append. By default :append.
|
|
108
|
-
|
|
109
|
-
append: In case the test file already exists will be only adding those tests that are missing from that file. If the test file doesn't exist, will be created and added all tests.
|
|
110
|
-
|
|
111
|
-
dont_overwrite: In case the test file exists any change will be done. If it doesn't exist then it will be created.
|
|
112
|
-
|
|
113
|
-
overwrite: In case the file exist you will loose the current code and a new code will be created. Take in consideration that all previous content will be deleted.
|
|
114
|
-
If it doesn't exist the test file then it will be created.
|
|
115
|
-
|
|
116
|
-
```ruby
|
|
117
|
-
require 'create_tests'
|
|
118
|
-
|
|
119
|
-
CreateTests.from "./requests/uber.yaml.rb", mode: :overwrite
|
|
252
|
+
5. Review `settings/general.rb` to configure your host and authentication, fill in the Helper methods in `spec/helper.rb`, then run:
|
|
120
253
|
|
|
254
|
+
```bash
|
|
255
|
+
rspec spec/ --format documentation
|
|
121
256
|
```
|
|
122
257
|
|
|
258
|
+
See the full example: https://github.com/MarioRuiz/create_tests/tree/master/example
|
|
123
259
|
|
|
124
260
|
## Contributing
|
|
125
261
|
|
|
126
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
|
127
|
-
|
|
262
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/MarioRuiz/create_tests.
|
|
128
263
|
|
|
129
264
|
## License
|
|
130
265
|
|
|
131
|
-
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
|
266
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/bin/create_tests
CHANGED
|
@@ -17,14 +17,31 @@ optparse = OptionParser.new do |opts|
|
|
|
17
17
|
options[:mode] = :dont_overwrite
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
opts.on("-w", "--overwrite", "In case the test file exists it will be
|
|
20
|
+
opts.on("-w", "--overwrite", "In case the test file exists it will be overwritten, so the previous content will be deleted") do
|
|
21
21
|
options[:mode] = :overwrite
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
-
opts.on("-a", "--append", "In case the test file exists
|
|
24
|
+
opts.on("-a", "--append", "In case the test file exists only missing tests will be appended, existing content is preserved") do
|
|
25
25
|
options[:mode] = :append
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
+
opts.on("-d", "--dry_run", "Preview which files would be created or modified without writing anything") do
|
|
29
|
+
options[:dry_run] = true
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
opts.on("--cleanup_each", "Generate cleanup in after(:each) instead of after(:all)") do
|
|
33
|
+
options[:cleanup] = :after_each
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
opts.on("--no_cleanup", "Do not generate setup/cleanup hooks") do
|
|
37
|
+
options[:cleanup] = false
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
opts.on("-v", "--version", "Show version") do
|
|
41
|
+
puts "create_tests #{CreateTests::VERSION}"
|
|
42
|
+
exit
|
|
43
|
+
end
|
|
44
|
+
|
|
28
45
|
end
|
|
29
46
|
|
|
30
47
|
optparse.parse!
|
|
@@ -3,19 +3,30 @@ class CreateTests
|
|
|
3
3
|
|
|
4
4
|
class << self
|
|
5
5
|
|
|
6
|
-
# Create the helper file
|
|
7
|
-
|
|
6
|
+
# Create the helper file (param stubs only).
|
|
7
|
+
# chain_requires: relative paths (from the helper file) for setup/cleanup, included only when those files are generated.
|
|
8
|
+
private def create_helper(params, helper_txt, settings_relative_path: '../settings/general', chain_requires: [])
|
|
9
|
+
chain_lines = chain_requires.map { |rel| "require_relative '#{rel}'\n" }.join
|
|
8
10
|
if helper_txt == ""
|
|
9
11
|
output = "# for the case we want to use it standalone, not inside the project
|
|
10
|
-
require_relative '
|
|
11
|
-
|
|
12
|
+
require_relative '#{settings_relative_path}' unless defined?(ROOT_DIR)
|
|
13
|
+
#{chain_lines}
|
|
12
14
|
# On the methods you can pass the active http connection or none, then it will be created a new one.
|
|
13
15
|
# Examples from tests:
|
|
14
16
|
# Helper.the_method_i_call(@http)
|
|
15
17
|
# Helper.the_method_i_call()
|
|
16
18
|
module Helper\n"
|
|
17
19
|
else
|
|
18
|
-
output = helper_txt
|
|
20
|
+
output = helper_txt.dup
|
|
21
|
+
missing = chain_requires.reject { |rel| output.include?("require_relative '#{rel}'") }
|
|
22
|
+
unless missing.empty?
|
|
23
|
+
insert = missing.map { |rel| "require_relative '#{rel}'\n" }.join
|
|
24
|
+
if output.include?("module Helper")
|
|
25
|
+
output.sub!("module Helper", "#{insert}module Helper")
|
|
26
|
+
else
|
|
27
|
+
output = insert + output
|
|
28
|
+
end
|
|
29
|
+
end
|
|
19
30
|
output.gsub!(/\s*end\s*\Z/,"\n")
|
|
20
31
|
end
|
|
21
32
|
|
|
@@ -33,5 +44,86 @@ class CreateTests
|
|
|
33
44
|
output += "\nend"
|
|
34
45
|
|
|
35
46
|
end
|
|
47
|
+
|
|
48
|
+
# Generate helper/setup.rb with cascading setup methods
|
|
49
|
+
private def create_setup_file(setup_cleanup_resources)
|
|
50
|
+
output = "require 'ostruct'\nmodule Helper\n"
|
|
51
|
+
|
|
52
|
+
ordered = deduplicate_resources(setup_cleanup_resources)
|
|
53
|
+
|
|
54
|
+
ordered.each do |res|
|
|
55
|
+
param_list = res[:params_up_to].map { |p| p.gsub("@", "") }.join(", ")
|
|
56
|
+
parent = find_parent_resource(res, ordered)
|
|
57
|
+
|
|
58
|
+
output += "# TODO: Create the #{res[:resource]} resource needed as prerequisite\n"
|
|
59
|
+
output += "def self.setup_#{res[:resource]}(http, #{param_list})\n"
|
|
60
|
+
if parent
|
|
61
|
+
parent_params = parent[:params_up_to].map { |p| p.gsub("@", "") }.join(", ")
|
|
62
|
+
output += "parent_result = setup_#{parent[:resource]}(http, #{parent_params})\n"
|
|
63
|
+
output += "return parent_result unless parent_result[:state] == 'Succeeded'\n"
|
|
64
|
+
end
|
|
65
|
+
output += 'http.logger.info "Helper.#{__method__}"'
|
|
66
|
+
output += "\n"
|
|
67
|
+
output += "OpenStruct.new(state: 'Succeeded')\n"
|
|
68
|
+
output += "end\n"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
output += "\nend\n"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Generate helper/cleanup.rb with cascading cleanup methods
|
|
75
|
+
private def create_cleanup_file(setup_cleanup_resources)
|
|
76
|
+
output = "module Helper\n"
|
|
77
|
+
|
|
78
|
+
ordered = deduplicate_resources(setup_cleanup_resources)
|
|
79
|
+
|
|
80
|
+
ordered.each do |res|
|
|
81
|
+
param_list = res[:params_up_to].map { |p| p.gsub("@", "") }.join(", ")
|
|
82
|
+
parent = find_parent_resource(res, ordered)
|
|
83
|
+
|
|
84
|
+
output += "# TODO: Delete the #{res[:resource]} resource after tests\n"
|
|
85
|
+
output += "def self.cleanup_#{res[:resource]}(http, #{param_list})\n"
|
|
86
|
+
output += 'http.logger.info "Helper.#{__method__}"'
|
|
87
|
+
output += "\n"
|
|
88
|
+
if parent
|
|
89
|
+
parent_params = parent[:params_up_to].map { |p| p.gsub("@", "") }.join(", ")
|
|
90
|
+
output += "cleanup_#{parent[:resource]}(http, #{parent_params})\n"
|
|
91
|
+
end
|
|
92
|
+
output += "end\n"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
output += "\nend\n"
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
private def deduplicate_resources(resources)
|
|
99
|
+
seen = []
|
|
100
|
+
resources.each_with_object([]) do |res, arr|
|
|
101
|
+
next if seen.include?(res[:resource])
|
|
102
|
+
seen << res[:resource]
|
|
103
|
+
arr << res
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Find the actual parent resource by matching params_up_to as a proper prefix
|
|
108
|
+
private def find_parent_resource(resource, all_resources)
|
|
109
|
+
my_params = resource[:params_up_to]
|
|
110
|
+
return nil if my_params.size <= 1
|
|
111
|
+
|
|
112
|
+
parent_params = my_params[0..-2]
|
|
113
|
+
all_resources.reverse_each do |candidate|
|
|
114
|
+
return candidate if candidate[:params_up_to] == parent_params
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
best = nil
|
|
118
|
+
all_resources.each do |candidate|
|
|
119
|
+
next if candidate[:resource] == resource[:resource]
|
|
120
|
+
next unless candidate[:params_up_to].size < my_params.size
|
|
121
|
+
next unless candidate[:params_up_to] == my_params[0, candidate[:params_up_to].size]
|
|
122
|
+
if best.nil? || candidate[:params_up_to].size > best[:params_up_to].size
|
|
123
|
+
best = candidate
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
best
|
|
127
|
+
end
|
|
36
128
|
end
|
|
37
129
|
end
|
|
@@ -4,7 +4,7 @@ class CreateTests
|
|
|
4
4
|
class << self
|
|
5
5
|
|
|
6
6
|
# Create the settings file
|
|
7
|
-
private def create_settings(
|
|
7
|
+
private def create_settings(requests_relative_path, modules_to_include, helper_relative_path: '../spec/helper.rb')
|
|
8
8
|
output = "# required libraries
|
|
9
9
|
require 'nice_http'
|
|
10
10
|
require 'nice_hash'
|
|
@@ -22,12 +22,12 @@ class CreateTests
|
|
|
22
22
|
NiceHttp.log = :file_run
|
|
23
23
|
# Add here the headers for authentication for example
|
|
24
24
|
NiceHttp.headers = {
|
|
25
|
-
|
|
25
|
+
Authentication: 'Token'
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
# Requests
|
|
29
|
-
require_relative '
|
|
30
|
-
require_relative '
|
|
29
|
+
require_relative '#{requests_relative_path}'\n
|
|
30
|
+
require_relative '#{helper_relative_path}'\n"
|
|
31
31
|
|
|
32
32
|
modules_to_include.each do |m|
|
|
33
33
|
output += "include #{m}\n"
|
|
@@ -4,22 +4,37 @@ class CreateTests
|
|
|
4
4
|
class << self
|
|
5
5
|
|
|
6
6
|
# Return true or false and the test source code
|
|
7
|
-
private def create_test(module_txt, method_txt, method_obj, test_txt)
|
|
7
|
+
private def create_test(module_txt, method_txt, method_obj, test_txt, settings_relative_path: '../../settings/general', cleanup: :after_all)
|
|
8
|
+
unless cleanup == false || [:after_all, :after_each].include?(cleanup)
|
|
9
|
+
message = "** Wrong cleanup parameter: #{cleanup}"
|
|
10
|
+
raise message
|
|
11
|
+
end
|
|
12
|
+
|
|
8
13
|
modified = false
|
|
9
14
|
mod_name = module_txt.scan(/::(\w+)$/).join
|
|
10
15
|
req_txt = "#{method_txt}("
|
|
11
16
|
params = []
|
|
12
17
|
keywords_required = []
|
|
18
|
+
keyreq_kwargs = {}
|
|
13
19
|
method_obj.parameters.each do |p|
|
|
14
|
-
if p[0] == :req #required
|
|
20
|
+
if p[0] == :req #required positional
|
|
15
21
|
params << "@#{p[1]}"
|
|
22
|
+
elsif p[0] == :keyreq
|
|
23
|
+
keywords_required << p[1]
|
|
24
|
+
keyreq_kwargs[p[1]] = nil
|
|
16
25
|
elsif p[0] == :key and p[1].to_s.match?(/^[a-z]/i) and Object.const_defined?(p[1].to_s.upcase)
|
|
26
|
+
# create_constants: required path/query as keyword with UPCASE constant default
|
|
17
27
|
keywords_required << p[1]
|
|
18
28
|
end
|
|
19
29
|
end
|
|
20
|
-
|
|
30
|
+
call_parts = params.dup
|
|
31
|
+
keywords_required.each { |k| call_parts << "#{k}: @#{k}" }
|
|
32
|
+
req_txt += call_parts.join(", ")
|
|
21
33
|
req_txt += ")"
|
|
22
|
-
|
|
34
|
+
require 'nice_hash'
|
|
35
|
+
# Keyword-only methods (create_constants) must not receive nil positionals;
|
|
36
|
+
# :keyreq needs an explicit keyword so Method#call succeeds.
|
|
37
|
+
request = method_obj.call(*Array.new(params.size), **keyreq_kwargs)
|
|
23
38
|
|
|
24
39
|
req_txt = "#{mod_name}.#{req_txt}"
|
|
25
40
|
params_declaration_txt = ""
|
|
@@ -28,21 +43,80 @@ class CreateTests
|
|
|
28
43
|
params_declaration_txt << "#{p} = Helper.#{p.gsub('@','')}(@http)\n"
|
|
29
44
|
@params << p
|
|
30
45
|
end
|
|
46
|
+
keywords_required.each do |k|
|
|
47
|
+
params_declaration_txt << "@#{k} = Helper.#{k}(@http)\n"
|
|
48
|
+
@params << "@#{k}"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
all_params_for_chain = params + keywords_required.map { |k| "@#{k}" }
|
|
52
|
+
resource_chain, target_resource = extract_resource_chain(request, all_params_for_chain)
|
|
53
|
+
@setup_cleanup_resources ||= []
|
|
54
|
+
@setup_cleanup_resources.concat(resource_chain) unless resource_chain.empty?
|
|
55
|
+
@setup_cleanup_resources << target_resource if target_resource
|
|
56
|
+
|
|
57
|
+
is_delete = request[:method] == :delete
|
|
58
|
+
needs_target_setup = target_resource && [:delete, :get, :patch].include?(request[:method])
|
|
59
|
+
parent_resource = resource_chain.last
|
|
60
|
+
|
|
61
|
+
method_suffix = method_txt.to_s.split("_").map { |w| w[0..2] }.join[0..2]
|
|
62
|
+
method_suffix = "-#{method_suffix}"
|
|
63
|
+
|
|
64
|
+
setup_before_all_txt = ""
|
|
65
|
+
setup_before_each_txt = ""
|
|
66
|
+
after_each_txt = ""
|
|
67
|
+
after_all_txt = ""
|
|
68
|
+
|
|
69
|
+
if is_delete && target_resource && parent_resource
|
|
70
|
+
setup_before_all_txt = "expect(Helper.setup_#{parent_resource[:resource]}(@http, #{parent_resource[:params_up_to].join(', ')}).state).to eq 'Succeeded'\n"
|
|
71
|
+
setup_before_each_txt = "expect(Helper.setup_#{target_resource[:resource]}(@http, #{target_resource[:params_up_to].join(', ')}).state).to eq 'Succeeded'\n"
|
|
72
|
+
if cleanup
|
|
73
|
+
after_each_txt = "after(:each) do\nHelper.cleanup_#{target_resource[:resource]}(@http, #{target_resource[:params_up_to].join(', ')})\nend\n"
|
|
74
|
+
after_all_txt = "after(:all) do\nunless defined?(DONT_DELETE) && DONT_DELETE\nHelper.cleanup_#{parent_resource[:resource]}(@http, #{parent_resource[:params_up_to].join(', ')})\nend\nend\n"
|
|
75
|
+
end
|
|
76
|
+
elsif needs_target_setup && target_resource
|
|
77
|
+
setup_before_all_txt = "expect(Helper.setup_#{target_resource[:resource]}(@http, #{target_resource[:params_up_to].join(', ')}).state).to eq 'Succeeded'\n"
|
|
78
|
+
if cleanup
|
|
79
|
+
after_all_txt = "after(:all) do\nunless defined?(DONT_DELETE) && DONT_DELETE\nHelper.cleanup_#{target_resource[:resource]}(@http, #{target_resource[:params_up_to].join(', ')})\nend\nend\n"
|
|
80
|
+
end
|
|
81
|
+
elsif parent_resource
|
|
82
|
+
setup_before_all_txt = "expect(Helper.setup_#{parent_resource[:resource]}(@http, #{parent_resource[:params_up_to].join(', ')}).state).to eq 'Succeeded'\n"
|
|
83
|
+
if cleanup && target_resource
|
|
84
|
+
after_each_txt = "after(:each) do\nHelper.cleanup_#{target_resource[:resource]}(@http, #{target_resource[:params_up_to].join(', ')})\nend\n"
|
|
85
|
+
after_all_txt = "after(:all) do\nunless defined?(DONT_DELETE) && DONT_DELETE\nHelper.cleanup_#{parent_resource[:resource]}(@http, #{parent_resource[:params_up_to].join(', ')})\nend\nend\n"
|
|
86
|
+
elsif cleanup
|
|
87
|
+
after_all_txt = "after(:all) do\nunless defined?(DONT_DELETE) && DONT_DELETE\nHelper.cleanup_#{parent_resource[:resource]}(@http, #{parent_resource[:params_up_to].join(', ')})\nend\nend\n"
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
if cleanup == :after_each && !after_all_txt.empty?
|
|
92
|
+
after_each_txt += after_all_txt.sub("after(:all)", "after(:each)")
|
|
93
|
+
after_all_txt = ""
|
|
94
|
+
end
|
|
31
95
|
|
|
32
96
|
if test_txt ==""
|
|
33
97
|
modified = true
|
|
98
|
+
|
|
99
|
+
suffix_txt = ""
|
|
100
|
+
if target_resource
|
|
101
|
+
target_param = target_resource[:params_up_to].last
|
|
102
|
+
suffix_txt = "#{target_param} = #{target_param} + \"#{method_suffix}\"\n"
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
before_each_block = "before(:each) do |example|\n"
|
|
106
|
+
before_each_block << "@http = NiceHttp.new()\n" if is_delete && target_resource
|
|
107
|
+
before_each_block << setup_before_each_txt
|
|
108
|
+
before_each_block << "@http.logger.info(\"\\n\\n\#{'='*100}\\nTest: \#{example.description}\\n\#{'-'*100}\")\nend\n"
|
|
109
|
+
|
|
34
110
|
output = "
|
|
35
|
-
require_relative '
|
|
111
|
+
require_relative '#{settings_relative_path}'
|
|
36
112
|
if defined?(#{mod_name}) and defined?(#{mod_name}.#{method_txt})
|
|
37
113
|
RSpec.describe #{mod_name}, '##{method_txt}' do
|
|
38
114
|
before(:all) do
|
|
39
115
|
@http = NiceHttp.new()
|
|
40
|
-
#{params_declaration_txt}@request = #{req_txt}
|
|
116
|
+
#{params_declaration_txt}#{suffix_txt}#{setup_before_all_txt}@request = #{req_txt}
|
|
41
117
|
@http.logger.info(\"\\n\#{'+'*50} Before All ends \#{'+'*50}\")
|
|
42
118
|
end
|
|
43
|
-
|
|
44
|
-
@http.logger.info(\"\\n\\n\#{'='*100}\\nTest: \#{example.description}\\n\#{'-'*100}\")
|
|
45
|
-
end\n"
|
|
119
|
+
#{after_all_txt}#{after_each_txt}#{before_each_block}\n"
|
|
46
120
|
else
|
|
47
121
|
output = test_txt
|
|
48
122
|
if output.match?(/\s*end\s*end\s*end\s*\Z/)
|
|
@@ -55,22 +129,42 @@ class CreateTests
|
|
|
55
129
|
|
|
56
130
|
tests = Hash.new()
|
|
57
131
|
|
|
132
|
+
has_data_examples = request.key?(:data_examples) &&
|
|
133
|
+
request[:data_examples].is_a?(Array) &&
|
|
134
|
+
!request[:data_examples].empty?
|
|
135
|
+
# Prefer data_pattern for variation/invalid-field tests; fall back to :data
|
|
136
|
+
payload_source = if request.key?(:data_pattern)
|
|
137
|
+
":data_pattern"
|
|
138
|
+
elsif request.key?(:data)
|
|
139
|
+
":data"
|
|
140
|
+
end
|
|
141
|
+
|
|
58
142
|
# first response on responses is the one expected to be returned when success
|
|
59
143
|
if request.key?(:responses) and request[:responses].size > 0
|
|
60
144
|
code = request[:responses].keys[0]
|
|
61
145
|
title="it 'has correct structure in successful response' "
|
|
62
|
-
|
|
146
|
+
if !request.key?(:data) && has_data_examples
|
|
147
|
+
tests[title] = "do
|
|
148
|
+
request = @request.deep_copy
|
|
149
|
+
request[:data] = @request[:data_examples].first
|
|
150
|
+
resp = @http.#{request[:method]}(request)
|
|
151
|
+
expect(resp.code).to eq #{code}\n"
|
|
152
|
+
if request[:responses][code].is_a?(Hash) and request[:responses][code].key?(:data)
|
|
153
|
+
tests[title] +="result = NiceHttp.validate_response(resp, @request.responses._#{code}.data, include_diff: true)\n"
|
|
154
|
+
tests[title] +="expect(result[:ok]).to be(true), \"Structure mismatch: \#{result[:diff]}\"\n"
|
|
155
|
+
end
|
|
156
|
+
tests[title] += "end\n"
|
|
157
|
+
else
|
|
158
|
+
tests[title] = "do
|
|
63
159
|
resp = @http.#{request[:method]}(@request)
|
|
64
160
|
expect(resp.code).to eq #{code}\n"
|
|
65
161
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
tests[title] +="expect(
|
|
69
|
-
else
|
|
70
|
-
tests[title] +="expect(NiceHash.compare_structure(@request.responses._#{code}.data, resp.data.json, true)).to be true\n"
|
|
162
|
+
if request[:responses][code].is_a?(Hash) and request[:responses][code].key?(:data)
|
|
163
|
+
tests[title] +="result = NiceHttp.validate_response(resp, @request.responses._#{code}.data, include_diff: true)\n"
|
|
164
|
+
tests[title] +="expect(result[:ok]).to be(true), \"Structure mismatch: \#{result[:diff]}\"\n"
|
|
71
165
|
end
|
|
166
|
+
tests[title] += "end\n"
|
|
72
167
|
end
|
|
73
|
-
tests[title] += "end\n"
|
|
74
168
|
end
|
|
75
169
|
|
|
76
170
|
title = "it 'doesn\\'t retrieve data if not authenticated'"
|
|
@@ -113,12 +207,56 @@ class CreateTests
|
|
|
113
207
|
tests["it 'returns error if required parameter empty' "] = "do\n#{empty_param}"
|
|
114
208
|
end
|
|
115
209
|
|
|
116
|
-
if request.key?(:
|
|
210
|
+
if request.key?(:mock_response)
|
|
211
|
+
title = "it 'returns expected mock response' "
|
|
212
|
+
tests[title] = "do
|
|
213
|
+
@http_mock = NiceHttp.new()
|
|
214
|
+
@http_mock.use_mocks = true
|
|
215
|
+
resp = @http_mock.#{request[:method]}(@request)
|
|
216
|
+
expect(resp.code).to eq @request[:mock_response][:code]
|
|
217
|
+
expect(resp.message).to eq @request[:mock_response][:message]\n"
|
|
218
|
+
if request[:mock_response].key?(:data)
|
|
219
|
+
tests[title] += "expect(NiceHash.compare_structure(@request[:mock_response][:data], resp.data.json, true)).to be true\n"
|
|
220
|
+
end
|
|
221
|
+
tests[title] += "end\n"
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
if payload_source && [:post, :put, :patch].include?(request[:method])
|
|
225
|
+
title = "it 'handles multiple valid data variations' "
|
|
226
|
+
tests[title] = "do
|
|
227
|
+
@request[#{payload_source}].generate_n(5, :correct).each do |generated_data|
|
|
228
|
+
request = @request.deep_copy
|
|
229
|
+
request[:data] = generated_data
|
|
230
|
+
resp = @http.#{request[:method]}(request)
|
|
231
|
+
expect(resp.code.to_i).to be_between(200, 299)
|
|
232
|
+
end
|
|
233
|
+
end\n"
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
if payload_source
|
|
237
|
+
title = "it 'returns error when individual data fields are invalid' "
|
|
238
|
+
tests[title] = "do
|
|
239
|
+
wrong = @request[#{payload_source}].generate(:correct, errors: :min_length)
|
|
240
|
+
NiceHash.change_one_by_one([@request[#{payload_source}], :correct], wrong).each do |one_wrong|
|
|
241
|
+
request = @request.deep_copy
|
|
242
|
+
request[:data] = one_wrong
|
|
243
|
+
resp = @http.#{request[:method]}(request)
|
|
244
|
+
expect(resp.code.to_i).not_to be_between(200, 299)
|
|
245
|
+
end
|
|
246
|
+
end\n"
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
if request.key?(:data_required) && (request.key?(:data) || has_data_examples)
|
|
250
|
+
seed_data_line = if !request.key?(:data) && has_data_examples
|
|
251
|
+
"request[:data] = request[:data_examples].first\n "
|
|
252
|
+
else
|
|
253
|
+
""
|
|
254
|
+
end
|
|
117
255
|
empty_param_data = ""
|
|
118
256
|
empty_param_data += "
|
|
119
257
|
@request[:data_required].each do |p|
|
|
120
258
|
request = @request.deep_copy
|
|
121
|
-
request.values_for[p] = ''
|
|
259
|
+
#{seed_data_line}request.values_for[p] = ''
|
|
122
260
|
resp = @http.#{request[:method]}(request)
|
|
123
261
|
expect(resp.code).not_to be_between('200', '299')
|
|
124
262
|
if request.responses.key?(resp.code.to_sym)
|
|
@@ -133,7 +271,7 @@ class CreateTests
|
|
|
133
271
|
missing_param_data += "
|
|
134
272
|
@request[:data_required].each do |p|
|
|
135
273
|
request = @request.deep_copy
|
|
136
|
-
NiceHash.delete_nested(request[:data], p)
|
|
274
|
+
#{seed_data_line}NiceHash.delete_nested(request[:data], p)
|
|
137
275
|
resp = @http.#{request[:method]}(request)
|
|
138
276
|
expect(resp.code).not_to be_between('200', '299')
|
|
139
277
|
if request.responses.key?(resp.code.to_sym)
|
data/lib/create-tests/from.rb
CHANGED
|
@@ -11,20 +11,27 @@ class CreateTests
|
|
|
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
|
+
# @param dry_run [Boolean]. (default false). Preview which files would be created or modified without writing anything.
|
|
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.
|
|
17
|
+
# @param settings_dir [String]. (default './settings'). Output directory for generated settings files.
|
|
18
|
+
# @param cleanup [Symbol, false]. (default :after_all). :after_all, :after_each, or false. Controls cleanup hook generation.
|
|
14
19
|
##############################################################################
|
|
15
|
-
def self.from(requests_file, type: :request_hash, test: :rspec, mode: :append)
|
|
20
|
+
def self.from(requests_file, type: :request_hash, test: :rspec, mode: :append, dry_run: false, return_data: false, spec_dir: './spec', settings_dir: './settings', cleanup: :after_all)
|
|
21
|
+
no_write = dry_run || return_data
|
|
22
|
+
@generated_data = {} if return_data
|
|
23
|
+
|
|
16
24
|
begin
|
|
17
|
-
f = File.new("#{requests_file}_create_tests.log", "w")
|
|
18
|
-
f.sync = true
|
|
19
|
-
@logger = Logger.new
|
|
20
|
-
puts "- Logs: #{requests_file}_create_tests.log"
|
|
25
|
+
f = File.new("#{requests_file}_create_tests.log", "w") unless no_write
|
|
26
|
+
f.sync = true if f
|
|
27
|
+
@logger = Logger.new(f)
|
|
28
|
+
puts "- Logs: #{requests_file}_create_tests.log" unless no_write
|
|
21
29
|
rescue StandardError => e
|
|
22
30
|
warn "** Not possible to create the Logger file"
|
|
23
31
|
warn e
|
|
24
32
|
@logger = Logger.new nil
|
|
25
33
|
end
|
|
26
34
|
@logger.info "requests_file: #{requests_file}, type: #{type}, test: #{test}, mode: #{mode}"
|
|
27
|
-
requests_file_orig = requests_file
|
|
28
35
|
|
|
29
36
|
requests_file = if requests_file["./"].nil?
|
|
30
37
|
requests_file
|
|
@@ -55,12 +62,18 @@ class CreateTests
|
|
|
55
62
|
raise message
|
|
56
63
|
end
|
|
57
64
|
|
|
65
|
+
unless cleanup == false || [:after_all, :after_each].include?(cleanup)
|
|
66
|
+
message = "** Wrong cleanup parameter: #{cleanup}"
|
|
67
|
+
@logger.fatal message
|
|
68
|
+
raise message
|
|
69
|
+
end
|
|
70
|
+
|
|
58
71
|
if mode == :overwrite
|
|
59
72
|
message = "** Pay attention, if any of the files exist, will be overwritten"
|
|
60
73
|
@logger.warn message
|
|
61
74
|
warn message
|
|
62
75
|
elsif mode == :append
|
|
63
|
-
if Dir["
|
|
76
|
+
if Dir["#{spec_dir}/*/**_spec.rb"].size > 0
|
|
64
77
|
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."
|
|
65
78
|
@logger.warn message
|
|
66
79
|
warn message
|
|
@@ -68,13 +81,25 @@ class CreateTests
|
|
|
68
81
|
end
|
|
69
82
|
|
|
70
83
|
@params = Array.new
|
|
84
|
+
@setup_cleanup_resources = Array.new
|
|
85
|
+
|
|
86
|
+
require 'pathname'
|
|
87
|
+
# Resolve symlinks (e.g. macOS /tmp -> /private/tmp) so require_relative
|
|
88
|
+
# paths stay valid when settings/spec dirs live outside the project tree.
|
|
89
|
+
resolved_settings_dir = resolve_pathname(settings_dir)
|
|
90
|
+
resolved_spec_dir = resolve_pathname(spec_dir)
|
|
91
|
+
settings_path = resolved_settings_dir.join('general')
|
|
92
|
+
helper_path = resolved_spec_dir.join('helper.rb')
|
|
93
|
+
settings_from_helper = settings_path.relative_path_from(resolved_spec_dir).to_s
|
|
94
|
+
helper_from_settings = helper_path.relative_path_from(resolved_settings_dir).to_s
|
|
95
|
+
requests_from_settings = resolve_pathname(requests_file).relative_path_from(resolved_settings_dir).to_s
|
|
71
96
|
|
|
72
|
-
Dir.mkdir
|
|
97
|
+
Dir.mkdir spec_dir unless no_write or test != :rspec or Dir.exist?(spec_dir)
|
|
73
98
|
|
|
74
99
|
add_settings = true
|
|
75
|
-
settings_file = "
|
|
76
|
-
helper_file =
|
|
77
|
-
Dir.mkdir
|
|
100
|
+
settings_file = "#{settings_dir}/general.rb"
|
|
101
|
+
helper_file = "#{spec_dir}/helper.rb"
|
|
102
|
+
Dir.mkdir settings_dir unless no_write or Dir.exist?(settings_dir)
|
|
78
103
|
if File.exist?(settings_file) and mode!=:overwrite
|
|
79
104
|
message = "** The file #{settings_file} already exists so no content will be added to it.\n"
|
|
80
105
|
message += " Remove the settings file to be able to be generated by create_tests or set mode: :overwrite"
|
|
@@ -97,8 +122,8 @@ class CreateTests
|
|
|
97
122
|
end
|
|
98
123
|
|
|
99
124
|
begin
|
|
100
|
-
|
|
101
|
-
rescue
|
|
125
|
+
require requests_file
|
|
126
|
+
rescue StandardError => stack
|
|
102
127
|
message = "\n\n** Error evaluating the ruby file containing the requests: \n" + stack.to_s
|
|
103
128
|
@logger.fatal message
|
|
104
129
|
raise message
|
|
@@ -126,22 +151,28 @@ class CreateTests
|
|
|
126
151
|
mods_to_include << m.scan(/^(.+)::/).join
|
|
127
152
|
end
|
|
128
153
|
mods_to_include.uniq!
|
|
129
|
-
|
|
154
|
+
settings_content = create_settings(requests_from_settings, mods_to_include, helper_relative_path: helper_from_settings)
|
|
155
|
+
if no_write
|
|
156
|
+
@generated_data[settings_file] = settings_content if return_data
|
|
157
|
+
puts "- Settings (dry run): #{settings_file}" if dry_run
|
|
158
|
+
else
|
|
159
|
+
File.open(settings_file, "w") { |file| file.write(settings_content) }
|
|
160
|
+
system("rufo", settings_file, out: File::NULL, err: File::NULL)
|
|
161
|
+
end
|
|
130
162
|
message = "- Settings: #{settings_file}"
|
|
131
163
|
@logger.info message
|
|
132
|
-
puts message
|
|
133
|
-
`rufo #{settings_file}`
|
|
164
|
+
puts message unless no_write
|
|
134
165
|
end
|
|
135
166
|
|
|
136
167
|
modules.each do |mod_txt|
|
|
137
168
|
mod_name = mod_txt.scan(/::(\w+)$/).join
|
|
138
|
-
folder = "
|
|
139
|
-
unless Dir.exist?(folder)
|
|
169
|
+
folder = "#{spec_dir}/#{mod_name}"
|
|
170
|
+
unless no_write or Dir.exist?(folder)
|
|
140
171
|
Dir.mkdir folder
|
|
141
172
|
@logger.info "Created folder: #{folder}"
|
|
142
173
|
end
|
|
143
|
-
mod_obj =
|
|
144
|
-
mod_methods_txt =
|
|
174
|
+
mod_obj = Object.const_get(mod_txt)
|
|
175
|
+
mod_methods_txt = mod_obj.methods(false)
|
|
145
176
|
mod_methods_txt.each do |method_txt|
|
|
146
177
|
test_file = "#{folder}/#{method_txt}_spec.rb"
|
|
147
178
|
if File.exist?(test_file) and mode==:dont_overwrite
|
|
@@ -155,13 +186,18 @@ class CreateTests
|
|
|
155
186
|
else
|
|
156
187
|
test_txt = ''
|
|
157
188
|
end
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
189
|
+
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)
|
|
191
|
+
if no_write
|
|
192
|
+
@generated_data[test_file] = txt if return_data
|
|
193
|
+
else
|
|
194
|
+
File.open(test_file, "w") { |file| file.write(txt) }
|
|
195
|
+
system("rufo", test_file, out: File::NULL, err: File::NULL)
|
|
196
|
+
end
|
|
161
197
|
if test_txt == ""
|
|
162
|
-
message = "- Test created: #{test_file}"
|
|
198
|
+
message = "- Test #{no_write ? 'would be created' : 'created'}: #{test_file}"
|
|
163
199
|
elsif modified
|
|
164
|
-
message = "- Test updated: #{test_file}"
|
|
200
|
+
message = "- Test #{no_write ? 'would be updated' : 'updated'}: #{test_file}"
|
|
165
201
|
else
|
|
166
202
|
message = "- Test without changes: #{test_file}"
|
|
167
203
|
end
|
|
@@ -175,11 +211,72 @@ class CreateTests
|
|
|
175
211
|
|
|
176
212
|
if add_helper
|
|
177
213
|
@params.uniq!
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
@
|
|
181
|
-
|
|
182
|
-
|
|
214
|
+
helper_dir = "./helper"
|
|
215
|
+
chain_requires = []
|
|
216
|
+
if @setup_cleanup_resources.size > 0
|
|
217
|
+
spec_pathname = Pathname.new(spec_dir)
|
|
218
|
+
chain_requires = [
|
|
219
|
+
Pathname.new("#{helper_dir}/setup").relative_path_from(spec_pathname).to_s,
|
|
220
|
+
Pathname.new("#{helper_dir}/cleanup").relative_path_from(spec_pathname).to_s,
|
|
221
|
+
]
|
|
222
|
+
end
|
|
223
|
+
helper_content = create_helper(@params, helper_txt, settings_relative_path: settings_from_helper, chain_requires: chain_requires)
|
|
224
|
+
if no_write
|
|
225
|
+
@generated_data[helper_file] = helper_content if return_data
|
|
226
|
+
puts "- Helper (dry run): #{helper_file}" if dry_run
|
|
227
|
+
else
|
|
228
|
+
File.open(helper_file, "w") { |file| file.write(helper_content) }
|
|
229
|
+
system("rufo", helper_file, out: File::NULL, err: File::NULL)
|
|
230
|
+
message = "- Helper: #{helper_file}"
|
|
231
|
+
@logger.info message
|
|
232
|
+
puts message
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
if @setup_cleanup_resources.size > 0
|
|
236
|
+
write_chain_file = lambda do |path, content, label|
|
|
237
|
+
if File.exist?(path) && mode != :overwrite
|
|
238
|
+
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"
|
|
240
|
+
@logger.warn message
|
|
241
|
+
warn message
|
|
242
|
+
return
|
|
243
|
+
end
|
|
244
|
+
if no_write
|
|
245
|
+
@generated_data[path] = content if return_data
|
|
246
|
+
puts "- #{label} (dry run): #{path}" if dry_run
|
|
247
|
+
else
|
|
248
|
+
Dir.mkdir helper_dir unless Dir.exist?(helper_dir)
|
|
249
|
+
File.open(path, "w") { |file| file.write(content) }
|
|
250
|
+
system("rufo", path, out: File::NULL, err: File::NULL)
|
|
251
|
+
message = "- #{label}: #{path}"
|
|
252
|
+
@logger.info message
|
|
253
|
+
puts message
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
write_chain_file.call("#{helper_dir}/setup.rb", create_setup_file(@setup_cleanup_resources), "Helper setup")
|
|
258
|
+
write_chain_file.call("#{helper_dir}/cleanup.rb", create_cleanup_file(@setup_cleanup_resources), "Helper cleanup")
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
return @generated_data if return_data
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# Expand path and resolve symlinks for existing ancestors so relative
|
|
266
|
+
# require paths stay valid across mounts like macOS /tmp -> /private/tmp.
|
|
267
|
+
def self.resolve_pathname(path)
|
|
268
|
+
pn = Pathname.new(File.expand_path(path.to_s))
|
|
269
|
+
return pn.realpath if pn.exist?
|
|
270
|
+
|
|
271
|
+
missing = []
|
|
272
|
+
current = pn
|
|
273
|
+
until current.exist? || current.root?
|
|
274
|
+
missing.unshift(current.basename)
|
|
275
|
+
current = current.dirname
|
|
183
276
|
end
|
|
277
|
+
resolved = current.exist? ? current.realpath : current
|
|
278
|
+
missing.each { |part| resolved += part }
|
|
279
|
+
resolved
|
|
184
280
|
end
|
|
281
|
+
private_class_method :resolve_pathname
|
|
185
282
|
end
|
|
@@ -6,13 +6,13 @@ class CreateTests
|
|
|
6
6
|
# fex: ['Swagger::UberApi::V1_0_0::Products', 'Swagger::UberApi::V1_0_0::Cities']
|
|
7
7
|
private def get_modules(mod)
|
|
8
8
|
modules = []
|
|
9
|
-
mod =
|
|
9
|
+
mod = Object.const_get(mod) if mod.is_a?(String)
|
|
10
10
|
mod.constants.each do |m|
|
|
11
|
-
|
|
12
|
-
if
|
|
11
|
+
child = mod.const_get(m)
|
|
12
|
+
if child.constants.empty?
|
|
13
13
|
modules << "#{mod}::#{m}"
|
|
14
14
|
else
|
|
15
|
-
modules
|
|
15
|
+
modules += get_modules("#{mod}::#{m}")
|
|
16
16
|
end
|
|
17
17
|
end
|
|
18
18
|
modules
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
|
|
2
|
+
class CreateTests
|
|
3
|
+
|
|
4
|
+
class << self
|
|
5
|
+
|
|
6
|
+
# Extracts the resource dependency chain from a request path.
|
|
7
|
+
# Works with both placeholder paths ({param}) and interpolated paths
|
|
8
|
+
# (where params are resolved, possibly to empty strings).
|
|
9
|
+
#
|
|
10
|
+
# @param request [Hash] Request hash containing :path and :method keys
|
|
11
|
+
# @param params [Array<String>] Positional parameter names (e.g. ["@subscription_id", "@pool_name"])
|
|
12
|
+
# @return [Array<Hash>, Hash|nil] Two values: prerequisite chain array and target resource hash (or nil).
|
|
13
|
+
# Each hash has keys: resource (String), param (String), params_up_to (Array)
|
|
14
|
+
private def extract_resource_chain(request, params)
|
|
15
|
+
return [], nil unless request.key?(:path)
|
|
16
|
+
return [], nil if params.size < 2
|
|
17
|
+
|
|
18
|
+
path = request[:path].split("?").first
|
|
19
|
+
param_names = params.map { |p| p.gsub("@", "") }
|
|
20
|
+
|
|
21
|
+
placeholder_mode = path.include?("{")
|
|
22
|
+
|
|
23
|
+
if placeholder_mode
|
|
24
|
+
segments = path.split("/").reject(&:empty?)
|
|
25
|
+
resource_pairs = []
|
|
26
|
+
i = 0
|
|
27
|
+
while i < segments.size
|
|
28
|
+
seg = segments[i]
|
|
29
|
+
next_seg = segments[i + 1]
|
|
30
|
+
if next_seg && next_seg.match?(/\A\{.+\}\z/)
|
|
31
|
+
placeholder = next_seg.gsub(/[{}]/, "")
|
|
32
|
+
resource_pairs << { segment: seg, placeholder: placeholder }
|
|
33
|
+
i += 2
|
|
34
|
+
else
|
|
35
|
+
i += 1
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
else
|
|
39
|
+
segments = path.split("/")
|
|
40
|
+
segments.shift if segments.first == ""
|
|
41
|
+
|
|
42
|
+
resource_pairs = []
|
|
43
|
+
param_idx = 0
|
|
44
|
+
i = 0
|
|
45
|
+
while i < segments.size && param_idx < param_names.size
|
|
46
|
+
seg = segments[i]
|
|
47
|
+
next_seg = segments[i + 1]
|
|
48
|
+
if seg != "" && (next_seg.nil? || next_seg == "")
|
|
49
|
+
resource_pairs << { segment: seg, placeholder: param_names[param_idx] }
|
|
50
|
+
param_idx += 1
|
|
51
|
+
i += 2
|
|
52
|
+
else
|
|
53
|
+
i += 1
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
return [], nil if resource_pairs.size <= 1
|
|
59
|
+
|
|
60
|
+
all_entries = resource_pairs.each_with_index.map do |pair, idx|
|
|
61
|
+
resource_name = to_snake_case(pair[:segment])
|
|
62
|
+
|
|
63
|
+
matching_param = param_names.find { |p| p == pair[:placeholder] || p.end_with?("_#{pair[:placeholder]}") || p.include?(pair[:placeholder]) }
|
|
64
|
+
matching_param ||= pair[:placeholder]
|
|
65
|
+
|
|
66
|
+
params_up_to = []
|
|
67
|
+
(0..idx).each do |j|
|
|
68
|
+
ph = resource_pairs[j][:placeholder]
|
|
69
|
+
mp = param_names.find { |p| p == ph || p.end_with?("_#{ph}") || p.include?(ph) }
|
|
70
|
+
params_up_to << "@#{mp || ph}"
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
{ resource: resource_name, param: matching_param, params_up_to: params_up_to }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
prerequisites = all_entries[0..-2]
|
|
77
|
+
target = all_entries.last
|
|
78
|
+
|
|
79
|
+
return prerequisites, target
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private def to_snake_case(str)
|
|
83
|
+
str.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
|
84
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
|
85
|
+
.downcase
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
data/lib/create_tests.rb
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
|
|
2
2
|
require "logger"
|
|
3
3
|
|
|
4
|
+
require_relative 'create-tests/version'
|
|
4
5
|
require_relative 'create-tests/create_helper'
|
|
5
6
|
require_relative 'create-tests/create_settings'
|
|
6
7
|
require_relative 'create-tests/create_test'
|
|
7
8
|
require_relative 'create-tests/from'
|
|
8
9
|
require_relative 'create-tests/get_modules'
|
|
10
|
+
require_relative 'create-tests/resource_chain'
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: create_tests
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mario Ruiz
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: rufo
|
|
@@ -30,14 +29,14 @@ dependencies:
|
|
|
30
29
|
requirements:
|
|
31
30
|
- - "~>"
|
|
32
31
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: '1.
|
|
32
|
+
version: '1.19'
|
|
34
33
|
type: :runtime
|
|
35
34
|
prerelease: false
|
|
36
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
36
|
requirements:
|
|
38
37
|
- - "~>"
|
|
39
38
|
- !ruby/object:Gem::Version
|
|
40
|
-
version: '1.
|
|
39
|
+
version: '1.19'
|
|
41
40
|
- !ruby/object:Gem::Dependency
|
|
42
41
|
name: rspec
|
|
43
42
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -58,6 +57,20 @@ dependencies:
|
|
|
58
57
|
- - ">="
|
|
59
58
|
- !ruby/object:Gem::Version
|
|
60
59
|
version: 3.8.0
|
|
60
|
+
- !ruby/object:Gem::Dependency
|
|
61
|
+
name: simplecov
|
|
62
|
+
requirement: !ruby/object:Gem::Requirement
|
|
63
|
+
requirements:
|
|
64
|
+
- - "~>"
|
|
65
|
+
- !ruby/object:Gem::Version
|
|
66
|
+
version: '0.22'
|
|
67
|
+
type: :development
|
|
68
|
+
prerelease: false
|
|
69
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
70
|
+
requirements:
|
|
71
|
+
- - "~>"
|
|
72
|
+
- !ruby/object:Gem::Version
|
|
73
|
+
version: '0.22'
|
|
61
74
|
description: CreateTests -- Create Tests automatically from a Requests file. Perfect
|
|
62
75
|
to be used with the result from importing a Swagger file using the open_api_import
|
|
63
76
|
gem
|
|
@@ -78,6 +91,8 @@ files:
|
|
|
78
91
|
- lib/create-tests/create_test.rb
|
|
79
92
|
- lib/create-tests/from.rb
|
|
80
93
|
- lib/create-tests/get_modules.rb
|
|
94
|
+
- lib/create-tests/resource_chain.rb
|
|
95
|
+
- lib/create-tests/version.rb
|
|
81
96
|
- lib/create_tests.rb
|
|
82
97
|
homepage: https://github.com/MarioRuiz/create_tests
|
|
83
98
|
licenses:
|
|
@@ -91,15 +106,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
91
106
|
requirements:
|
|
92
107
|
- - ">="
|
|
93
108
|
- !ruby/object:Gem::Version
|
|
94
|
-
version: '
|
|
109
|
+
version: '3.0'
|
|
95
110
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
96
111
|
requirements:
|
|
97
112
|
- - ">="
|
|
98
113
|
- !ruby/object:Gem::Version
|
|
99
114
|
version: '0'
|
|
100
115
|
requirements: []
|
|
101
|
-
rubygems_version:
|
|
102
|
-
signing_key:
|
|
116
|
+
rubygems_version: 4.0.6
|
|
103
117
|
specification_version: 4
|
|
104
118
|
summary: CreateTests -- Create Tests automatically from a Requests file. Perfect to
|
|
105
119
|
be used with the result from importing a Swagger file using the open_api_import
|