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.
@@ -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=
@@ -0,0 +1,13 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ .DS_Store
11
+ .idea/
12
+ lib/gertrude/items/.DS_Store
13
+
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.3
4
+ before_install: gem install bundler -v 1.11.2
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in gertrude.gemspec
4
+ gemspec
@@ -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.
@@ -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
+ ```
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+
@@ -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
+
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
@@ -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
+
@@ -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,3 @@
1
+ module Gertrude
2
+ VERSION = "0.0.1"
3
+ 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
@@ -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
@@ -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