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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0c59ffb44e91a7402acf3d3384268d541e44e27d48ac395baa1387c1b20692a6
4
- data.tar.gz: 131c739da1db5bb00bd586e1647bfa74a672dce6dd33ea0c7004b8e4e475d446
3
+ metadata.gz: 754317e21adff9283ea8d3424b46ee09de1f5a53d9e875b9388014e261f6d561
4
+ data.tar.gz: e70d64dddd1f4312c67149e7ca3761e1d5038332252e9c8a191427e53627c3b9
5
5
  SHA512:
6
- metadata.gz: 3f1c1283a88fe41d8ae4abbdbe80cafa14d7874a89e62b30133160d945d602ea5785747f66103d646921f0e7025744e4aa60b087dbab2aef3af451e06905faad
7
- data.tar.gz: dbb440e25eeeb45c5c22376f5fe474fca830a475b94e5ec042495cb61755b4b79a44c3709165deb756684fae2a8edaeb491ca719fcd019b2bce107ef3435f3c3
6
+ metadata.gz: 10f9687590b335bd26ab6bc039d9ad05eed6ca780608363c934db2a929f11ea229e267d95498935ac9755c4eb6bb4eec437b985d114efe747870e7371fc50dd5
7
+ data.tar.gz: '048048f6f02865499ee314d55841e9e3259fad8c9edcb74c6fb3937e508eb2ccbdebe9b49c6870fe1e8984a466410aa61f0ea7a6b03ef2dfbbe1f1eeabadd752'
data/README.md CHANGED
@@ -1,22 +1,18 @@
1
1
  # CreateTests
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/create_tests.svg)](https://rubygems.org/gems/create_tests)
4
- [![Build Status](https://travis-ci.com/MarioRuiz/create_tests.svg?branch=master)](https://github.com/MarioRuiz/create_tests)
5
- [![Coverage Status](https://coveralls.io/repos/github/MarioRuiz/create_tests/badge.svg?branch=master)](https://coveralls.io/github/MarioRuiz/create_tests?branch=master)
4
+ [![CI](https://github.com/MarioRuiz/create_tests/actions/workflows/ci.yml/badge.svg)](https://github.com/MarioRuiz/create_tests/actions/workflows/ci.yml)
5
+ [![coverage](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/MarioRuiz/create_tests/master/.github/badges/coverage.json)](https://github.com/MarioRuiz/create_tests/actions/workflows/ci.yml)
6
6
  ![Gem](https://img.shields.io/gem/dt/create_tests)
7
7
  ![GitHub commit activity](https://img.shields.io/github/commit-activity/y/MarioRuiz/create_tests)
8
8
  ![GitHub last commit](https://img.shields.io/github/last-commit/MarioRuiz/create_tests)
9
9
  ![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/MarioRuiz/create_tests)
10
10
 
11
+ Create RSpec tests automatically from Request Hash files. Perfect to be used with the result from importing a Swagger / OpenAPI file using the [open_api_import](https://github.com/MarioRuiz/open_api_import) gem.
11
12
 
12
- Create Tests automatically from a Requests file. Perfect to be used with the result from importing a Swagger file using the open_api_import gem. Now we are supporting RSpec.
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
- Take in consideration create_tests gem is using the 'rufo' gem that executes in command line the `rufo` command. In case you experience any trouble with it, visit: https://github.com/ruby-formatter/rufo
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 just from Ruby.
29
+ After installation you can run using the command line executable or from Ruby code.
33
30
 
34
- The execution will create an spec folder where you will have all the RSpec tests. Also it will be added to that file a `helper.rb` file.
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
- Also a `settings` folder that will contain a `general.rb` file that will be required by the tests.
50
+ Example:
37
51
 
38
- ### Executable
52
+ ```bash
53
+ create_tests ./requests/uber.yaml.rb
54
+ ```
39
55
 
40
- For help and see the options, run in command line / bash: `create_tests -h`
56
+ Dry run (preview without writing files):
41
57
 
42
- Example:
43
58
  ```bash
44
- create_tests ./requests/uber.yaml.rb
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
- ### Ruby file
48
- Write your ruby code on a file and in command line/bash: `ruby my_file.rb`
106
+ ### dry_run
49
107
 
50
- This is an example:
108
+ Preview which files would be created or modified without writing anything to disk.
51
109
 
52
110
  ```ruby
53
- require 'create_tests'
54
-
55
- CreateTests.from "./requests/uber.yaml.rb"
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
- On this example we will be creating tests for the Uber API using the Swagger / Open API file.
206
+ Creating tests for the Uber API using a Swagger / OpenAPI file:
62
207
 
63
- 1. Create a folder in your computer called for example `create_tests_example`
208
+ 1. Create a project folder:
64
209
 
65
- 2. Copy the file that we have on `./example/requests/uber.yaml` into a folder called `requests` inside `create_tests_example folder`.
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
- Now all the Request files were created on the `requests` folder:
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. Now we will create the tests by running:
233
+ 4. Generate the tests:
234
+
82
235
  ```bash
83
236
  create_tests ./requests/uber.yaml.rb
84
237
  ```
85
238
 
86
- 5. All your tests will be on `spec` folder, and a `general.rb` file inside `settings` folder was created and also take a look at your `helper.rb` file on `spec` folder.
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
- You can see a reproduction of what we did before on here: https://github.com/MarioRuiz/create_tests/tree/master/example
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/marioruiz/create_tests.
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 ovewritten, so the previous content will deleted") do
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 it will be ovewritten, so the previous content will deleted") do
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
- private def create_helper(params, helper_txt)
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 '../settings/general' unless defined?(ROOT_DIR)
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(requests_file_orig, modules_to_include)
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
- Auhentication: 'Token'
25
+ Authentication: 'Token'
26
26
  }
27
27
 
28
28
  # Requests
29
- require_relative '.#{requests_file_orig}'\n
30
- require_relative '../spec/helper.rb'\n"
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
- req_txt += params.join(", ")
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
- request = eval("require 'nice_hash';#{module_txt}.#{req_txt}")
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 '../../settings/general'
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
- before(:each) do |example|
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
- tests[title] = "do
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
- if request[:responses][code].is_a?(Hash) and request[:responses][code].key?(:data)
67
- if request.key?(:data_pattern)
68
- tests[title] +="expect(NiceHash.compare_structure(@request.responses._#{code}.data, resp.data.json, true, @request.data_pattern)).to be true\n"
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?(:data) and request.key?(:data_required)
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)
@@ -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 f
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["./spec/*/**_spec.rb"].size > 0
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 "./spec" unless test != :rspec or Dir.exist?("./spec")
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 = "./settings/general.rb"
76
- helper_file = './spec/helper.rb'
77
- Dir.mkdir "./settings" unless Dir.exist?("./settings")
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
- eval("require '#{requests_file}'")
101
- rescue Exception => stack
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
- File.open(settings_file, "w") { |file| file.write(create_settings(requests_file_orig, mods_to_include)) }
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 = "./spec/#{mod_name}"
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 = eval("#{mod_txt}")
144
- mod_methods_txt = eval ("#{mod_txt}.methods(false)")
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
- modified, txt = create_test(mod_txt, method_txt, mod_obj.method(method_txt),test_txt)
159
- File.open(test_file, "w") { |file| file.write(txt) }
160
- `rufo #{test_file}`
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
- File.open(helper_file, "w") { |file| file.write(create_helper(@params, helper_txt)) }
179
- message = "- Helper: #{helper_file}"
180
- @logger.info message
181
- puts message
182
- `rufo #{helper_file}`
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 = eval(mod) if mod.kind_of?(String)
9
+ mod = Object.const_get(mod) if mod.is_a?(String)
10
10
  mod.constants.each do |m|
11
- o = eval ("#{mod}::#{m}.constants")
12
- if o.size == 0
11
+ child = mod.const_get(m)
12
+ if child.constants.empty?
13
13
  modules << "#{mod}::#{m}"
14
14
  else
15
- modules = get_modules("#{mod}::#{m}")
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
@@ -0,0 +1,3 @@
1
+ class CreateTests
2
+ VERSION = "1.1.0"
3
+ 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: 0.6.5
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: 2023-07-04 00:00:00.000000000 Z
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.18'
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.18'
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: '2.7'
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: 3.4.3
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