miiCardConsumers 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,377 @@
1
+ require "oauth"
2
+ require "json"
3
+
4
+ # Describes the overall status of an API call.
5
+ module MiiApiCallStatus
6
+ # The API call succeeded - the associated result information can be found
7
+ # in the data property of the response object.
8
+ SUCCESS = 0
9
+ # The API call failed. You can get more information about the nature of
10
+ # the failure by examining the error_code property.
11
+ FAILURE = 1
12
+ end
13
+
14
+ # Details a specific problem that occurred when accessing the API.
15
+ module MiiApiErrorCode
16
+ # The API call succeeded.
17
+ SUCCESS = 0
18
+ # The user has revoked access to your application. The user would
19
+ # have to repeat the OAuth authorisation process before access would be
20
+ # restored to your application.
21
+ ACCESS_REVOKED = 100
22
+ # The user's miiCard subscription has elapsed. Only users with a current
23
+ # subscription can share their data with other applications and websites.
24
+ USER_SUBSCRIPTION_LAPSED = 200
25
+ # A general exception occurred during processing - details may be available
26
+ # in the error_message property of the response object depending upon the
27
+ # nature of the exception.
28
+ EXCEPTION = 10000
29
+ end
30
+
31
+ # Describes the overall status of an API call.
32
+ module WebPropertyType
33
+ # Indicates that the WebProperty relates to a domain name.
34
+ DOMAIN = 0
35
+ # Indicates that the WebProperty relates to a website.
36
+ WEBSITE = 1
37
+ end
38
+
39
+ # Base class for most verifiable identity data.
40
+ class Claim
41
+ attr_accessor :verified
42
+
43
+ def initialize(verified)
44
+ @verified = verified
45
+ end
46
+ end
47
+
48
+ class Identity < Claim
49
+ attr_accessor :source, :user_id, :profile_url
50
+
51
+ def initialize(verified, source, user_id, profile_url)
52
+ super(verified)
53
+
54
+ @source = source
55
+ @user_id = user_id
56
+ @profile_url = profile_url
57
+ end
58
+
59
+ def self.from_hash(hash)
60
+ return Identity.new(
61
+ hash["Verified"],
62
+ hash["Source"],
63
+ hash["UserId"],
64
+ hash["ProfileUrl"]
65
+ )
66
+ end
67
+ end
68
+
69
+ class EmailAddress < Claim
70
+ attr_accessor :display_name, :address, :is_primary
71
+
72
+ def initialize(verified, display_name, address, is_primary)
73
+ super(verified)
74
+
75
+ @display_name = display_name
76
+ @address = address
77
+ @is_primary = is_primary
78
+ end
79
+
80
+ def self.from_hash(hash)
81
+ return EmailAddress.new(
82
+ hash["Verified"],
83
+ hash["DisplayName"],
84
+ hash["Address"],
85
+ hash["IsPrimary"]
86
+ )
87
+ end
88
+ end
89
+
90
+ class PhoneNumber < Claim
91
+ attr_accessor :display_name, :country_code, :national_number, :is_mobile, :is_primary
92
+
93
+ def initialize(verified, display_name, country_code, national_number, is_mobile, is_primary)
94
+ super(verified)
95
+
96
+ @display_name = display_name
97
+ @country_code = country_code
98
+ @national_number = national_number
99
+ @is_mobile = is_mobile
100
+ @is_primary = is_primary
101
+ end
102
+
103
+ def self.from_hash(hash)
104
+ return PhoneNumber.new(
105
+ hash["Verified"],
106
+ hash["DisplayName"],
107
+ hash["CountryCode"],
108
+ hash["NationalNumber"],
109
+ hash["IsMobile"],
110
+ hash["IsPrimary"]
111
+ )
112
+ end
113
+ end
114
+
115
+ class PostalAddress < Claim
116
+ attr_accessor :house, :line1, :line2, :city, :region, :code, :country, :is_primary
117
+
118
+ def initialize(verified, house, line1, line2, city, region, code, country, is_primary)
119
+ super(verified)
120
+
121
+ @house = house
122
+ @line1 = line1
123
+ @line2 = line2
124
+ @city = city
125
+ @region = region
126
+ @code = code
127
+ @country = country
128
+ @is_primary = is_primary
129
+ end
130
+
131
+ def self.from_hash(hash)
132
+ return PostalAddress.new(
133
+ hash["Verified"],
134
+ hash["House"],
135
+ hash["Line1"],
136
+ hash["Line2"],
137
+ hash["City"],
138
+ hash["Region"],
139
+ hash["Code"],
140
+ hash["Country"],
141
+ hash["IsPrimary"]
142
+ )
143
+ end
144
+ end
145
+
146
+ class WebProperty < Claim
147
+ attr_accessor :display_name, :identifier, :type
148
+
149
+ def initialize(verified, display_name, identifier, type)
150
+ super(verified)
151
+
152
+ @display_name = display_name
153
+ @identifier = identifier
154
+ @type = type
155
+ end
156
+
157
+ def self.from_hash(hash)
158
+ return WebProperty.new(
159
+ hash["Verified"],
160
+ hash["DisplayName"],
161
+ hash["Identifier"],
162
+ hash["Type"]
163
+ )
164
+ end
165
+ end
166
+
167
+ class MiiUserProfile
168
+ attr_accessor :username, :salutation, :first_name, :middle_name, :last_name
169
+ attr_accessor :previous_first_name, :previous_middle_name, :previous_last_name
170
+ attr_accessor :last_verified, :profile_url, :profile_short_url, :card_image_url, :email_addresses, :identities, :postal_addresses
171
+ attr_accessor :phone_numbers, :web_properties, :identity_assured, :has_public_profile
172
+ attr_accessor :public_profile
173
+
174
+ def initialize(
175
+ username,
176
+ salutation,
177
+ first_name,
178
+ middle_name,
179
+ last_name,
180
+ previous_first_name,
181
+ previous_middle_name,
182
+ previous_last_name,
183
+ last_verified,
184
+ profile_url,
185
+ profile_short_url,
186
+ card_image_url,
187
+ email_addresses,
188
+ identities,
189
+ phone_numbers,
190
+ postal_addresses,
191
+ web_properties,
192
+ identity_assured,
193
+ has_public_profile,
194
+ public_profile
195
+ )
196
+
197
+ @username= username
198
+ @salutation = salutation
199
+ @first_name = first_name
200
+ @middle_name = middle_name
201
+ @last_name = last_name
202
+ @previous_first_name = previous_first_name
203
+ @previous_middle_name = previous_middle_name
204
+ @previous_last_name = previous_last_name
205
+ @last_verified = last_verified
206
+ @profile_url = profile_url
207
+ @profile_short_url = profile_short_url
208
+ @card_image_url = card_image_url
209
+ @email_addresses = email_addresses
210
+ @identities = identities
211
+ @phone_numbers = phone_numbers
212
+ @postal_addresses = postal_addresses
213
+ @web_properties = web_properties
214
+ @identity_assured = identity_assured
215
+ @has_public_profile = has_public_profile
216
+ @public_profile = public_profile
217
+ end
218
+
219
+ def self.from_hash(hash)
220
+ emails = hash["EmailAddresses"]
221
+ emails_parsed = nil
222
+ unless (emails.nil? || emails.empty?)
223
+ emails_parsed = emails.map{|item| EmailAddress::from_hash(item)}
224
+ end
225
+
226
+ identities = hash["Identities"]
227
+ identities_parsed = nil
228
+ unless (identities.nil? || identities.empty?)
229
+ identities_parsed = identities.map{|item| Identity::from_hash(item)}
230
+ end
231
+
232
+ phone_numbers = hash["PhoneNumbers"]
233
+ phone_numbers_parsed = nil
234
+ unless (phone_numbers.nil? || phone_numbers.empty?)
235
+ phone_numbers_parsed = phone_numbers.map{|item| PhoneNumber::from_hash(item)}
236
+ end
237
+
238
+ postal_addresses = hash["PostalAddresses"]
239
+ postal_addresses_parsed = nil
240
+ unless (postal_addresses.nil? || postal_addresses.empty?)
241
+ postal_addresses_parsed = postal_addresses.map{|item| PostalAddress::from_hash(item)}
242
+ end
243
+
244
+ web_properties = hash["WebProperties"]
245
+ web_properties_parsed = nil
246
+ unless (web_properties.nil? || web_properties.empty?)
247
+ web_properties_parsed = web_properties.map{|item| WebProperty::from_hash(item)}
248
+ end
249
+
250
+ public_profile = hash["PublicProfile"]
251
+ public_profile_parsed = nil
252
+ unless public_profile.nil?
253
+ public_profile_parsed = MiiUserProfile::from_hash(public_profile)
254
+ end
255
+
256
+ return MiiUserProfile.new(
257
+ hash['Username'],
258
+ hash['Salutation'],
259
+ hash['FirstName'],
260
+ hash['MiddleName'],
261
+ hash['LastName'],
262
+ hash['PreviousFirstName'],
263
+ hash['PreviousMiddleName'],
264
+ hash['PreviousLastName'],
265
+ hash['LastVerified'],
266
+ hash['ProfileUrl'],
267
+ hash['ProfileShortUrl'],
268
+ hash['CardImageUrl'],
269
+ emails_parsed,
270
+ identities_parsed,
271
+ phone_numbers_parsed,
272
+ postal_addresses_parsed,
273
+ web_properties_parsed,
274
+ hash['IdentityAssured'],
275
+ hash['HasPublicProfile'],
276
+ public_profile_parsed
277
+ )
278
+ end
279
+ end
280
+
281
+ class MiiApiResponse
282
+ attr_accessor :status, :error_code, :error_message, :data
283
+
284
+ def initialize(status, error_code, error_message, data)
285
+ @status = status
286
+ @error_code = error_code
287
+ @error_message = error_message
288
+ @data = data
289
+ end
290
+
291
+ def self.from_hash(hash, data_processor)
292
+ payload_json = hash["Data"]
293
+
294
+ if payload_json && !data_processor.nil?
295
+ payload = data_processor.call(payload_json)
296
+ elsif !(payload_json.nil?)
297
+ payload = payload_json
298
+ else
299
+ payload = nil
300
+ end
301
+
302
+ return MiiApiResponse.new(
303
+ hash["Status"],
304
+ hash["ErrorCode"],
305
+ hash["ErrorMessage"],
306
+ payload
307
+ )
308
+ end
309
+ end
310
+
311
+ class MiiCardOAuthServiceBase
312
+ attr_accessor :consumer_key, :consumer_secret, :access_token, :access_token_secret
313
+
314
+ def initialize(consumer_key, consumer_secret, access_token, access_token_secret)
315
+ if consumer_key.nil? || consumer_secret.nil? || access_token.nil? || access_token_secret.nil?
316
+ raise ArgumentError
317
+ end
318
+
319
+ @consumer_key = consumer_key
320
+ @consumer_secret = consumer_secret
321
+ @access_token = access_token
322
+ @access_token_secret = access_token_secret
323
+ end
324
+ end
325
+
326
+ class MiiCardOAuthClaimsService < MiiCardOAuthServiceBase
327
+ def initialize(consumer_key, consumer_secret, access_token, access_token_secret)
328
+ super(consumer_key, consumer_secret, access_token, access_token_secret)
329
+ end
330
+
331
+ def get_claims
332
+ return make_request(MiiCardServiceUrls.get_method_url('GetClaims'), nil, MiiUserProfile.method(:from_hash), true)
333
+ end
334
+
335
+ def is_social_account_assured(social_account_id, social_account_type)
336
+ params = Hash["socialAccountId", social_account_id, "socialAccountType", social_account_type]
337
+
338
+ return make_request(MiiCardServiceUrls.get_method_url('IsSocialAccountAssured'), params, nil, true)
339
+ end
340
+
341
+ def is_user_assured
342
+ return make_request(MiiCardServiceUrls.get_method_url('IsUserAssured'), nil, nil, true)
343
+ end
344
+
345
+ def assurance_image(type)
346
+ params = Hash["type", type]
347
+
348
+ return make_request(MiiCardServiceUrls.get_method_url('AssuranceImage'), params, nil, false)
349
+ end
350
+
351
+ private
352
+ def make_request(url, post_data, payload_processor, wrapped_response)
353
+ consumer = OAuth::Consumer.new(@consumer_key, @consumer_secret, {:site => MiiCardServiceUrls::STS_SITE, :request_token_path => MiiCardServiceUrls::OAUTH_ENDPOINT, :access_token_path => MiiCardServiceUrls::OAUTH_ENDPOINT, :authorize_path => MiiCardServiceUrls::OAUTH_ENDPOINT })
354
+ access_token = OAuth::AccessToken.new(consumer, @access_token, @access_token_secret)
355
+
356
+ response = access_token.post(url, post_data.to_json(), { 'Content-Type' => 'application/json' })
357
+
358
+ if wrapped_response
359
+ return MiiApiResponse::from_hash(JSON.parse(response.body), payload_processor)
360
+ elsif !payload_processor.nil?
361
+ return payload_processor.call(response.body)
362
+ else
363
+ return response.body
364
+ end
365
+ end
366
+ end
367
+
368
+ class MiiCardServiceUrls
369
+ OAUTH_ENDPOINT = "https://sts.miicard.com/auth/OAuth.ashx"
370
+ STS_SITE = "https://sts.miicard.com"
371
+ CLAIMS_SVC = "https://sts.miicard.com/api/v1/Claims.svc/json"
372
+
373
+ def self.get_method_url(method_name)
374
+ return MiiCardServiceUrls::CLAIMS_SVC + "/" + method_name
375
+ end
376
+ end
377
+
@@ -0,0 +1,137 @@
1
+ require 'json'
2
+ require 'miiCardConsumers'
3
+ require 'test/unit'
4
+
5
+ class TestSomething < Test::Unit::TestCase
6
+ def setup
7
+ @jsonBody = '{"CardImageUrl":"https:\/\/my.miicard.com\/img\/test.png","EmailAddresses":[{"Verified":true,"Address":"test@example.com","DisplayName":"testEmail","IsPrimary":true},{"Verified":false,"Address":"test2@example.com","DisplayName":"test2Email","IsPrimary":false}],"FirstName":"Test","HasPublicProfile":true,"Identities":null,"IdentityAssured":true,"LastName":"User","LastVerified":"\/Date(1345812103)\/","MiddleName":"Middle","PhoneNumbers":[{"Verified":true,"CountryCode":"44","DisplayName":"Default","IsMobile":true,"IsPrimary":true,"NationalNumber":"7800123456"},{"Verified":false,"CountryCode":"44","DisplayName":"Default","IsMobile":false,"IsPrimary":false,"NationalNumber":"7800123457"}],"PostalAddresses":[{"House":"Addr1 House1","Line1":"Addr1 Line1","Line2":"Addr1 Line2","City":"Addr1 City","Region":"Addr1 Region","Code":"Addr1 Code","Country":"Addr1 Country","IsPrimary":true,"Verified":true},{"House":"Addr2 House1","Line1":"Addr2 Line1","Line2":"Addr2 Line2","City":"Addr2 City","Region":"Addr2 Region","Code":"Addr2 Code","Country":"Addr2 Country","IsPrimary":false,"Verified":false}],"PreviousFirstName":"PrevFirst","PreviousLastName":"PrevLast","PreviousMiddleName":"PrevMiddle","ProfileShortUrl":"http:\/\/miicard.me\/123456","ProfileUrl":"https:\/\/my.miicard.com\/card\/test","PublicProfile":{"CardImageUrl":"https:\/\/my.miicard.com\/img\/test.png","FirstName":"Test","HasPublicProfile":true,"IdentityAssured":true,"LastName":"User","LastVerified":"\/Date(1345812103)\/","MiddleName":"Middle","PreviousFirstName":"PrevFirst","PreviousLastName":"PrevLast","PreviousMiddleName":"PrevMiddle","ProfileShortUrl":"http:\/\/miicard.me\/123456","ProfileUrl":"https:\/\/my.miicard.com\/card\/test","PublicProfile":null,"Salutation":"Ms","Username":"testUser"},"Salutation":"Ms","Username":"testUser","WebProperties":[{"Verified":true,"DisplayName":"example.com","Identifier":"example.com","Type":0},{"Verified":false,"DisplayName":"2.example.com","Identifier":"http:\/\/www.2.example.com","Type":1}]}'
8
+ @jsonResponseBody = '{"ErrorCode":0,"Status":0,"ErrorMessage":"A test error message","Data":true}'
9
+ end
10
+
11
+ def test_can_deserialise_user_profile
12
+ o = MiiUserProfile.from_hash(JSON.parse(@jsonBody))
13
+
14
+ assertBasics(o)
15
+
16
+ # Email addresses
17
+ email1 = o.email_addresses[0]
18
+ assert_equal(true, email1.verified)
19
+ assert_equal("test@example.com", email1.address)
20
+ assert_equal("testEmail", email1.display_name)
21
+ assert_equal(true, email1.is_primary)
22
+
23
+ email2 = o.email_addresses[1]
24
+ assert_equal(false, email2.verified)
25
+ assert_equal("test2@example.com", email2.address)
26
+ assert_equal("test2Email", email2.display_name)
27
+ assert_equal(false, email2.is_primary)
28
+
29
+ # Phone numbers
30
+ phone1 = o.phone_numbers[0]
31
+ assert_equal(true, phone1.verified)
32
+ assert_equal("44", phone1.country_code)
33
+ assert_equal("Default", phone1.display_name)
34
+ assert_equal(true, phone1.is_mobile)
35
+ assert_equal(true, phone1.is_primary)
36
+ assert_equal("7800123456", phone1.national_number)
37
+
38
+ phone2 = o.phone_numbers[1]
39
+ assert_equal(false, phone2.verified)
40
+ assert_equal("44", phone2.country_code)
41
+ assert_equal("Default", phone2.display_name)
42
+ assert_equal(false, phone2.is_mobile)
43
+ assert_equal(false, phone2.is_primary)
44
+ assert_equal("7800123457", phone2.national_number)
45
+
46
+ # Web properties
47
+ prop1 = o.web_properties[0]
48
+ assert_equal(true, prop1.verified)
49
+ assert_equal("example.com", prop1.display_name)
50
+ assert_equal("example.com", prop1.identifier)
51
+ assert_equal(WebPropertyType::DOMAIN, prop1.type)
52
+
53
+ prop2 = o.web_properties[1]
54
+ assert_equal(false, prop2.verified)
55
+ assert_equal("2.example.com", prop2.display_name)
56
+ assert_equal("http://www.2.example.com", prop2.identifier)
57
+ assert_equal(WebPropertyType::WEBSITE, prop2.type)
58
+
59
+ # Postal addresses
60
+ addr1 = o.postal_addresses[0]
61
+ assert_equal("Addr1 House1", addr1.house)
62
+ assert_equal("Addr1 Line1", addr1.line1)
63
+ assert_equal("Addr1 Line2", addr1.line2)
64
+ assert_equal("Addr1 City", addr1.city)
65
+ assert_equal("Addr1 Region", addr1.region)
66
+ assert_equal("Addr1 Code", addr1.code)
67
+ assert_equal("Addr1 Country", addr1.country)
68
+ assert_equal(true, addr1.is_primary)
69
+ assert_equal(true, addr1.verified)
70
+
71
+ addr2 = o.postal_addresses[1]
72
+ assert_equal("Addr2 House1", addr2.house)
73
+ assert_equal("Addr2 Line1", addr2.line1)
74
+ assert_equal("Addr2 Line2", addr2.line2)
75
+ assert_equal("Addr2 City", addr2.city)
76
+ assert_equal("Addr2 Region", addr2.region)
77
+ assert_equal("Addr2 Code", addr2.code)
78
+ assert_equal("Addr2 Country", addr2.country)
79
+ assert_equal(false, addr2.is_primary)
80
+ assert_equal(false, addr2.verified)
81
+
82
+ assert_equal(true, o.has_public_profile)
83
+
84
+ pp = o.public_profile
85
+ assertBasics(pp)
86
+ assert_equal("testUser", pp.username)
87
+ end
88
+
89
+ def assertBasics(obj)
90
+ assert_not_nil(obj)
91
+
92
+ assert_equal("https://my.miicard.com/img/test.png", obj.card_image_url)
93
+ assert_equal("Test", obj.first_name)
94
+ assert_equal("Middle", obj.middle_name)
95
+ assert_equal("User", obj.last_name)
96
+
97
+ assert_equal("PrevFirst", obj.previous_first_name)
98
+ assert_equal("PrevMiddle", obj.previous_middle_name)
99
+ assert_equal("PrevLast", obj.previous_last_name)
100
+
101
+ assert_equal(true, obj.identity_assured)
102
+ assert_equal("/Date(1345812103)/", obj.last_verified)
103
+
104
+ assert_equal(true, obj.has_public_profile)
105
+ assert_equal("http://miicard.me/123456", obj.profile_short_url)
106
+ assert_equal("https://my.miicard.com/card/test", obj.profile_url)
107
+ assert_equal("Ms", obj.salutation)
108
+ assert_equal("testUser", obj.username)
109
+ end
110
+
111
+ def test_can_deserialise_boolean
112
+ o = MiiApiResponse.from_hash(JSON.parse(@jsonResponseBody), nil)
113
+
114
+ assert_equal(MiiApiCallStatus::SUCCESS, o.status)
115
+ assert_equal(MiiApiErrorCode::SUCCESS, o.error_code)
116
+ assert_equal("A test error message", o.error_message)
117
+ assert_equal(true, o.data)
118
+ end
119
+
120
+ def test_wrapper_throws_on_null_consumer_key
121
+ assert_raise ArgumentError do
122
+ MiiCardOAuthClaimsService.new(nil, "ConsumerSecret", "AccessToken", "AccessTokenSecret")
123
+ end
124
+
125
+ assert_raise ArgumentError do
126
+ MiiCardOAuthClaimsService.new("ConsumerKey", nil, "AccessToken", "AccessTokenSecret")
127
+ end
128
+
129
+ assert_raise ArgumentError do
130
+ MiiCardOAuthClaimsService.new("ConsumerKey", "ConsumerSecret", nil, "AccessTokenSecret")
131
+ end
132
+
133
+ assert_raise ArgumentError do
134
+ MiiCardOAuthClaimsService.new("ConsumerKey", "ConsumerSecret", "AccessToken", nil)
135
+ end
136
+ end
137
+ end
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: miiCardConsumers
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Paul O'Neill
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-08-14 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: oauth
16
+ requirement: &27018276 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *27018276
25
+ - !ruby/object:Gem::Dependency
26
+ name: json
27
+ requirement: &27018012 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *27018012
36
+ description: A simple wrapper library around the miiCard API that makes calling into
37
+ it easier - just new up a MiiApiOAuthClaimsService with your consumer key, secret,
38
+ access token and secret and start calling methods.
39
+ email: paul.oneill@miicard.com
40
+ executables: []
41
+ extensions: []
42
+ extra_rdoc_files: []
43
+ files:
44
+ - lib/miiCardConsumers.rb
45
+ - test/test_miiCardConsumers.rb
46
+ homepage: http://www.miicard.com/developers
47
+ licenses: []
48
+ post_install_message:
49
+ rdoc_options: []
50
+ require_paths:
51
+ - lib
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ! '>='
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ none: false
60
+ requirements:
61
+ - - ! '>='
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ requirements: []
65
+ rubyforge_project:
66
+ rubygems_version: 1.7.2
67
+ signing_key:
68
+ specification_version: 3
69
+ summary: Wrapper library around the miiCard API.
70
+ test_files:
71
+ - test/test_miiCardConsumers.rb