fieldview 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +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
|