sparkby 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 17f3aac5f38abb95ae2b493ab4fc743f7b7d8ed8
4
+ data.tar.gz: d8a0bf7e5e6b406584ec125aca7fa6b5ee549548
5
+ SHA512:
6
+ metadata.gz: 0245fa59348a7a85461ca03e95bd9373e30b8c9e2e346cf21537912c274e7efabd171d3b6fdac1c9a8da89b89952757b7085eaf546231b72ed26c2e97438de25
7
+ data.tar.gz: 32d6cbca501854ba46cf816a06ffe2ad9375e1a722d1c44a7aae26e82e5de6f3c0d12ab28883d536a108c869fed33b5ae192d1f1234918e1d4a1a0ae02c5de28
@@ -0,0 +1,23 @@
1
+ require 'httparty'
2
+
3
+ module Sparkby
4
+
5
+ class HTTPCaller
6
+ include HTTParty
7
+ base_uri 'https://api.particle.io'
8
+
9
+ def get(url, header = nil, basic_auth = nil)
10
+ self.class.get(url, :headers => header, :basic_auth => basic_auth)
11
+ end
12
+
13
+ def post(url, body, basic_auth = nil, header = nil)
14
+ self.class.post(url, :headers => header, :basic_auth => basic_auth, :body => body)
15
+ end
16
+
17
+ def delete(url, basic_auth = nil)
18
+ self.class.delete(url, :basic_auth => basic_auth)
19
+ end
20
+
21
+ end
22
+
23
+ end
@@ -0,0 +1,52 @@
1
+ require_relative 'http_caller'
2
+
3
+ module Sparkby
4
+
5
+ # Main core driver. Exposes ability to read Spark variable,
6
+ # call Spark function, view device info, and view other
7
+ # devices.
8
+ class SparkCore
9
+
10
+ # Particle API access token
11
+ attr_accessor :access_token
12
+ # Device ID of Particle core
13
+ attr_accessor :device_id
14
+
15
+ # ==== Arguments
16
+ # * +access_token+ - Particle API access token
17
+ # * +device_id+ - Device ID of Particle core
18
+ def initialize(access_token, device_id)
19
+ @access_token = access_token
20
+ @device_id = device_id
21
+ @http_caller = HTTPCaller.new
22
+ @header = {"Authorization" => "Bearer #{@access_token}"}
23
+ end
24
+
25
+ # View devices authenticated user has access to
26
+ def devices
27
+ @http_caller.get '/v1/devices', @header
28
+ end
29
+
30
+ # View information about device
31
+ def device_info
32
+ @http_caller.get '/v1/devices/' + @device_id, @header
33
+ end
34
+
35
+ # Read the value of a Spark variable
36
+ # ==== Arguments
37
+ # * +variable_name+ - Name of the Spark variable
38
+ def spark_variable(variable_name)
39
+ @http_caller.get '/v1/devices/' + @device_id + '/' + variable_name, @header
40
+ end
41
+
42
+ # Call a Spark function
43
+ # ==== Arguments
44
+ # * +function+ - Name of the Spark function
45
+ # * +args+ - Argument string to pass to Spark function
46
+ def spark_function(function, args=nil)
47
+ @http_caller.post '/v1/devices/' + @device_id + '/' + function, {'params' => args}, nil, @header
48
+ end
49
+
50
+ end
51
+
52
+ end
@@ -0,0 +1,53 @@
1
+ require_relative 'http_caller'
2
+
3
+ module Sparkby
4
+
5
+ class TokenManager
6
+
7
+ # Email address of Particle account
8
+ attr_accessor :email
9
+ # Password of Particle account
10
+ attr_accessor :password
11
+
12
+ def initialize(email, password)
13
+ @email = email
14
+ @password = password
15
+ @http_caller = HTTPCaller.new
16
+ end
17
+
18
+ # Generate a new access token
19
+ # ==== Arguments
20
+ # * +expires_in+ - Time in seconds when token should expire, nil means token never expires
21
+ # * +expires_at+ - Time in YYYY-MM-DD Format when token should expire. At most one of expires_in and expires_at should be specified
22
+ # * +client_id+ - Particle client ID
23
+ # * +client_secret+ - Particle client secret
24
+ #
25
+ # ==== Examples
26
+ #
27
+ # Token that expires in 3600 seconds (one hour)
28
+ # manager = TokenManager.new email, pass
29
+ # manager.gen_access_token 3600
30
+ #
31
+ # Token that expires on September 25, 2015
32
+ # manager = TokenManager.new email, pass
33
+ # manager.gen_access_token nil, '2015-09-25'
34
+ def gen_access_token(expires_in = nil, expires_at = nil, client_id = 'particle', client_secret = 'particle')
35
+ @http_caller.post '/oauth/token', {:grant_type => 'password', :username => @email, :password => @password,
36
+ :expires_in => expires_in, :expires_at => expires_at}.reject{ |k,v| v.nil?},
37
+ {:username => client_id, :password => client_secret}
38
+ end
39
+
40
+ # Delete an access token
41
+ # ==== Arguments
42
+ # * +token+ - Access token to delete
43
+ def del_access_token(token)
44
+ @http_caller.delete '/v1/access_tokens/' + token, {:username => @email, :password => @password}
45
+ end
46
+
47
+ # List all access tokens the user has
48
+ def list_tokens
49
+ @http_caller.get '/v1/access_tokens', nil, {:username => @email, :password => @password}
50
+ end
51
+
52
+ end
53
+ end
data/lib/sparkby.rb ADDED
@@ -0,0 +1,2 @@
1
+ require_relative 'sparkby/spark_core'
2
+ require_relative 'sparkby/token_manager'
@@ -0,0 +1,73 @@
1
+ require 'spec_helper'
2
+
3
+ describe Sparkby::SparkCore do
4
+ context "valid_access_token and device_id" do
5
+ let(:core) {Sparkby::SparkCore.new 'valid_access_token', 'valid_device_id'}
6
+
7
+ describe "#devices" do
8
+
9
+ it "returns a list of devices owned by the user" do
10
+ response = core.devices
11
+ expect(response).to respond_to :each
12
+ end
13
+
14
+ it "should return the id, name, last_app, last_heard, and connected fields for each device" do
15
+ response = core.devices
16
+ response.each do |device|
17
+ expect(device).to include 'id', 'name', 'last_app', 'last_heard', 'connected'
18
+ end
19
+ end
20
+
21
+ end
22
+
23
+ describe "#device_info" do
24
+
25
+ it "should return the id, name, connected, variables, functions and cc3000_patch_version for the device id" do
26
+ response = core.device_info
27
+ expect(response).to include 'id', 'name', 'connected', 'variables', 'functions', 'cc3000_patch_version'
28
+ end
29
+
30
+ end
31
+
32
+ describe "#spark_variable" do
33
+ context "valid_variable_name" do
34
+ let(:variable) {'valid_variable_name'}
35
+
36
+ it "should return the cmd, name, result, and coreInfo fields" do
37
+ response = core.spark_variable variable
38
+ expect(response).to include 'cmd', 'name', 'result', 'coreInfo'
39
+ end
40
+
41
+ it "the variable returned should have the same name as the variable in the request" do
42
+ response = core.spark_variable variable
43
+ expect(response['name']).to eq variable
44
+ end
45
+
46
+ end
47
+
48
+ context "invalid_variable_name" do
49
+ let(:variable) {'invalid_variable_name'}
50
+
51
+ it "should return 'Variable not found' in the error field" do
52
+ response = core.spark_variable variable
53
+ expect(response['error']).to eq 'Variable not found'
54
+ end
55
+
56
+ end
57
+ end
58
+
59
+ describe "#spark_function" do
60
+ context "valid_function_name" do
61
+ let(:function) {'valid_function_name'}
62
+ let(:arguments) {'args'}
63
+
64
+ it "should return id, name, connected, and return_value fields" do
65
+ response = core.spark_function function, arguments
66
+ expect(response).to include 'id', 'name', 'connected', 'return_value'
67
+ end
68
+
69
+ end
70
+ end
71
+
72
+ end
73
+ end
@@ -0,0 +1,12 @@
1
+ require 'webmock/rspec'
2
+ require 'json'
3
+ require_relative '../lib/sparkby'
4
+ require_relative 'support/fake_spark_api'
5
+
6
+ WebMock.disable_net_connect!
7
+
8
+ RSpec.configure do |config|
9
+ config.before(:each) do
10
+ stub_request(:any, /.*api.particle.io(\/v1)?.*/).to_rack(FakeSparkApi)
11
+ end
12
+ end
@@ -0,0 +1,85 @@
1
+ require 'json'
2
+ require 'sinatra/base'
3
+
4
+ class FakeSparkApi < Sinatra::Base
5
+
6
+ helpers do
7
+ def protected!
8
+ return if authorized?
9
+ headers['WWW-Authenticate'] = 'Basic realm="Restricted Area"'
10
+ if @auth.credentials == ['valid_email', 'wrong_password']
11
+ halt 401, {'Content-Type' => 'text/json'}, File.open(File.dirname(__FILE__) + '/fixtures/invalid_password_access_tokens.json', 'rb').read
12
+ elsif @auth.credentials == ['invalid_email', 'correct_password']
13
+ halt 401, {'Content-Type' => 'text/json'}, File.open(File.dirname(__FILE__) + '/fixtures/invalid_email_access_tokens.json', 'rb').read
14
+ end
15
+ end
16
+
17
+ def authorized?
18
+ @auth ||= Rack::Auth::Basic::Request.new(request.env)
19
+ @auth.provided? and @auth.basic? and @auth.credentials and @auth.credentials == ['valid_email', 'correct_password']
20
+ end
21
+
22
+ def check_username_and_password_in_body(body)
23
+ args = Rack::Utils.parse_nested_query(body.read)
24
+ if args["username"] != 'valid_email' or args["password"] != 'correct_password'
25
+ halt 401, {'Content-Type' => 'text/json'}, File.open(File.dirname(__FILE__) + '/fixtures/invalid_email_password_combo.json', 'rb').read
26
+ end
27
+ end
28
+ end
29
+
30
+ get '/v1/devices' do
31
+ json_response 200, 'devices.json'
32
+ end
33
+
34
+ get '/v1/devices/:id' do
35
+ json_response 200, 'device_info.json'
36
+ end
37
+
38
+ get '/v1/devices/:id/:variable' do
39
+ if params[:variable] == 'valid_variable_name'
40
+ json_response 200, 'variable_request.json'
41
+ else
42
+ json_response 404, 'invalid_variable.json'
43
+ end
44
+ end
45
+
46
+ post '/v1/devices/:id/:function' do
47
+ if params[:function] == 'valid_function_name'
48
+ json_response 200, 'function_call.json'
49
+ else
50
+ json_response 400, 'invalid_function_call.json'
51
+ end
52
+ end
53
+
54
+ get '/v1/access_tokens' do
55
+ protected!
56
+ json_response 200, 'get_access_tokens.json'
57
+ end
58
+
59
+ post '/oauth/token' do
60
+ check_username_and_password_in_body(request.body)
61
+ request.body.rewind
62
+ args = Rack::Utils.parse_nested_query(request.body.read)
63
+ if args.has_key?("expires_in")
64
+ json_response 200, 'gen_access_token_expires_in.json'
65
+ elsif args.has_key?("expires_at")
66
+ json_response 200, 'gen_access_token_expires_at.json'
67
+ else
68
+ json_response 200, 'gen_access_token_no_expiry.json'
69
+ end
70
+ end
71
+
72
+ delete '/v1/access_tokens/:token' do
73
+ protected!
74
+ json_response 200, 'delete_access_token.json'
75
+ end
76
+
77
+ private
78
+
79
+ def json_response(code, file_name)
80
+ content_type :json
81
+ status code
82
+ File.open(File.dirname(__FILE__) + '/fixtures/' + file_name, 'rb').read
83
+ end
84
+
85
+ end
@@ -0,0 +1,3 @@
1
+ {
2
+ "ok": true
3
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "id": "core_id",
3
+ "name": "core_name",
4
+ "connected": "true/false",
5
+ "variables": {},
6
+ "functions": [],
7
+ "cc3000_patch_version": "version"
8
+ }
@@ -0,0 +1,16 @@
1
+ [
2
+ {
3
+ "id": "core_id1",
4
+ "name": "core_name1",
5
+ "last_app": "last_app_name",
6
+ "last_heard": "last_heard_time",
7
+ "connected": "true/false"
8
+ },
9
+ {
10
+ "id": "core_id2",
11
+ "name": "core_name2",
12
+ "last_app": "last_app_name",
13
+ "last_heard": "last_heard_time",
14
+ "connected": "true/false"
15
+ }
16
+ ]
@@ -0,0 +1,6 @@
1
+ {
2
+ "id": "access_id",
3
+ "name": "core_name",
4
+ "connected": "true/false",
5
+ "return_value": "function_return_value"
6
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "access_token": "245hfgty67tydh",
3
+ "token_type": "bearer",
4
+ "expires_at": "2020-01-01"
5
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "access_token": "245hfgty67tydh",
3
+ "token_type": "bearer",
4
+ "expires_in": 33600
5
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "access_token": "245hfgty67tydh",
3
+ "token_type": "bearer",
4
+ "expires_in": 300
5
+ }
@@ -0,0 +1,12 @@
1
+ [
2
+ {
3
+ "token": "access_token_1",
4
+ "expires_at": "expiry_time_1",
5
+ "client": "spark"
6
+ },
7
+ {
8
+ "token": "access_token_2",
9
+ "expires_at": "expiry_time_2",
10
+ "client": "spark-ide"
11
+ }
12
+ ]
@@ -0,0 +1,4 @@
1
+ {
2
+ "ok": false,
3
+ "errors": ["Unknown user"]
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "error": "invalid_grant",
3
+ "error_description": "User credentials are invalid"
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "ok": false,
3
+ "errors": ["Bad password"]
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "ok": false,
3
+ "error": "Variable not found"
4
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "cmd": "VarReturn",
3
+ "name": "valid_variable_name",
4
+ "result": 42,
5
+ "coreInfo": {
6
+ "last_app": "",
7
+ "last_heard": "2014-08-22T22:33:25.4072",
8
+ "connected": true,
9
+ "deviceID": "4fdfrt56d"
10
+ }
11
+ }
@@ -0,0 +1,120 @@
1
+ require 'spec_helper'
2
+
3
+ describe Sparkby::TokenManager do
4
+
5
+ describe "#gen_access_token" do
6
+ context "valid email and password" do
7
+ let(:manager) {Sparkby::TokenManager.new 'valid_email', 'correct_password'}
8
+
9
+ context "no expiration arg provided" do
10
+
11
+ it "returns a new access_token in the 'access_token' field" do
12
+ response = manager.gen_access_token
13
+ expect(response).to include 'access_token'
14
+ expect(response["access_token"]).to_not be_nil
15
+ end
16
+
17
+ end
18
+
19
+ context "expiration arg provided" do
20
+ let(:expires_in) { 33600 }
21
+ let(:expires_at) { "2020-01-01" }
22
+
23
+ it "returns a new access_token that expires_in when specified" do
24
+ response = manager.gen_access_token expires_in
25
+ expect(response["expires_in"]).to eq expires_in
26
+ end
27
+
28
+ it "returns a new access_token that expires_at when specified" do
29
+ response = manager.gen_access_token nil, expires_at
30
+ expect(response["expires_at"]).to eq expires_at
31
+ end
32
+ end
33
+ end
34
+
35
+ context "valid email, invalid password" do
36
+ let(:manager) {Sparkby::TokenManager.new 'valid_email', 'wrong_password'}
37
+
38
+ it "returns 'User credentials are invalid' in the response" do
39
+ response = manager.gen_access_token
40
+ expect(response["error_description"]).to eq "User credentials are invalid"
41
+ end
42
+
43
+ end
44
+
45
+ context "invalid email, valid password" do
46
+ let(:manager) {Sparkby::TokenManager.new 'invalid_email', 'correct_password'}
47
+
48
+ it "returns 'User credentials are invalid' in the response" do
49
+ response = manager.gen_access_token
50
+ expect(response["error_description"]).to eq "User credentials are invalid"
51
+ end
52
+
53
+ end
54
+ end
55
+
56
+ describe "#del_access_token" do
57
+ let(:token) { "23456hty78" }
58
+
59
+ context "valid email and password" do
60
+ let(:manager) {Sparkby::TokenManager.new 'valid_email', 'correct_password'}
61
+
62
+ it "responds with the 'ok' field as true" do
63
+ response = manager.del_access_token token
64
+ expect(response["ok"]).to be true
65
+ end
66
+
67
+ end
68
+
69
+ context "valid email, invalid password" do
70
+ let(:manager) {Sparkby::TokenManager.new 'valid_email', 'wrong_password'}
71
+
72
+ it "should return 'Bad password' in the 'errors' field" do
73
+ response = manager.del_access_token token
74
+ expect(response["errors"].pop).to eq "Bad password"
75
+ end
76
+
77
+ end
78
+
79
+ context "invalid email, valid password" do
80
+ let(:manager) {Sparkby::TokenManager.new 'invalid_email', 'correct_password'}
81
+
82
+ it "should return 'Unknown user' in the 'errors' field" do
83
+ response = manager.del_access_token token
84
+ expect(response["errors"].pop).to eq "Unknown user"
85
+ end
86
+ end
87
+ end
88
+
89
+ describe "#list_tokens" do
90
+ context "valid email and password combo" do
91
+ let(:manager) {Sparkby::TokenManager.new 'valid_email', 'correct_password'}
92
+
93
+ it "should return the token, expires_at, and client fields for each access token" do
94
+ response = manager.list_tokens
95
+ response.each do |token|
96
+ expect(token).to include 'token', 'expires_at', 'client'
97
+ end
98
+ end
99
+ end
100
+
101
+ context "valid email, invalid password" do
102
+ let(:manager) {Sparkby::TokenManager.new 'valid_email', 'wrong_password'}
103
+
104
+ it "should return 'Bad password' in the 'errors' field" do
105
+ response = manager.list_tokens
106
+ expect(response["errors"].pop).to eq "Bad password"
107
+ end
108
+ end
109
+
110
+ context "invalid email, valid password" do
111
+ let(:manager) {Sparkby::TokenManager.new 'invalid_email', 'correct_password'}
112
+
113
+ it "should return 'Unknown user' in the 'errors' field" do
114
+ response = manager.list_tokens
115
+ expect(response["errors"].pop).to eq "Unknown user"
116
+ end
117
+ end
118
+
119
+ end
120
+ end
metadata ADDED
@@ -0,0 +1,123 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sparkby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Karl Parkinson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-05-31 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: httparty
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.13.3
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.13.3
27
+ - !ruby/object:Gem::Dependency
28
+ name: webmock
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 1.20.4
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 1.20.4
41
+ - !ruby/object:Gem::Dependency
42
+ name: sinatra
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 1.4.5
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 1.4.5
55
+ description: Ruby wrapper around the Particle cloud API
56
+ email: kparkins@ualberta.ca
57
+ executables: []
58
+ extensions: []
59
+ extra_rdoc_files: []
60
+ files:
61
+ - lib/sparkby.rb
62
+ - lib/sparkby/http_caller.rb
63
+ - lib/sparkby/spark_core.rb
64
+ - lib/sparkby/token_manager.rb
65
+ - spec/spark_core_spec.rb
66
+ - spec/spec_helper.rb
67
+ - spec/support/fake_spark_api.rb
68
+ - spec/support/fixtures/delete_access_token.json
69
+ - spec/support/fixtures/device_info.json
70
+ - spec/support/fixtures/devices.json
71
+ - spec/support/fixtures/function_call.json
72
+ - spec/support/fixtures/gen_access_token_expires_at.json
73
+ - spec/support/fixtures/gen_access_token_expires_in.json
74
+ - spec/support/fixtures/gen_access_token_no_expiry.json
75
+ - spec/support/fixtures/get_access_tokens.json
76
+ - spec/support/fixtures/invalid_email_access_tokens.json
77
+ - spec/support/fixtures/invalid_email_password_combo.json
78
+ - spec/support/fixtures/invalid_password_access_tokens.json
79
+ - spec/support/fixtures/invalid_variable.json
80
+ - spec/support/fixtures/variable_request.json
81
+ - spec/token_manager_spec.rb
82
+ homepage: https://github.com/KarlParkinson/sparkby
83
+ licenses:
84
+ - GPL
85
+ metadata: {}
86
+ post_install_message:
87
+ rdoc_options: []
88
+ require_paths:
89
+ - lib
90
+ required_ruby_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ required_rubygems_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ requirements: []
101
+ rubyforge_project:
102
+ rubygems_version: 2.4.6
103
+ signing_key:
104
+ specification_version: 4
105
+ summary: Ruby wrapper around the Particle cloud API
106
+ test_files:
107
+ - spec/support/fake_spark_api.rb
108
+ - spec/spark_core_spec.rb
109
+ - spec/token_manager_spec.rb
110
+ - spec/spec_helper.rb
111
+ - spec/support/fixtures/device_info.json
112
+ - spec/support/fixtures/invalid_password_access_tokens.json
113
+ - spec/support/fixtures/gen_access_token_expires_at.json
114
+ - spec/support/fixtures/invalid_email_password_combo.json
115
+ - spec/support/fixtures/gen_access_token_no_expiry.json
116
+ - spec/support/fixtures/variable_request.json
117
+ - spec/support/fixtures/delete_access_token.json
118
+ - spec/support/fixtures/function_call.json
119
+ - spec/support/fixtures/invalid_email_access_tokens.json
120
+ - spec/support/fixtures/invalid_variable.json
121
+ - spec/support/fixtures/get_access_tokens.json
122
+ - spec/support/fixtures/gen_access_token_expires_in.json
123
+ - spec/support/fixtures/devices.json