withings-api 0.0.3
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.
- data/.gitignore +9 -0
- data/.simplecov +5 -0
- data/Gemfile +4 -0
- data/LICENSE +7 -0
- data/README.rdoc +84 -0
- data/Rakefile +14 -0
- data/cucumber.yml +2 -0
- data/examples/callback_landing.txt +1 -0
- data/examples/create_access_token.rb +62 -0
- data/features/get_access_token.feature +70 -0
- data/features/get_request_token.feature +68 -0
- data/features/measure_getmeas_api.feature +16 -0
- data/features/step_definitions/api.rb +113 -0
- data/features/support/method_mocker.rb +36 -0
- data/features/support/world.rb +34 -0
- data/lib/withings-api.rb +19 -0
- data/lib/withings-api/api_actions.rb +27 -0
- data/lib/withings-api/api_response.rb +23 -0
- data/lib/withings-api/consts.rb +12 -0
- data/lib/withings-api/errors.rb +23 -0
- data/lib/withings-api/oauth_actions.rb +94 -0
- data/lib/withings-api/oauth_base.rb +121 -0
- data/lib/withings-api/query_string.rb +16 -0
- data/lib/withings-api/results/measure_getmeas_results.rb +73 -0
- data/lib/withings-api/tokens.rb +35 -0
- data/lib/withings-api/types.rb +108 -0
- data/lib/withings-api/utils.rb +14 -0
- data/lib/withings-api/version.rb +5 -0
- data/spec/api_actions/measure_getmeas_spec.rb +22 -0
- data/spec/measure_getmeas_results_spec.rb +124 -0
- data/spec/method_aliaser_spec.rb +96 -0
- data/spec/query_string_spec.rb +20 -0
- data/spec/spec_helper.rb +30 -0
- data/spec/tokens_spec.rb +38 -0
- data/spec/types/atttribution_type_spec.rb +15 -0
- data/spec/types/category_type_spec.rb +15 -0
- data/spec/types/device_type_spec.rb +15 -0
- data/spec/types/measurement_type_spec.rb +15 -0
- data/spec/withings_api_spec.rb +67 -0
- data/test/README +1 -0
- data/test/helpers/http_stubber.rb +32 -0
- data/test/helpers/method_aliaser.rb +48 -0
- data/test/helpers/stubbed_withings_api.rb +41 -0
- data/test/http_stub_responses/access_token/invalid.txt +10 -0
- data/test/http_stub_responses/access_token/success.txt +11 -0
- data/test/http_stub_responses/access_token/unauthorized_token.txt +11 -0
- data/test/http_stub_responses/authorization_callback/success.txt +9 -0
- data/test/http_stub_responses/measure_getmeas/forbidden.txt +12 -0
- data/test/http_stub_responses/measure_getmeas/oauth_error.txt +9 -0
- data/test/http_stub_responses/measure_getmeas/success.txt +9 -0
- data/test/http_stub_responses/request_token/invalid_consumer_credentials.txt +10 -0
- data/test/http_stub_responses/request_token/success.txt +11 -0
- data/test/sample_json/measure_getmeas.json +49 -0
- data/test/sample_json/notify_get.json +7 -0
- data/test/sample_json/notify_list.json +15 -0
- data/test/sample_json/notify_revoke.json +3 -0
- data/test/sample_json/notify_subscribe.json +3 -0
- data/test/sample_json/once_probe.json +1 -0
- data/test/sample_json/user_getbyuserid.json +16 -0
- data/withings-api.gemspec +32 -0
- metadata +220 -0
@@ -0,0 +1,35 @@
|
|
1
|
+
module Withings
|
2
|
+
module Api
|
3
|
+
# Encapsulates a consumer app key/secret pair
|
4
|
+
class ConsumerToken
|
5
|
+
attr_accessor :key, :secret
|
6
|
+
|
7
|
+
# Multiple variations
|
8
|
+
#
|
9
|
+
# - #initialize()
|
10
|
+
# - #initialize(key, secret)
|
11
|
+
def initialize(*arguments)
|
12
|
+
if arguments.length == 0
|
13
|
+
elsif arguments.length == 2
|
14
|
+
self.key = arguments.shift
|
15
|
+
self.secret = arguments.shift
|
16
|
+
else
|
17
|
+
raise ArgumentError
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_a
|
22
|
+
[key, secret]
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
class RequestToken < ConsumerToken
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
class AccessToken < ConsumerToken
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module Withings
|
2
|
+
module Api
|
3
|
+
|
4
|
+
class TypeBase
|
5
|
+
attr_accessor :id, :name, :description
|
6
|
+
|
7
|
+
def initialize(id, name, description)
|
8
|
+
self.id = id
|
9
|
+
self.name = name
|
10
|
+
self.description = description
|
11
|
+
end
|
12
|
+
|
13
|
+
def inspect
|
14
|
+
self.description
|
15
|
+
end
|
16
|
+
|
17
|
+
#
|
18
|
+
# Lookup helpers for static types
|
19
|
+
#
|
20
|
+
|
21
|
+
@@lookup_by_id = {}
|
22
|
+
@@lookup_by_name = {}
|
23
|
+
|
24
|
+
def self.register(instance)
|
25
|
+
lookup_by_id = @@lookup_by_id[self] ||= {}
|
26
|
+
lookup_by_name = @@lookup_by_name[self] ||= {}
|
27
|
+
|
28
|
+
lookup_by_id[instance.id] = instance
|
29
|
+
lookup_by_name[instance.name] = instance
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.lookup(key)
|
33
|
+
lookup_by_id = @@lookup_by_id[self] ||= {}
|
34
|
+
lookup_by_name = @@lookup_by_name[self] ||= {}
|
35
|
+
|
36
|
+
lookup_by_id[key] || lookup_by_name[key]
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
class MeasurementType < TypeBase
|
43
|
+
end
|
44
|
+
|
45
|
+
class DeviceType < TypeBase
|
46
|
+
end
|
47
|
+
|
48
|
+
class CategoryType < TypeBase
|
49
|
+
end
|
50
|
+
|
51
|
+
class AttributionType < TypeBase
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
# TODO: Try to figure out how to get calls to this documented in yard
|
57
|
+
def self.measurement_type(name, description, id)
|
58
|
+
instance = MeasurementType.new(id, name, description)
|
59
|
+
|
60
|
+
MeasurementType.const_set(name, instance)
|
61
|
+
MeasurementType.register(instance)
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.device_type(name, description, id)
|
65
|
+
instance = DeviceType.new(id, name, description)
|
66
|
+
|
67
|
+
DeviceType.const_set(name, instance)
|
68
|
+
DeviceType.register(instance)
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.category_type(name, id, description = nil)
|
72
|
+
instance = CategoryType.new(id, name, description)
|
73
|
+
|
74
|
+
CategoryType.const_set(name, instance);
|
75
|
+
CategoryType.register(instance)
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.attribution_type(name, id, description = nil)
|
79
|
+
instance = AttributionType.new(id, name, description)
|
80
|
+
|
81
|
+
AttributionType.const_set(name, instance)
|
82
|
+
AttributionType.register(instance)
|
83
|
+
end
|
84
|
+
|
85
|
+
public
|
86
|
+
|
87
|
+
measurement_type :WEIGHT, "Weight (kg)", 1
|
88
|
+
measurement_type :HEIGHT, "Height (meter)", 4
|
89
|
+
measurement_type :FAT_FREE_MASS, "Fat Free Mass (kg)", 5
|
90
|
+
measurement_type :FAT_RATIO, "Fat Ratio (%)", 6
|
91
|
+
measurement_type :FAT_MASS, "Fat Mass Weight (kg)", 8
|
92
|
+
measurement_type :DIASTOLIC_BLOOD_PRESSURE, "Diastolic Blood Pressure (mmHg)", 9
|
93
|
+
measurement_type :SYSTOLIC_BLOOD_PRESSURE, "Systolic Blood Pressure (mmHg)", 10
|
94
|
+
measurement_type :HEART_PULSE, "Heart Pulse (bpm)", 11
|
95
|
+
|
96
|
+
device_type :USER, "User related", 0
|
97
|
+
device_type :BODY_SCALE, "Body scale", 1
|
98
|
+
device_type :BLOOD_PRESSURE_MONITOR, "Blood pressure monitor", 4
|
99
|
+
|
100
|
+
category_type :MEASURE, 1
|
101
|
+
category_type :TARGET, 2
|
102
|
+
|
103
|
+
attribution_type :DEVICE, 0, "The measuregroup has been captured by a device and is known to belong to this user (and is not ambiguous)"
|
104
|
+
attribution_type :DEVICE_AMBIGUOUS, 1, "The measuregroup has been captured by a device but may belong to other users as well as this one (it is ambiguous)"
|
105
|
+
attribution_type :MANUAL, 2, "The measuregroup has been entered manually for this particular user"
|
106
|
+
attribution_type :MANUAL_AT_CREATION, 4, "The measuregroup has been entered manually during user creation (and may not be accurate)"
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
describe "Withings::Api.measure_getmeas()" do
|
5
|
+
let(:valid_consumer_token) { CONSUMER_CREDENTIALS[:valid] }
|
6
|
+
let(:valid_access_token) { ACCESS_TOKENS[:valid] }
|
7
|
+
|
8
|
+
context "Stubbed" do
|
9
|
+
context "Valid" do
|
10
|
+
before(:each) do
|
11
|
+
stub_http_with_canned("measure_getmeas/success")
|
12
|
+
puts_http
|
13
|
+
end
|
14
|
+
|
15
|
+
it "Succeeds With No Parameters" do
|
16
|
+
api_response = Withings::Api.measure_getmeas({:userid => 766103}, valid_access_token, valid_consumer_token)
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
MEASURE_GETMEAS_SAMPLE_HASH = JSON::parse(File.read(File.join(SAMPLE_JSON_DIR, "measure_getmeas.json")))["body"]
|
4
|
+
|
5
|
+
#
|
6
|
+
# Measurement
|
7
|
+
#
|
8
|
+
|
9
|
+
describe API::Measurement do
|
10
|
+
#"value":79300,
|
11
|
+
#"type":1,
|
12
|
+
#"unit":-3
|
13
|
+
shared_examples_for "Sample JSON Measurement" do
|
14
|
+
it "value_raw == 79300" do
|
15
|
+
subject.value_raw.should == 79300
|
16
|
+
end
|
17
|
+
|
18
|
+
it "type == MeasurementType::WEIGHT" do
|
19
|
+
subject.measurement_type.should == API::MeasurementType::WEIGHT
|
20
|
+
end
|
21
|
+
|
22
|
+
it "unit == -3" do
|
23
|
+
subject.unit.should == -3
|
24
|
+
end
|
25
|
+
|
26
|
+
it "value == 79.3" do
|
27
|
+
subject.value.should == 79.3
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context "Measurement Sample JSON from Hash" do
|
32
|
+
subject { API::Measurement.new(MEASURE_GETMEAS_SAMPLE_HASH["measuregrps"].first["measures"].first) }
|
33
|
+
|
34
|
+
it_behaves_like "Sample JSON Measurement"
|
35
|
+
end
|
36
|
+
|
37
|
+
context "Measurement Sample JSON from String" do
|
38
|
+
subject { API::Measurement.new(MEASURE_GETMEAS_SAMPLE_HASH["measuregrps"].first["measures"].first) }
|
39
|
+
|
40
|
+
it_behaves_like "Sample JSON Measurement"
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
describe API::MeasurementGroup do
|
47
|
+
#"grpid":2909,
|
48
|
+
#"attrib":0,
|
49
|
+
#"date":1222930968,
|
50
|
+
#"category":1,
|
51
|
+
#"measures":[
|
52
|
+
shared_examples_for "Sample JSON MeasurementGroup" do
|
53
|
+
it "id == 2909" do
|
54
|
+
subject.id.should == 2909
|
55
|
+
end
|
56
|
+
|
57
|
+
it "attribution == AttributionType::DEVICE" do
|
58
|
+
subject.attribution.should == API::AttributionType::DEVICE
|
59
|
+
end
|
60
|
+
|
61
|
+
it "category == CategoryType::MEASURE" do
|
62
|
+
subject.category.should == API::CategoryType::MEASURE
|
63
|
+
end
|
64
|
+
|
65
|
+
it "date_raw == 1222930968" do
|
66
|
+
subject.date_raw.should == 1222930968
|
67
|
+
end
|
68
|
+
|
69
|
+
it "date == Time.at(1222930968)" do
|
70
|
+
subject.date.should == Time.at(1222930968)
|
71
|
+
end
|
72
|
+
|
73
|
+
it "measurements == []" do
|
74
|
+
subject.measurements.should be_instance_of Array
|
75
|
+
subject.measurements.length.should == 4
|
76
|
+
subject.measurements.each { |m| m.should be_instance_of API::Measurement }
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
subject { API::MeasurementGroup.new(MEASURE_GETMEAS_SAMPLE_HASH["measuregrps"].first) }
|
81
|
+
|
82
|
+
it_behaves_like "Sample JSON MeasurementGroup"
|
83
|
+
end
|
84
|
+
|
85
|
+
#
|
86
|
+
# MeasureGetmeasResults
|
87
|
+
#
|
88
|
+
|
89
|
+
describe API::MeasureGetmeasResults do
|
90
|
+
shared_examples_for "Sample Measure/Getmeas Result" do
|
91
|
+
it "update_time_raw Should Be 1249409679" do
|
92
|
+
subject.update_time_raw.should be(1249409679)
|
93
|
+
end
|
94
|
+
|
95
|
+
it "update_time == Time.at(1249409679)" do
|
96
|
+
subject.update_time.should == Time.at(1249409679)
|
97
|
+
end
|
98
|
+
|
99
|
+
it "more? Should Be false" do
|
100
|
+
subject.should_not be_more
|
101
|
+
end
|
102
|
+
|
103
|
+
it "measure_groups" do
|
104
|
+
measure_groups = subject.measure_groups
|
105
|
+
|
106
|
+
measure_groups.should be_instance_of Array
|
107
|
+
measure_groups.length.should == 2
|
108
|
+
measure_groups.each { |mg| mg.should be_instance_of API::MeasurementGroup }
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
context "Constructor From Sample JSON String" do
|
113
|
+
subject { API::MeasureGetmeasResults.new(MEASURE_GETMEAS_SAMPLE_HASH.to_json) }
|
114
|
+
|
115
|
+
it_behaves_like "Sample Measure/Getmeas Result"
|
116
|
+
end
|
117
|
+
|
118
|
+
context "Constructor From Sample JSON Hash" do
|
119
|
+
subject { API::MeasureGetmeasResults.new(MEASURE_GETMEAS_SAMPLE_HASH) }
|
120
|
+
|
121
|
+
it_behaves_like "Sample Measure/Getmeas Result"
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class A
|
4
|
+
def value(v = nil)
|
5
|
+
v || 'a'
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
describe MethodAliaser do
|
10
|
+
after(:each) do
|
11
|
+
# undo a previously applied alias
|
12
|
+
while @aliased && ! @aliased.empty?
|
13
|
+
@aliased.shift.unalias_it
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def alias_value(&block)
|
18
|
+
@aliased ||= []
|
19
|
+
@aliased << MethodAliaser.alias_it(A, :value, &block)
|
20
|
+
end
|
21
|
+
|
22
|
+
def new_value
|
23
|
+
A.new.value
|
24
|
+
end
|
25
|
+
|
26
|
+
def new_value_with_parameter(p)
|
27
|
+
A.new.value(p)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "Can Alias Method To Return Nil" do
|
31
|
+
alias_value { |aliased, *arguments| }
|
32
|
+
|
33
|
+
new_value().should == nil
|
34
|
+
end
|
35
|
+
|
36
|
+
it "Can Alias Method To Return Aribitrary Value" do
|
37
|
+
alias_value { |aliased, *arguments| "b" }
|
38
|
+
|
39
|
+
new_value().should == "b"
|
40
|
+
end
|
41
|
+
|
42
|
+
it "Can Have Alias Implementation Delegate To The Original Method" do
|
43
|
+
alias_value { |aliased, *arguments| aliased.call(*arguments) }
|
44
|
+
|
45
|
+
new_value().should == "a"
|
46
|
+
end
|
47
|
+
|
48
|
+
it "Can Have Alias Implementation Use The Original Method Return Value In It's Return Value'" do
|
49
|
+
alias_value { |aliased, *arguments| "#{aliased.call(*arguments)}b" }
|
50
|
+
|
51
|
+
new_value.should == "ab"
|
52
|
+
end
|
53
|
+
|
54
|
+
it "Can Pass The Arguments To the Original Method" do
|
55
|
+
alias_value { |aliased, *arguments| "#{aliased.call(*arguments)}"}
|
56
|
+
|
57
|
+
new_value_with_parameter("b").should == "b"
|
58
|
+
end
|
59
|
+
|
60
|
+
it "Can Overwrite Arguments When Calling The Original Method" do
|
61
|
+
alias_value { |aliased, *arguments| "#{aliased.call(*['c'])}"}
|
62
|
+
|
63
|
+
new_value_with_parameter("b").should == "c"
|
64
|
+
end
|
65
|
+
|
66
|
+
it "Can Have An Alias Implementation That Removes Itself" do
|
67
|
+
wrapped = MethodAliaser.alias_it(A, :value) do |aliased, *arguments|
|
68
|
+
wrapped.unalias_it
|
69
|
+
|
70
|
+
"b"
|
71
|
+
end
|
72
|
+
|
73
|
+
A.new.value.should == 'b'
|
74
|
+
A.new.value.should == 'a'
|
75
|
+
A.new.value.should == 'a'
|
76
|
+
end
|
77
|
+
|
78
|
+
it "Can Nest Multiple Alias Implementations That Removes Itself" do
|
79
|
+
wrapped1 = MethodAliaser.alias_it(A, :value) do |aliased, *arguments|
|
80
|
+
wrapped1.unalias_it
|
81
|
+
|
82
|
+
"b"
|
83
|
+
end
|
84
|
+
|
85
|
+
wrapped2 = MethodAliaser.alias_it(A, :value) do |aliased, *arguments|
|
86
|
+
wrapped2.unalias_it
|
87
|
+
|
88
|
+
"c"
|
89
|
+
end
|
90
|
+
|
91
|
+
A.new.value.should == 'c'
|
92
|
+
A.new.value.should == 'b'
|
93
|
+
A.new.value.should == 'a'
|
94
|
+
A.new.value.should == 'a'
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Hash#to_query_string" do
|
4
|
+
context "Expected Results" do
|
5
|
+
SAMPLES = {
|
6
|
+
{} => "",
|
7
|
+
{:name => :Eric} => "name=Eric",
|
8
|
+
{"count" => 69 } => "count=69",
|
9
|
+
{:first => "eric", :last => "webb", "age" => 30} => "first=eric&last=webb&age=30",
|
10
|
+
{ :special => ",./<>?=\"&" } => "special=%2C.%2F%3C%3E%3F%3D%22%26",
|
11
|
+
}
|
12
|
+
|
13
|
+
SAMPLES.each_key do |key|
|
14
|
+
expect = SAMPLES[key]
|
15
|
+
it "#{key.inspect} => \"#{expect}\"" do
|
16
|
+
key.to_query_string.should == expect
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# code coverage
|
2
|
+
require 'simplecov'
|
3
|
+
SimpleCov.start
|
4
|
+
|
5
|
+
require 'withings-api'
|
6
|
+
|
7
|
+
TEST_RESOURCES = File.expand_path("../test/", File.dirname(__FILE__))
|
8
|
+
SAMPLE_JSON_DIR = File.expand_path("../test/sample_json", File.dirname(__FILE__))
|
9
|
+
|
10
|
+
require_relative "../test/helpers/http_stubber"
|
11
|
+
require_relative "../test/helpers/stubbed_withings_api"
|
12
|
+
|
13
|
+
API = Withings::Api
|
14
|
+
API_MODULE = API
|
15
|
+
|
16
|
+
def puts_http
|
17
|
+
MethodAliaser.alias_it(Net::HTTP, :transport_request) do |aliased, *arguments|
|
18
|
+
puts "HTTP Request: #{arguments.first.path}"
|
19
|
+
res = aliased.call(*arguments)
|
20
|
+
puts "HTTP Response:"
|
21
|
+
puts "HTTP/#{res.http_version} #{res.code} #{res.message}"
|
22
|
+
res.to_hash.each_pair do |key, value|
|
23
|
+
puts "#{key}: #{value.join("; ")}"
|
24
|
+
end
|
25
|
+
puts ""
|
26
|
+
puts res.body
|
27
|
+
|
28
|
+
res
|
29
|
+
end
|
30
|
+
end
|