newman_scenario 0.1.2 → 0.1.3
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/Gemfile.lock +3 -3
- data/README.md +28 -28
- data/lib/newman_scenario/scenario.rb +32 -20
- data/lib/newman_scenario/version.rb +1 -1
- data/newman_scenario.gemspec +16 -16
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a249bfa843b4d5696be896e2ac9066cd3891da46cbcc0348ca29b587b6f742ad
|
4
|
+
data.tar.gz: 6c4f66cb953c7ffdf5dacb496889123425ecf4143eefaa83aaf438d42d4bdbcf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4dec924de1eea41bce0cc3ca79605521cc33235c254469819126104bf93bbf48ae12b75434e23d35071131f3cf72048b12670b68e1dea9aa57b4a51cd1750623
|
7
|
+
data.tar.gz: 1e925af5a9c6865e52d4bf8d06f39a88f48448244ffb2613595882325850c86816888d469c7baf6ff0439f59b15f4e578f8e1d7ba5d2eaa773e934cbd6bec688
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
newman_scenario (0.1.
|
4
|
+
newman_scenario (0.1.3)
|
5
5
|
dotenv (= 2.7.5)
|
6
6
|
httparty (= 0.16.2)
|
7
7
|
thor (= 1.0.1)
|
@@ -20,7 +20,7 @@ GEM
|
|
20
20
|
pastel (0.7.3)
|
21
21
|
equatable (~> 0.6)
|
22
22
|
tty-color (~> 0.5)
|
23
|
-
rake (
|
23
|
+
rake (12.3.3)
|
24
24
|
rspec (3.9.0)
|
25
25
|
rspec-core (~> 3.9.0)
|
26
26
|
rspec-expectations (~> 3.9.0)
|
@@ -54,7 +54,7 @@ PLATFORMS
|
|
54
54
|
DEPENDENCIES
|
55
55
|
bundler (~> 2.0)
|
56
56
|
newman_scenario!
|
57
|
-
rake (~>
|
57
|
+
rake (~> 12.3)
|
58
58
|
rspec (~> 3.0)
|
59
59
|
|
60
60
|
BUNDLED WITH
|
data/README.md
CHANGED
@@ -5,15 +5,18 @@ It supports:
|
|
5
5
|
- loading a Postman environment file against the requests.
|
6
6
|
- running a "folder" of requests
|
7
7
|
|
8
|
-
|
8
|
+
It's awesome, but if you want to perform the same request in multiple "folder", you
|
9
9
|
will end up duplicating this requests, which make it hard to maintain.
|
10
10
|
|
11
|
-
Also, it can be clumbersome to add new "scenario" ("folder") from Postman.
|
11
|
+
Also, it can be clumbersome to add new "scenario" ("folder") from [Postman](https://www.postman.com).
|
12
|
+
|
13
|
+
At @babylist, we (I?) use it to feed some pre-built scenario ("create a user", "sign-in", "add a product to the cart", "checkout").
|
14
|
+
Even if using [Postman](https://www.postman.com) , you can group your requests in a folder ("checkout flow") and run `newman --folder "checkout flow"`, it can be tricky to maintain, if you're re-using "create a user" in different scenarios.
|
12
15
|
|
13
16
|
Here comes `NewmanScenario`.
|
14
17
|
|
15
|
-
It basically allow you to cherry pick some requests to be chained, saved them, and run
|
16
|
-
the newly created "scenario".
|
18
|
+
It basically allow you to cherry pick some requests to be chained, saved them (locally), and run
|
19
|
+
the newly created (locally) "scenario".
|
17
20
|
|
18
21
|
The newly builded scenarios are just a list of requests, store in a json format file.
|
19
22
|
The file is store in the current working directory under `newman_scenarios.json`
|
@@ -39,21 +42,22 @@ Or install it yourself as:
|
|
39
42
|
|
40
43
|
### Using configure`
|
41
44
|
|
42
|
-
configure will guide you to set Postman related collection
|
45
|
+
configure will guide you to set Postman related collection and environments (fetch from [Postman](https://www.postman.com) ), and
|
43
46
|
stores them in `.env`
|
44
47
|
|
45
|
-
|
48
|
+
$ newman_scenario configure
|
46
49
|
|
47
50
|
### Setting `.env` manually
|
48
51
|
|
49
52
|
Add this to your `ENV` or `.env`
|
50
53
|
|
51
54
|
```
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
55
|
+
# from https://YOURPOSTMAN.postman.co/settings/me/api-keys
|
56
|
+
POSTMAN_API_KEY: POSTMAN_API_KEY ()
|
57
|
+
# postman environments id/name in json format
|
58
|
+
NEWMAN_SCENARIO_ENVIRONMENTS: {"staging1": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","staging3": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","staging5": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","local": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"}
|
59
|
+
# postman collection id
|
60
|
+
NEWMAN_SCENARIO_COLLECTION_ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
57
61
|
```
|
58
62
|
|
59
63
|
### Rails App
|
@@ -63,9 +67,9 @@ NEWMAN_SCENARIO_COLLECTION_ID: 7361507-9627fa69-1fe0-0000-AAAA-XXXXXX
|
|
63
67
|
require 'newman_scenario'
|
64
68
|
|
65
69
|
NewmanScenario::Scenario.configure(
|
66
|
-
default_api_key: 'PMAK-
|
67
|
-
default_collection_id: '
|
68
|
-
default_environment_ids: { staging: '
|
70
|
+
default_api_key: 'PMAK-xxxxxxxxxxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', # ENV['POSTMAN_API_KEY'], no default value
|
71
|
+
default_collection_id: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', # ENV['NEWMAN_SCENARIO_COLLECTION_ID'], no default value
|
72
|
+
default_environment_ids: { staging: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', production: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'}, # ENV['NEWMAN_SCENARIO_ENVIRONMENTS'] (json format), no default value
|
69
73
|
default_custom_scenarios_file_path: 'newman-scenarios.json', # ENV['NEWMAN_SCENARIO_CUSTOM_COLLECTION_FILE_PATH'], default: `newman_scenarios.json`
|
70
74
|
default_last_scenario_file_path: '/tmp/last_newman_scenario.json' # ENV['NEWMAN_SCENARIO_LAST_SCENARIO_FILE_PATH'], default: `last_newman_scenario.json`
|
71
75
|
)
|
@@ -77,24 +81,16 @@ NewmanScenario::Scenario.configure(
|
|
77
81
|
|
78
82
|
### Stand alone
|
79
83
|
|
80
|
-
|
81
|
-
|
82
|
-
# and create or re-use a `NewmanScenario`
|
83
|
-
# newly created scenario can be saved
|
84
|
-
newman_scenario
|
84
|
+
> running the gem itself will prompt you to select a environment (by it's name, see configuration)
|
85
|
+
and create or re-use a `NewmanScenario` newly created scenario which can be saved.
|
85
86
|
|
86
|
-
|
87
|
-
newman_scenario staging3 Signup
|
88
|
-
```
|
87
|
+
$ newman_scenario
|
89
88
|
|
90
|
-
|
89
|
+
> run with a environment name and/or a scenario name will run the previous created scenario 'Signup' against staging3 environment (with no extra prompt)
|
91
90
|
|
92
|
-
|
93
|
-
require 'newman_scenario'
|
91
|
+
$ newman_scenario staging3 Signup
|
94
92
|
|
95
|
-
|
96
|
-
NewmanScenario::Scenario.new.run(scenario_name: 'Signup', environment_name: 'staging3', no_prompt: true)
|
97
|
-
```
|
93
|
+
### Within App
|
98
94
|
|
99
95
|
```ruby
|
100
96
|
require 'newman_scenario'
|
@@ -108,6 +104,10 @@ NewmanScenario::Scenario.new.run
|
|
108
104
|
NewmanScenario::Scenario.new.run(scenario_name: 'Signup', environment_name: 'staging3', no_prompt: true)
|
109
105
|
```
|
110
106
|
|
107
|
+
## How it works
|
108
|
+
|
109
|
+
Beside all the "trivial" Postman collection and environments fetching, it basically scan your collection, cherry pick the requests which match request names stored for a "custom" scenario, create a brand new (local) collection which requests and run this new scenario (collection) using `newman`
|
110
|
+
|
111
111
|
## Roadmap
|
112
112
|
|
113
113
|
- [x] `NewmanScenario::Scenario.run`
|
@@ -10,9 +10,10 @@ module NewmanScenario
|
|
10
10
|
class Scenario
|
11
11
|
DEFAULT_CUSTOM_SCENARIOS_FILE_PATH = 'newman_scenarios.json'.freeze
|
12
12
|
DEFAULT_LAST_SCENARIO_FILE_PATH = '/tmp/last_newman_scenario.json'.freeze
|
13
|
+
DEBUG = false
|
13
14
|
|
14
15
|
@default_collection_id = ENV['NEWMAN_SCENARIO_COLLECTION_ID']
|
15
|
-
@
|
16
|
+
@default_environments = nil
|
16
17
|
@default_api_key = ENV['POSTMAN_API_KEY']
|
17
18
|
@default_custom_scenarios_file_path = ENV['NEWMAN_SCENARIO_CUSTOM_COLLECTION_FILE_PATH'] || DEFAULT_CUSTOM_SCENARIOS_FILE_PATH
|
18
19
|
@default_last_scenario_file_path = ENV['NEWMAN_SCENARIO_LAST_SCENARIO_FILE_PATH'] || DEFAULT_LAST_SCENARIO_FILE_PATH
|
@@ -20,21 +21,31 @@ module NewmanScenario
|
|
20
21
|
class << self
|
21
22
|
attr_accessor :default_api_key
|
22
23
|
attr_accessor :default_collection_id
|
23
|
-
attr_accessor :
|
24
|
+
attr_accessor :default_environments
|
24
25
|
attr_accessor :default_custom_scenarios_file_path
|
25
26
|
attr_accessor :default_last_scenario_file_path
|
26
27
|
|
27
|
-
def configure(default_api_key: nil, default_collection_id: nil,
|
28
|
+
def configure(default_api_key: nil, default_collection_id: nil, default_environments: nil, default_custom_scenarios_file_path: nil, default_last_scenario_file_path: nil)
|
28
29
|
self.default_api_key = default_api_key || prompt.ask('Postman API Key (https://YOURPOSTMAN.postman.co/settings/me/api-keys):', value: ENV['POSTMAN_API_KEY'].to_s)
|
29
|
-
collections =
|
30
|
+
collections = nil
|
31
|
+
environments = nil
|
32
|
+
if prompt.yes?('Using workspace?')
|
33
|
+
workspaces = fetch_postman('/workspaces', api_key: self.default_api_key).parsed_response&.fetch('workspaces', nil) || []
|
34
|
+
workspaces = workspaces.map { |workspace| workspace.slice('name', 'id').values }.to_h
|
35
|
+
workspace = prompt.select('Workspace', workspaces)
|
36
|
+
workspace = fetch_postman("/workspaces/#{workspace}", api_key: self.default_api_key).parsed_response&.fetch('workspace', nil) || {}
|
37
|
+
collections = workspace['collections']
|
38
|
+
environments = workspace['environments']
|
39
|
+
end
|
40
|
+
collections ||= fetch_postman('/collections', api_key: self.default_api_key).parsed_response&.fetch('collections', nil) || []
|
30
41
|
collections = collections.map { |collection| collection.slice('name', 'id').values }.to_h
|
31
42
|
self.default_collection_id = default_collection_id || prompt.select('Postman Collection', collections, default: 1)
|
32
|
-
self.
|
33
|
-
unless self.
|
34
|
-
environments
|
43
|
+
self.default_environments = default_environments
|
44
|
+
unless self.default_environments
|
45
|
+
environments ||= fetch_postman('/environments', api_key: self.default_api_key).parsed_response&.fetch('environments', nil) || []
|
35
46
|
environments = environments.map { |environment| environment.slice('name', 'id').values }.to_h
|
36
47
|
environment_ids = prompt.multi_select('Postman Collection', environments)
|
37
|
-
self.
|
48
|
+
self.default_environments = environments.select { |_, id| environment_ids.include?(id) }
|
38
49
|
end
|
39
50
|
self.default_custom_scenarios_file_path = default_custom_scenarios_file_path || prompt.ask('Custom scenarios file path:', value: DEFAULT_CUSTOM_SCENARIOS_FILE_PATH)
|
40
51
|
self.default_last_scenario_file_path = default_last_scenario_file_path || prompt.ask('Last scenario file path:', value: DEFAULT_LAST_SCENARIO_FILE_PATH)
|
@@ -42,7 +53,7 @@ module NewmanScenario
|
|
42
53
|
envs = {
|
43
54
|
POSTMAN_API_KEY: self.default_api_key,
|
44
55
|
NEWMAN_SCENARIO_COLLECTION_ID: self.default_collection_id,
|
45
|
-
NEWMAN_SCENARIO_ENVIRONMENTS: self.
|
56
|
+
NEWMAN_SCENARIO_ENVIRONMENTS: self.default_environments.to_json,
|
46
57
|
NEWMAN_SCENARIO_CUSTOM_COLLECTION_FILE_PATH: self.default_custom_scenarios_file_path,
|
47
58
|
NEWMAN_SCENARIO_LAST_SCENARIO_FILE_PATH: self.default_last_scenario_file_path,
|
48
59
|
}
|
@@ -51,7 +62,7 @@ module NewmanScenario
|
|
51
62
|
existing_lines.each { |line| file.puts line }
|
52
63
|
file.puts "POSTMAN_API_KEY: #{self.default_api_key}"
|
53
64
|
file.puts "NEWMAN_SCENARIO_COLLECTION_ID: #{self.default_collection_id}"
|
54
|
-
file.puts "NEWMAN_SCENARIO_ENVIRONMENTS: #{self.
|
65
|
+
file.puts "NEWMAN_SCENARIO_ENVIRONMENTS: #{self.default_environments.to_json}"
|
55
66
|
file.puts "NEWMAN_SCENARIO_CUSTOM_COLLECTION_FILE_PATH: #{self.default_custom_scenarios_file_path}"
|
56
67
|
file.puts "NEWMAN_SCENARIO_LAST_SCENARIO_FILE_PATH: #{self.default_last_scenario_file_path}"
|
57
68
|
end
|
@@ -59,6 +70,7 @@ module NewmanScenario
|
|
59
70
|
end
|
60
71
|
|
61
72
|
def fetch_postman(url_path, expected_response_codes: [200], api_key: nil)
|
73
|
+
puts "fetching #{url_path}" if DEBUG
|
62
74
|
response = HTTParty.get("https://api.getpostman.com#{url_path}", headers: { 'X-Api-Key' => api_key})
|
63
75
|
raise Error, "Invalid response code: #{response.code}" unless expected_response_codes.include?(response.code)
|
64
76
|
|
@@ -73,18 +85,18 @@ module NewmanScenario
|
|
73
85
|
end
|
74
86
|
|
75
87
|
attr_accessor :collection_id
|
76
|
-
attr_accessor :
|
88
|
+
attr_accessor :environments
|
77
89
|
attr_accessor :api_key
|
78
90
|
attr_accessor :custom_collection_file_path
|
79
91
|
attr_accessor :last_scenario_file_path
|
80
92
|
|
81
|
-
def initialize(collection_id: nil,
|
93
|
+
def initialize(collection_id: nil, environments: nil, api_key: nil, custom_collection_file_path: nil, last_scenario_file_path: nil)
|
82
94
|
self.collection_id ||= self.class.default_collection_id
|
83
95
|
raise ConfigurationError, 'Missing Collection Id' unless self.collection_id
|
84
96
|
|
85
|
-
self.
|
86
|
-
self.
|
87
|
-
raise ConfigurationError, 'Missing Environment Ids' unless self.
|
97
|
+
self.environments ||= self.class.default_environments
|
98
|
+
self.environments ||= JSON.parse(ENV['NEWMAN_SCENARIO_ENVIRONMENTS'], symbolize_names: true) if ENV['NEWMAN_SCENARIO_ENVIRONMENTS']
|
99
|
+
raise ConfigurationError, 'Missing Environment Ids' unless self.environments
|
88
100
|
|
89
101
|
self.api_key ||= self.class.default_api_key
|
90
102
|
raise ConfigurationError, 'Missing Postman API Key' unless self.api_key
|
@@ -105,8 +117,8 @@ module NewmanScenario
|
|
105
117
|
def run(environment_name: nil, scenario_name: nil, bail: true, no_prompt: false)
|
106
118
|
return if `which newman`.empty? && !prompt_to_install_newman
|
107
119
|
|
108
|
-
environment =
|
109
|
-
environment ||= prompt.select(
|
120
|
+
environment = environments[environment_name.to_sym] if environment_name
|
121
|
+
environment ||= prompt.select('Environment', environments, default: 1)
|
110
122
|
load_postman_environment(environment, no_prompt: no_prompt)
|
111
123
|
collection = JSON.parse(File.read("/tmp/postman-collection-#{collection_id}.json"), symbolize_names: true)[:collection]
|
112
124
|
unless File.exist?(last_scenario_file_path) && (!scenario_name && prompt.yes?('Replay last scenario?'))
|
@@ -123,7 +135,7 @@ module NewmanScenario
|
|
123
135
|
all_request_names = extract_all_requests.call(collection, '')
|
124
136
|
loop do
|
125
137
|
scenario_requests.delete('duplicate')
|
126
|
-
scenario_requests += prompt.multi_select(
|
138
|
+
scenario_requests += prompt.multi_select('Requests (type to filter prefix, choose duplicate to perform action multiple times)', ['duplicate'] + all_request_names, cycle: true, filter: true)
|
127
139
|
break unless scenario_requests.include?('duplicate')
|
128
140
|
|
129
141
|
end
|
@@ -194,7 +206,7 @@ module NewmanScenario
|
|
194
206
|
if no_prompt
|
195
207
|
false
|
196
208
|
else
|
197
|
-
!prompt.no?(
|
209
|
+
!prompt.no?('Refetch postman config?')
|
198
210
|
end
|
199
211
|
if File.file?("/tmp/postman-environment-#{environment}.json") && !reload
|
200
212
|
prompt.ok "reusing env /tmp/postman-environment-#{environment}.json"
|
@@ -203,7 +215,7 @@ module NewmanScenario
|
|
203
215
|
fetch_postman_to_file("/environments/#{environment}", "/tmp/postman-environment-#{environment}.json")
|
204
216
|
end
|
205
217
|
if File.file?('/tmp/postman-collection.json') && !reload
|
206
|
-
prompt.ok
|
218
|
+
prompt.ok 'reusing collection /tmp/postman-collection.json'
|
207
219
|
else
|
208
220
|
prompt.ok "fetching collection #{collection_id}"
|
209
221
|
fetch_postman_to_file("/collections/#{collection_id}", "/tmp/postman-collection-#{collection_id}.json")
|
data/newman_scenario.gemspec
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
lib = File.expand_path("lib", __dir__)
|
2
2
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
-
require
|
3
|
+
require 'newman_scenario/version'
|
4
4
|
|
5
5
|
Gem::Specification.new do |spec|
|
6
|
-
spec.name =
|
6
|
+
spec.name = 'newman_scenario'
|
7
7
|
spec.version = NewmanScenario::VERSION
|
8
|
-
spec.authors = [
|
9
|
-
spec.email = [
|
8
|
+
spec.authors = ['Hugues Bernet-Rollande']
|
9
|
+
spec.email = ['hugues@xdev.fr']
|
10
10
|
|
11
|
-
spec.summary =
|
11
|
+
spec.summary = 'Allow to run re-usable collection of requests using newman'
|
12
12
|
spec.description = <<~EOF
|
13
13
|
Postman doesn't support re-using the same requests in multiple scenario.
|
14
14
|
Duplicating request will make it hard to maintain them.
|
@@ -18,29 +18,29 @@ Gem::Specification.new do |spec|
|
|
18
18
|
|
19
19
|
NewmanScenario try to fill this gap.
|
20
20
|
EOF
|
21
|
-
spec.homepage =
|
22
|
-
spec.license =
|
21
|
+
spec.homepage = 'https://github.com/huguesbr/newman_scenario'
|
22
|
+
spec.license = 'MIT'
|
23
23
|
|
24
|
-
spec.metadata[
|
25
|
-
spec.metadata[
|
26
|
-
spec.metadata[
|
24
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
25
|
+
spec.metadata['source_code_uri'] = 'https://github.com/huguesbr/newman_scenario'
|
26
|
+
spec.metadata['changelog_uri'] = 'https://github.com/huguesbr/newman_scenario/README.md'
|
27
27
|
|
28
28
|
# Specify which files should be added to the gem when it is released.
|
29
29
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
30
30
|
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
31
31
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
32
32
|
end
|
33
|
-
spec.bindir =
|
33
|
+
spec.bindir = 'bin'
|
34
34
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
35
|
-
spec.require_paths = [
|
36
|
-
spec.executables = [
|
35
|
+
spec.require_paths = ['lib']
|
36
|
+
spec.executables = ['newman_scenario']
|
37
37
|
|
38
38
|
spec.add_dependency 'tty-prompt', '0.19.0'
|
39
39
|
spec.add_dependency 'httparty', '0.16.2'
|
40
40
|
spec.add_dependency 'thor', '1.0.1'
|
41
41
|
spec.add_dependency 'dotenv', '2.7.5'
|
42
42
|
|
43
|
-
spec.add_development_dependency
|
44
|
-
spec.add_development_dependency
|
45
|
-
spec.add_development_dependency
|
43
|
+
spec.add_development_dependency 'bundler', '~> 2.0'
|
44
|
+
spec.add_development_dependency 'rake', '~> 12.3'
|
45
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
46
46
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: newman_scenario
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Hugues Bernet-Rollande
|
@@ -86,14 +86,14 @@ dependencies:
|
|
86
86
|
requirements:
|
87
87
|
- - "~>"
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version: '
|
89
|
+
version: '12.3'
|
90
90
|
type: :development
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
94
|
- - "~>"
|
95
95
|
- !ruby/object:Gem::Version
|
96
|
-
version: '
|
96
|
+
version: '12.3'
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
98
|
name: rspec
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|