gertrude 0.0.1
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 +15 -0
- data/.gitignore +13 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +134 -0
- data/Rakefile +6 -0
- data/bin/console +4 -0
- data/bin/gertrude +67 -0
- data/bin/setup +7 -0
- data/gertrude.gemspec +30 -0
- data/lib/gertrude.rb +13 -0
- data/lib/gertrude/core_ext/hash.rb +17 -0
- data/lib/gertrude/items/item_error.rb +16 -0
- data/lib/gertrude/items/item_server.rb +51 -0
- data/lib/gertrude/items/items_list.rb +108 -0
- data/lib/gertrude/version.rb +3 -0
- data/spec/gertrude_spec.rb +83 -0
- data/spec/hash_spec.rb +43 -0
- data/spec/items_list_spec.rb +186 -0
- data/spec/spec_helper.rb +28 -0
- data/spec/test_items_spec.yml +19 -0
- metadata +176 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
NmQ3MGEzYjg1ZTJmNjhiNWE2N2JjMjRiNmE5ODEwZTE3NDJjNTZiYw==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
ZWNmMzc5Y2I3NDJhMWMyMmQ3MmFlMDI0YzljMzFiM2I0ZDAzMzY0ZQ==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
NWYzYTEyZGM2YjYwYjk2MDdjMDI3MjlhNzU3MTU1MjNlZTk3M2U0YjZkNTY1
|
10
|
+
OTA2ZDMyZGIzMmY4ZDRjZThmMWU1OWNmNDgwYTM3M2E3YjRhNzAwMGYzMzJh
|
11
|
+
ZmQwYTUyZmZiYjgzNWMxYjJiMzkwMWVkNzk5Nzk5ZGVhZDk3ZDA=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
ZmIwNWUxNzA3ZjZiZWM0ZWRiNDFmMzcwOWZiODIwNWFlNDU5ZTE3OTcxYWFi
|
14
|
+
YWMxNjljNDVlZGI2ZjUyNjZhYTBiNjIzNDI3MTI1NGRmY2Y2ZmZmNjhiNjZl
|
15
|
+
NTRjNzk3MzgwNDQ4OTkxMDdjZjg0ZWU4MDc0NDc5MzI3ZDZjZmI=
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Wallace Harwood
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
Gertrude
|
2
|
+
========
|
3
|
+
|
4
|
+
Gertrude is your librarian. It's a simple rest service that feeds you unique items, that you define, by type. Those items are reserved
|
5
|
+
until you release them back to the service.
|
6
|
+
|
7
|
+
As an example, this can be used to limit conflicts in users when testing. Testers (or automation) can request users
|
8
|
+
from the service, and use them for testing. The service won't provide that specific user to another request, until the original
|
9
|
+
requester releases it back to the service. Check your stuff back in to Gertrude. Dont be that guy.
|
10
|
+
|
11
|
+
How do I use this?
|
12
|
+
------------------
|
13
|
+
|
14
|
+
- Install the gem.
|
15
|
+
- Define your items in a yaml file, by type.
|
16
|
+
- Start the service, using your created yaml file.
|
17
|
+
- Request items from the service.
|
18
|
+
- Use the requested items to your hearts content.
|
19
|
+
- Release the item back to the service when you are done.
|
20
|
+
- ???
|
21
|
+
- PROFIT.
|
22
|
+
|
23
|
+
Define items in a yaml file, by type
|
24
|
+
------------------------------------
|
25
|
+
|
26
|
+
Create a yaml file, using the following format:
|
27
|
+
```ruby
|
28
|
+
---
|
29
|
+
item_type1:
|
30
|
+
type1_item1:
|
31
|
+
item1_property1: property1_value
|
32
|
+
items1_property2: property2_value
|
33
|
+
type1_item2:
|
34
|
+
item2_property1: property1_value
|
35
|
+
items2_property2: property2_value
|
36
|
+
```
|
37
|
+
|
38
|
+
Example (for an implementation to provide users):
|
39
|
+
```ruby
|
40
|
+
---
|
41
|
+
admin:
|
42
|
+
admin1:
|
43
|
+
password: admin_password_1
|
44
|
+
other: admin_other
|
45
|
+
admin2:
|
46
|
+
password: admin_password_2
|
47
|
+
other: admin_other
|
48
|
+
admin3:
|
49
|
+
password: admin_password_3
|
50
|
+
other: admin_other
|
51
|
+
basic:
|
52
|
+
basic1:
|
53
|
+
basic2:
|
54
|
+
basic3:
|
55
|
+
custom_user:
|
56
|
+
john_smith:
|
57
|
+
password: jsmithpw
|
58
|
+
rep_id: 8675309
|
59
|
+
other_info:
|
60
|
+
- foo
|
61
|
+
- bar
|
62
|
+
- baz
|
63
|
+
```
|
64
|
+
|
65
|
+
Alternately, you can replace the "all_items.yml" file in the repo with your yaml file, and then start the service.
|
66
|
+
|
67
|
+
Install the Gem
|
68
|
+
--------------
|
69
|
+
|
70
|
+
Install the gem
|
71
|
+
```ruby
|
72
|
+
$gem install gertrude
|
73
|
+
```
|
74
|
+
|
75
|
+
Start service, using created yaml file
|
76
|
+
--------------------------------------
|
77
|
+
|
78
|
+
Start the service. In this example, we use rack:
|
79
|
+
```ruby
|
80
|
+
$gertrude start -f path/to/yml
|
81
|
+
```
|
82
|
+
|
83
|
+
Request items from the service
|
84
|
+
------------------------------
|
85
|
+
|
86
|
+
Routes are as follows (please replace with your host address, item type, and port)
|
87
|
+
|
88
|
+
See all available items, and their properties.
|
89
|
+
```ruby
|
90
|
+
http://0.0.0.0:8080/
|
91
|
+
```
|
92
|
+
|
93
|
+
Get a list of all items available for reservation
|
94
|
+
```ruby
|
95
|
+
http://0.0.0.0:8080/available
|
96
|
+
```
|
97
|
+
|
98
|
+
Reserve an item.
|
99
|
+
```ruby
|
100
|
+
http:/0.0.0.0:8080/reserve/item?type=admin
|
101
|
+
```
|
102
|
+
|
103
|
+
Gertrude will return you the requested item as a hash.
|
104
|
+
```ruby
|
105
|
+
{'admin1' => {'password' => 'admin_password_1', 'other' => 'admin_other'}
|
106
|
+
```
|
107
|
+
|
108
|
+
You can pass a timeout, in seconds (default is 30). Sometimes you have to wait for an item to become available.
|
109
|
+
```ruby
|
110
|
+
http://0.0.0.0:8080/reserve/item?type=admin&timeout=120
|
111
|
+
```
|
112
|
+
|
113
|
+
Get a list of all items that are currently reserved.
|
114
|
+
```ruby
|
115
|
+
http://0.0.0.0:8080/reserved
|
116
|
+
```
|
117
|
+
|
118
|
+
Use the requested items to your hearts content
|
119
|
+
---------------------------------------------
|
120
|
+
|
121
|
+
Enjoy your item, for however long you need to. Go ahead. Have a blast.
|
122
|
+
|
123
|
+
Release the item back to the service when you are done
|
124
|
+
------------------------------------------------------
|
125
|
+
|
126
|
+
Release an item back to the service. You can't keep it forever.
|
127
|
+
```ruby
|
128
|
+
http://0.0.0.0:8080/release/item?item=admin1
|
129
|
+
```
|
130
|
+
|
131
|
+
Release all currently reserved items.
|
132
|
+
```ruby
|
133
|
+
http://0.0.0.0:8080/release
|
134
|
+
```
|
data/Rakefile
ADDED
data/bin/console
ADDED
data/bin/gertrude
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'optparse'
|
5
|
+
require 'yaml'
|
6
|
+
|
7
|
+
options = {}
|
8
|
+
|
9
|
+
OptionParser.new do |opts|
|
10
|
+
opts.banner = "Usage: example.rb [options]"
|
11
|
+
|
12
|
+
opts.on("-f", "--file [FILEPATH]", String, "Pass the yml file.") do |file|
|
13
|
+
options[:file] = file
|
14
|
+
end
|
15
|
+
|
16
|
+
opts.on("-p", "--port [PORT]", Integer, "Assign server port (Default is 4567)") do |p|
|
17
|
+
options[:port] = p
|
18
|
+
end
|
19
|
+
|
20
|
+
opts.on("-d", "--daemonize", "Run the server in the background") do |d|
|
21
|
+
options[:daemonize] = d
|
22
|
+
end
|
23
|
+
|
24
|
+
opts.on("-e", "--environment [ENV]", String, "Set Sinatra environment (Default is development)") do |env|
|
25
|
+
options[:environment] = env
|
26
|
+
end
|
27
|
+
|
28
|
+
opts.on("-o", "--host [HOST]", String, "Set the host address (Default is 0.0.0.0)") do |host|
|
29
|
+
options[:host] = host
|
30
|
+
end
|
31
|
+
|
32
|
+
opts.on("-h", "--help", "Display this help") do
|
33
|
+
puts opts
|
34
|
+
exit
|
35
|
+
end
|
36
|
+
end.parse!
|
37
|
+
|
38
|
+
options[:port] = 4567 if options[:port].nil?
|
39
|
+
options[:environment] = 'development' if options[:environment].nil?
|
40
|
+
options[:host] = '0.0.0.0' if options[:host].nil?
|
41
|
+
|
42
|
+
require 'gertrude'
|
43
|
+
require 'webrick'
|
44
|
+
|
45
|
+
config_file = "#{Dir.pwd}/config.yml"
|
46
|
+
|
47
|
+
case ARGV[0].downcase
|
48
|
+
when 'start'
|
49
|
+
fail "File is a required parameter" if options[:file].nil?
|
50
|
+
options[:file] = File.expand_path(options[:file])
|
51
|
+
if options[:daemonize]
|
52
|
+
File.write(config_file, options.to_yaml)
|
53
|
+
WEBrick::Daemon.start
|
54
|
+
end
|
55
|
+
ItemServer.run!(options)
|
56
|
+
when 'stop'
|
57
|
+
config = YAML.load_file(config_file)
|
58
|
+
port = config[:port]
|
59
|
+
fail "Port not found in config file. You'll need to manually kill the server." if config[:port].nil?
|
60
|
+
`kill $(lsof -t -i :#{port})`
|
61
|
+
`rm #{config_file}` if File.exists?(config_file)
|
62
|
+
else
|
63
|
+
fail "Start and stop are the only two options."
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
|
data/bin/setup
ADDED
data/gertrude.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'gertrude/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "gertrude"
|
8
|
+
spec.version = Gertrude::VERSION
|
9
|
+
spec.authors = ["Wallace Harwood", "Jarod Adair", "Umair Chagani"]
|
10
|
+
spec.email = ["wallace.harwood@manheim.com", "jarod.adair@manheim.com", "umair.chagani@manheim.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Librarian to manage your things for you.}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split("\n")
|
17
|
+
spec.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
spec.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
24
|
+
spec.add_development_dependency 'pry'
|
25
|
+
spec.add_development_dependency 'rack-test'
|
26
|
+
spec.add_runtime_dependency 'bundler', "~> 1.11"
|
27
|
+
spec.add_runtime_dependency 'sinatra'
|
28
|
+
spec.add_runtime_dependency 'test-helpers'
|
29
|
+
end
|
30
|
+
|
data/lib/gertrude.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
require 'sinatra'
|
3
|
+
require 'pry'
|
4
|
+
require 'test-helpers/all'
|
5
|
+
require 'yaml'
|
6
|
+
require 'json'
|
7
|
+
require 'gertrude/items/item_error'
|
8
|
+
require 'gertrude/items/items_list'
|
9
|
+
require 'gertrude/core_ext/hash'
|
10
|
+
require 'gertrude/items/item_server'
|
11
|
+
|
12
|
+
module Gertrude
|
13
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class Hash
|
2
|
+
def has_deep_key?(key)
|
3
|
+
self.has_key?(key) || any? { |k, v| v.has_deep_key?(key) if v.is_a? Hash }
|
4
|
+
end
|
5
|
+
|
6
|
+
def has_deep_value?(value)
|
7
|
+
self.has_value?(value) || any? { |k, v| v.has_deep_value?(value) if v.is_a? Hash }
|
8
|
+
end
|
9
|
+
|
10
|
+
def deep_find(key, hash = self)
|
11
|
+
return hash[key] if hash.respond_to?(:key?) && hash.key?(key)
|
12
|
+
return unless hash.is_a?(Enumerable)
|
13
|
+
found = nil
|
14
|
+
hash.find { |*x| found = deep_find(key, x.last) }
|
15
|
+
found
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class ItemError
|
2
|
+
class ItemTypeNotDefined < ArgumentError
|
3
|
+
end
|
4
|
+
|
5
|
+
class NoReservedItems < ArgumentError
|
6
|
+
end
|
7
|
+
|
8
|
+
class ItemsNotUnique < StandardError
|
9
|
+
end
|
10
|
+
|
11
|
+
class InvalidItem < ArgumentError
|
12
|
+
end
|
13
|
+
|
14
|
+
class NoAvailableItems < StandardError
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
class ItemServer < Sinatra::Base
|
2
|
+
|
3
|
+
def initialize
|
4
|
+
super
|
5
|
+
@items_list = ItemsList.new.load_items!(settings.file)
|
6
|
+
end
|
7
|
+
|
8
|
+
error ::ItemError::ItemTypeNotDefined do |type|
|
9
|
+
error 422, "Item type not defined: #{type}"
|
10
|
+
end
|
11
|
+
|
12
|
+
error ::ItemError::NoReservedItems do
|
13
|
+
halt 422, 'No items currently reserved'
|
14
|
+
end
|
15
|
+
|
16
|
+
error ::ItemError::InvalidItem do |item|
|
17
|
+
halt 422, "Item does not exist in the service: #{item}"
|
18
|
+
end
|
19
|
+
|
20
|
+
error ::ItemError::NoAvailableItems do |type|
|
21
|
+
halt 422, "No #{type} items available to reserve"
|
22
|
+
end
|
23
|
+
|
24
|
+
get '/' do
|
25
|
+
@items_list.get_all_items.to_json
|
26
|
+
end
|
27
|
+
|
28
|
+
get '/reserve/item' do
|
29
|
+
@items_list.get_item(params[:type], params[:timeout] || 30).to_json
|
30
|
+
end
|
31
|
+
|
32
|
+
get '/release/item' do
|
33
|
+
(!@items_list.release_item(params[:item])).to_json
|
34
|
+
end
|
35
|
+
|
36
|
+
get '/reserved' do
|
37
|
+
@items_list.get_reserved_items.to_json
|
38
|
+
end
|
39
|
+
|
40
|
+
get '/release' do
|
41
|
+
status 204 if @items_list.release_all_items
|
42
|
+
end
|
43
|
+
|
44
|
+
get '/available' do
|
45
|
+
@items_list.get_available_items.to_json
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
|
51
|
+
|
@@ -0,0 +1,108 @@
|
|
1
|
+
class ItemsList
|
2
|
+
include TestHelpers::Wait
|
3
|
+
|
4
|
+
attr_accessor :items
|
5
|
+
RESERVE_KEY = "reserved_#{SecureRandom.hex(4)}".to_sym
|
6
|
+
|
7
|
+
def load_items!(yml)
|
8
|
+
config = YAML.load_file(yml)
|
9
|
+
config.each_key do |type|
|
10
|
+
config[type].each_key do |item|
|
11
|
+
config[type][item] = {} if config[type][item].nil?
|
12
|
+
config[type][item][RESERVE_KEY] = false
|
13
|
+
end
|
14
|
+
end
|
15
|
+
raise ItemError::ItemsNotUnique.new unless unique_keys_across_items?(config)
|
16
|
+
@items = config
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
def unique_keys_across_items?(config)
|
21
|
+
item_keys = []
|
22
|
+
config.each_value { |v| item_keys.push(v.keys) }
|
23
|
+
item_keys.flatten!
|
24
|
+
item_keys.count == item_keys.uniq.count
|
25
|
+
end
|
26
|
+
|
27
|
+
def get_item(type, timeout)
|
28
|
+
raise ItemError::ItemTypeNotDefined.new(type) unless @items.has_key? type
|
29
|
+
loop_for_item(type, timeout)
|
30
|
+
end
|
31
|
+
|
32
|
+
def release_item(item)
|
33
|
+
raise ItemError::InvalidItem.new(item) unless @items.has_deep_key?(item)
|
34
|
+
@items.deep_find(item)[RESERVE_KEY] = false
|
35
|
+
end
|
36
|
+
|
37
|
+
def get_reserved_items
|
38
|
+
reserved_items.join(", ")
|
39
|
+
end
|
40
|
+
|
41
|
+
def get_available_items
|
42
|
+
available_items.join(",")
|
43
|
+
end
|
44
|
+
|
45
|
+
def get_all_items
|
46
|
+
list = Marshal.load(Marshal.dump(@items))
|
47
|
+
list.each_key do |type|
|
48
|
+
list[type].each do |item|
|
49
|
+
list[type][item[0]] = sanitize_response({item[0] => item[1]})
|
50
|
+
end
|
51
|
+
end
|
52
|
+
list
|
53
|
+
end
|
54
|
+
|
55
|
+
def release_all_items
|
56
|
+
raise ItemError::NoReservedItems if reserved_items.empty?
|
57
|
+
@items.each_key do |type|
|
58
|
+
@items[type].each_key do |item|
|
59
|
+
@items[type][item][RESERVE_KEY] = false if @items[type][item][RESERVE_KEY]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
true
|
63
|
+
end
|
64
|
+
|
65
|
+
def available_items
|
66
|
+
available_items = []
|
67
|
+
@items.each_key do |type|
|
68
|
+
@items[type].each_key do |item|
|
69
|
+
available_items << item unless @items[type][item][RESERVE_KEY]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
available_items
|
73
|
+
end
|
74
|
+
|
75
|
+
def reserved_items
|
76
|
+
taken_items = []
|
77
|
+
@items.each_key do |type|
|
78
|
+
@items[type].each_key do |item|
|
79
|
+
taken_items << item if @items[type][item][RESERVE_KEY]
|
80
|
+
end
|
81
|
+
end
|
82
|
+
taken_items
|
83
|
+
end
|
84
|
+
|
85
|
+
def loop_for_item(type, timeout)
|
86
|
+
item = wait_until(timeout: timeout) do
|
87
|
+
item = get_available_item(type)
|
88
|
+
raise ItemError::NoAvailableItems if item.empty?
|
89
|
+
item
|
90
|
+
end
|
91
|
+
reserve_item(type, item.keys.first)
|
92
|
+
sanitize_response(item)
|
93
|
+
end
|
94
|
+
|
95
|
+
def sanitize_response(item)
|
96
|
+
x = Marshal.load(Marshal.dump(item))
|
97
|
+
x.first.last.delete(RESERVE_KEY)
|
98
|
+
x
|
99
|
+
end
|
100
|
+
|
101
|
+
def reserve_item(type, item_key)
|
102
|
+
@items[type][item_key][RESERVE_KEY] = true
|
103
|
+
end
|
104
|
+
|
105
|
+
def get_available_item(type)
|
106
|
+
Hash[*@items[type].select { |item| !@items[type][item][RESERVE_KEY] }.first]
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe('item server') do
|
4
|
+
let (:item) { {'danny7' => {'user_name' => 'danny9', 'rep_id' => '100014624', 'profile_id' => '8192'}} }
|
5
|
+
let (:hash) { {type: {test: {hello: 'world', foo: 'bar'}, basic: {basic1: {}, basic2: {}}}} }
|
6
|
+
|
7
|
+
it('should reserve an item') do
|
8
|
+
allow_any_instance_of(ItemsList).to receive(:get_item).with('admin', 30).and_return(item)
|
9
|
+
get '/reserve/item?type=admin'
|
10
|
+
expect(last_response.body).to eql item.to_json
|
11
|
+
end
|
12
|
+
|
13
|
+
it('should release an item') do
|
14
|
+
allow_any_instance_of(ItemsList).to receive(:release).with('danny7').and_return(false)
|
15
|
+
get '/release/item?item=danny7'
|
16
|
+
expect(last_response.body).to eql 'true'
|
17
|
+
end
|
18
|
+
|
19
|
+
it('should release all items') do
|
20
|
+
allow_any_instance_of(ItemsList).to receive(:release_all_items).and_return(true)
|
21
|
+
get '/release'
|
22
|
+
expect(last_response.status).to eql 204
|
23
|
+
end
|
24
|
+
|
25
|
+
it('should return 500 error if release all items fails') do
|
26
|
+
allow_any_instance_of(ItemsList).to receive(:release_all_items).and_raise(ArgumentError)
|
27
|
+
get '/release'
|
28
|
+
expect(last_response.status).to eql 500
|
29
|
+
end
|
30
|
+
|
31
|
+
it('should return a list of reserved items') do
|
32
|
+
allow_any_instance_of(ItemsList).to receive(:get_reserved_items).and_return('danny7')
|
33
|
+
get '/reserved'
|
34
|
+
expect(last_response.body).to eql "\"danny7\""
|
35
|
+
end
|
36
|
+
|
37
|
+
it('should return a list of available items') do
|
38
|
+
allow_any_instance_of(ItemsList).to receive(:get_available_items).and_return('johnny5')
|
39
|
+
get '/available'
|
40
|
+
expect(last_response.body).to eql "\"johnny5\""
|
41
|
+
end
|
42
|
+
|
43
|
+
it('should return Item Type Not Defined error if item type does not exist') do
|
44
|
+
allow_any_instance_of(ItemsList).to receive(:get_item).with('foo', 30).and_raise(ItemError::ItemTypeNotDefined.new('foo'))
|
45
|
+
get '/reserve/item?type=foo'
|
46
|
+
expect(last_response.status).to eql 422
|
47
|
+
expect(last_response.body).to eql 'Item type not defined: foo'
|
48
|
+
end
|
49
|
+
|
50
|
+
it('should return No Available Items error if item type does not exist') do
|
51
|
+
allow_any_instance_of(ItemsList).to receive(:get_item).with('foo', 30).and_raise(ItemError::NoAvailableItems.new('foo'))
|
52
|
+
get '/reserve/item?type=foo'
|
53
|
+
expect(last_response.status).to eql 422
|
54
|
+
expect(last_response.body).to eql "No foo items available to reserve"
|
55
|
+
end
|
56
|
+
|
57
|
+
it('should return No Reserved Items error if no items in reserved list') do
|
58
|
+
allow_any_instance_of(ItemsList).to receive(:get_reserved_items).and_raise(ItemError::NoReservedItems)
|
59
|
+
get '/reserved'
|
60
|
+
expect(last_response.status).to eql 422
|
61
|
+
expect(last_response.body).to eql 'No items currently reserved'
|
62
|
+
end
|
63
|
+
|
64
|
+
it('should return Invalid Item error if item does not exist') do
|
65
|
+
allow_any_instance_of(ItemsList).to receive(:release).with('foo').and_raise(ItemError::InvalidItem.new('foo'))
|
66
|
+
get '/release/item?item=foo'
|
67
|
+
expect(last_response.status).to eql 422
|
68
|
+
expect(last_response.body).to eql 'Item does not exist in the service: foo'
|
69
|
+
end
|
70
|
+
|
71
|
+
it('should raise No Reserved Items error if no items reserved') do
|
72
|
+
allow_any_instance_of(ItemsList).to receive(:release_all_items).and_raise(ItemError::NoReservedItems)
|
73
|
+
get '/release'
|
74
|
+
expect(last_response.status).to eql 422
|
75
|
+
expect(last_response.body).to eql 'No items currently reserved'
|
76
|
+
end
|
77
|
+
|
78
|
+
it('should return all items') do
|
79
|
+
allow_any_instance_of(ItemsList).to receive(:get_all_items).and_return(hash)
|
80
|
+
get '/'
|
81
|
+
expect(last_response.body).to eql hash.to_json
|
82
|
+
end
|
83
|
+
end
|
data/spec/hash_spec.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe('Hash') do
|
4
|
+
before(:each) do
|
5
|
+
@some_hash = {foo: {bar: {bat: 'thing'}}}
|
6
|
+
end
|
7
|
+
|
8
|
+
describe('#has_deep_key') do
|
9
|
+
it('should return true if deep key exists') do
|
10
|
+
expect(@some_hash.has_deep_key?(:bar)).to be true
|
11
|
+
end
|
12
|
+
|
13
|
+
it('should return false if deep key does not exist') do
|
14
|
+
expect(@some_hash.has_deep_key?(:blah)).to be false
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe('#has_deep_value') do
|
19
|
+
it('should return true if deep value exists') do
|
20
|
+
expect(@some_hash.has_deep_value?('thing')).to be true
|
21
|
+
end
|
22
|
+
|
23
|
+
it('should return false if deep value does not exist') do
|
24
|
+
expect(@some_hash.has_deep_value?('not there')).to be false
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe('#deep_find') do
|
29
|
+
it('should return hash if key value is hash') do
|
30
|
+
expect(@some_hash.deep_find(:bar)).to eql({bat: 'thing'})
|
31
|
+
end
|
32
|
+
|
33
|
+
it('should return value if key is not a hash') do
|
34
|
+
expect(@some_hash.deep_find(:bat)).to eql 'thing'
|
35
|
+
end
|
36
|
+
|
37
|
+
it('should return first value for duplicate keys') do
|
38
|
+
@some_hash = {foo: {bar: {foo: 'thing'}}}
|
39
|
+
expect(@some_hash.deep_find(:foo)).to eql({bar: {foo: 'thing'}})
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
@@ -0,0 +1,186 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
|
3
|
+
describe 'items list' do
|
4
|
+
let(:item_list) { ItemsList.new }
|
5
|
+
let(:item) { {'danny7' => {'user_name' => 'danny9', 'rep_id' => '100014624', 'profile_id' => '8192'}} }
|
6
|
+
|
7
|
+
before(:each) do
|
8
|
+
item_list.items = {'admin' =>
|
9
|
+
{'danny7' => {'user_name' => 'danny9', 'rep_id' => '100014620', 'profile_id' => '8190', ItemsList::RESERVE_KEY.to_sym => false},
|
10
|
+
'johnny5' => {'user_name' => 'danny7', 'rep_id' => '100014624', 'profile_id' => '8192', ItemsList::RESERVE_KEY.to_sym => false}}}
|
11
|
+
end
|
12
|
+
|
13
|
+
describe '#unique_keys_across_items' do
|
14
|
+
it 'should raise Item Type Not Defined' do
|
15
|
+
expect { item_list.get_item(:blah, 1) }.to raise_error(ItemError::ItemTypeNotDefined)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'should return true if unique keys' do
|
19
|
+
expect(item_list.unique_keys_across_items?(item_list.items)).to be true
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'should return false if duplicate keys' do
|
23
|
+
item_list.items.merge!({'basic' => {'danny7' => {'user_name' => 'danny7'}}})
|
24
|
+
expect(item_list.unique_keys_across_items?(item_list.items)).to be false
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe '#get_item' do
|
29
|
+
it 'should raise an error if type not defined' do
|
30
|
+
expect { item_list.get_item('foo', 0.01) }.to raise_error ItemError::ItemTypeNotDefined
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should return an item' do
|
34
|
+
allow(item_list).to receive(:loop_for_item).with('admin', 0.01).and_return(item)
|
35
|
+
expect(item_list.get_item('admin', 0.01)).to eql item
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe '#release_item' do
|
40
|
+
it 'should release an item' do
|
41
|
+
item_list.items['admin']['danny7'][ItemsList::RESERVE_KEY] = true
|
42
|
+
expect(item_list.release_item('danny7')).to be false
|
43
|
+
expect(item_list.items['admin']['danny7'][ItemsList::RESERVE_KEY]).to be false
|
44
|
+
end
|
45
|
+
|
46
|
+
it('should raise Invalid Item error when trying to release non reserved item') do
|
47
|
+
expect { item_list.release_item('foo') }.to raise_error(ItemError::InvalidItem)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe '#get_reserved_items' do
|
52
|
+
it 'should return a string of reserved items' do
|
53
|
+
allow(item_list).to receive(:reserved_items).and_return(%w(danny7 johnny5))
|
54
|
+
expect(item_list.get_reserved_items).to eql 'danny7, johnny5'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe '#get_available_items' do
|
59
|
+
it 'should return a string of reserved items' do
|
60
|
+
allow(item_list).to receive(:available_items).and_return(%w(danny7))
|
61
|
+
expect(item_list.get_available_items).to eql 'danny7'
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe '#get_all_items' do
|
66
|
+
it 'should return a string of reserved items' do
|
67
|
+
allow(item_list).to receive(:all_items).and_return(item_list.items)
|
68
|
+
response_hash = {"admin" => {"danny7" => {"danny7" => {"user_name" => "danny9", "rep_id" => "100014620", "profile_id" => "8190"}}, "johnny5" => {"johnny5" => {"user_name" => "danny7", "rep_id" => "100014624", "profile_id" => "8192"}}}}
|
69
|
+
expect(item_list.get_all_items).to eql response_hash
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe '#release_all_items' do
|
74
|
+
it 'should release all items' do
|
75
|
+
allow(item_list).to receive(:reserved_items).and_return(['danny7'])
|
76
|
+
item_list.items['admin']['danny7'][ItemsList::RESERVE_KEY] = true
|
77
|
+
item_list.items['admin']['johnny5'][ItemsList::RESERVE_KEY] = true
|
78
|
+
item_list.release_all_items
|
79
|
+
expect(item_list.items['admin']['danny7'][ItemsList::RESERVE_KEY]).to be false
|
80
|
+
expect(item_list.items['admin']['johnny5'][ItemsList::RESERVE_KEY]).to be false
|
81
|
+
end
|
82
|
+
|
83
|
+
it('should return true') do
|
84
|
+
allow(item_list).to receive(:reserved_items).and_return(['danny7'])
|
85
|
+
item_list.items['admin']['danny7'][ItemsList::RESERVE_KEY] = true
|
86
|
+
item_list.items['admin']['johnny5'][ItemsList::RESERVE_KEY] = true
|
87
|
+
expect(item_list.release_all_items).to be true
|
88
|
+
end
|
89
|
+
|
90
|
+
it('should raise No Reserved Items if no items are reserved') do
|
91
|
+
expect { item_list.release_all_items }.to raise_error(ItemError::NoReservedItems)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe '#reserved_items' do
|
96
|
+
it 'should return an array of reserved items' do
|
97
|
+
item_list.items['admin']['danny7'][ItemsList::RESERVE_KEY] = true
|
98
|
+
expect(item_list.reserved_items).to eql ['danny7']
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
describe '#available_items' do
|
103
|
+
it 'should return an array of available items' do
|
104
|
+
item_list.items['admin']['danny7'][ItemsList::RESERVE_KEY] = true
|
105
|
+
expect(item_list.available_items).to eql ['johnny5']
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
describe '#loop_for_item' do
|
110
|
+
it 'should reserve an item' do
|
111
|
+
allow(item_list).to receive(:get_available_item).with('admin').and_return(item)
|
112
|
+
allow(item_list).to receive(:reserve_item).with('admin', 'danny7').and_return(item_list.items['admin']['danny7'][ItemsList::RESERVE_KEY] = true)
|
113
|
+
allow(item_list).to receive(:sanitize_response).with(item)
|
114
|
+
item_list.loop_for_item('admin', 0.01)
|
115
|
+
expect(item_list.items['admin']['danny7'][ItemsList::RESERVE_KEY]).to be true
|
116
|
+
end
|
117
|
+
|
118
|
+
it 'should return a sanitized response' do
|
119
|
+
allow(item_list).to receive(:get_available_item).with('admin').and_return(item)
|
120
|
+
allow(item_list).to receive(:reserve_item).with('admin', 'danny7')
|
121
|
+
allow(item_list).to receive(:sanitize_response).with(item).and_return(item)
|
122
|
+
expect(item_list.loop_for_item('admin', 0.01)['danny7'].keys).to_not include ItemsList::RESERVE_KEY
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'should return a hash' do
|
126
|
+
allow(item_list).to receive(:get_available_item).with('admin').and_return(item)
|
127
|
+
allow(item_list).to receive(:reserve_item).with('admin', 'danny7')
|
128
|
+
allow(item_list).to receive(:sanitize_response).with(item).and_return(item)
|
129
|
+
expect(item_list.loop_for_item('admin', 0.01)).to be_a_kind_of Hash
|
130
|
+
end
|
131
|
+
|
132
|
+
it 'should raise a timeout error if not items available' do
|
133
|
+
allow(item_list).to receive(:get_available_item).with('admin').and_return([])
|
134
|
+
expect { item_list.loop_for_item('admin', 0.01) }.to raise_error ItemError::NoAvailableItems
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
describe '#sanitize_response' do
|
139
|
+
it 'should not include reserve key' do
|
140
|
+
item = {'danny7' => {'user_name' => 'danny9', 'rep_id' => '100014624', 'profile_id' => '8192', ItemsList::RESERVE_KEY.to_sym => false}}
|
141
|
+
expect(item_list.sanitize_response(item)['danny7'].keys).to_not include ItemsList::RESERVE_KEY
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
describe '#reserve_item' do
|
146
|
+
it 'should reserve an item' do
|
147
|
+
expect(item_list.reserve_item('admin', 'danny7')).to be true
|
148
|
+
expect(item_list.items['admin']['danny7'][ItemsList::RESERVE_KEY]).to be true
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
describe '#get_available_item' do
|
153
|
+
it 'should get next available item' do
|
154
|
+
item_list.items['admin']['danny7'][ItemsList::RESERVE_KEY] = true
|
155
|
+
expect(item_list.get_available_item('admin')).to eql({"johnny5" => {"user_name" => "danny7", "rep_id" => "100014624", "profile_id" => "8192", ItemsList::RESERVE_KEY.to_sym => false}})
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
describe '#load_items!' do
|
160
|
+
let(:hash) { {type: {test: {hello: 'world', foo: 'bar'}, basic: {basic1: {}, basic2: {}}}} }
|
161
|
+
let(:duplicate_hash) { {type: {test: {hello: 'world', foo: 'bar'}}, type2: {test: {hello: 'world'}}} }
|
162
|
+
|
163
|
+
it 'should add a unique key 2nd level to the given hash' do
|
164
|
+
allow(YAML).to receive(:load_file).with('').and_return(hash)
|
165
|
+
expect(ItemsList.new.load_items!('').items[:type][:test].keys).to include ItemsList::RESERVE_KEY
|
166
|
+
end
|
167
|
+
|
168
|
+
it 'should set @items to provided hash' do
|
169
|
+
allow(YAML).to receive(:load_file).with('').and_return(hash)
|
170
|
+
items = ItemsList.new
|
171
|
+
items.load_items!('')
|
172
|
+
expect(items.instance_variable_get('@items')).to eql hash
|
173
|
+
end
|
174
|
+
|
175
|
+
it 'should raise an error if duplicate keys' do
|
176
|
+
allow(YAML).to receive(:load_file).with('').and_return(duplicate_hash)
|
177
|
+
items = ItemsList.new
|
178
|
+
expect { items.load_items!('') }.to raise_error ItemError::ItemsNotUnique
|
179
|
+
end
|
180
|
+
|
181
|
+
it 'should raise an error on invalid config file' do
|
182
|
+
expect { ItemsList.new.load_items!('blah123') }.to raise_error(Errno::ENOENT)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
$project_root = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
2
|
+
#ARGV[1] = "#{$project_root}/spec/test_items_spec.yml"
|
3
|
+
|
4
|
+
require 'sinatra'
|
5
|
+
require 'pry'
|
6
|
+
require 'test-helpers/all'
|
7
|
+
require 'yaml'
|
8
|
+
require 'json'
|
9
|
+
require 'rspec'
|
10
|
+
require 'rack/test'
|
11
|
+
require_relative '../lib/gertrude/core_ext/hash'
|
12
|
+
require_relative '../lib/gertrude/items/item_error'
|
13
|
+
require_relative '../lib/gertrude/items/items_list'
|
14
|
+
require_relative '../lib/gertrude/items/item_server'
|
15
|
+
|
16
|
+
def app
|
17
|
+
ItemServer.tap do |svr|
|
18
|
+
svr.settings.set(:file, "#{$project_root}/spec/test_items_spec.yml")
|
19
|
+
svr.set :environment, :production
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
RSpec.configure do |config|
|
24
|
+
config.color = true
|
25
|
+
config.tty = true
|
26
|
+
config.formatter = :documentation
|
27
|
+
config.include Rack::Test::Methods
|
28
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
---
|
2
|
+
admin:
|
3
|
+
danny7:
|
4
|
+
user_name: 'danny7'
|
5
|
+
rep_id: '100014624'
|
6
|
+
profile_id: '8192'
|
7
|
+
man1:
|
8
|
+
user_name: 'man1'
|
9
|
+
rep_id: '100012345'
|
10
|
+
profile_id: '8100'
|
11
|
+
rep:
|
12
|
+
hhummer:
|
13
|
+
user_name: 'hhummer'
|
14
|
+
rep_id: '100428682'
|
15
|
+
profile_id: '8064'
|
16
|
+
man3:
|
17
|
+
user_name: 'man3'
|
18
|
+
rep_id: '100976252'
|
19
|
+
profile_id: '8238'
|
metadata
ADDED
@@ -0,0 +1,176 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gertrude
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Wallace Harwood
|
8
|
+
- Jarod Adair
|
9
|
+
- Umair Chagani
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2016-04-19 00:00:00.000000000 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rake
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '10.0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
requirements:
|
26
|
+
- - ~>
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
version: '10.0'
|
29
|
+
- !ruby/object:Gem::Dependency
|
30
|
+
name: rspec
|
31
|
+
requirement: !ruby/object:Gem::Requirement
|
32
|
+
requirements:
|
33
|
+
- - ~>
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: '3.0'
|
36
|
+
type: :development
|
37
|
+
prerelease: false
|
38
|
+
version_requirements: !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - ~>
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '3.0'
|
43
|
+
- !ruby/object:Gem::Dependency
|
44
|
+
name: pry
|
45
|
+
requirement: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ! '>='
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '0'
|
50
|
+
type: :development
|
51
|
+
prerelease: false
|
52
|
+
version_requirements: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ! '>='
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
57
|
+
- !ruby/object:Gem::Dependency
|
58
|
+
name: rack-test
|
59
|
+
requirement: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ! '>='
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
type: :development
|
65
|
+
prerelease: false
|
66
|
+
version_requirements: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - ! '>='
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0'
|
71
|
+
- !ruby/object:Gem::Dependency
|
72
|
+
name: bundler
|
73
|
+
requirement: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '1.11'
|
78
|
+
type: :runtime
|
79
|
+
prerelease: false
|
80
|
+
version_requirements: !ruby/object:Gem::Requirement
|
81
|
+
requirements:
|
82
|
+
- - ~>
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: '1.11'
|
85
|
+
- !ruby/object:Gem::Dependency
|
86
|
+
name: sinatra
|
87
|
+
requirement: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - ! '>='
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '0'
|
92
|
+
type: :runtime
|
93
|
+
prerelease: false
|
94
|
+
version_requirements: !ruby/object:Gem::Requirement
|
95
|
+
requirements:
|
96
|
+
- - ! '>='
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
- !ruby/object:Gem::Dependency
|
100
|
+
name: test-helpers
|
101
|
+
requirement: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - ! '>='
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
106
|
+
type: :runtime
|
107
|
+
prerelease: false
|
108
|
+
version_requirements: !ruby/object:Gem::Requirement
|
109
|
+
requirements:
|
110
|
+
- - ! '>='
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: '0'
|
113
|
+
description:
|
114
|
+
email:
|
115
|
+
- wallace.harwood@manheim.com
|
116
|
+
- jarod.adair@manheim.com
|
117
|
+
- umair.chagani@manheim.com
|
118
|
+
executables:
|
119
|
+
- console
|
120
|
+
- gertrude
|
121
|
+
- setup
|
122
|
+
extensions: []
|
123
|
+
extra_rdoc_files: []
|
124
|
+
files:
|
125
|
+
- .gitignore
|
126
|
+
- .rspec
|
127
|
+
- .travis.yml
|
128
|
+
- Gemfile
|
129
|
+
- LICENSE.txt
|
130
|
+
- README.md
|
131
|
+
- Rakefile
|
132
|
+
- bin/console
|
133
|
+
- bin/gertrude
|
134
|
+
- bin/setup
|
135
|
+
- gertrude.gemspec
|
136
|
+
- lib/gertrude.rb
|
137
|
+
- lib/gertrude/core_ext/hash.rb
|
138
|
+
- lib/gertrude/items/item_error.rb
|
139
|
+
- lib/gertrude/items/item_server.rb
|
140
|
+
- lib/gertrude/items/items_list.rb
|
141
|
+
- lib/gertrude/version.rb
|
142
|
+
- spec/gertrude_spec.rb
|
143
|
+
- spec/hash_spec.rb
|
144
|
+
- spec/items_list_spec.rb
|
145
|
+
- spec/spec_helper.rb
|
146
|
+
- spec/test_items_spec.yml
|
147
|
+
homepage: ''
|
148
|
+
licenses:
|
149
|
+
- MIT
|
150
|
+
metadata: {}
|
151
|
+
post_install_message:
|
152
|
+
rdoc_options: []
|
153
|
+
require_paths:
|
154
|
+
- lib
|
155
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ! '>='
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
161
|
+
requirements:
|
162
|
+
- - ! '>='
|
163
|
+
- !ruby/object:Gem::Version
|
164
|
+
version: '0'
|
165
|
+
requirements: []
|
166
|
+
rubyforge_project:
|
167
|
+
rubygems_version: 2.4.6
|
168
|
+
signing_key:
|
169
|
+
specification_version: 4
|
170
|
+
summary: Librarian to manage your things for you.
|
171
|
+
test_files:
|
172
|
+
- spec/gertrude_spec.rb
|
173
|
+
- spec/hash_spec.rb
|
174
|
+
- spec/items_list_spec.rb
|
175
|
+
- spec/spec_helper.rb
|
176
|
+
- spec/test_items_spec.yml
|