ucb-hcm 3.0 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 62c9ba3f962308201cf3d2668c222e3b74615d7a5ae111335d09ab6c7334d181
4
- data.tar.gz: a2cde1633debc69794e4d5ad4acc4755f258bb26da31ccf701623fbed0a09774
3
+ metadata.gz: 9dd61ba145d30df5ca3bebc81963f47dcb2ae2ff3216034c088f91ce8b76459f
4
+ data.tar.gz: f4037ac0a767966befa1b6f2e500167caa294afa70cf5f3f6627ed9b8ee6b2e2
5
5
  SHA512:
6
- metadata.gz: 9bef13a7e84c972a74b126b32544cf15eeb77ba79d32fcba3e4c543a1f1e8d159e42bb17f9daa1ac44aadcd84ee7a0e2017aa4efceedbf8e3043815f98f09c91
7
- data.tar.gz: 3a9feb88955fae6f323ca4cb10a6095a19b9e8ffe38da05686f15bc17eb8f82e670e8bce70bde31b8c7042c51fa7078da399addf24d02f0ee9c9190d6ded9b90
6
+ metadata.gz: dc1aecea9bb8fac982ae06bfd574f4fee4fa24300a2611355ff06e8e173303009c65f35d3c58a43ff180337485a31c6bd32b684d44ef229644ffd0c2c99ef2bc
7
+ data.tar.gz: c399c17bf5712a3accb5cde7835b0721e1dd76d2cab2e13305c50fb2b55a0a02acd456e378cf845de449c91805f96ba9414f23187fd8ca706584509b722bb256
data/.travis.yml CHANGED
@@ -1,16 +1,18 @@
1
1
  branches:
2
2
  except:
3
- - /^.*\/.*+$/
3
+ - "/^.*\\/.*+$/"
4
4
  language: ruby
5
5
  rvm:
6
- - 2.5.1
6
+ - 2.5.1
7
7
  install: true
8
8
  script:
9
- - bin/setup
9
+ - bin/setup
10
10
  before_deploy:
11
- - git tag $TRAVIS_BRANCH-$TRAVIS_BUILD_NUMBER
11
+ - git tag $TRAVIS_BRANCH-$TRAVIS_BUILD_NUMBER
12
12
  cache:
13
13
  directories:
14
- - vendor/bundle
14
+ - vendor/bundle
15
15
  notifications:
