gertrude 0.0.1

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