newman_scenario 0.1.2 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|