16
- slack: infinitered:4s2uT0dj614H6BUJteVhFGqo
16
+ slack: infinitered:4s2uT0dj614H6BUJteVhFGqo
17
+ env:
18
+ secure: VLlNFC1FgeMxXwPmuHwDYQ4QAmv3M/1r3Eew2rjiNH5+t1h+pdZozMPiBJ8TrOicISz7qnf6Eb8kufN9TzW9oVQNSSLi3/FgPw78TV8tFcgG3ZGIOw1ZKzqKaiYDAXwHrWhcLytYaK187IloYQkmifSMf+qll9La8vAqHDGnLk8=
data/CHANGELOG.md ADDED
@@ -0,0 +1,14 @@
1
+ 3.1.0
2
+ -----
3
+
4
+ Improved response parsing ([PR #5](https://github.com/ucb-ist-eas/ucb-hcm/pull/5)):
5
+
6
+ * Deprecated `Ucb::Hcm::Response#data` and `Ucb::Hcm::Response#response` - `data` was ambiguous as it sometimes returned an array and sometimes a hash
7
+ * Added `all` and `each` methods to `Ucb::Hcm::Response` for retrieving response data. The API returns response data as an array, even though it's often an array with one element. The client now better reflects that behavior
8
+ * Added `all_fetchers` and `each_fetcher` to wrap response data into [`Ucb::Hcm::DataFetcher`](https://github.com/ucb-ist-eas/ucb-hcm/blob/master/lib/ucb/hcm/data_fetcher.rb) instances
9
+
10
+ 3.0.0
11
+ -----
12
+
13
+ Switch to v3 of the HCM API ([PR #2](https://github.com/ucb-ist-eas/ucb-hcm/pull/2))
14
+
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Ucb::Hcm is a lightweight ruby wrapper around [UC Berkeley's Human Capital Management API](https://api-central.berkeley.edu/api/14/interactive-docs).
4
4
 
5
- Version 1.0 of this gem supports the v2 API, documented in the link above. UCB now has v3 of the API - we'll skip version 2.0 of the gem and make version 3.0 support the v3 API.
5
+ The current version (3.x) is intended for v3 of the HCM API. If you need to access v2, you should use 1.0 of this gem.
6
6
 
7
7
  ## Installation
8
8
 
@@ -25,20 +25,24 @@ Configure your app with your API credentials from [UCB's API Central](https://ap
25
25
  Ucb::Hcm.configure do |hcm|
26
26
  hcm.app_id = "APP_ID"
27
27
  hcm.app_key = "APP_KEY"
28
- hcm.endpoint = "https://apis.berkeley.edu/dev/hr/v3"
28
+ hcm.endpoint = "https://apis.berkeley.edu/uat/hr/v3"
29
29
  end
30
30
 
31
31
  ## Usage
32
32
 
33
33
  :rotating_light: Note: Version 3.x of this gem only supports v3 of the API. :rotating_light:
34
34
 
35
+ See the [API documentation](https://api-central.berkeley.edu/api/14/interactive-docs) for details on the different endpoints and what they return.
36
+
37
+ Note that the main part of the returned payload (`["response"]`) is always an `Array`. It will often have only one element, but there could be more, even if you're not expecting them. To make certain you're getting all the data you asked for, be sure to iterate over the payload.
38
+
35
39
  ### Fetch Employees
36
40
 
37
41
  ```ruby
38
42
  client = Ucb::Hcm::Client.new
39
43
  response = client.get("/employees", { limit: 20, previous: 0, next: 20 })
40
44
 
41
- >> response.data
45
+ >> response.all
42
46
  => [
43
47
  {
44
48
  "identifiers"=> [
@@ -61,20 +65,22 @@ Configure your app with your API credentials from [UCB's API Central](https://ap
61
65
  client = Ucb::Hcm::Client.new
62
66
  response = client.get("/employees/10272831", {"id-type" => "hr-employee-id"})
63
67
 
64
- >> response.data
65
- => {
66
- "identifiers" => [
67
- {"type" => "campus-uid", "id" => "20108691"}, {"type" => "campus-solutions-id"}, {"type" => "student-id"}, {"type" => "hr-employee-id", "id" => "10272831"}, {"type" => "legacy-hr-employee-id"}, {"type" => "calnet-id"}
68
- ],
69
- "names" => [{
70
- "type "= >{"code" => "PRI", "description" => "Primary"},
71
- "familyName" => "Kumar",
72
- "givenName" => "Siri",
73
- "lastChangedBy" => {"id" => "10000499"},
74
- "fromDate" => "2018-10-01"
75
- }],
76
- ...
77
- }
68
+ >> response.all
69
+ => [
70
+ {
71
+ "identifiers" => [
72
+ {"type" => "campus-uid", "id" => "20108691"}, {"type" => "campus-solutions-id"}, {"type" => "student-id"}, {"type" => "hr-employee-id", "id" => "10272831"}, {"type" => "legacy-hr-employee-id"}, {"type" => "calnet-id"}
73
+ ],
74
+ "names" => [{
75
+ "type "= >{"code" => "PRI", "description" => "Primary"},
76
+ "familyName" => "Kumar",
77
+ "givenName" => "Siri",
78
+ "lastChangedBy" => {"id" => "10000499"},
79
+ "fromDate" => "2018-10-01"
80
+ }],
81
+ ...
82
+ }
83
+ ]
78
84
 
79
85
  ```
80
86
 
@@ -84,23 +90,165 @@ Configure your app with your API credentials from [UCB's API Central](https://ap
84
90
  client = Ucb::Hcm::Client.new
85
91
  response = client.get("/employees/10272831/jobs", {"id-type" => "hr-employee-id"})
86
92
 
87
- # The jobs api sometimes returns an array with the first element
88
- # containing the real job data and the second an empty hash :(.
89
- # For example: [{<real_data>}, {}]
90
- >> response.data.first["jobs"]
93
+ >> response.all
91
94
  => [
92
- {
93
- "number" => 0,
94
- "sequence" => 0,
95
- "type" => {
96
- "code" => "2",
97
- "description" => "Staff: Career"
98
- },
99
- ...
95
+ {
96
+ "identifiers" => [
97
+ {"type" => "campus-uid", "id" => "20108691"}, {"type" => "campus-solutions-id"}, {"type" => "student-id"}, {"type" => "hr-employee-id", "id" => "10272831"}, {"type" => "legacy-hr-employee-id"}, {"type" => "calnet-id"}
98
+ ],
99
+ "jobs" => [
100
+ {
101
+ "number" => 0,
102
+ "sequence" => 0,
103
+ "type" => {
104
+ "code" => "2",
105
+ "description" => "Staff: Career"
106
+ },
107
+ ...
108
+ }
109
+ ...
110
+ ]
100
111
  }
101
112
  ]
102
113
  ```
103
114
 
115
+ ## Parsing The Response
116
+
117
+ All of the API calls return an instance of `Ucb::Hcm::Response`. There are various ways to get to the underlying data, depending on your needs.
118
+
119
+ ### Checking The Response Status
120
+
121
+ You can find out if the call was successful by checking the return code, or just calling `success?`:
122
+
123
+ ```ruby
124
+ response = client.get("/employees/10272831/jobs", {"id-type" => "hr-employee-id"})
125
+ response.code
126
+ => 200
127
+
128
+ response.success?
129
+ => true
130
+ ```
131
+
132
+ Any status code other than `200` will cause `success?` to return false
133
+
134
+ ### Extracting The Response Data: The Low-Level Approach
135
+
136
+ Calling `raw_response` will give the response object of the underlying HTTP library (currently `HTTParty::Response`). That object behaves like a hash, so you can access any part of the payload directly:
137
+
138
+ ```ruby
139
+ response = client.get("/employees/10272831/jobs", {"id-type" => "hr-employee-id"})
140
+ response.raw_response["source"]
141
+ => "UCB-HR-PATH-DB"
142
+ ```
143
+
144
+ The `all` method will return the contents of the `response` portion of the payload - this is where the meat of the data is usually found. The `response` node is always an `Array` of `Hash` objects even if one element is expected:
145
+
146
+ ```ruby
147
+ response = client.get("/employees/10272831/jobs", {"id-type" => "hr-employee-id"})
148
+
149
+ response.all
150
+ => [
151
+ {
152
+ "identifiers" => [
153
+ {"type" => "campus-uid", "id" => "20108691"}, {"type" => "campus-solutions-id"}, {"type" => "student-id"}, {"type" => "hr-employee-id", "id" => "10272831"}, {"type" => "legacy-hr-employee-id"}, {"type" => "calnet-id"}
154
+ ],
155
+ "jobs" => [
156
+ {
157
+ "number" => 0,
158
+ "sequence" => 0,
159
+ "type" => {
160
+ "code" => "2",
161
+ "description" => "Staff: Career"
162
+ },
163
+ ...
164
+ }
165
+ ...
166
+ ]
167
+ }
168
+ ]
169
+
170
+ response.all.first["jobs"].first["number"]
171
+ => 0
172
+ ```
173
+
174
+ You can also iterate through each of the response items with the `each` method:
175
+
176
+ ```ruby
177
+ response = client.get("/employees/10272831/jobs", {"id-type" => "hr-employee-id"})
178
+
179
+ response.each do |item|
180
+ puts item["jobs"].first["number"]
181
+ end
182
+ => 0
183
+ ```
184
+
185
+ ### Extracting The Response Data Using DataFetcher
186
+
187
+ The payloads from the HCM API are often very large and deeply nested, so navigating them as raw hashes can be a little cumbersome. To help get around this, it's possible to read the response data using the `Ucb::Hcm::DataFetcher` object.
188
+
189
+ #### DataFetcher Overview
190
+
191
+ A `DataFetcher` takes a hash when initialized and it can then be safely navigated using dot notation:
192
+
193
+ ```ruby
194
+ data = {foo: "bar", baz: { bam: "bang" }}
195
+ fetcher = Ucb::Hcm::DataFetcher.new(data)
196
+ fetcher.baz.bam.value
197
+ => "bang"
198
+ ```
199
+
200
+ You need to call `value` when you've reached the node you're trying to read.
201
+
202
+ If you hit a node that contains an array, you can use the usual `Array` methods to navigate through them:
203
+
204
+ ```ruby
205
+ data = {foo: "bar", baz: [{ bam: "bang" }]} # baz is an Array this time
206
+ fetcher = Ucb::Hcm::DataFetcher.new(data)
207
+ fetcher.baz.first.bam.value
208
+ => "bang"
209
+
210
+ fetcher.baz[0].bam.value
211
+ => "bang"
212
+ ```
213
+
214
+ Apart from the conciseness of the syntax, `DataFetcher` has another advantage over digging through hashes, as it will handle nodes that are `nil` without raising an exception:
215
+
216
+ ```ruby
217
+ fetcher.baz.this_is_not_a_real_node.bam.value
218
+ => nil
219
+ ```
220
+
221
+ If any part of the traversal yields `nil`, the call to `value` will return `nil` but you can change this by passing a different default to the `value` call:
222
+
223
+ ```ruby
224
+ fetcher.baz.this_is_not_a_real_node.bam.value("oops!")
225
+ => "oops!"
226
+ ```
227
+
228
+ #### Getting DataFetchers From A Response
229
+
230
+ You can access an API payload with `DataFetcher` objects by calling `all_fetchers` or `each_fetcher` on a `Response` instance. These are equivalent to `all` and `each` (described earlier) but they return `DataFetcher` instances rather than hashes:
231
+
232
+ ```ruby
233
+ response = client.get("/employees/10272831/jobs", {"id-type" => "hr-employee-id"})
234
+
235
+ response.all.first.class
236
+ => Hash
237
+
238
+ response.all_fetchers.first.class
239
+ => Ucb::Hcm::DataFetcher
240
+
241
+ response.each do |item|
242
+ p item.class
243
+ end
244
+ => Hash
245
+
246
+ response.each_fetcher do |item|
247
+ p item.class
248
+ end
249
+ => Ucb::Hcm::DataFetcher
250
+ ```
251
+
104
252
  ## Contributing
105
253
 
106
254
  1. Fork it ( https://github.com/[my-github-username]/ucb-hcm/fork )
@@ -5,7 +5,7 @@ module Ucb
5
5
  VALID_OPTIONS_KEYS = [:app_id, :app_key, :format].freeze
6
6
  VALID_CONFIG_KEYS = VALID_CONNECTION_KEYS + VALID_OPTIONS_KEYS
7
7
 
8
- DEFAULT_ENDPOINT = 'https://apis.berkeley.edu/test/hr/v3'
8
+ DEFAULT_ENDPOINT = 'https://apis.berkeley.edu/uat/hr/v3'
9
9
  DEFAULT_METHOD = :get
10
10
  DEFAULT_USER_AGENT = "Ucb::Hcm API Ruby Gem #{Ucb::Hcm::VERSION}".freeze
11
11
 
@@ -0,0 +1,72 @@
1
+ require "hashie"
2
+
3
+ # This is a utility class that makes it easy to grab deeply nested values safely. Given a Hash like:
4
+ # fetcher = DataFetcher.new(
5
+ # {
6
+ # "names"=>[{
7
+ # "type"=>{"code"=>"PRI", "description"=>"Primary"},
8
+ # "familyName"=>"Montalban",
9
+ # "givenName"=>"Ricardo",
10
+ # }],
11
+ # }
12
+ # )
13
+ #
14
+ # you could access the name code like this:
15
+ # fetcher.names.first.type.code.value
16
+ #
17
+ # If any part of the path is missing or nil, no exception will be raised, and the value() call
18
+ # will return nil.
19
+ #
20
+ # By default, you'll get nil back if any part of call chain is missing or nil. If you want a different
21
+ # fallback value, you can pass that to value(). This would return "not found":
22
+ # fetcher.thisIsNotInTheResponseHash.first.type.code.value("not found")
23
+ #
24
+ # DataFetcher will accept node names in camel case or snake case. The following are equivalent:
25
+ # fetcher.names.first.familyName.value
26
+ # fetcher.names.first.family_name.value
27
+ #
28
+
29
+ module Ucb
30
+ module Hcm
31
+ class DataFetcher
32
+ attr_reader :current_value
33
+
34
+ def initialize(raw_data)
35
+ @original_value = raw_data
36
+ reset_current_value(raw_data)
37
+ end
38
+
39
+ def value(default = nil)
40
+ (@current_value || default).tap do
41
+ reset_current_value(@original_value)
42
+ end
43
+ end
44
+
45
+ # This is where the fetching logic happens - each time we're given a value to find, we store
46
+ # that value as @current_value and wait for the next call. If we ever get nil, then none of the
47
+ # subsequent calls matter, so we short-circuit them.
48
+ def method_missing(method, *args)
49
+ return self if @current_value.nil?
50
+
51
+ normalized_method = camelize(method)
52
+ if @current_value.respond_to?(normalized_method)
53
+ @current_value = @current_value.send(normalized_method, *args)
54
+ else
55
+ @current_value = nil
56
+ end
57
+ self
58
+ end
59
+
60
+ private
61
+
62
+ def camelize(str)
63
+ str.to_s.gsub(/_([a-z])/) { "#{$1.upcase}" }
64
+ end
65
+
66
+ def reset_current_value(hash)
67
+ @current_value = Hashie::Mash.new(hash)
68
+ end
69
+
70
+ end
71
+ end
72
+ end
@@ -1,25 +1,53 @@
1
+ require "hashie"
2
+ require_relative "data_fetcher"
3
+
1
4
  module Ucb
2
5
  module Hcm
3
6
  class Response
4
7
  extend Forwardable
5
8
 
6
- attr_reader :raw_response, :data_fetcher
9
+ attr_reader :raw_response
7
10
 
8
- def_delegators :response, :code
11
+ def_delegators :raw_response, :code
9
12
 
10
13
  def initialize(raw_response)
11
14
  @raw_response = raw_response
12
15
  end
13
16
 
14
- def response
17
+ def success?
18
+ code == 200
19
+ end
20
+
21
+ def raw
15
22
  raw_response
16
23
  end
17
24
 
18
- def success?
19
- code == 200
25
+ def all
26
+ raw_response&.fetch("response", []) || []
27
+ end
28
+
29
+ def all_fetchers
30
+ all.map { |item| Ucb::Hcm::DataFetcher.new(item) }
31
+ end
32
+
33
+ def each
34
+ all.each { |item| yield item }
35
+ end
36
+
37
+ def each_fetcher
38
+ all.each { |item| yield Ucb::Hcm::DataFetcher.new(item) }
39
+ end
40
+
41
+ ##############################
42
+ # deprecations
43
+
44
+ def response
45
+ warn "Ucb::Hcm#response is deprecated and will be removed in future versions - use #raw_response instead"
46
+ raw_response
20
47
  end
21
48
 
22
49
  def data
50
+ warn "Ucb::Hcm#data is deprecated and will be removed in future versions - use #raw_response['response'] instead"
23
51
  if raw_response["response"]&.count == 1
24
52
  raw_response["response"]&.first
25
53
  else
@@ -27,9 +55,6 @@ module Ucb
27
55
  end
28
56
  end
29
57
 
30
- def data_fetcher=(block)
31
- @data_fetcher = block
32
- end
33
58
  end
34
59
  end
35
- end
60
+ end
@@ -1,5 +1,5 @@
1
1
  module Ucb
2
2
  module Hcm
3
- VERSION = "3.0"
3
+ VERSION = "3.1.0"
4
4
  end
5
5
  end
data/spec/spec_helper.rb CHANGED
@@ -1,3 +1,63 @@
1
1
  # spec/spec_helper.rb
2
2
  require 'ucb/hcm'
3
3
  require 'support/test_credentials'
4
+ require 'support/vcr'
5
+
6
+ TEST_RESPONSE = {
7
+ "source"=>"UCB-HR-PATH-DB",
8
+ "correlationId"=>"2cf1e603-95cb-405c-a0ad-535b468e0a11",
9
+ "response"=>[
10
+ {
11
+ "identifiers"=>[
12
+ {"type"=>"hr-employee-id", "id"=>"1234567"},
13
+ {"type"=>"campus-uid", "id"=>"12346"},
14
+ ],
15
+ "names"=>[{
16
+ "type"=>{"code"=>"PRI", "description"=>"Primary"},
17
+ "familyName"=>"Montalban",
18
+ "givenName"=>"Ricardo",
19
+ "middleName"=>"Roark",
20
+ "lastChangedBy"=>{"id"=>"UC_AROY"},
21
+ "fromDate"=>"2015-08-01"
22
+ }],
23
+ "gender"=>{
24
+ "genderOfRecord"=>{
25
+ "code"=>"M", "description"=>"Male", "fromDate"=>"2015-08-01"
26
+ }
27
+ },
28
+ "emails"=>[{
29
+ "type"=>{
30
+ "code"=>"BUSN", "description"=>"Business"
31
+ },
32
+ "emailAddress"=>"rmontalban@berkeley.edu",
33
+ "primary"=>true
34
+ }]
35
+ },
36
+ {
37
+ "identifiers"=>[
38
+ {"type"=>"hr-employee-id", "id"=>"9876543"},
39
+ {"type"=>"campus-uid", "id"=>"9876"},
40
+ ],
41
+ "names"=>[{
42
+ "type"=>{"code"=>"PRI", "description"=>"Primary"},
43
+ "familyName"=>"McCoy",
44
+ "givenName"=>"Julie",
45
+ "lastChangedBy"=>{"id"=>"UC_AROY"},
46
+ "fromDate"=>"2014-08-01"
47
+ }],
48
+ "gender"=>{
49
+ "genderOfRecord"=>{
50
+ "code"=>"F", "description"=>"Female", "fromDate"=>"2015-08-01"
51
+ }
52
+ },
53
+ "emails"=>[{
54
+ "type"=>{
55
+ "code"=>"BUSN", "description"=>"Business"
56
+ },
57
+ "emailAddress"=>"jmccoy@berkeley.edu",
58
+ "primary"=>true
59
+ }]
60
+ }
61
+ ]
62
+ }
63
+
@@ -0,0 +1,43 @@
1
+ require 'openssl'
2
+ require 'vcr'
3
+
4
+ class VcrEncryptor
5
+
6
+ def encrypt(string)
7
+ cipher = init_cipher(:encrypt)
8
+ cipher.update(string) + cipher.final
9
+ end
10
+
11
+ def decrypt(string)
12
+ decipher = init_cipher(:decrypt)
13
+ decipher.update(string) + decipher.final
14
+ end
15
+
16
+ private
17
+
18
+ def init_cipher(type)
19
+ OpenSSL::Cipher::AES256.new(:CBC).tap do |cipher|
20
+ type == :encrypt ? cipher.encrypt : cipher.decrypt
21
+ cipher.key = ENV['RAILS_MASTER_KEY']
22
+ end
23
+ end
24
+
25
+ end
26
+
27
+ encrypted_serializer = Object.new
28
+ class << encrypted_serializer
29
+ def file_extension; "enc"; end
30
+ def serialize(hash); VcrEncryptor.new.encrypt(hash.to_json); end
31
+ def deserialize(string); JSON.parse(VcrEncryptor.new.decrypt(string)); end
32
+ end
33
+
34
+
35
+ VCR.configure do |c|
36
+ c.cassette_library_dir = "spec/cassettes"
37
+ c.cassette_serializers[:enc] = encrypted_serializer
38
+ c.default_cassette_options = { :serialize_with => :enc }
39
+ c.hook_into :webmock
40
+ c.configure_rspec_metadata!
41
+ c.ignore_localhost = true
42
+ end
43
+
@@ -7,54 +7,55 @@ RSpec.describe 'api' do
7
7
  end
8
8
 
9
9
  describe "#get" do
10
- describe "GET /employees/:id" do
10
+ describe "GET /employees/:id", :vcr do
11
11
  it "returns employee data w/ valid id & legacy-hr-employee-type" do
12
- response = @client.get("/employees/10135532", {"id-type" => "hr-employee-id"})
12
+ response = @client.get("/employees/10161331", {"id-type" => "hr-employee-id"})
13
13
 
14
14
  expect(response.success?).to eq true
15
- expect(response.data["identifiers"].first.has_key?("type")).to eq true
15
+ data = response.all.first
16
+ expect(data["identifiers"].first.has_key?("type")).to eq true
16
17
 
17
- names = response.data["names"]
18
+ names = data["names"]
18
19
  expect(names.empty? || names.first.has_key?("familyName")).to eq true
19
20
 
20
- addresses = response.data["addresses"]
21
+ addresses = data["addresses"]
21
22
  expect(addresses.empty? || addresses.first.has_key?("address1")).to eq true
22
23
 
23
- emails = response.data["emails"]
24
+ emails = data["emails"]
24
25
  expect(emails.empty? || emails.first.has_key?("emailAddress")).to eq true
25
26
 
26
- phones = response.data["phones"]
27
+ phones = data["phones"]
27
28
  expect(phones.empty? || phones.first.has_key?("number")).to eq true
28
29
  end
29
30
 
30
- # this used to return 404 - now it just seems to default to hr-employee-id
31
+ # this should return 404
31
32
  it "succeeds with invalid id-type" do
32
33
  response = @client.get("/employees/10135532", {"id-type" => "invalid-type"})
33
34
 
34
- expect(response.success?).to be true
35
+ expect(response.success?).to be false
35
36
  end
36
37
 
37
- # this used to return 500 - now it returns 200 with an empty response
38
+ # this should return 400
38
39
  it "returns empty response if id is invalid" do
39
40
  response = @client.get("/employees/invalidId", {"id-type" => "legacy-hr-employee-id"})
40
41
 
41
- expect(response.success?).to be true
42
- expect(response.data).to be_nil
42
+ expect(response.success?).to be false
43
+ expect(response.all.first).to be_nil
43
44
  end
44
45
  end
45
46
 
46
- describe "fetch employee jobs" do
47
+ describe "fetch employee jobs", :vcr do
47
48
  it "returns all an employees jobs" do
48
- response = @client.get("/employees/010789601/jobs", {"id-type" => "legacy-hr-employee-id"})
49
+ response = @client.get("/employees/10161331/jobs", {"id-type" => "hr-employee-id"})
49
50
 
50
51
  expect(response.success?).to eq true
51
52
 
52
- job = response.data["jobs"].first
53
+ job = response.all.first["jobs"].first
53
54
  expect(job.has_key?("compensation")).to eq true
54
55
  end
55
56
  end
56
57
 
57
- describe "GET /employees" do
58
+ describe "GET /employees", :vcr do
58
59
  it "supports pagination" do
59
60
  params = {
60
61
  limit: 5,
@@ -64,7 +65,7 @@ RSpec.describe 'api' do
64
65
 
65
66
  response = @client.get("/employees", params)
66
67
 
67
- expect(response.data.count).to eq 5
68
+ expect(response.all.count).to eq 5
68
69
  end
69
70
  end
70
71
  end
@@ -0,0 +1,41 @@
1
+ require "spec_helper"
2
+ require "pry"
3
+
4
+ RSpec.describe "response" do
5
+ let(:fetcher) { Ucb::Hcm::DataFetcher.new(TEST_RESPONSE["response"].first) } # TEST_RESPONSE defined in spec_helper
6
+
7
+ it "can respond to dot notation" do
8
+ expect(fetcher.gender.genderOfRecord.code.value).to eq("M")
9
+ end
10
+
11
+ it "returns nil if any part of the call chain cannot be found in the response" do
12
+ expect(fetcher.notInTheResponse.genderOfRecord.code.value).to eq(nil)
13
+ end
14
+
15
+ it "returns the specified default value if any part of the call chain cannot be found in the response" do
16
+ expect(fetcher.notInTheResponse.genderOfRecord.code.value("not found")).to eq("not found")
17
+ end
18
+
19
+ it "can respond to snake case as well as camel case" do
20
+ expect(fetcher.gender.gender_of_record.from_date.value).to eq("2015-08-01")
21
+ end
22
+
23
+ it "can handle arrays in the call chain" do
24
+ expect(fetcher.names.first.type.code.value).to eq("PRI")
25
+ end
26
+
27
+ it "can be reused" do
28
+ fetcher.names.first.type.code.value
29
+ expect(fetcher.names.first.type.code.value).to eq("PRI")
30
+ end
31
+
32
+ it "can handle arrays with bracket notation" do
33
+ expect(fetcher.names[0].type.code.value).to eq("PRI")
34
+ end
35
+
36
+ it "can respond to accessing the data as a hash" do
37
+ expect(fetcher["names"].first["type"]["code"].value).to eq("PRI")
38
+ end
39
+
40
+ end
41
+
@@ -26,8 +26,8 @@ describe 'request' do
26
26
  end
27
27
 
28
28
  describe 'get' do
29
- it 'performs a get request' do
30
- response = @client.get("/employees/010789601", {"id-type" => "legacy-hr-employee-id"})
29
+ it 'performs a get request', :vcr do
30
+ response = @client.get("/employees/10161331", {"id-type" => "hr-employee-id"})
31
31
  expect(response).to be_an_instance_of Ucb::Hcm::Response
32
32
  expect(response.success?).to eq true
33
33
  end
@@ -0,0 +1,61 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe "response" do
4
+ let(:raw_response) { TEST_RESPONSE } # TEST_RESPONSE defined in spec_helper
5
+ let(:response) {
6
+ allow(raw_response).to receive(:code) { "200" }
7
+ Ucb::Hcm::Response.new(raw_response)
8
+ }
9
+
10
+ it "returns the HTTP status code" do
11
+ expect(response.code).to eq("200")
12
+ end
13
+
14
+ it "returns the underlying JSON data" do
15
+ expect(response.all).to eq(TEST_RESPONSE["response"])
16
+ end
17
+
18
+ it "can iterate through the responses" do
19
+ count = 0
20
+ response.each do |item|
21
+ expect(item["names"]).to be_a(Array)
22
+ count += 1
23
+ end
24
+ expect(count).to eq(2)
25
+ end
26
+
27
+ it "returns an empty list when calling #all if the response was empty" do
28
+ response = Ucb::Hcm::Response.new(nil)
29
+ expect(response.all).to be_empty
30
+ end
31
+
32
+ it "returns an empty list when calling #all if the response does not have a 'response' element" do
33
+ response = Ucb::Hcm::Response.new({foo: "bar"})
34
+ expect(response.all).to be_empty
35
+ end
36
+
37
+ it "returns a working iterator if the response was empty" do
38
+ response = Ucb::Hcm::Response.new({foo: "bar"})
39
+ count = 0
40
+ response.each do |item|
41
+ count += 1
42
+ end
43
+ expect(count).to eq(0)
44
+ end
45
+
46
+ it "returns the underlying JSON data wrapped in DataFetcher instances" do
47
+ response_fetchers = TEST_RESPONSE["response"].map { |item| Ucb::Hcm::DataFetcher.new(item) }
48
+ expect(response.all_fetchers.map(&:class)).to eq(response_fetchers.map(&:class))
49
+ end
50
+
51
+ it "can interate through a list of DataFetcher instances" do
52
+ count = 0
53
+ response.each_fetcher do |item|
54
+ expect(item).to be_a(Ucb::Hcm::DataFetcher)
55
+ count += 1
56
+ end
57
+ expect(count).to eq(2)
58
+ end
59
+
60
+ end
61
+
data/ucb-hcm.gemspec CHANGED
@@ -20,10 +20,14 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.add_runtime_dependency "activesupport"
22
22
  spec.add_runtime_dependency "httparty", "~> 0.13.1"
23
+ spec.add_runtime_dependency "hashie"
23
24
  spec.add_development_dependency "bundler", "~> 1.6"
24
25
  spec.add_development_dependency "rake"
25
- spec.add_development_dependency 'pry'
26
- spec.add_development_dependency 'byebug'
27
- spec.add_development_dependency 'rspec-core'
28
- spec.add_development_dependency 'rspec-expectations'
26
+ spec.add_development_dependency "pry"
27
+ spec.add_development_dependency "byebug"
28
+ spec.add_development_dependency "rspec-core"
29
+ spec.add_development_dependency "rspec-expectations"
30
+ spec.add_development_dependency "rspec-mocks"
31
+ spec.add_development_dependency "vcr"
32
+ spec.add_development_dependency "webmock"
29
33
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ucb-hcm
3
3
  version: !ruby/object:Gem::Version
4
- version: '3.0'
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter Philips
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2019-02-07 00:00:00.000000000 Z
13
+ date: 2019-03-21 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activesupport
@@ -40,6 +40,20 @@ dependencies:
40
40
  - - "~>"
41
41
  - !ruby/object:Gem::Version
42
42
  version: 0.13.1
43
+ - !ruby/object:Gem::Dependency
44
+ name: hashie
45
+ requirement: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ type: :runtime
51
+ prerelease: false
52
+ version_requirements: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
43
57
  - !ruby/object:Gem::Dependency
44
58
  name: bundler
45
59
  requirement: !ruby/object:Gem::Requirement
@@ -124,6 +138,48 @@ dependencies:
124
138
  - - ">="
125
139
  - !ruby/object:Gem::Version
126
140
  version: '0'
141
+ - !ruby/object:Gem::Dependency
142
+ name: rspec-mocks
143
+ requirement: !ruby/object:Gem::Requirement
144
+ requirements:
145
+ - - ">="
146
+ - !ruby/object:Gem::Version
147
+ version: '0'
148
+ type: :development
149
+ prerelease: false
150
+ version_requirements: !ruby/object:Gem::Requirement
151
+ requirements:
152
+ - - ">="
153
+ - !ruby/object:Gem::Version
154
+ version: '0'
155
+ - !ruby/object:Gem::Dependency
156
+ name: vcr
157
+ requirement: !ruby/object:Gem::Requirement
158
+ requirements:
159
+ - - ">="
160
+ - !ruby/object:Gem::Version
161
+ version: '0'
162
+ type: :development
163
+ prerelease: false
164
+ version_requirements: !ruby/object:Gem::Requirement
165
+ requirements:
166
+ - - ">="
167
+ - !ruby/object:Gem::Version
168
+ version: '0'
169
+ - !ruby/object:Gem::Dependency
170
+ name: webmock
171
+ requirement: !ruby/object:Gem::Requirement
172
+ requirements:
173
+ - - ">="
174
+ - !ruby/object:Gem::Version
175
+ version: '0'
176
+ type: :development
177
+ prerelease: false
178
+ version_requirements: !ruby/object:Gem::Requirement
179
+ requirements:
180
+ - - ">="
181
+ - !ruby/object:Gem::Version
182
+ version: '0'
127
183
  description: Lightweight wrapper for the UCB HCM Api - https://developer.berkeley.edu/apidocs/employee
128
184
  email:
129
185
  - hello@infinite.red
@@ -135,6 +191,7 @@ files:
135
191
  - ".gitignore"
136
192
  - ".ruby-version"
137
193
  - ".travis.yml"
194
+ - CHANGELOG.md
138
195
  - Gemfile
139
196
  - LICENSE.txt
140
197
  - README.md
@@ -143,15 +200,25 @@ files:
143
200
  - lib/ucb/hcm/api.rb
144
201
  - lib/ucb/hcm/client.rb
145
202
  - lib/ucb/hcm/configuration.rb
203
+ - lib/ucb/hcm/data_fetcher.rb
146
204
  - lib/ucb/hcm/request.rb
147
205
  - lib/ucb/hcm/response.rb
148
206
  - lib/ucb/hcm/version.rb
207
+ - spec/cassettes/api/_get/GET_/employees/_id/returns_employee_data_w/_valid_id_legacy-hr-employee-type.enc
208
+ - spec/cassettes/api/_get/GET_/employees/_id/returns_empty_response_if_id_is_invalid.enc
209
+ - spec/cassettes/api/_get/GET_/employees/_id/succeeds_with_invalid_id-type.enc
210
+ - spec/cassettes/api/_get/GET_/employees/supports_pagination.enc
211
+ - spec/cassettes/api/_get/fetch_employee_jobs/returns_all_an_employees_jobs.enc
212
+ - spec/cassettes/request/get/performs_a_get_request.enc
149
213
  - spec/spec_helper.rb
150
214
  - spec/support/test_credentials.rb
215
+ - spec/support/vcr.rb
151
216
  - spec/ucb/hcm/api_spec.rb
152
217
  - spec/ucb/hcm/configuration_spec.rb
218
+ - spec/ucb/hcm/data_fetcher_spec.rb
153
219
  - spec/ucb/hcm/hcm_spec.rb
154
220
  - spec/ucb/hcm/request_spec.rb
221
+ - spec/ucb/hcm/response_spec.rb
155
222
  - ucb-hcm.gemspec
156
223
  homepage: https://infinite.red
157
224
  licenses:
@@ -178,9 +245,18 @@ signing_key:
178
245
  specification_version: 4
179
246
  summary: Ucb HCM - Human Capital Management API gem
180
247
  test_files:
248
+ - spec/cassettes/api/_get/GET_/employees/_id/returns_employee_data_w/_valid_id_legacy-hr-employee-type.enc
249
+ - spec/cassettes/api/_get/GET_/employees/_id/returns_empty_response_if_id_is_invalid.enc
250
+ - spec/cassettes/api/_get/GET_/employees/_id/succeeds_with_invalid_id-type.enc
251
+ - spec/cassettes/api/_get/GET_/employees/supports_pagination.enc
252
+ - spec/cassettes/api/_get/fetch_employee_jobs/returns_all_an_employees_jobs.enc
253
+ - spec/cassettes/request/get/performs_a_get_request.enc
181
254
  - spec/spec_helper.rb
182
255
  - spec/support/test_credentials.rb
256
+ - spec/support/vcr.rb
183
257
  - spec/ucb/hcm/api_spec.rb
184
258
  - spec/ucb/hcm/configuration_spec.rb
259
+ - spec/ucb/hcm/data_fetcher_spec.rb
185
260
  - spec/ucb/hcm/hcm_spec.rb
186
261
  - spec/ucb/hcm/request_spec.rb
262
+ - spec/ucb/hcm/response_spec.rb