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.
Files changed (61) hide show
  1. data/.gitignore +9 -0
  2. data/.simplecov +5 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE +7 -0
  5. data/README.rdoc +84 -0
  6. data/Rakefile +14 -0
  7. data/cucumber.yml +2 -0
  8. data/examples/callback_landing.txt +1 -0
  9. data/examples/create_access_token.rb +62 -0
  10. data/features/get_access_token.feature +70 -0
  11. data/features/get_request_token.feature +68 -0
  12. data/features/measure_getmeas_api.feature +16 -0
  13. data/features/step_definitions/api.rb +113 -0
  14. data/features/support/method_mocker.rb +36 -0
  15. data/features/support/world.rb +34 -0
  16. data/lib/withings-api.rb +19 -0
  17. data/lib/withings-api/api_actions.rb +27 -0
  18. data/lib/withings-api/api_response.rb +23 -0
  19. data/lib/withings-api/consts.rb +12 -0
  20. data/lib/withings-api/errors.rb +23 -0
  21. data/lib/withings-api/oauth_actions.rb +94 -0
  22. data/lib/withings-api/oauth_base.rb +121 -0
  23. data/lib/withings-api/query_string.rb +16 -0
  24. data/lib/withings-api/results/measure_getmeas_results.rb +73 -0
  25. data/lib/withings-api/tokens.rb +35 -0
  26. data/lib/withings-api/types.rb +108 -0
  27. data/lib/withings-api/utils.rb +14 -0
  28. data/lib/withings-api/version.rb +5 -0
  29. data/spec/api_actions/measure_getmeas_spec.rb +22 -0
  30. data/spec/measure_getmeas_results_spec.rb +124 -0
  31. data/spec/method_aliaser_spec.rb +96 -0
  32. data/spec/query_string_spec.rb +20 -0
  33. data/spec/spec_helper.rb +30 -0
  34. data/spec/tokens_spec.rb +38 -0
  35. data/spec/types/atttribution_type_spec.rb +15 -0
  36. data/spec/types/category_type_spec.rb +15 -0
  37. data/spec/types/device_type_spec.rb +15 -0
  38. data/spec/types/measurement_type_spec.rb +15 -0
  39. data/spec/withings_api_spec.rb +67 -0
  40. data/test/README +1 -0
  41. data/test/helpers/http_stubber.rb +32 -0
  42. data/test/helpers/method_aliaser.rb +48 -0
  43. data/test/helpers/stubbed_withings_api.rb +41 -0
  44. data/test/http_stub_responses/access_token/invalid.txt +10 -0
  45. data/test/http_stub_responses/access_token/success.txt +11 -0
  46. data/test/http_stub_responses/access_token/unauthorized_token.txt +11 -0
  47. data/test/http_stub_responses/authorization_callback/success.txt +9 -0
  48. data/test/http_stub_responses/measure_getmeas/forbidden.txt +12 -0
  49. data/test/http_stub_responses/measure_getmeas/oauth_error.txt +9 -0
  50. data/test/http_stub_responses/measure_getmeas/success.txt +9 -0
  51. data/test/http_stub_responses/request_token/invalid_consumer_credentials.txt +10 -0
  52. data/test/http_stub_responses/request_token/success.txt +11 -0
  53. data/test/sample_json/measure_getmeas.json +49 -0
  54. data/test/sample_json/notify_get.json +7 -0
  55. data/test/sample_json/notify_list.json +15 -0
  56. data/test/sample_json/notify_revoke.json +3 -0
  57. data/test/sample_json/notify_subscribe.json +3 -0
  58. data/test/sample_json/once_probe.json +1 -0
  59. data/test/sample_json/user_getbyuserid.json +16 -0
  60. data/withings-api.gemspec +32 -0
  61. 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,14 @@
1
+ module Withings::Api
2
+ module ResultsHelpers
3
+ # @raise [JSON::ParserError]
4
+ def coerce_hash(o)
5
+ if o.instance_of? Hash
6
+ o
7
+ elsif o.instance_of? String
8
+ JSON::parse(o)
9
+ else
10
+ nil
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,5 @@
1
+ module Withings
2
+ module Api
3
+ VERSION = "0.0.3"
4
+ end
5
+ 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
@@ -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