sis_ruby 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +41 -0
- data/Gemfile +3 -0
- data/LICENSE +28 -0
- data/README.md +188 -0
- data/RELEASE_NOTES.md +3 -0
- data/Rakefile +35 -0
- data/lib/sis_ruby/client.rb +85 -0
- data/lib/sis_ruby/common.rb +5 -0
- data/lib/sis_ruby/endpoint.rb +108 -0
- data/lib/sis_ruby/exceptions/bad_response_error.rb +35 -0
- data/lib/sis_ruby/exceptions/missing_id_error.rb +12 -0
- data/lib/sis_ruby/get_helper.rb +41 -0
- data/lib/sis_ruby/hash_builder.rb +31 -0
- data/lib/sis_ruby/params.rb +117 -0
- data/lib/sis_ruby/result_enumerable.rb +86 -0
- data/lib/sis_ruby/version.rb +3 -0
- data/lib/sis_ruby.rb +8 -0
- data/samples/sample-1.rb +26 -0
- data/samples/sample-2.rb +33 -0
- data/sis_ruby.gemspec +22 -0
- data/spec/client_spec.rb +57 -0
- data/spec/endpoint_spec.rb +54 -0
- data/spec/hash_builder_spec.rb +83 -0
- data/spec/params_spec.rb +75 -0
- data/spec/setup/drop-db.js +4 -0
- data/spec/setup/recreate-db.sh +14 -0
- data/spec/setup/test-user.json +6 -0
- data/spec/spec_helper.rb +1 -0
- data/spec-writing/entity.json +50 -0
- data/spec-writing/hiera.json +58 -0
- data/spec-writing/hook.json +64 -0
- data/spec-writing/schema.json +131 -0
- data/spec-writing/write_spec.rb +105 -0
- metadata +149 -0
@@ -0,0 +1,117 @@
|
|
1
|
+
module SisRuby
|
2
|
+
class Params
|
3
|
+
|
4
|
+
# These methods behave as simple accessors when called with no arguments.
|
5
|
+
# When called with an argument, that argument is used to set the value corresponding to the method name.
|
6
|
+
|
7
|
+
# The conventional 'attribute=' method forms for this class are supported
|
8
|
+
# in addition to the methods below, as an alternate way to set values.
|
9
|
+
|
10
|
+
attr_writer :fields, :filter, :limit, :offset, :sort
|
11
|
+
|
12
|
+
def fields(*args)
|
13
|
+
if args.none?
|
14
|
+
return @fields
|
15
|
+
elsif args.one?
|
16
|
+
@fields = Array(args.first)
|
17
|
+
else
|
18
|
+
@fields = args
|
19
|
+
end
|
20
|
+
@fields = @fields.to_set # order shouldn't matter, and no dups
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
def filter(*args)
|
26
|
+
if args.any?
|
27
|
+
@filter = args.first
|
28
|
+
self
|
29
|
+
else
|
30
|
+
@filter
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
def limit(*args)
|
36
|
+
if args.any?
|
37
|
+
@limit = args.first
|
38
|
+
self
|
39
|
+
else
|
40
|
+
@limit
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
def offset(*args)
|
46
|
+
if args.any?
|
47
|
+
@offset = args.first
|
48
|
+
self
|
49
|
+
else
|
50
|
+
@offset
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
def sort(*args)
|
56
|
+
if args.any?
|
57
|
+
@sort = args.first
|
58
|
+
self
|
59
|
+
else
|
60
|
+
@sort
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
def to_hash
|
66
|
+
h = {}
|
67
|
+
h['limit'] = limit if limit
|
68
|
+
h['offset'] = offset if offset
|
69
|
+
h['fields'] = fields.to_a.join(',') if fields
|
70
|
+
h['q'] = filter if filter
|
71
|
+
h['sort'] = sort if sort
|
72
|
+
h
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
def self.from_hash(other)
|
77
|
+
instance = self.new
|
78
|
+
instance.limit(other['limit']) if other['limit']
|
79
|
+
instance.fields(other['fields'].split(',').to_set) if other['fields']
|
80
|
+
instance.offset(other['offset']) if other['offset']
|
81
|
+
instance.filter(other['q']) if other['q']
|
82
|
+
instance.sort(other['sort']) if other['sort']
|
83
|
+
instance
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
def clone
|
88
|
+
other = Params.new
|
89
|
+
other.limit(self.limit) if self.limit
|
90
|
+
other.fields(self.fields.clone) if self.fields
|
91
|
+
other.offset(self.offset) if self.offset
|
92
|
+
other.filter(self.filter.clone) if self.filter
|
93
|
+
other.sort(self.sort) if self.sort
|
94
|
+
other
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
def to_h
|
99
|
+
to_hash
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
def hash
|
104
|
+
to_h.hash
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
def ==(other)
|
109
|
+
other.is_a?(self.class) && other.to_h == self.to_h
|
110
|
+
end
|
111
|
+
|
112
|
+
|
113
|
+
def <=>(other)
|
114
|
+
self.to_h <=> other.to_h
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'trick_bag'
|
2
|
+
require 'ostruct'
|
3
|
+
require 'typhoeus'
|
4
|
+
require_relative 'get_helper'
|
5
|
+
require_relative 'params'
|
6
|
+
|
7
|
+
|
8
|
+
module SisRuby
|
9
|
+
|
10
|
+
|
11
|
+
# This class is an enumerator over a result set of SIS objects, as specified
|
12
|
+
# in a hash-like object (that is, any object that implements to_hash,
|
13
|
+
# such as a SisParams instance). It can be used as any Enumerable;
|
14
|
+
# that is, methods such as each, map, select, etc. can be called;
|
15
|
+
# an array of all results can be produced by calling to_a,
|
16
|
+
# and an Enumerator can be gotten by calling each without a chunk.
|
17
|
+
#
|
18
|
+
# Internally it fetches chunks of records only when needed to serve the values
|
19
|
+
# to the caller. The chunk size can be specified by the caller.
|
20
|
+
#
|
21
|
+
# By calling each or each_slice you can access some of the data before fetching
|
22
|
+
# all of it. This can be handy if:
|
23
|
+
#
|
24
|
+
# 1) there may not be enough available memory to process the entire result set
|
25
|
+
# at once
|
26
|
+
# 2) you want to process some records while others are being fetched, by
|
27
|
+
# using multiple threads, for example.
|
28
|
+
#
|
29
|
+
class ResultEnumerable < TrickBag::Enumerables::BufferedEnumerable
|
30
|
+
|
31
|
+
include GetHelper
|
32
|
+
include Enumerable
|
33
|
+
|
34
|
+
DEFAULT_CHUNK_RECORD_COUNT = 5_000
|
35
|
+
|
36
|
+
|
37
|
+
# @param endpoint_url the SIS endpoint URL string or an Endpoint instance
|
38
|
+
# @param params any object that implements to_hash, e.g. a Params instance
|
39
|
+
# @param chunk_size the maximum number of records to fetch from the server in a request
|
40
|
+
def initialize(endpoint_url, params = {}, chunk_size = DEFAULT_CHUNK_RECORD_COUNT)
|
41
|
+
super(chunk_size)
|
42
|
+
@endpoint_url = endpoint_url.is_a?(Endpoint) ? endpoint_url.url : endpoint_url
|
43
|
+
@outer_params = params.is_a?(Params) ? params : Params.from_hash(params.to_hash)
|
44
|
+
|
45
|
+
inner_limit = @outer_params.limit ? [chunk_size, @outer_params.limit].min : chunk_size
|
46
|
+
@inner_params = @outer_params.clone.limit(inner_limit)
|
47
|
+
|
48
|
+
@inner_params.offset ||= 0
|
49
|
+
@position = 0
|
50
|
+
@total_count = nil
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
def fetch
|
55
|
+
# This exits the fetching when the desired number of records has been fetched.
|
56
|
+
if @outer_params.limit && @yield_count >= @outer_params.limit
|
57
|
+
self.data = []
|
58
|
+
return
|
59
|
+
end
|
60
|
+
|
61
|
+
request = Typhoeus::Request.new(@endpoint_url, params: @inner_params.to_hash, headers: create_headers(true))
|
62
|
+
response = request.run
|
63
|
+
validate_response_success(response)
|
64
|
+
self.data = JSON.parse(response.body)
|
65
|
+
@total_count = response.headers['x-total-count'].to_i
|
66
|
+
@inner_params.offset(@inner_params.offset + chunk_size)
|
67
|
+
|
68
|
+
# TODO: Deal with this
|
69
|
+
# if @total_count > chunk_size && @inner_params.sort.nil?
|
70
|
+
# raise "Total count (#{@total_count}) exceeds chunk size (#{chunk_size})." +
|
71
|
+
# "When this is the case, a sort order must be specified, or chunk size increased."
|
72
|
+
# end
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
def total_count
|
77
|
+
fetch unless @total_count
|
78
|
+
@total_count
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
def fetch_notify
|
83
|
+
# puts "Fetch at #{Time.now}: chunk size: #{chunk_size}, yield count: #{yield_count}, total count = #{@total_count}"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
data/lib/sis_ruby.rb
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
# Load all *.rb files in lib/sis_ruby and below.
|
2
|
+
# Use a lambda so that the intermediate variables do not survive this file.
|
3
|
+
->() {
|
4
|
+
start_dir = File.join(File.dirname(__FILE__), 'sis_ruby') # the lib directory
|
5
|
+
file_mask = "#{start_dir}/**/*.rb"
|
6
|
+
Dir[file_mask].each { |file| require file }
|
7
|
+
}.()
|
8
|
+
|
data/samples/sample-1.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'sis_ruby'
|
4
|
+
require 'awesome_print'
|
5
|
+
|
6
|
+
include SisRuby
|
7
|
+
|
8
|
+
raise "Must specify SIS server URL in SIS_URL environment variable." unless ENV['SIS_URL']
|
9
|
+
client = Client.new(ENV['SIS_URL'])
|
10
|
+
host_endpoint = client.entities('host')
|
11
|
+
|
12
|
+
records = host_endpoint.list(Params.new.fields('hostname').limit(3))
|
13
|
+
puts "Fetched #{records.size} host records."
|
14
|
+
puts "Last one is: #{records.last}, host name is #{records.last['hostname']}"
|
15
|
+
|
16
|
+
|
17
|
+
full_record = host_endpoint.list(Params.new.limit(1)).first
|
18
|
+
puts 'Host record keys:'
|
19
|
+
ap full_record.keys
|
20
|
+
|
21
|
+
puts "\n\n"
|
22
|
+
query = Params.new.limit(1).fields('hostname', 'fqdn', 'status')
|
23
|
+
result = host_endpoint.list_as_openstructs(query).first
|
24
|
+
puts "Host: #{result.hostname}, FQDN: #{result.fqdn}, Status: #{result.status}"
|
25
|
+
puts "\nEntire record:"
|
26
|
+
ap result
|
data/samples/sample-2.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'awesome_print'
|
4
|
+
require 'pry'
|
5
|
+
require 'sis_ruby'
|
6
|
+
|
7
|
+
raise "Must specify SIS server URL in SIS_URL environment variable." unless ENV['SIS_URL']
|
8
|
+
client = Client.new(ENV['SIS_URL'])
|
9
|
+
HOSTS = client.entities('host')
|
10
|
+
# puts "Total host count is #{hosts.count}"
|
11
|
+
# puts "Total qa host count is #{hosts.count('environment' => 'qa')}"
|
12
|
+
|
13
|
+
# p1 = SisRuby::Params.new.sort('host').offset(9000).limit(2)
|
14
|
+
# p1 = SisRuby::Params.new.sort('host').offset(9000).limit(2).fields('hostname', 'environment')
|
15
|
+
# records = hosts.list(p1)
|
16
|
+
# ap records.first
|
17
|
+
|
18
|
+
|
19
|
+
def fetch(count, offset)
|
20
|
+
HOSTS.list(SisRuby::Params.new.fields('hostname').limit(count).offset(offset))
|
21
|
+
end
|
22
|
+
|
23
|
+
arrays = (0..10).to_a.map { |i| fetch(1000, i * 1000) }
|
24
|
+
a_combined = arrays.flatten
|
25
|
+
a_uniq = a_combined.uniq
|
26
|
+
host_names = a_combined.map { |h| h['hostname'] }
|
27
|
+
|
28
|
+
puts "Combined count is #{a_combined.size}"
|
29
|
+
puts "Unique count is #{a_uniq.size}"
|
30
|
+
puts "Host name count is #{host_names.size}"
|
31
|
+
puts "Unique host name count is #{host_names.uniq.size}"
|
32
|
+
binding.pry
|
33
|
+
|
data/sis_ruby.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'lib', 'sis_ruby', 'version')
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = 'sis_ruby'
|
5
|
+
s.version = SisRuby::VERSION
|
6
|
+
s.platform = Gem::Platform::RUBY
|
7
|
+
s.authors = ['Keith Bennett', 'Neel Goyal']
|
8
|
+
s.email = ['keithrbennett@gmail.com', 'sis-dev@verisign.com']
|
9
|
+
s.homepage = 'https://github.com/keithrbennett/sis_ruby'
|
10
|
+
s.summary = 'SIS Ruby Client'
|
11
|
+
s.description = 'Ruby client for SIS web service (https://github.com/sis-cmdb/sis-api)'
|
12
|
+
s.files = `git ls-files`.split($/)
|
13
|
+
s.require_path = 'lib'
|
14
|
+
s.license = 'BSD 3-Clause'
|
15
|
+
|
16
|
+
s.add_dependency 'awesome_print', '>= 1.6.0'
|
17
|
+
s.add_dependency 'rake'
|
18
|
+
s.add_dependency 'trick_bag', '>= 0.63.1'
|
19
|
+
s.add_dependency 'typhoeus', '>= 0.8.0'
|
20
|
+
|
21
|
+
s.add_development_dependency 'rspec', '>= 3.0'
|
22
|
+
end
|
data/spec/client_spec.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
require_relative '../lib/sis_ruby/exceptions/missing_id_error'
|
3
|
+
|
4
|
+
# These tests test client behavior that does not require a server.
|
5
|
+
|
6
|
+
module SisRuby
|
7
|
+
|
8
|
+
describe Client do
|
9
|
+
|
10
|
+
let(:server_url) { 'https://fictitious-domain.com' }
|
11
|
+
let(:id_fieldname) { '_id_' }
|
12
|
+
let(:client) { Client.new(server_url) }
|
13
|
+
let(:host_endpoint) { client.entities('host', id_fieldname) }
|
14
|
+
|
15
|
+
context 'constructor parameters' do
|
16
|
+
|
17
|
+
specify 'omitting version gets default version' do
|
18
|
+
expect(client.api_version).to eq(Client::DEFAULT_API_VERSION)
|
19
|
+
end
|
20
|
+
|
21
|
+
specify 'overriding default version works' do
|
22
|
+
api_v = 'v_not_default'
|
23
|
+
expect(Client.new(server_url, api_version: api_v).api_version).to eq(api_v)
|
24
|
+
end
|
25
|
+
|
26
|
+
specify 'setting auth_token works' do
|
27
|
+
auth_token = 'abcdefgxyz'
|
28
|
+
expect(Client.new(server_url, auth_token: auth_token).auth_token).to eq(auth_token)
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'precreated endpoints' do
|
34
|
+
|
35
|
+
specify 'are precreated' do
|
36
|
+
endpoints = [client.hooks, client.schemas, client.hiera]
|
37
|
+
expect(endpoints.all? { |ep| ep.is_a?(Endpoint) }).to eq(true)
|
38
|
+
end
|
39
|
+
|
40
|
+
specify 'have URLs that end in the right name' do
|
41
|
+
endpoints = [client.hooks, client.schemas, client.hiera]
|
42
|
+
endpoints_and_names = endpoints.zip(%w(hooks schemas hiera))
|
43
|
+
expect(endpoints_and_names.all? { |ep, name| ep.url.end_with?(name) }).to eq(true)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
context 'tokens' do
|
49
|
+
|
50
|
+
specify 'the URL is correct' do
|
51
|
+
username = 'david_bisbal'
|
52
|
+
token = client.tokens(username)
|
53
|
+
expect(token.url.end_with?("users/#{username}/tokens")).to eq(true)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
require_relative '../lib/sis_ruby/exceptions/missing_id_error'
|
3
|
+
|
4
|
+
module SisRuby
|
5
|
+
|
6
|
+
describe Endpoint do
|
7
|
+
|
8
|
+
let(:id_fieldname) { '_id_' }
|
9
|
+
let(:server_url) { 'https://fictitious-domain.com' }
|
10
|
+
let(:client) { Client.new(server_url) }
|
11
|
+
let(:host_endpoint) { client.entities('host', id_fieldname) }
|
12
|
+
|
13
|
+
context '#id_from_param' do
|
14
|
+
|
15
|
+
it 'works with an object other than hash' do
|
16
|
+
expect(host_endpoint.id_from_param(3)).to eq(3)
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'works with a hash' do
|
20
|
+
expect(host_endpoint.id_from_param({ id_fieldname => 3 } )).to eq(3)
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'raises an error on nil' do
|
24
|
+
expect { host_endpoint.id_from_param(nil) }.to raise_error(MissingIdError)
|
25
|
+
expect { host_endpoint.id_from_param( { id_fieldname => nil } ) }.to raise_error((MissingIdError))
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
context '#==' do
|
31
|
+
|
32
|
+
specify '2 endpoints created with the same parameters are ==' do
|
33
|
+
create_endpoint = -> { Endpoint.new(client, 'abcxyz', 'foo') }
|
34
|
+
endpoint_0 = create_endpoint.()
|
35
|
+
endpoint_1 = create_endpoint.()
|
36
|
+
expect(endpoint_1).to eq(endpoint_0)
|
37
|
+
end
|
38
|
+
|
39
|
+
specify '2 endpoints created with different clients are not ==' do
|
40
|
+
client1 = Client.new('url1')
|
41
|
+
client2 = Client.new('url2')
|
42
|
+
expect(Endpoint.new(client1, 'foo')).not_to eq(Endpoint.new(client2, 'foo'))
|
43
|
+
end
|
44
|
+
|
45
|
+
specify '2 endpoints created with different endpoint names are not ==' do
|
46
|
+
expect(Endpoint.new(client, 'foo')).not_to eq(Endpoint.new(client, 'bar'))
|
47
|
+
end
|
48
|
+
|
49
|
+
specify '2 endpoints created with different id fields are not ==' do
|
50
|
+
expect(Endpoint.new(client, 'foo', 'x')).not_to eq(Endpoint.new(client, 'foo', 'y'))
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
|
3
|
+
describe HashBuilder do
|
4
|
+
|
5
|
+
PERMITTED_ENTITY_ENDPOINT_TOP_LEVEL_KEYS = %w(
|
6
|
+
fields
|
7
|
+
id
|
8
|
+
limit
|
9
|
+
offset
|
10
|
+
q
|
11
|
+
)
|
12
|
+
|
13
|
+
QUERY_HASH = {
|
14
|
+
'environment' => 'prod',
|
15
|
+
'status' => 'live', # not maintenance
|
16
|
+
'role.product.name' => 'mdns',
|
17
|
+
}
|
18
|
+
|
19
|
+
specify 'will create a limit key/value pair correctly' do
|
20
|
+
builder = HashBuilder.new
|
21
|
+
expect(builder.limit(10).to_h).to eq({ 'limit' => 10 })
|
22
|
+
end
|
23
|
+
|
24
|
+
specify 'subsequent calls will replace an existing value' do
|
25
|
+
builder = HashBuilder.new
|
26
|
+
builder.limit(10)
|
27
|
+
expect(builder.limit(11).to_h).to eq({ 'limit' => 11 })
|
28
|
+
end
|
29
|
+
|
30
|
+
specify 'a query hash will be correctly inserted' do
|
31
|
+
builder = HashBuilder.new
|
32
|
+
builder.q(QUERY_HASH)
|
33
|
+
expect(builder.to_h).to eq({ 'q' => QUERY_HASH})
|
34
|
+
end
|
35
|
+
|
36
|
+
specify '2 key/value pairs are no problem' do
|
37
|
+
builder = HashBuilder.new
|
38
|
+
builder.q(QUERY_HASH).limit(20)
|
39
|
+
expect(builder.to_h).to eq({ 'q' => QUERY_HASH, 'limit' => 20 })
|
40
|
+
end
|
41
|
+
|
42
|
+
specify 'keys are all Strings when key type of String is specified' do
|
43
|
+
builder = HashBuilder.new(String)
|
44
|
+
expect(builder.foo(1).bar(2).to_h.keys.all? { |key| key.is_a?(String) }).to eq(true)
|
45
|
+
end
|
46
|
+
|
47
|
+
specify 'keys are all Symbols when key type of Symbol is specified' do
|
48
|
+
builder = HashBuilder.new(Symbol)
|
49
|
+
expect(builder.foo(1).bar(2).to_h.keys.all? { |key| key.is_a?(Symbol) }).to eq(true)
|
50
|
+
end
|
51
|
+
|
52
|
+
specify 'respond_to_missing? always returns true' do
|
53
|
+
builder = HashBuilder.new
|
54
|
+
random_name = 'x' + Random.rand(1_000_000).to_s
|
55
|
+
expect(builder.respond_to?(random_name, false)).to eq(true)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Disable permitted methods feature for now; it was not fully implemented,
|
59
|
+
# and should be a predicate lambda rather than an array anyway.
|
60
|
+
|
61
|
+
# context 'permitted methods' do
|
62
|
+
#
|
63
|
+
# specify 'when not specified, all are allowed' do
|
64
|
+
# builder = HashBuilder.new
|
65
|
+
# expect(builder.respond_to?('foo')).to eq(true)
|
66
|
+
# end
|
67
|
+
#
|
68
|
+
# specify 'when empty, none are allowed' do
|
69
|
+
# builder = HashBuilder.new(String, [])
|
70
|
+
# expect(builder.new(String).respond_to?('foo')).to eq(false)
|
71
|
+
# builder.something(123)
|
72
|
+
# expect { builder.something(123) }.to raise_error
|
73
|
+
# end
|
74
|
+
#
|
75
|
+
# specify 'when non-empty, only permitted are permitted' do
|
76
|
+
# builder = HashBuilder.new(String, [:foo])
|
77
|
+
# expect(builder.new(String).respond_to?('foo')).to eq(true)
|
78
|
+
# expect(builder.new(String).respond_to?('bar')).to eq(false)
|
79
|
+
#
|
80
|
+
# end
|
81
|
+
# end
|
82
|
+
end
|
83
|
+
|
data/spec/params_spec.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
|
3
|
+
module SisRuby
|
4
|
+
|
5
|
+
describe Params do
|
6
|
+
|
7
|
+
it 'results in an empty hash when nothing is set' do
|
8
|
+
expect(Params.new.to_hash).to eq({})
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'correctly concatenate fields' do
|
12
|
+
fields = %w(foo bar).to_set
|
13
|
+
params = Params.new.fields(fields)
|
14
|
+
expect(params.fields).to eq(fields)
|
15
|
+
expect(params.to_hash).to eq({ 'fields' => 'foo,bar'})
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'sets limit correctly' do
|
19
|
+
params = Params.new.limit(3)
|
20
|
+
expect(params.limit).to eq(3)
|
21
|
+
expect(params.to_hash).to eq({ 'limit' => 3 })
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'sets offset correctly' do
|
25
|
+
params = Params.new.offset(4)
|
26
|
+
expect(params.offset).to eq(4)
|
27
|
+
expect(params.to_hash).to eq({ 'offset' => 4 })
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'sets filter correctly' do
|
31
|
+
filter = { 'my_numeric_field' => { '$gt' => 10 }}
|
32
|
+
params = Params.new.filter(filter)
|
33
|
+
expect(params.filter).to eq(filter)
|
34
|
+
expect(params.to_hash).to eq({ 'q' => filter })
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'sets sort correctly' do
|
38
|
+
params = Params.new.sort('-count')
|
39
|
+
expect(params.sort).to eq('-count')
|
40
|
+
expect(params.to_hash).to eq({ 'sort' => '-count' })
|
41
|
+
end
|
42
|
+
|
43
|
+
specify 'to_h and to_hash return equal hashes' do
|
44
|
+
params = Params.new.sort('-count')
|
45
|
+
expect(params.to_h).to eq(params.to_hash)
|
46
|
+
end
|
47
|
+
|
48
|
+
specify '2 identical objects are ==, and <=> returns 0' do
|
49
|
+
params1 = Params.new.sort('-count')
|
50
|
+
params2 = Params.new.sort('-count')
|
51
|
+
expect(params1).to eq(params2)
|
52
|
+
expect(params1 <=> params2).to eq(0)
|
53
|
+
end
|
54
|
+
|
55
|
+
specify '2 different objects are not ==, and <=> returns non-0' do
|
56
|
+
params1 = Params.new.sort('-count')
|
57
|
+
params2 = Params.new.sort('-date')
|
58
|
+
expect(params1).not_to eq(params2)
|
59
|
+
expect(params1 <=> params2).not_to eq(0)
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
specify 'clone returns an equal copy' do
|
64
|
+
params_orig = Params.new.sort('-count').limit(300).filter({key: 'value'}).offset(100).fields('a', 'b')
|
65
|
+
params_clone = params_orig.clone
|
66
|
+
expect(params_clone).to eq(params_orig)
|
67
|
+
end
|
68
|
+
|
69
|
+
specify 'from_hash returns an equal copy' do
|
70
|
+
params_orig = Params.new.sort('-count').limit(300).filter({key: 'value'}).offset(100).fields('a', 'b')
|
71
|
+
params_copy = Params.from_hash(params_orig.to_hash)
|
72
|
+
expect(params_copy).to eq(params_orig)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
# This script drops the Mongo DB and adds the test user.
|
4
|
+
# It assumes the existence of a ~/opt/sis-api directory
|
5
|
+
# that is a clone of the sis-api git repo, and a 'test-user.json'
|
6
|
+
# data file that is a copy of the file by that name in this directory.
|
7
|
+
#
|
8
|
+
# The script should be run from the directory in which it exists.
|
9
|
+
|
10
|
+
mongo drop-db.js
|
11
|
+
#echo "use sis;\n db.dropDatabase();\n exit" | mongo
|
12
|
+
cd ~/opt/sis-api
|
13
|
+
node tools/useradmin.js update test-user.json
|
14
|
+
cd -
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require_relative '../lib/sis_ruby'
|
@@ -0,0 +1,50 @@
|
|
1
|
+
{
|
2
|
+
"entity_type": "entity_test",
|
3
|
+
"id_field": "_id",
|
4
|
+
"required_schema": {
|
5
|
+
"name": "entity_test",
|
6
|
+
"owner": "schema_owner",
|
7
|
+
"definition": {
|
8
|
+
"name": {
|
9
|
+
"type": "String",
|
10
|
+
"required": true,
|
11
|
+
"unique": true
|
12
|
+
},
|
13
|
+
"number": {
|
14
|
+
"type": "Number",
|
15
|
+
"unique": true,
|
16
|
+
"required": true
|
17
|
+
}
|
18
|
+
},
|
19
|
+
"_sis": {
|
20
|
+
"owner": "test"
|
21
|
+
}
|
22
|
+
},
|
23
|
+
"valid_items": [
|
24
|
+
{
|
25
|
+
"name": "entity1",
|
26
|
+
"number": 0
|
27
|
+
},
|
28
|
+
{
|
29
|
+
"name": "entity2",
|
30
|
+
"number": 1
|
31
|
+
},
|
32
|
+
{
|
33
|
+
"name": "entity3",
|
34
|
+
"number": 2
|
35
|
+
}
|
36
|
+
],
|
37
|
+
"invalid_items": [
|
38
|
+
{
|
39
|
+
"foo": "bar"
|
40
|
+
},
|
41
|
+
{
|
42
|
+
"name": "entity1",
|
43
|
+
"number": 4
|
44
|
+
},
|
45
|
+
{
|
46
|
+
"name": "non_unique_num",
|
47
|
+
"number": 0
|
48
|
+
}
|
49
|
+
]
|
50
|
+
}
|