fieldview 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +52 -0
- data/Gemfile +16 -0
- data/Gemfile.lock +39 -0
- data/LICENSE +21 -0
- data/README.md +57 -0
- data/Rakefile +8 -0
- data/fieldview.gemspec +21 -0
- data/lib/fieldview/auth_token.rb +111 -0
- data/lib/fieldview/boundary.rb +28 -0
- data/lib/fieldview/errors.rb +64 -0
- data/lib/fieldview/field.rb +22 -0
- data/lib/fieldview/fields.rb +34 -0
- data/lib/fieldview/fieldview_response.rb +24 -0
- data/lib/fieldview/list_object.rb +43 -0
- data/lib/fieldview/util.rb +7 -0
- data/lib/fieldview.rb +130 -0
- data/spec/fixtures.json +141 -0
- data/test/api_fixtures.rb +26 -0
- data/test/test_auth_token.rb +91 -0
- data/test/test_boundary.rb +17 -0
- data/test/test_field.rb +26 -0
- data/test/test_fields.rb +70 -0
- data/test/test_fieldview.rb +46 -0
- data/test/test_helper.rb +52 -0
- data/test/test_list_object.rb +32 -0
- metadata +91 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c291877633d7f4126668164ee7cc60c7c5c67bac
|
4
|
+
data.tar.gz: df16497405345a46fab2c65066eec659ffc074b7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 883b9f63fcaf3af60958bc8407660a76608afca697fa043afae906240d6c7f2f36ed3116dea2de6c76630c3e4ba82effe08d6d11ab3e349c4cb03b962eae052d
|
7
|
+
data.tar.gz: 8d027e67cdabf34fd66a7284196c34533d82b2f002df8826b2d91b8005ae4246fb8632225f5b67041dd518c465a4826244f5e5eabc9e7a2d6927acb91d63808d
|
data/.gitignore
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
*agribotix*
|
2
|
+
*.gem
|
3
|
+
*.rbc
|
4
|
+
*.byebug*
|
5
|
+
/.config
|
6
|
+
/coverage/
|
7
|
+
/InstalledFiles
|
8
|
+
/pkg/
|
9
|
+
/spec/reports/
|
10
|
+
/spec/examples.txt
|
11
|
+
/test/tmp/
|
12
|
+
/test/version_tmp/
|
13
|
+
/tmp/
|
14
|
+
|
15
|
+
# Used by dotenv library to load environment variables.
|
16
|
+
# .env
|
17
|
+
|
18
|
+
## Specific to RubyMotion:
|
19
|
+
.dat*
|
20
|
+
.repl_history
|
21
|
+
build/
|
22
|
+
*.bridgesupport
|
23
|
+
build-iPhoneOS/
|
24
|
+
build-iPhoneSimulator/
|
25
|
+
|
26
|
+
## Specific to RubyMotion (use of CocoaPods):
|
27
|
+
#
|
28
|
+
# We recommend against adding the Pods directory to your .gitignore. However
|
29
|
+
# you should judge for yourself, the pros and cons are mentioned at:
|
30
|
+
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
31
|
+
#
|
32
|
+
# vendor/Pods/
|
33
|
+
|
34
|
+
## Documentation cache and generated files:
|
35
|
+
/.yardoc/
|
36
|
+
/_yardoc/
|
37
|
+
/doc/
|
38
|
+
/rdoc/
|
39
|
+
|
40
|
+
## Environment normalization:
|
41
|
+
/.bundle/
|
42
|
+
/vendor/bundle
|
43
|
+
/lib/bundler/man/
|
44
|
+
|
45
|
+
# for a library or gem, you might want to ignore these files since the code is
|
46
|
+
# intended to run in multiple environments; otherwise, check them in:
|
47
|
+
# Gemfile.lock
|
48
|
+
# .ruby-version
|
49
|
+
# .ruby-gemset
|
50
|
+
|
51
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
52
|
+
.rvmrc
|
data/Gemfile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
source "https://rubygems.org"
|
2
|
+
|
3
|
+
gemspec
|
4
|
+
|
5
|
+
group :development do
|
6
|
+
gem 'minitest'
|
7
|
+
gem 'webmock'
|
8
|
+
gem 'rake'
|
9
|
+
platforms :mri do
|
10
|
+
# to avoid problems, bring Byebug in on just versions of Ruby under which
|
11
|
+
# it's known to work well
|
12
|
+
if Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new('2.0.0')
|
13
|
+
gem 'byebug'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
fieldview (0.0.0)
|
5
|
+
faraday (~> 0.9)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
addressable (2.5.1)
|
11
|
+
public_suffix (~> 2.0, >= 2.0.2)
|
12
|
+
byebug (9.0.6)
|
13
|
+
crack (0.4.3)
|
14
|
+
safe_yaml (~> 1.0.0)
|
15
|
+
faraday (0.12.0.1)
|
16
|
+
multipart-post (>= 1.2, < 3)
|
17
|
+
hashdiff (0.3.2)
|
18
|
+
minitest (5.8.4)
|
19
|
+
multipart-post (2.0.0)
|
20
|
+
public_suffix (2.0.5)
|
21
|
+
rake (11.3.0)
|
22
|
+
safe_yaml (1.0.4)
|
23
|
+
webmock (3.0.1)
|
24
|
+
addressable (>= 2.3.6)
|
25
|
+
crack (>= 0.3.2)
|
26
|
+
hashdiff
|
27
|
+
|
28
|
+
PLATFORMS
|
29
|
+
ruby
|
30
|
+
|
31
|
+
DEPENDENCIES
|
32
|
+
byebug
|
33
|
+
fieldview!
|
34
|
+
minitest
|
35
|
+
rake
|
36
|
+
webmock
|
37
|
+
|
38
|
+
BUNDLED WITH
|
39
|
+
1.13.2
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2017 Paul Susmarski
|
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 all
|
13
|
+
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 THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
# FieldView API Library
|
2
|
+
|
3
|
+
The FieldView Ruby library provides convenient access to the FieldView API from applications written in the Ruby language. It includes a pre-defined set of classes for API resources that are available currently from the API. You will need to get access from a Climate Corporation representative and the interface utilize OAUTH 2.0.
|
4
|
+
|
5
|
+
## Usage
|
6
|
+
|
7
|
+
The library needs to be configured with your FieldView account's client secret,
|
8
|
+
client id, and x-api-key which would be provided to you by a Climate
|
9
|
+
representative. Set these according to the their values
|
10
|
+
|
11
|
+
``` ruby
|
12
|
+
require "fieldview"
|
13
|
+
|
14
|
+
FieldView.client_id = "fake-client"
|
15
|
+
FieldView.client_secret = "fake-client-..."
|
16
|
+
FieldView.x_api_key = "..."
|
17
|
+
|
18
|
+
# Required if acquiring a new auth token without refresh tokens
|
19
|
+
FieldView.redirect_uri = "http://my.local.host/path/that/users/are/directed"
|
20
|
+
|
21
|
+
|
22
|
+
# Get an Auth token
|
23
|
+
auth_token = FieldView::AuthToken.new_auth_token_with_code_from_redirect_code(<CODE>)
|
24
|
+
|
25
|
+
# Or initialize with previous information
|
26
|
+
auth_token = FieldView::AuthToken.new(
|
27
|
+
access_token: <ATOKEN>,
|
28
|
+
expiration_at: <DATETIME>,
|
29
|
+
refresh_token: <RTOKEN>,
|
30
|
+
refresh_token_expiration_at: <DATETIME>)
|
31
|
+
|
32
|
+
# Or with just auth_token (assuming it hasn't expired)
|
33
|
+
auth_token = FieldView::AuthToken.new(access_token: <ATOKEN>)
|
34
|
+
|
35
|
+
# refresh token and a new access/refresh token will be associated with the object
|
36
|
+
auth_token = FieldView::AuthToken.new(refresh_token: <RTOKEN>)
|
37
|
+
|
38
|
+
|
39
|
+
```
|
40
|
+
|
41
|
+
## Development
|
42
|
+
|
43
|
+
Run all tests:
|
44
|
+
|
45
|
+
bundle exec rake
|
46
|
+
|
47
|
+
Run a single test suite:
|
48
|
+
|
49
|
+
bundle exec ruby -Ilib/ test/field_view_test.rb
|
50
|
+
|
51
|
+
Run a single test:
|
52
|
+
|
53
|
+
bundle exec ruby -Ilib/ test/field_view_test.rb -n /client.id/
|
54
|
+
|
55
|
+
## Disclaimer
|
56
|
+
|
57
|
+
This Gem is in no way associated with The Climate Corporation, and they are in no way associated with it's support, maintenance, or updates.
|
data/Rakefile
ADDED
data/fieldview.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
$:.unshift(File.join(File.dirname(__FILE__), 'lib'))
|
2
|
+
|
3
|
+
spec = Gem::Specification.new do |s|
|
4
|
+
s.name = 'fieldview'
|
5
|
+
s.version = "0.0.0"
|
6
|
+
s.required_ruby_version = '>= 1.9.3'
|
7
|
+
s.summary = 'Ruby bindings for the FieldView API'
|
8
|
+
s.description = ' FieldView is used make data-driven decisions to maximize your return on every acre.'
|
9
|
+
s.author = 'Paul Susmarski'
|
10
|
+
s.email = 'paul@susmarski.com'
|
11
|
+
s.homepage = 'http://rubygems.org/gems/fielview'
|
12
|
+
s.license = 'MIT'
|
13
|
+
|
14
|
+
s.add_dependency('faraday', '~> 0.9')
|
15
|
+
|
16
|
+
s.files = Dir['lib/**/*.rb']
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- test/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ['lib']
|
21
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
module FieldView
|
2
|
+
class AuthToken
|
3
|
+
attr_accessor :access_token, :access_token_expiration_at, :refresh_token, :refresh_token_expiration_at
|
4
|
+
attr_accessor :last_request
|
5
|
+
def initialize(params)
|
6
|
+
# Assume now was 5 seconds ago
|
7
|
+
now = FieldView.get_now_for_auth_token - 5
|
8
|
+
|
9
|
+
# Used on every request
|
10
|
+
self.access_token = params[:access_token]
|
11
|
+
|
12
|
+
# When the access token expires we'll need to refresh,
|
13
|
+
# can be specified as part of the object, 14399 is what is being
|
14
|
+
# returned at the time of writing this (2017-04-11)
|
15
|
+
self.access_token_expiration_at = params[:access_token_expiration_at] || (now + (params[:expires_in]||14399) - 10)
|
16
|
+
|
17
|
+
# Refresh token isn't required, but can be initialized with this
|
18
|
+
self.refresh_token = params[:refresh_token]
|
19
|
+
|
20
|
+
# Refresh token technically expires in 30 days, but
|
21
|
+
# we'll subtract one day for safety
|
22
|
+
self.refresh_token_expiration_at = params[:access_token_expiration_at] || (now + (30-1)*24*60*60)
|
23
|
+
end
|
24
|
+
|
25
|
+
def access_token_expired?
|
26
|
+
return !!(self.access_token.nil? || self.access_token_expiration_at <= Time.now)
|
27
|
+
end
|
28
|
+
|
29
|
+
def refresh_token_expired?
|
30
|
+
return !!(self.refresh_token.nil? || self.refresh_token_expiration_at <= Time.now)
|
31
|
+
end
|
32
|
+
|
33
|
+
def refresh_access_token!()
|
34
|
+
http, request = self.class.build_token_request([
|
35
|
+
["grant_type", "refresh_token"],
|
36
|
+
["refresh_token", self.refresh_token]
|
37
|
+
])
|
38
|
+
response = http.request(request)
|
39
|
+
if response.code != "200" then
|
40
|
+
# An error has occurred, log to roll bar and notify them
|
41
|
+
raise RefreshTokenError.new("Failed to refresh FieldView token, response body: #{response.body}")
|
42
|
+
else
|
43
|
+
json = JSON.parse(response.body, symbolize_names: true)
|
44
|
+
initialize(json)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def execute_request!(method, path, headers: {}, params: {})
|
49
|
+
self.last_request = path
|
50
|
+
if access_token_expired? && refresh_token_expired? then
|
51
|
+
raise AllTokensExpiredError.new("All of your tokens have expired. " \
|
52
|
+
"You'll need to re-log into FieldView.")
|
53
|
+
end
|
54
|
+
|
55
|
+
if access_token_expired? then
|
56
|
+
refresh_access_token!()
|
57
|
+
end
|
58
|
+
uri = URI.parse("#{FieldView.api_base}#{FieldView.api_version}/#{path}")
|
59
|
+
|
60
|
+
# TODO: Handle parameters
|
61
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
62
|
+
http.use_ssl = FieldView.api_base.start_with?("https")
|
63
|
+
request = Net::HTTP.class_eval(method.to_s.capitalize).new(uri.request_uri)
|
64
|
+
request["Accept"] = "*/*"
|
65
|
+
request["Authorization"] = "Bearer #{self.access_token}"
|
66
|
+
request["X-Api-Key"] = FieldView.x_api_key
|
67
|
+
request["Content-Type"] = "application/json"
|
68
|
+
|
69
|
+
headers.each do |header,value|
|
70
|
+
request[header] = value.to_s
|
71
|
+
end
|
72
|
+
|
73
|
+
response = http.request(request)
|
74
|
+
FieldView.handle_response_error_codes(response)
|
75
|
+
return FieldViewResponse.new(response)
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.build_token_request(url_params)
|
79
|
+
uri = URI.parse(FieldView.oauth_token_base)
|
80
|
+
new_query_ar = URI.decode_www_form(uri.query || '')
|
81
|
+
url_params.each do |param|
|
82
|
+
new_query_ar << param
|
83
|
+
end
|
84
|
+
uri.query = URI.encode_www_form(new_query_ar)
|
85
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
86
|
+
http.use_ssl = true
|
87
|
+
request = Net::HTTP::Post.new(uri.request_uri)
|
88
|
+
request["Accept"] = "*/*"
|
89
|
+
request["Authorization"] = "Basic #{Base64.encode64("#{FieldView.client_id}:#{FieldView.client_secret}").strip}"
|
90
|
+
request["Content-Type"] = "application/x-www-form-urlencoded"
|
91
|
+
return http, request
|
92
|
+
end
|
93
|
+
|
94
|
+
# Code will be collected from the redirect to your server
|
95
|
+
def self.new_auth_token_with_code_from_redirect_code(code)
|
96
|
+
http, request = build_token_request([
|
97
|
+
["grant_type", "authorization_code"],
|
98
|
+
["redirect_uri", FieldView.redirect_uri],
|
99
|
+
["code", code]
|
100
|
+
])
|
101
|
+
response = http.request(request)
|
102
|
+
if response.code != "200" then
|
103
|
+
raise AuthenticationError.new("Was unable to get a new auth token using the code provided. " \
|
104
|
+
"See response body: #{response.body}")
|
105
|
+
else
|
106
|
+
json = JSON.parse(response.body, symbolize_names: true)
|
107
|
+
return AuthToken.new(json)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module FieldView
|
2
|
+
class Feature
|
3
|
+
attr_accessor :type, :coordinates
|
4
|
+
def initialize(json_feature_object)
|
5
|
+
# Use RGEO??
|
6
|
+
self.type = json_feature_object[:type]
|
7
|
+
self.coordinates = json_feature_object[:coordinates]
|
8
|
+
end
|
9
|
+
|
10
|
+
def point?()
|
11
|
+
return !!(self.type =~ /\Apoint\z/i)
|
12
|
+
end
|
13
|
+
|
14
|
+
def multi_polygon?()
|
15
|
+
return !!(self.type =~ /\Amultipolygon\z/i)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
class Boundary
|
19
|
+
attr_accessor :id, :units, :area, :centroid, :geometry
|
20
|
+
def initialize(json_object)
|
21
|
+
self.id = json_object[:id]
|
22
|
+
self.area = json_object[:area][:q]
|
23
|
+
self.units = json_object[:area][:u]
|
24
|
+
self.centroid = Feature.new(json_object[:centroid])
|
25
|
+
self.geometry = Feature.new(json_object[:geometry])
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module FieldView
|
2
|
+
# FieldViewError is the base error from which all other more specific FieldView
|
3
|
+
# errors derive.
|
4
|
+
class FieldViewError < StandardError
|
5
|
+
attr_reader :message
|
6
|
+
|
7
|
+
# These fields are now available as part of #response and that usage should
|
8
|
+
# be preferred.
|
9
|
+
attr_reader :http_body
|
10
|
+
attr_reader :http_headers
|
11
|
+
attr_reader :http_status
|
12
|
+
attr_reader :request_id
|
13
|
+
|
14
|
+
attr_accessor :response
|
15
|
+
# Initializes a FieldViewError.
|
16
|
+
def initialize(message=nil, http_status: nil, http_body: nil,
|
17
|
+
http_headers: nil)
|
18
|
+
@message = message
|
19
|
+
@http_status = http_status
|
20
|
+
@http_body = http_body
|
21
|
+
@http_headers = http_headers || {}
|
22
|
+
@request_id = @http_headers[FieldView::REQUEST_ID_HEADER_KEY]
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_s
|
26
|
+
status_string = @http_status.nil? ? "" : "(Status #{@http_status}) "
|
27
|
+
id_string = @request_id.nil? ? "" : "(Request #{@request_id}) "
|
28
|
+
"#{status_string}#{id_string}#{@message}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# AuthenticationError is raised when invalid credentials are used to connect
|
33
|
+
# to FieldView's servers.
|
34
|
+
class AuthenticationError < FieldViewError
|
35
|
+
end
|
36
|
+
|
37
|
+
# Raised when all the tokens expired and nothing can be done.
|
38
|
+
class AllTokensExpiredError < FieldViewError
|
39
|
+
end
|
40
|
+
|
41
|
+
# Raised when access resources you don't have access to
|
42
|
+
class PermissionError < FieldViewError
|
43
|
+
end
|
44
|
+
|
45
|
+
# Raised when a refresh token is attempted to be used but it can't be
|
46
|
+
class RefreshTokenError < FieldViewError
|
47
|
+
end
|
48
|
+
|
49
|
+
# Raised when too many requests are being made
|
50
|
+
class RateLimitError < FieldViewError
|
51
|
+
end
|
52
|
+
|
53
|
+
# Raised when accessing a non-existent resource
|
54
|
+
class InvalidRequestError < FieldViewError
|
55
|
+
end
|
56
|
+
|
57
|
+
# Raised when something goes wrong with FieldView
|
58
|
+
class InternalServerError < FieldViewError
|
59
|
+
end
|
60
|
+
|
61
|
+
# Raised when the server is busy
|
62
|
+
class ServerBusyError < FieldViewError
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module FieldView
|
2
|
+
class Field
|
3
|
+
attr_accessor :id
|
4
|
+
attr_accessor :name
|
5
|
+
attr_accessor :boundary_id
|
6
|
+
attr_accessor :auth_token
|
7
|
+
def initialize(json_object, auth_token = nil)
|
8
|
+
self.id = json_object[:id]
|
9
|
+
self.name = json_object[:name]
|
10
|
+
self.boundary_id = json_object[:boundaryId]
|
11
|
+
self.auth_token = auth_token
|
12
|
+
end
|
13
|
+
|
14
|
+
def boundary
|
15
|
+
@boundary ||= nil
|
16
|
+
if @boundary.nil?
|
17
|
+
@boundary = Boundary.new(self.auth_token.execute_request!(:get, "boundaries/#{self.boundary_id}").data)
|
18
|
+
end
|
19
|
+
return @boundary
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module FieldView
|
2
|
+
class Fields
|
3
|
+
PATH = "fields"
|
4
|
+
def self.list(auth_token, limit: nil, next_token: nil)
|
5
|
+
limit ||= FieldView.default_page_limit
|
6
|
+
response = auth_token.execute_request!(:get, PATH,
|
7
|
+
headers: {
|
8
|
+
FieldView::NEXT_TOKEN_HEADER_KEY => next_token,
|
9
|
+
FieldView::PAGE_LIMIT_HEADER_KEY => limit
|
10
|
+
})
|
11
|
+
next_token = response.http_headers[FieldView::NEXT_TOKEN_HEADER_KEY]
|
12
|
+
|
13
|
+
if (response.http_status == 200 || response.http_status == 206) then
|
14
|
+
# 206: Partial result, will have more data
|
15
|
+
# 200: When all the results were in the list
|
16
|
+
return_data = response.data[:results]
|
17
|
+
elsif (response.http_status == 304)
|
18
|
+
# 304: Nothing modified since last request
|
19
|
+
return_data = []
|
20
|
+
else
|
21
|
+
# This should never happen
|
22
|
+
return_data = nil
|
23
|
+
end
|
24
|
+
|
25
|
+
return ListObject.new(
|
26
|
+
self,
|
27
|
+
auth_token,
|
28
|
+
return_data.collect { |i| Field.new(i, auth_token) },
|
29
|
+
response.http_status,
|
30
|
+
next_token: next_token,
|
31
|
+
limit: limit)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module FieldView
|
2
|
+
class FieldViewResponse
|
3
|
+
attr_accessor :request_id
|
4
|
+
attr_accessor :http_body
|
5
|
+
attr_accessor :http_headers
|
6
|
+
attr_accessor :http_status
|
7
|
+
attr_accessor :data
|
8
|
+
def initialize(response)
|
9
|
+
self.http_headers = {}
|
10
|
+
response.each_capitalized_name do |n|
|
11
|
+
self.http_headers[n] = response[n]
|
12
|
+
end
|
13
|
+
|
14
|
+
self.http_body = response.body
|
15
|
+
begin
|
16
|
+
self.data = JSON.parse(response.body, symbolize_names: true)
|
17
|
+
rescue JSON::ParserError
|
18
|
+
self.data = nil
|
19
|
+
end
|
20
|
+
self.http_status = response.code.to_i
|
21
|
+
self.request_id = self.http_headers[FieldView::REQUEST_ID_HEADER_KEY]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module FieldView
|
2
|
+
class ListObject
|
3
|
+
attr_accessor :limit
|
4
|
+
attr_reader :auth_token, :data, :last_http_status, :next_token, :listable
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
def initialize(listable, auth_token, data, http_status, next_token: nil, limit: 100)
|
8
|
+
@listable = listable
|
9
|
+
@auth_token = auth_token
|
10
|
+
@data = data
|
11
|
+
@last_http_status = http_status
|
12
|
+
@next_token = next_token
|
13
|
+
@limit = 100
|
14
|
+
end
|
15
|
+
|
16
|
+
def each(&blk)
|
17
|
+
self.data.each(&blk)
|
18
|
+
end
|
19
|
+
|
20
|
+
def next_page!()
|
21
|
+
return if !self.more_pages?()
|
22
|
+
new_list = @listable.list(auth_token, limit: self.limit, next_token: self.next_token)
|
23
|
+
@data = new_list.data
|
24
|
+
@last_http_status = new_list.last_http_status
|
25
|
+
@auth_token = new_list.auth_token
|
26
|
+
end
|
27
|
+
|
28
|
+
# alias for more_pages
|
29
|
+
def has_more?()
|
30
|
+
return self.more_pages?()
|
31
|
+
end
|
32
|
+
|
33
|
+
def more_pages?()
|
34
|
+
return Util.http_status_is_more_in_list?(self.last_http_status)
|
35
|
+
end
|
36
|
+
|
37
|
+
def restart!()
|
38
|
+
@last_http_status = nil
|
39
|
+
@next_token = nil
|
40
|
+
next_page!()
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/fieldview.rb
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'json'
|
3
|
+
require 'rbconfig'
|
4
|
+
require 'base64'
|
5
|
+
|
6
|
+
# API Support Classes
|
7
|
+
require 'fieldview/errors'
|
8
|
+
require 'fieldview/auth_token'
|
9
|
+
require 'fieldview/fields'
|
10
|
+
require 'fieldview/util'
|
11
|
+
require 'fieldview/fieldview_response'
|
12
|
+
require 'fieldview/field'
|
13
|
+
require 'fieldview/list_object'
|
14
|
+
require 'fieldview/boundary'
|
15
|
+
|
16
|
+
module FieldView
|
17
|
+
@oauth_token_base = "https://api.climate.com/api/oauth/token"
|
18
|
+
@api_base = "https://platform.climate.com/v"
|
19
|
+
@max_network_retries = 0
|
20
|
+
@api_version = 4
|
21
|
+
@default_page_limit = 100
|
22
|
+
NEXT_TOKEN_HEADER_KEY = "X-Next-Token"
|
23
|
+
REQUEST_ID_HEADER_KEY = "X-Http-Request-Id"
|
24
|
+
PAGE_LIMIT_HEADER_KEY = "X-Limit"
|
25
|
+
|
26
|
+
class << self
|
27
|
+
attr_accessor :oauth_token_base, :api_base, :max_network_retries, :api_version, :now,
|
28
|
+
:default_page_limit
|
29
|
+
|
30
|
+
def get_now_for_auth_token
|
31
|
+
now || Time.now
|
32
|
+
end
|
33
|
+
|
34
|
+
def x_api_key
|
35
|
+
@_x_api_key ||= nil
|
36
|
+
unless @_x_api_key
|
37
|
+
raise AuthenticationError.new('No x-api-key provided. ' \
|
38
|
+
'Set your x-api-key using "FieldView.x_api_key = <X-API-KEY>". ' \
|
39
|
+
'This should have been provided to you by a Climate representative. ' \
|
40
|
+
'This takes the form of "my-client"')
|
41
|
+
end
|
42
|
+
return @_x_api_key
|
43
|
+
end
|
44
|
+
def x_api_key=(value)
|
45
|
+
@_x_api_key = value
|
46
|
+
end
|
47
|
+
|
48
|
+
def redirect_uri
|
49
|
+
@_redirect_uri ||= nil
|
50
|
+
unless @_redirect_uri
|
51
|
+
raise AuthenticationError.new("You must set the redirect uri to your proper server " \
|
52
|
+
"to get new auth tokens if you haven't set one. " \
|
53
|
+
"Set your redirect uri using FieldView.redirect_uri = <REDIRECT_URI>")
|
54
|
+
end
|
55
|
+
return @_redirect_uri
|
56
|
+
end
|
57
|
+
def redirect_uri=(value)
|
58
|
+
@_redirect_uri = value
|
59
|
+
end
|
60
|
+
|
61
|
+
def client_id
|
62
|
+
@_client_id ||= nil
|
63
|
+
unless @_client_id
|
64
|
+
raise AuthenticationError.new('No client id provided. ' \
|
65
|
+
'Set your client id using "FieldView.client_id = <CLIENT-ID>". ' \
|
66
|
+
'This should have been provided to you by a Climate representative. ' \
|
67
|
+
'This takes the form of "my-client"')
|
68
|
+
end
|
69
|
+
return @_client_id
|
70
|
+
end
|
71
|
+
def client_id=(value)
|
72
|
+
@_client_id = value
|
73
|
+
end
|
74
|
+
|
75
|
+
def client_secret
|
76
|
+
@_client_secret ||= nil
|
77
|
+
unless @_client_secret
|
78
|
+
raise AuthenticationError.new('No client_secret provided. ' \
|
79
|
+
'Set your client secret using "FieldView.client_secret = <CLIENT-SECRET>". ' \
|
80
|
+
'This should have been provided to you by a Climate representative. ' \
|
81
|
+
'This takes the form of "my-client-fz9900x98-x98908j-jslx"')
|
82
|
+
end
|
83
|
+
return @_client_secret
|
84
|
+
end
|
85
|
+
def client_secret=(value)
|
86
|
+
@_client_secret = value
|
87
|
+
end
|
88
|
+
|
89
|
+
def handle_response_error_codes(response)
|
90
|
+
headers = response.to_hash
|
91
|
+
code = response.code.to_i
|
92
|
+
body = response.body
|
93
|
+
error = nil
|
94
|
+
case code
|
95
|
+
when 503
|
96
|
+
error = ServerBusyError.new(
|
97
|
+
"Server Busy", http_status: code, http_body: body,
|
98
|
+
http_headers: headers)
|
99
|
+
when 400, 404
|
100
|
+
error = InvalidRequestError.new(
|
101
|
+
"Bad input", http_status: code, http_body: body,
|
102
|
+
http_headers: headers)
|
103
|
+
when 401
|
104
|
+
error = AuthenticationError.new(
|
105
|
+
"Unauthorized", http_status: code, http_body: body,
|
106
|
+
http_headers: headers)
|
107
|
+
when 403
|
108
|
+
error = PermissionError.new(
|
109
|
+
"Forbidden", http_status: code, http_body: body,
|
110
|
+
http_headers: headers)
|
111
|
+
when 429
|
112
|
+
# Retry-After will be in the headers
|
113
|
+
error = RateLimitError.new(
|
114
|
+
"Too many requests", http_status: code, http_body: body,
|
115
|
+
http_headers: headers)
|
116
|
+
when 500
|
117
|
+
error = InternalServerError.new(
|
118
|
+
"Internal server error", http_status: code, http_body: body,
|
119
|
+
http_headers: headers)
|
120
|
+
end
|
121
|
+
|
122
|
+
if error.nil? then
|
123
|
+
return
|
124
|
+
else
|
125
|
+
error.response = response
|
126
|
+
raise error
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
data/spec/fixtures.json
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
{
|
2
|
+
"boundary_one": {
|
3
|
+
"id": "e694b43e-86eb-4773-86d0-3c4f546178dc",
|
4
|
+
"area": {
|
5
|
+
"q": 58.923473472390256,
|
6
|
+
"u": "ac"
|
7
|
+
},
|
8
|
+
"centroid": {
|
9
|
+
"type": "Point",
|
10
|
+
"coordinates": [
|
11
|
+
-105.223479062131,
|
12
|
+
40.0579906147381
|
13
|
+
]
|
14
|
+
},
|
15
|
+
"geometry": {
|
16
|
+
"type": "MultiPolygon",
|
17
|
+
"coordinates": [
|
18
|
+
[
|
19
|
+
[
|
20
|
+
[
|
21
|
+
-105.2255165,
|
22
|
+
40.0437726
|
23
|
+
],
|
24
|
+
[
|
25
|
+
-105.2208496,
|
26
|
+
40.0438021
|
27
|
+
],
|
28
|
+
[
|
29
|
+
-105.2208915,
|
30
|
+
40.0402151
|
31
|
+
],
|
32
|
+
[
|
33
|
+
-105.2255049,
|
34
|
+
40.0402473
|
35
|
+
],
|
36
|
+
[
|
37
|
+
-105.2255165,
|
38
|
+
40.0437726
|
39
|
+
]
|
40
|
+
]
|
41
|
+
],
|
42
|
+
[
|
43
|
+
[
|
44
|
+
[
|
45
|
+
-105.2257098,
|
46
|
+
40.087157
|
47
|
+
],
|
48
|
+
[
|
49
|
+
-105.2257173,
|
50
|
+
40.0903676
|
51
|
+
],
|
52
|
+
[
|
53
|
+
-105.2249648,
|
54
|
+
40.0898429
|
55
|
+
],
|
56
|
+
[
|
57
|
+
-105.2232786,
|
58
|
+
40.0894078
|
59
|
+
],
|
60
|
+
[
|
61
|
+
-105.2226989,
|
62
|
+
40.0886577
|
63
|
+
],
|
64
|
+
[
|
65
|
+
-105.2218189,
|
66
|
+
40.0883161
|
67
|
+
],
|
68
|
+
[
|
69
|
+
-105.2218299,
|
70
|
+
40.0879038
|
71
|
+
],
|
72
|
+
[
|
73
|
+
-105.2217414,
|
74
|
+
40.0874009
|
75
|
+
],
|
76
|
+
[
|
77
|
+
-105.2214516,
|
78
|
+
40.0871603
|
79
|
+
],
|
80
|
+
[
|
81
|
+
-105.2257098,
|
82
|
+
40.087157
|
83
|
+
]
|
84
|
+
]
|
85
|
+
]
|
86
|
+
]
|
87
|
+
}
|
88
|
+
},
|
89
|
+
"single_field_list": {
|
90
|
+
"results": [
|
91
|
+
{
|
92
|
+
"id": "530dd6e4-170b-4254-b10c-ef07b9533f74",
|
93
|
+
"name": "NE of Airport",
|
94
|
+
"boundaryId": "e694b43e-86eb-4773-86d0-3c4f546178dc"
|
95
|
+
}
|
96
|
+
]
|
97
|
+
},
|
98
|
+
"field_two_list": {
|
99
|
+
"results": [
|
100
|
+
{
|
101
|
+
"id": "687c5ad6-7969-4112-8234-f8b5474a7389",
|
102
|
+
"name": "SE of Airport",
|
103
|
+
"boundaryId": "0a34b7bc-3f6b-4ace-9e6d-f97a7c6b66b9"
|
104
|
+
}
|
105
|
+
]
|
106
|
+
},
|
107
|
+
"authorization": {
|
108
|
+
"access_token": "b2a58121-e02e-4c56-9036-ac23eb295ddc",
|
109
|
+
"token_type": "bearer",
|
110
|
+
"refresh_token": "23bba1b4-4e61-4e41-83fb-620061158239",
|
111
|
+
"expires_in": 14399,
|
112
|
+
"scope": "openid partnerapis platform",
|
113
|
+
"id_token": "8s9nxjx908uoij12oik2j4.1j4kjs90ivcoixkjmxrw3um493u8924u4yuemnshfjdsen450q.dG9rZW4iLCJhdWQiOiJwYXJ0bmVyLWFncmlib3RpeCIsImV4cCI6MTQ5MTk0NTUzNSwiaWF0IjoxNDkxOTQ0OTM1LCJhdXRoX3RpbWUiOjE0OTE5NDQ5MzUsInVzZXJfaWQiOiIxODYzNzUiLCJzdWIiOiIxODYzNzUiLCJlbWFpbCI6ImNvbXB1dGVAYWdyaWJvdGl4LmNvbSIsImFkZHJlc3MiOnt9LCJyb2xlcyI6WyJjbGllbnQiXSwicGhvbmVfbnVtYmVyIjoiMzAzNzE3ODk3OSJ9.TjSPzpdIUUQMD3wh_-PCdB2o0M8n1tpWAeicQ-Wz-AwXsbmxesDnZroppsN-0_H_8ULOhSKwlGVGEuuD6QfYElhlGZuviSZcYkwYDNtXNN9oVa4MgfWK76txLV59oaGpFuHlqKGMskL4uV5Ao_H9_0jN1THqluav-WUh4WwxB-LclIu4i7gv1g5Bw5zpzzccQAzErc04u_Gf1gwi1IZtFYJJncSnnm1hp5XhBZgLZD0twiNOYzxML3nFbM37WEf1G3iqSLiTH1bIKc_MO7dG3o8oq3SoYjDmjRWl7iL7WD3jZHWiAuvVBa0rDqOOeVpCPUYPk62n9HJ6JNtMnSp3eUvnR8nfaOvm7P1ZehOAVnJVq1yWfjOtxEvjLBmD7wRs9I5N2OANiLgqvnvJVy_BuAqQXDqmgusrU9FotGRP2obSRfZvaPzxXurCYY9OfS8uxxHBlzVe06dTNRezuOzy_op7y33dOEOzFfoBNyFcTtlptcICs6uIsGC_k239auWTiMKIGgAdtbgoAvVLK49-s7IdX9gfUv9ugaZgduPDq6hzxWQh-Uahyf1LkpLfb9wTHPdq9upW8Dt5iSBQKcngCBdLjR3WrGIRi0M0m2beRSB-acUiIWreeoCOY78UErEd5ebYwfBaAFh1Igy5EfvfYyaNjhv1-cYX8q3fc2JBPPU",
|
114
|
+
"user": {
|
115
|
+
"tosacceptedat": null,
|
116
|
+
"email": "testing@test.com",
|
117
|
+
"firstviews": 0,
|
118
|
+
"phone": "5555555555",
|
119
|
+
"roles": [
|
120
|
+
"Client"
|
121
|
+
],
|
122
|
+
"city": null,
|
123
|
+
"firstname": "Testing",
|
124
|
+
"source": "user-service-default",
|
125
|
+
"address1": null,
|
126
|
+
"mailchimp": false,
|
127
|
+
"address2": null,
|
128
|
+
"zip": null,
|
129
|
+
"id": 186375,
|
130
|
+
"state": null,
|
131
|
+
"lastname": "Lastname",
|
132
|
+
"uuid": "ca9cdad5-c759-44b5-9442-371338f301a9",
|
133
|
+
"enabled": true,
|
134
|
+
"country": "United States",
|
135
|
+
"asOf": 1491944935932
|
136
|
+
},
|
137
|
+
"site": {
|
138
|
+
"siteCode": "fccd95c45c048f7c8c9a60c8"
|
139
|
+
}
|
140
|
+
}
|
141
|
+
}
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class APIFixtures
|
2
|
+
def initialize
|
3
|
+
@fixtures = ::JSON.parse(File.read("#{PROJECT_ROOT}/spec/fixtures.json"),
|
4
|
+
symbolize_names: true)
|
5
|
+
freeze_recursively(@fixtures)
|
6
|
+
end
|
7
|
+
|
8
|
+
def [](name)
|
9
|
+
@fixtures[name]
|
10
|
+
end
|
11
|
+
|
12
|
+
def fetch(*args)
|
13
|
+
@fixtures.fetch(*args)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def freeze_recursively(data)
|
19
|
+
data.each do |k, v|
|
20
|
+
if v.is_a?(Hash)
|
21
|
+
freeze_recursively(v)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
data.freeze
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require File.expand_path('../test_helper', __FILE__)
|
2
|
+
|
3
|
+
class TestAuthToken < Minitest::Test
|
4
|
+
FIXTURE = API_FIXTURES.fetch(:authorization)
|
5
|
+
def auth_uri_stub
|
6
|
+
"#{prefix_for_all_requests}#{FieldView.oauth_token_base[8..-1]}"
|
7
|
+
end
|
8
|
+
|
9
|
+
def check_token_matches_fixture(token)
|
10
|
+
assert_equal FIXTURE[:access_token], token.access_token
|
11
|
+
assert_equal FIXTURE[:refresh_token], token.refresh_token
|
12
|
+
assert_equal FieldView.now + FIXTURE[:expires_in] - 15, token.access_token_expiration_at
|
13
|
+
assert_equal FieldView.now - 5 + 29*24*60*60, token.refresh_token_expiration_at, "Should be 29 days in the future"
|
14
|
+
end
|
15
|
+
|
16
|
+
def setup
|
17
|
+
setup_for_api_requests
|
18
|
+
end
|
19
|
+
|
20
|
+
def teardown
|
21
|
+
teardown_for_api_request
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_build_token_request
|
25
|
+
http, request = FieldView::AuthToken.build_token_request([["dont", "care"]])
|
26
|
+
assert_equal "/api/oauth/token?dont=care", request.path
|
27
|
+
|
28
|
+
headers = request.to_hash
|
29
|
+
assert_equal ["*/*"], headers["accept"]
|
30
|
+
assert_equal ["Basic #{Base64.encode64("#{FieldView.client_id}:#{FieldView.client_secret}").strip}"], headers["authorization"]
|
31
|
+
assert_equal ["application/x-www-form-urlencoded"], headers["content-type"]
|
32
|
+
|
33
|
+
assert_equal Net::HTTP::Post, request.class
|
34
|
+
assert_equal "api.climate.com", http.address
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_new_auth_token_with_code_from_redirect_code_bad
|
38
|
+
stub_request(:post, "#{auth_uri_stub}?" \
|
39
|
+
"code&grant_type=authorization_code&redirect_uri=#{FieldView.redirect_uri}").
|
40
|
+
to_return(status: 401, body: JSON.generate({}))
|
41
|
+
assert_raises FieldView::AuthenticationError do
|
42
|
+
FieldView::AuthToken.new_auth_token_with_code_from_redirect_code(nil)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_new_auth_token_with_code_from_redirect_code_good
|
47
|
+
# now a good token
|
48
|
+
code = "code"
|
49
|
+
FieldView.now = Time.now
|
50
|
+
stub_request(:post, "#{auth_uri_stub}?" \
|
51
|
+
"code=#{code}&grant_type=authorization_code&redirect_uri=#{FieldView.redirect_uri}").
|
52
|
+
to_return(status: 200, body: JSON.generate(FIXTURE))
|
53
|
+
token = FieldView::AuthToken.new_auth_token_with_code_from_redirect_code(code)
|
54
|
+
check_token_matches_fixture(token)
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_create_with_just_access_token
|
58
|
+
token = FieldView::AuthToken.new(access_token: "yyy")
|
59
|
+
assert !token.access_token_expired?
|
60
|
+
assert token.refresh_token_expired?
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_create_with_just_refresh_token
|
64
|
+
FieldView.now = Time.now
|
65
|
+
token = FieldView::AuthToken.new(refresh_token: "xxx")
|
66
|
+
assert token.access_token_expired?
|
67
|
+
assert !token.refresh_token_expired?
|
68
|
+
|
69
|
+
stub_request(:post, "#{auth_uri_stub}?" \
|
70
|
+
"grant_type=refresh_token&refresh_token=#{token.refresh_token}").
|
71
|
+
to_return(status: 200, body: JSON.generate(FIXTURE))
|
72
|
+
token.refresh_access_token!
|
73
|
+
check_token_matches_fixture(token)
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_completely_expired_token
|
77
|
+
token = FieldView::AuthToken.new(
|
78
|
+
access_token: "yyy",
|
79
|
+
refresh_token: "xxx",
|
80
|
+
refresh_token_expiration_at: Time.now - 20,
|
81
|
+
access_token_expiration_at: Time.now - 20
|
82
|
+
)
|
83
|
+
|
84
|
+
assert token.access_token_expired?
|
85
|
+
assert token.refresh_token_expired?
|
86
|
+
|
87
|
+
assert_raises FieldView::AllTokensExpiredError do
|
88
|
+
token.execute_request!(:get, "dont/care")
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require File.expand_path('../test_helper', __FILE__)
|
2
|
+
|
3
|
+
class TestBoundary < Minitest::Test
|
4
|
+
FIXTURE = API_FIXTURES.fetch(:boundary_one)
|
5
|
+
def test_initialization
|
6
|
+
boundary = FieldView::Boundary.new(FIXTURE)
|
7
|
+
|
8
|
+
assert_equal FIXTURE[:id], boundary.id
|
9
|
+
assert_equal 58.923473472390256, boundary.area
|
10
|
+
assert_equal "ac", boundary.units
|
11
|
+
assert_equal 2, boundary.centroid.coordinates.length
|
12
|
+
assert boundary.centroid.point?
|
13
|
+
assert_equal 2, boundary.geometry.coordinates.length
|
14
|
+
assert boundary.geometry.multi_polygon?
|
15
|
+
assert boundary.geometry.multi_polygon? != boundary.centroid.multi_polygon?, "make sure they are different types"
|
16
|
+
end
|
17
|
+
end
|
data/test/test_field.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require File.expand_path('../test_helper', __FILE__)
|
2
|
+
|
3
|
+
class TestField < Minitest::Test
|
4
|
+
FIXTURE = API_FIXTURES.fetch(:single_field_list)[:results][0]
|
5
|
+
def test_initialization
|
6
|
+
field = FieldView::Field.new(FIXTURE, new_auth_token)
|
7
|
+
|
8
|
+
assert_equal FIXTURE[:id], field.id
|
9
|
+
assert_equal FIXTURE[:name], field.name
|
10
|
+
assert_equal FIXTURE[:boundaryId], field.boundary_id
|
11
|
+
assert_equal new_auth_token.access_token, field.auth_token.access_token
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_get_boundary
|
15
|
+
field = FieldView::Field.new(FIXTURE, new_auth_token)
|
16
|
+
|
17
|
+
stub_request(:get, /boundaries\/#{API_FIXTURES[:boundary_one][:id]}/).
|
18
|
+
to_return(status: 200, body: API_FIXTURES[:boundary_one].to_json())
|
19
|
+
|
20
|
+
api_requests() do
|
21
|
+
field.boundary
|
22
|
+
end
|
23
|
+
|
24
|
+
assert_equal FIXTURE[:boundaryId], field.boundary.id
|
25
|
+
end
|
26
|
+
end
|
data/test/test_fields.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
require File.expand_path('../test_helper', __FILE__)
|
2
|
+
|
3
|
+
class TestFields < Minitest::Test
|
4
|
+
FIXTURE = API_FIXTURES.fetch(:single_field_list)
|
5
|
+
|
6
|
+
def setup
|
7
|
+
setup_for_api_requests
|
8
|
+
end
|
9
|
+
|
10
|
+
def teardown
|
11
|
+
teardown_for_api_request
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_list_with_one_page()
|
15
|
+
next_token = "AZXJKLA123"
|
16
|
+
stub_request(:get, /fields/).
|
17
|
+
with(headers: { FieldView::PAGE_LIMIT_HEADER_KEY => FieldView.default_page_limit.to_s}).
|
18
|
+
to_return(status: 200, body: FIXTURE.to_json(),
|
19
|
+
headers: next_token_headers)
|
20
|
+
fields = FieldView::Fields.list(new_auth_token)
|
21
|
+
|
22
|
+
assert_equal FIXTURE[:results].length, fields.data.length
|
23
|
+
assert_equal FIXTURE[:results][0][:id], fields.data[0].id
|
24
|
+
assert_equal next_token, fields.next_token
|
25
|
+
assert_equal FieldView::Fields, fields.listable
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_list_with_no_more_data
|
29
|
+
stub_request(:get, /fields/).
|
30
|
+
with(headers: { FieldView::PAGE_LIMIT_HEADER_KEY => FieldView.default_page_limit.to_s}).
|
31
|
+
to_return(status: 304, body: {}.to_json(),
|
32
|
+
headers: next_token_headers)
|
33
|
+
fields = FieldView::Fields.list(new_auth_token)
|
34
|
+
|
35
|
+
assert_equal 0, fields.data.length
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_with_more_data
|
39
|
+
next_token = "AZXJKLA123"
|
40
|
+
stub_request(:get, /fields/).
|
41
|
+
with(headers: { FieldView::PAGE_LIMIT_HEADER_KEY => FieldView.default_page_limit.to_s}).
|
42
|
+
to_return(status: 200, body: FIXTURE.to_json(),
|
43
|
+
headers: next_token_headers)
|
44
|
+
fields = FieldView::Fields.list(new_auth_token)
|
45
|
+
|
46
|
+
assert_equal FIXTURE[:results].length, fields.data.length
|
47
|
+
assert_equal FIXTURE[:results][0][:id], fields.data[0].id
|
48
|
+
assert_equal next_token, fields.next_token
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_with_limit
|
52
|
+
stub_request(:get, /fields/).
|
53
|
+
with(headers: { FieldView::PAGE_LIMIT_HEADER_KEY => 1}).
|
54
|
+
to_return(status: 200, body: FIXTURE.to_json(),
|
55
|
+
headers: next_token_headers)
|
56
|
+
fields = FieldView::Fields.list(new_auth_token, limit: 1)
|
57
|
+
|
58
|
+
assert_equal 1, fields.data.length
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_with_next_token
|
62
|
+
next_token = "AZXJKLA123"
|
63
|
+
stub_request(:get, /fields/).
|
64
|
+
with(headers: { FieldView::NEXT_TOKEN_HEADER_KEY => next_token }).
|
65
|
+
to_return(status: 200, body: FIXTURE.to_json())
|
66
|
+
|
67
|
+
fields = FieldView::Fields.list(new_auth_token, next_token: next_token)
|
68
|
+
assert_equal 1, fields.data.length
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require File.expand_path('../test_helper', __FILE__)
|
2
|
+
|
3
|
+
class TestFieldView < Minitest::Test
|
4
|
+
def test_now
|
5
|
+
assert (Time.now - FieldView.get_now_for_auth_token) <= 5, "should be close"
|
6
|
+
expected_time = Time.now - 20
|
7
|
+
FieldView.now = expected_time
|
8
|
+
assert_equal expected_time, FieldView.get_now_for_auth_token
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_requires_client_id
|
12
|
+
FieldView.client_id = nil
|
13
|
+
assert_raises FieldView::AuthenticationError do
|
14
|
+
FieldView.client_id
|
15
|
+
end
|
16
|
+
FieldView.client_id = "test"
|
17
|
+
assert FieldView.client_id
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_requires_client_secret
|
21
|
+
FieldView.client_secret = nil
|
22
|
+
assert_raises FieldView::AuthenticationError do
|
23
|
+
FieldView.client_secret
|
24
|
+
end
|
25
|
+
FieldView.client_secret = "test"
|
26
|
+
assert FieldView.client_secret
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_requires_redirect_uri
|
30
|
+
FieldView.redirect_uri = nil
|
31
|
+
assert_raises FieldView::AuthenticationError do
|
32
|
+
FieldView.redirect_uri
|
33
|
+
end
|
34
|
+
FieldView.redirect_uri = "test"
|
35
|
+
assert FieldView.redirect_uri
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_requires_x_api_key
|
39
|
+
FieldView.x_api_key = nil
|
40
|
+
assert_raises FieldView::AuthenticationError do
|
41
|
+
FieldView.x_api_key
|
42
|
+
end
|
43
|
+
FieldView.x_api_key = "test"
|
44
|
+
assert FieldView.x_api_key
|
45
|
+
end
|
46
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'fieldview'
|
3
|
+
require 'byebug'
|
4
|
+
require 'webmock/minitest'
|
5
|
+
|
6
|
+
PROJECT_ROOT = File.expand_path("../../", __FILE__)
|
7
|
+
require File.expand_path('../api_fixtures', __FILE__)
|
8
|
+
|
9
|
+
class Minitest::Test
|
10
|
+
# Fixtures are available in tests using something like:
|
11
|
+
#
|
12
|
+
# API_FIXTURES[:fields][:id]
|
13
|
+
#
|
14
|
+
API_FIXTURES = APIFixtures.new
|
15
|
+
|
16
|
+
|
17
|
+
def next_token_headers(next_token = "AZXJKLA123")
|
18
|
+
return {FieldView::NEXT_TOKEN_HEADER_KEY => next_token}
|
19
|
+
end
|
20
|
+
|
21
|
+
def new_auth_token
|
22
|
+
FieldView::AuthToken.new(
|
23
|
+
access_token: "yyy",
|
24
|
+
refresh_token: "xxx"
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
def api_requests()
|
29
|
+
setup_for_api_requests
|
30
|
+
yield
|
31
|
+
teardown_for_api_request
|
32
|
+
end
|
33
|
+
|
34
|
+
def setup_for_api_requests()
|
35
|
+
FieldView.redirect_uri = "http://fake.test/"
|
36
|
+
FieldView.client_id = "fake-client"
|
37
|
+
FieldView.client_secret = "fake-client-abcdef12-3456-7890-abcd-ef0123456789"
|
38
|
+
FieldView.x_api_key = "ixj98c98njkn109su9jxkjks61"
|
39
|
+
end
|
40
|
+
|
41
|
+
def teardown_for_api_request()
|
42
|
+
FieldView.redirect_uri = nil
|
43
|
+
FieldView.client_id = nil
|
44
|
+
FieldView.client_secret = nil
|
45
|
+
FieldView.x_api_key = nil
|
46
|
+
FieldView.now = nil
|
47
|
+
end
|
48
|
+
|
49
|
+
def prefix_for_all_requests
|
50
|
+
"https://"
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require File.expand_path('../test_helper', __FILE__)
|
2
|
+
|
3
|
+
class TestListObject < Minitest::Test
|
4
|
+
FIXTURE = API_FIXTURES.fetch(:single_field_list)
|
5
|
+
|
6
|
+
def test_each_loop
|
7
|
+
data = ["x","y","z"]
|
8
|
+
list = FieldView::ListObject.new(FieldView::Fields, new_auth_token, data, 200, next_token: nil)
|
9
|
+
|
10
|
+
list.each_with_index do |x, i|
|
11
|
+
assert_equal data[i], x
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_get_next_page
|
16
|
+
next_token = "JZIOJKLJ"
|
17
|
+
list = FieldView::ListObject.new(FieldView::Fields, new_auth_token,
|
18
|
+
["dont","care"], 206, next_token: next_token)
|
19
|
+
stub_request(:get, /fields/).
|
20
|
+
with(headers: next_token_headers(next_token)).
|
21
|
+
to_return(status: 200, body: API_FIXTURES[:field_two_list].to_json(),
|
22
|
+
headers: next_token_headers())
|
23
|
+
|
24
|
+
api_requests() do
|
25
|
+
list.next_page!()
|
26
|
+
end
|
27
|
+
|
28
|
+
assert_equal 1, list.data.length
|
29
|
+
assert_equal API_FIXTURES[:field_two_list][:results][0][:id], list.data[0].id
|
30
|
+
assert !list.more_pages?()
|
31
|
+
end
|
32
|
+
end
|
metadata
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fieldview
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Paul Susmarski
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-04-15 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: faraday
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.9'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.9'
|
27
|
+
description: ' FieldView is used make data-driven decisions to maximize your return
|
28
|
+
on every acre.'
|
29
|
+
email: paul@susmarski.com
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- .gitignore
|
35
|
+
- Gemfile
|
36
|
+
- Gemfile.lock
|
37
|
+
- LICENSE
|
38
|
+
- README.md
|
39
|
+
- Rakefile
|
40
|
+
- fieldview.gemspec
|
41
|
+
- lib/fieldview.rb
|
42
|
+
- lib/fieldview/auth_token.rb
|
43
|
+
- lib/fieldview/boundary.rb
|
44
|
+
- lib/fieldview/errors.rb
|
45
|
+
- lib/fieldview/field.rb
|
46
|
+
- lib/fieldview/fields.rb
|
47
|
+
- lib/fieldview/fieldview_response.rb
|
48
|
+
- lib/fieldview/list_object.rb
|
49
|
+
- lib/fieldview/util.rb
|
50
|
+
- spec/fixtures.json
|
51
|
+
- test/api_fixtures.rb
|
52
|
+
- test/test_auth_token.rb
|
53
|
+
- test/test_boundary.rb
|
54
|
+
- test/test_field.rb
|
55
|
+
- test/test_fields.rb
|
56
|
+
- test/test_fieldview.rb
|
57
|
+
- test/test_helper.rb
|
58
|
+
- test/test_list_object.rb
|
59
|
+
homepage: http://rubygems.org/gems/fielview
|
60
|
+
licenses:
|
61
|
+
- MIT
|
62
|
+
metadata: {}
|
63
|
+
post_install_message:
|
64
|
+
rdoc_options: []
|
65
|
+
require_paths:
|
66
|
+
- lib
|
67
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - '>='
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: 1.9.3
|
72
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
requirements: []
|
78
|
+
rubyforge_project:
|
79
|
+
rubygems_version: 2.0.14.1
|
80
|
+
signing_key:
|
81
|
+
specification_version: 4
|
82
|
+
summary: Ruby bindings for the FieldView API
|
83
|
+
test_files:
|
84
|
+
- test/api_fixtures.rb
|
85
|
+
- test/test_auth_token.rb
|
86
|
+
- test/test_boundary.rb
|
87
|
+
- test/test_field.rb
|
88
|
+
- test/test_fields.rb
|
89
|
+
- test/test_fieldview.rb
|
90
|
+
- test/test_helper.rb
|
91
|
+
- test/test_list_object.rb
|