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 +4 -4
- data/.travis.yml +8 -6
- data/CHANGELOG.md +14 -0
- data/README.md +177 -29
- data/lib/ucb/hcm/configuration.rb +1 -1
- data/lib/ucb/hcm/data_fetcher.rb +72 -0
- data/lib/ucb/hcm/response.rb +34 -9
- data/lib/ucb/hcm/version.rb +1 -1
- data/spec/cassettes/api/_get/GET_/employees/_id/returns_employee_data_w/_valid_id_legacy-hr-employee-type.enc +0 -0
- data/spec/cassettes/api/_get/GET_/employees/_id/returns_empty_response_if_id_is_invalid.enc +0 -0
- data/spec/cassettes/api/_get/GET_/employees/_id/succeeds_with_invalid_id-type.enc +0 -0
- data/spec/cassettes/api/_get/GET_/employees/supports_pagination.enc +0 -0
- data/spec/cassettes/api/_get/fetch_employee_jobs/returns_all_an_employees_jobs.enc +0 -0
- data/spec/cassettes/request/get/performs_a_get_request.enc +0 -0
- data/spec/spec_helper.rb +60 -0
- data/spec/support/vcr.rb +43 -0
- data/spec/ucb/hcm/api_spec.rb +18 -17
- data/spec/ucb/hcm/data_fetcher_spec.rb +41 -0
- data/spec/ucb/hcm/request_spec.rb +2 -2
- data/spec/ucb/hcm/response_spec.rb +61 -0
- data/ucb-hcm.gemspec +8 -4
- metadata +78 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9dd61ba145d30df5ca3bebc81963f47dcb2ae2ff3216034c088f91ce8b76459f
|
4
|
+
data.tar.gz: f4037ac0a767966befa1b6f2e500167caa294afa70cf5f3f6627ed9b8ee6b2e2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
6
|
+
- 2.5.1
|
7
7
|
install: true
|
8
8
|
script:
|
9
|
-
|
9
|
+
- bin/setup
|
10
10
|
before_deploy:
|
11
|
-
|
11
|
+
- git tag $TRAVIS_BRANCH-$TRAVIS_BUILD_NUMBER
|
12
12
|
cache:
|
13
13
|
directories:
|
14
|
-
|
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
|
-
|
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/
|
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.
|
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.
|
65
|
-
=>
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
"
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
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
|
-
|
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
|
-
|
94
|
-
|
95
|
-
|
96
|
-
"
|
97
|
-
|
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/
|
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
|
data/lib/ucb/hcm/response.rb
CHANGED
@@ -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
|
9
|
+
attr_reader :raw_response
|
7
10
|
|
8
|
-
def_delegators :
|
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
|
17
|
+
def success?
|
18
|
+
code == 200
|
19
|
+
end
|
20
|
+
|
21
|
+
def raw
|
15
22
|
raw_response
|
16
23
|
end
|
17
24
|
|
18
|
-
def
|
19
|
-
|
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
|
data/lib/ucb/hcm/version.rb
CHANGED
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
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
|
+
|
data/spec/support/vcr.rb
ADDED
@@ -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
|
+
|
data/spec/ucb/hcm/api_spec.rb
CHANGED
@@ -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/
|
12
|
+
response = @client.get("/employees/10161331", {"id-type" => "hr-employee-id"})
|
13
13
|
|
14
14
|
expect(response.success?).to eq true
|
15
|
-
|
15
|
+
data = response.all.first
|
16
|
+
expect(data["identifiers"].first.has_key?("type")).to eq true
|
16
17
|
|
17
|
-
names =
|
18
|
+
names = data["names"]
|
18
19
|
expect(names.empty? || names.first.has_key?("familyName")).to eq true
|
19
20
|
|
20
|
-
addresses =
|
21
|
+
addresses = data["addresses"]
|
21
22
|
expect(addresses.empty? || addresses.first.has_key?("address1")).to eq true
|
22
23
|
|
23
|
-
emails =
|
24
|
+
emails = data["emails"]
|
24
25
|
expect(emails.empty? || emails.first.has_key?("emailAddress")).to eq true
|
25
26
|
|
26
|
-
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
|
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
|
35
|
+
expect(response.success?).to be false
|
35
36
|
end
|
36
37
|
|
37
|
-
# this
|
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
|
42
|
-
expect(response.
|
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/
|
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.
|
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.
|
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/
|
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
|
26
|
-
spec.add_development_dependency
|
27
|
-
spec.add_development_dependency
|
28
|
-
spec.add_development_dependency
|
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:
|
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-
|
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
|