flurry_harvest 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/.rvmrc.example +1 -0
- data/Gemfile +2 -0
- data/LICENSE.txt +22 -0
- data/README.md +51 -0
- data/Rakefile +11 -0
- data/flurry_harvest.gemspec +25 -0
- data/lib/flurry_harvest.rb +16 -0
- data/lib/flurry_harvest/client.rb +115 -0
- data/lib/flurry_harvest/exceptions.rb +6 -0
- data/lib/flurry_harvest/feed.rb +10 -0
- data/lib/flurry_harvest/mock_client.rb +24 -0
- data/lib/flurry_harvest/offer.rb +19 -0
- data/lib/flurry_harvest/version.rb +3 -0
- data/test/client_test.rb +157 -0
- data/test/fixtures/data.json +40 -0
- data/test/fixtures/data_only_one_element.json +13 -0
- data/test/mock_client_test.rb +30 -0
- data/test/test_helper.rb +11 -0
- metadata +113 -0
data/.gitignore
ADDED
data/.rvmrc.example
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use 1.9.2@flurry_harvest
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Tom Meinlschmidt
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# FlurryHarvest
|
2
|
+
|
3
|
+
Fetch Flurry app _offers_ through their API.
|
4
|
+
|
5
|
+
See ``http://support.flurry.com/index.php?title=API/Code/GetRecommendations`` for more info.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
gem "flurry_harvest"
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install flurry_harvest
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
@client = FlurryHarvest::Client.new(:api_access_code => "my_api_access_code", :api_key => "my_api_key")
|
24
|
+
@response = @client.fetch_offers(:udid => "asfereffajnfasfafafawfafaoweb", :mac => "D9:2A:1A:0C:FD:0B", :ip => "127.0.0.2")
|
25
|
+
|
26
|
+
_offers_ are then in ``@response.offers`` array
|
27
|
+
|
28
|
+
## Available options for Client
|
29
|
+
|
30
|
+
* ``api_access_code`` - your API Access code, **required**
|
31
|
+
* ``api_key`` - your API key, **required**
|
32
|
+
* ``api_host`` - default set to ``api.flurry.com``
|
33
|
+
* ``api_url`` - default set to ``/appCircle/v2/getRecommendations``
|
34
|
+
* ``api_port`` - 80 or 443 for SSL
|
35
|
+
* ``agent`` - optionally Agent string, sent with request
|
36
|
+
|
37
|
+
## Available options for fetching _offers_
|
38
|
+
|
39
|
+
all options are required
|
40
|
+
|
41
|
+
* ``udid`` - non-modified UDID from device,
|
42
|
+
* ``mac`` - MAC address of the device
|
43
|
+
* ``ip`` - IP address of the user
|
44
|
+
|
45
|
+
## Contributing
|
46
|
+
|
47
|
+
1. Fork it
|
48
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
49
|
+
3. Commit your changes (`git commit -am "Add some feature"`)
|
50
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
51
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "flurry_harvest/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "flurry_harvest"
|
8
|
+
gem.version = FlurryHarvest::VERSION
|
9
|
+
gem.authors = ["Tom Meinlschmidt"]
|
10
|
+
gem.email = ["tom@meinlschmidt.org"]
|
11
|
+
gem.description = %q{Used to fetch and communcate with Flurry service}
|
12
|
+
gem.summary = %q{Flurre offers, AppCircle etc}
|
13
|
+
gem.homepage = ""
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.add_development_dependency "fakeweb", "1.3.0"
|
21
|
+
gem.add_development_dependency "mocha", "0.13.1"
|
22
|
+
gem.add_development_dependency "delorean", "2.1.0"
|
23
|
+
|
24
|
+
gem.add_dependency "json", "~> 1.7.7"
|
25
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require "ostruct"
|
2
|
+
require "json"
|
3
|
+
require "net/http"
|
4
|
+
require "net/https"
|
5
|
+
require "uri"
|
6
|
+
|
7
|
+
require_relative "flurry_harvest/version"
|
8
|
+
require_relative "flurry_harvest/offer"
|
9
|
+
require_relative "flurry_harvest/feed"
|
10
|
+
require_relative "flurry_harvest/exceptions"
|
11
|
+
require_relative "flurry_harvest/client"
|
12
|
+
require_relative "flurry_harvest/mock_client"
|
13
|
+
|
14
|
+
|
15
|
+
module FlurryHarvest
|
16
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
class FlurryHarvest::Client
|
2
|
+
attr_reader :api_access_code
|
3
|
+
attr_reader :api_key
|
4
|
+
attr_reader :api_host # api.flurry.com
|
5
|
+
attr_reader :api_url # appCircle/v2/getRecommendations
|
6
|
+
attr_reader :api_port # 80 || 443
|
7
|
+
attr_reader :raw_data
|
8
|
+
attr_reader :query
|
9
|
+
attr_reader :agent # custom agent string
|
10
|
+
attr_reader :debug_mode
|
11
|
+
|
12
|
+
def initialize(options = {})
|
13
|
+
@api_access_code = options[:api_access_code] || (raise FlurryHarvest::ApiAccessCodeNotSet)
|
14
|
+
@api_key = options[:api_key] || (raise FlurryHarvest::ApiKeyNotSet)
|
15
|
+
@api_host = options[:api_host] || 'api.flurry.com'
|
16
|
+
@api_url = options[:api_url] || '/appCircle/v2/getRecommendations'
|
17
|
+
@api_port = options[:api_port] || 80
|
18
|
+
@agent = options[:agent]
|
19
|
+
@debug_mode = options[:debug_mode] || false
|
20
|
+
|
21
|
+
log "Initialize:"
|
22
|
+
log "-----------"
|
23
|
+
log "api_access_code: #{api_access_code}"
|
24
|
+
log "api_key: #{api_key}"
|
25
|
+
log "api_host: #{api_host}"
|
26
|
+
log "api_url: #{api_url}"
|
27
|
+
log "api_port: #{api_port}"
|
28
|
+
log "agent: #{agent}"
|
29
|
+
log "full options: #{options}"
|
30
|
+
end
|
31
|
+
|
32
|
+
# mandatory params
|
33
|
+
# iosUdid - :udid
|
34
|
+
# sha1Mac - :mac
|
35
|
+
# platform - :platform (IPHONE, IPAD, AND)
|
36
|
+
# ipAddress - :ip
|
37
|
+
def fetch_offers(options = {})
|
38
|
+
log "fetch_offers.options: #{options}"
|
39
|
+
|
40
|
+
raise Exception, "UDID is Required" if !options.has_key?(:udid)
|
41
|
+
raise Exception, "IP is Required" if !options.has_key?(:ip)
|
42
|
+
|
43
|
+
query_options = {
|
44
|
+
:apiAccessCode => @api_access_code,
|
45
|
+
:apiKey => @api_key,
|
46
|
+
:iosUdid => options[:udid],
|
47
|
+
:sha1Mac => options[:mac] ? encode_mac(options[:mac]) : nil,
|
48
|
+
:platform => (options[:platform] || 'IPHONE').upcase,
|
49
|
+
:ipAddress => options[:ip]
|
50
|
+
}
|
51
|
+
|
52
|
+
@query = to_query(query_options)
|
53
|
+
|
54
|
+
http = Net::HTTP.new(@api_host, @api_port)
|
55
|
+
http.use_ssl = (@api_port.to_i == 443)
|
56
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
57
|
+
|
58
|
+
path = "#{@api_url}?#{@query}"
|
59
|
+
log "Request: http://#{api_host}:#{api_port}#{path}"
|
60
|
+
|
61
|
+
response = http.get(path, headers)
|
62
|
+
log "Response.body: #{response.body}"
|
63
|
+
|
64
|
+
@raw_data = JSON.parse(response.body)
|
65
|
+
log "Response.raw_data: #{@raw_data}"
|
66
|
+
|
67
|
+
if response.code.to_i == 200
|
68
|
+
result = decode
|
69
|
+
log "Result: #{result.inspect}"
|
70
|
+
|
71
|
+
return result unless @raw_data.empty?
|
72
|
+
else
|
73
|
+
raise FlurryHarvest::ApiError, message: "#{@raw_data['code']} - #{@raw_data['message']}"
|
74
|
+
end
|
75
|
+
|
76
|
+
false
|
77
|
+
end
|
78
|
+
|
79
|
+
# SHA1 encode of mac address
|
80
|
+
# D9:2A:1A:0C:FD:0B should become CF41A96B9A6E4FE2942B4A51F350D7FD722E38B2
|
81
|
+
def encode_mac(mac_address)
|
82
|
+
Digest::SHA1.hexdigest(mac_address).upcase
|
83
|
+
end
|
84
|
+
|
85
|
+
def log(message)
|
86
|
+
return unless debug_mode
|
87
|
+
|
88
|
+
final_message = "FlurryHarvest [#{Time.now.strftime("%Y-%m-%d %H:%M:%S")}]: #{message}"
|
89
|
+
|
90
|
+
if defined? ::Rails
|
91
|
+
::Rails.logger.info final_message
|
92
|
+
else
|
93
|
+
Kernel.puts final_message
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
def to_query(data = {})
|
100
|
+
_data = data.select { |k,v| !v.nil? }
|
101
|
+
_data.map{|k,v| "#{k.to_s}=#{URI.escape(v.to_s)}"}.join('&')
|
102
|
+
end
|
103
|
+
|
104
|
+
# parse json and make object
|
105
|
+
def decode
|
106
|
+
FlurryHarvest::Feed.new(@raw_data).offers
|
107
|
+
end
|
108
|
+
|
109
|
+
# fetch headers
|
110
|
+
def headers
|
111
|
+
{'User-Agent' => "Flurry-#{@agent}",
|
112
|
+
'Accept' => "application/json"}
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
class FlurryHarvest::Feed
|
2
|
+
attr_reader :offers
|
3
|
+
|
4
|
+
def initialize(data)
|
5
|
+
flurry_offers = data["recommendation"] ? data["recommendation"] : []
|
6
|
+
flurry_offers = [flurry_offers] if flurry_offers.is_a? Hash
|
7
|
+
|
8
|
+
@offers = flurry_offers.map{ |r| FlurryHarvest::Offer.new(r) }
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module FlurryHarvest
|
2
|
+
class MockClient < FlurryHarvest::Client
|
3
|
+
def initialize(options = {})
|
4
|
+
@debug_mode = options[:debug_mode] || false
|
5
|
+
|
6
|
+
log "Initialize MockClient:"
|
7
|
+
log "-------------"
|
8
|
+
log "full options: #{options}"
|
9
|
+
end
|
10
|
+
|
11
|
+
def fetch_offers(options = {})
|
12
|
+
log "fetch_offers.options: #{options}"
|
13
|
+
|
14
|
+
@raw_data = JSON.parse(File.read("#{File.dirname(__FILE__)}/../../test/fixtures/data.json"))
|
15
|
+
|
16
|
+
result = decode
|
17
|
+
|
18
|
+
log "Response.raw_data: #{@raw_data}"
|
19
|
+
log "Result: #{result.inspect}"
|
20
|
+
|
21
|
+
return result
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class FlurryHarvest::Offer < OpenStruct
|
2
|
+
def initialize(data)
|
3
|
+
super
|
4
|
+
|
5
|
+
self.icon = data["@appIconUrl"]
|
6
|
+
self.time_to_payout = nil
|
7
|
+
self.title = data["@appName"]
|
8
|
+
self.required_actions = "Download and Start"
|
9
|
+
self.link = data["@actionUrl"]
|
10
|
+
self.price = data["@appPrice"].to_f / 100
|
11
|
+
self.provider = "Flurry"
|
12
|
+
self.credits = nil
|
13
|
+
self.id = get_id
|
14
|
+
end
|
15
|
+
|
16
|
+
def get_id
|
17
|
+
Digest::MD5.hexdigest("#{title}-#{icon}")
|
18
|
+
end
|
19
|
+
end
|
data/test/client_test.rb
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
require_relative "test_helper"
|
2
|
+
|
3
|
+
class FlurryHarvest::ClientTest < Test::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
FlurryHarvest::Client.any_instance.stubs(:log)
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_initialize
|
9
|
+
client =
|
10
|
+
FlurryHarvest::Client.new(
|
11
|
+
:api_access_code => "access_code",
|
12
|
+
:api_key => "api_key"
|
13
|
+
)
|
14
|
+
|
15
|
+
assert_equal("access_code", client.api_access_code)
|
16
|
+
assert_equal("api_key", client.api_key)
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_debug_mode
|
20
|
+
client =
|
21
|
+
FlurryHarvest::Client.new(
|
22
|
+
:api_access_code => "access_code",
|
23
|
+
:api_key => "api_key",
|
24
|
+
:debug_mode => true
|
25
|
+
)
|
26
|
+
|
27
|
+
FlurryHarvest::Client.any_instance.unstub(:log)
|
28
|
+
|
29
|
+
Kernel.expects(:puts).with("FlurryHarvest [2012-02-03 10:11:12]: wadus")
|
30
|
+
|
31
|
+
Delorean.time_travel_to("2012-02-03 10:11:12") do
|
32
|
+
client.log "wadus"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_debug_mode_off
|
37
|
+
client =
|
38
|
+
FlurryHarvest::Client.new(
|
39
|
+
:api_access_code => "access_code",
|
40
|
+
:api_key => "api_key"
|
41
|
+
)
|
42
|
+
|
43
|
+
Kernel.expects(:puts).never
|
44
|
+
client.log "wadus"
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_debug_mode_with_rails_defined
|
48
|
+
client =
|
49
|
+
FlurryHarvest::Client.new(
|
50
|
+
:api_access_code => "access_code",
|
51
|
+
:api_key => "api_key",
|
52
|
+
:debug_mode => true
|
53
|
+
)
|
54
|
+
|
55
|
+
FlurryHarvest::Client.any_instance.unstub(:log)
|
56
|
+
|
57
|
+
rails_mock = mock();
|
58
|
+
logger_mock = mock();
|
59
|
+
|
60
|
+
Kernel.send(:const_set, :Rails, rails_mock)
|
61
|
+
rails_mock.stubs(:logger).returns(logger_mock)
|
62
|
+
logger_mock.expects(:info).with("FlurryHarvest [2012-02-03 10:11:12]: wadus")
|
63
|
+
Kernel.expects(:puts).never
|
64
|
+
|
65
|
+
Delorean.time_travel_to("2012-02-03 10:11:12") do
|
66
|
+
client.log "wadus"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_no_api_access_code
|
71
|
+
assert_raise FlurryHarvest::ApiAccessCodeNotSet do
|
72
|
+
FlurryHarvest::Client.new
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_no_api_key
|
77
|
+
assert_raise FlurryHarvest::ApiKeyNotSet do
|
78
|
+
FlurryHarvest::Client.new(:api_access_code => "access_code")
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# missing UDID
|
83
|
+
def test_udid_not_found
|
84
|
+
client = FlurryHarvest::Client.new(:api_access_code => "access_code", :api_key => "my_key")
|
85
|
+
assert_raise(Exception) { client.fetch_offers(:mac => "abcd", :ip => "192") }
|
86
|
+
end
|
87
|
+
|
88
|
+
# missing ip
|
89
|
+
def test_ip_not_found
|
90
|
+
client = FlurryHarvest::Client.new(:api_access_code => "access_code", :api_key => "my_key")
|
91
|
+
assert_raise(Exception) { client.fetch_offers(:udid => "sadfadsfas", :mac => "abcd") }
|
92
|
+
end
|
93
|
+
|
94
|
+
# test valid feed
|
95
|
+
def test_valid_request
|
96
|
+
data = File.read("#{FIXTURES}/data.json")
|
97
|
+
FakeWeb.allow_net_connect = "http://api.example.com"
|
98
|
+
FakeWeb.register_uri(:get, "http://api.example.com/appCircle/v2/getRecommendations?apiAccessCode=4CWRDCC8PDJC99R8BSPC&apiKey=ZM7M97D79X3D5258M2BF&iosUdid=ea750fdbc619ca406d066d7ed158d54483fc382f&sha1Mac=CF41A96B9A6E4FE2942B4A51F360D7FD722E38B2&platform=IPHONE&ipAddress=92.240.181.8", :body => data, :content_type => "application/json")
|
99
|
+
|
100
|
+
client = FlurryHarvest::Client.new(:api_access_code => "4CWRDCC8PDJC99R8BSPC", :api_key => "ZM7M97D79X3D5258M2BF", :api_host => "api.example.com")
|
101
|
+
|
102
|
+
offers = client.fetch_offers(:udid => "ea750fdbc619ca406d066d7ed158d54483fc382f", :mac => "D9:2A:1A:0C:FD:0B", :ip => "92.240.181.8")
|
103
|
+
|
104
|
+
assert_equal(5, offers.count)
|
105
|
+
assert_equal(["System Status Lite - device activity monitor app", "Gossip Fix", "ART Jigsaw Puzzles. Van Gogh", "travelmeter", "Poker Dawgs"], offers.map(&:title))
|
106
|
+
|
107
|
+
offer = offers.first
|
108
|
+
assert_equal("http://ad.flurry.com/manualGetIPhoneApp.do?v=1&e=NsuJZSsBlxdPFL3Zi4IK97tHKU19JIjZbVS7gXuaqHb9SMkFnZQ7GUIcJWFxWslAKWFDJRCidAnzWhAEZG58VAiBgta3aOrpxBTsHAgwzZHqHr8hwbw4p0WxHL_AZSJnC4Xm3snrURgTMpY_6wZKgLtrFoyh5EMWVFVq2bOMds-3zgj7pKzti5ZWkVvvhzOJUEjBSf2ZQbPsgDTmMoMgmiLUA60I0t4BTMp3v8aw2sOoNfwGv1F4sw", offer.link)
|
109
|
+
assert_equal("http://flurry.cachefly.net/ad/ad_image/5275_80x80.png", offer.icon)
|
110
|
+
assert_equal("System Status Lite - device activity monitor app", offer.title)
|
111
|
+
assert_equal("Download and Start", offer.required_actions)
|
112
|
+
assert_equal(1.12, offer.price)
|
113
|
+
assert_equal(nil, offer.time_to_payout)
|
114
|
+
assert_equal("f0359fc7fd864408ca29fdf1bb0c26fe", offer.id)
|
115
|
+
assert_equal(nil, offer.credits)
|
116
|
+
assert_equal("Flurry", offer.provider)
|
117
|
+
end
|
118
|
+
|
119
|
+
def test_valid_request_with_only_one_element
|
120
|
+
data = File.read("#{FIXTURES}/data_only_one_element.json")
|
121
|
+
FakeWeb.allow_net_connect = "http://api.example.com"
|
122
|
+
FakeWeb.register_uri(:get, "http://api.example.com/appCircle/v2/getRecommendations?apiAccessCode=4CWRDCC8PDJC99R8BSPC&apiKey=ZM7M97D79X3D5258M2BF&iosUdid=ea750fdbc619ca406d066d7ed158d54483fc382f&sha1Mac=CF41A96B9A6E4FE2942B4A51F360D7FD722E38B2&platform=IPHONE&ipAddress=92.240.181.8", :body => data, :content_type => "application/json")
|
123
|
+
|
124
|
+
client = FlurryHarvest::Client.new(:api_access_code => "4CWRDCC8PDJC99R8BSPC", :api_key => "ZM7M97D79X3D5258M2BF", :api_host => "api.example.com")
|
125
|
+
|
126
|
+
offers = client.fetch_offers(:udid => "ea750fdbc619ca406d066d7ed158d54483fc382f", :mac => "D9:2A:1A:0C:FD:0B", :ip => "92.240.181.8")
|
127
|
+
|
128
|
+
assert_equal(1, offers.count)
|
129
|
+
assert_equal(["System Status Lite - device activity monitor app"], offers.map(&:title))
|
130
|
+
|
131
|
+
offer = offers.first
|
132
|
+
assert_equal("http://ad.flurry.com/manualGetIPhoneApp.do?v=1&e=NsuJZSsBlxdPFL3Zi4IK97tHKU19JIjZbVS7gXuaqHb9SMkFnZQ7GUIcJWFxWslAKWFDJRCidAnzWhAEZG58VAiBgta3aOrpxBTsHAgwzZHqHr8hwbw4p0WxHL_AZSJnC4Xm3snrURgTMpY_6wZKgLtrFoyh5EMWVFVq2bOMds-3zgj7pKzti5ZWkVvvhzOJUEjBSf2ZQbPsgDTmMoMgmiLUA60I0t4BTMp3v8aw2sOoNfwGv1F4sw", offer.link)
|
133
|
+
end
|
134
|
+
|
135
|
+
# test without mac address
|
136
|
+
def test_valid_request_without_mac_address
|
137
|
+
data = File.read("#{FIXTURES}/data.json")
|
138
|
+
FakeWeb.allow_net_connect = "http://api.example.com"
|
139
|
+
FakeWeb.register_uri(:get, "http://api.example.com/appCircle/v2/getRecommendations?apiAccessCode=4CWRDCC8PDJC99R8BSPC&apiKey=ZM7M97D79X3D5258M2BF&iosUdid=ea750fdbc619ca406d066d7ed158d54483fc382f&platform=IPHONE&ipAddress=92.240.181.8", :body => data, :content_type => "application/json")
|
140
|
+
|
141
|
+
client = FlurryHarvest::Client.new(:api_access_code => "4CWRDCC8PDJC99R8BSPC", :api_key => "ZM7M97D79X3D5258M2BF", :api_host => "api.example.com")
|
142
|
+
|
143
|
+
client.fetch_offers(:udid => "ea750fdbc619ca406d066d7ed158d54483fc382f", :ip => "92.240.181.8")
|
144
|
+
end
|
145
|
+
|
146
|
+
# any error
|
147
|
+
def test_invalid_udid
|
148
|
+
FakeWeb.allow_net_connect = "http://api.example.com"
|
149
|
+
FakeWeb.register_uri(:get, "http://api.example.com/appCircle/v2/getRecommendations?apiAccessCode=4CWRDCC8PDJC99R8BSPC&apiKey=ZM7M97D79X3DF&iosUdid=ea750fdbc619ca406d066d7ed158d54483fc382f&sha1Mac=CF41A96B9A6E4FE2942B4A51F360D7FD722E38B2&platform=IPHONE&ipAddress=92.240.181.8", :body => '{"code":100, "message":"API Key not found"}', :content_type => "application/json", :status => ["400","Error"])
|
150
|
+
|
151
|
+
client = FlurryHarvest::Client.new(:api_access_code => "4CWRDCC8PDJC99R8BSPC", :api_key => "ZM7M97D79X3DF", :api_host => "api.example.com")
|
152
|
+
assert_raise FlurryHarvest::ApiError do
|
153
|
+
client.fetch_offers(:udid => "ea750fdbc619ca406d066d7ed158d54483fc382f", :mac => "D9:2A:1A:0C:FD:0B", :ip => "92.240.181.8")
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
{ "@generatedDate" : "12/5/12 2:40 PM",
|
2
|
+
"@type" : "Recommendations",
|
3
|
+
"@version" : "1.0",
|
4
|
+
"recommendation" : [ { "@actionUrl" : "http://ad.flurry.com/manualGetIPhoneApp.do?v=1&e=NsuJZSsBlxdPFL3Zi4IK97tHKU19JIjZbVS7gXuaqHb9SMkFnZQ7GUIcJWFxWslAKWFDJRCidAnzWhAEZG58VAiBgta3aOrpxBTsHAgwzZHqHr8hwbw4p0WxHL_AZSJnC4Xm3snrURgTMpY_6wZKgLtrFoyh5EMWVFVq2bOMds-3zgj7pKzti5ZWkVvvhzOJUEjBSf2ZQbPsgDTmMoMgmiLUA60I0t4BTMp3v8aw2sOoNfwGv1F4sw",
|
5
|
+
"@appIconUrl" : "http://flurry.cachefly.net/ad/ad_image/5275_80x80.png",
|
6
|
+
"@appName" : "System Status Lite - device activity monitor app",
|
7
|
+
"@appPrice" : "112",
|
8
|
+
"@description" : "System Status Lite is a lite version of System Status - the ultimate application for monitoring and optimizing your device's performance. The lite version monitors your device's basic parameters such as battery level, disk usage, CPU load and connections.<br /><br />GENERAL FEATURES<br />- Full iOS 4 multitasking support<br />- Universal binary - full support of all iPhone, iPod Touch and iPad models<br />- Graphics optimized both for retina and older displays for pixel perfect graphics<br />- Well-arranged user interface so the most frequently used information available immediately<br />- Use of color coding for indicators and graphs to improve readability <br />- Real time updates of the display once the monitored parameters change<br />- Detailed help<br /><br />BATTERY MONITORING<br />- Graphical battery level display<br />- Battery state monitoring (discharging, charging, full)<br /><br />DISK MONITORING<br />- Used and free disk capacity monitoring<br /><br />CPU MONITORING<br />- CPU usage updated in real time<br />- Average load over the last 1, 5 and 15 minutes<br /><br />OPERATING SYSTEM <br />- System boot time and uptime<br /><br />CELL AND NETWORK MONITORING<br />- 3G and Wi-Fi network connection information<br />- IP and MAC addresses of the current connection<br />- Carrier information such as network provider and MCC/MNC codes<br />- External IP address<br /><br />Facebook fan page: www.facebook.com/SystemStatusApp<br /><br />Full version YouTube video: http://youtu.be/0n9DduGUGyk<br /><br />▶ Search the store for 'System Status' to see the extra features the full version offers ◀",
|
9
|
+
"@publisherName" : "Jiri Techet"
|
10
|
+
},
|
11
|
+
{ "@actionUrl" : "http://ad.flurry.com/manualGetIPhoneApp.do?v=1&e=Pb6RlE7WxF29wRbs80vmsfVV6Uf3V-pPbLBcQeA_fMifgUjI-Xn2J7ELuER93UkF8AIl9RRt944wN1vwRcPcJIDDhll0biCSnIE7FZpUnMajqRxMd5eaiuX75jJTMDbLe_tHoukH0qWmd08nA28KNQ1fDvgS_gyaq1EflSRtF7bbsWHweRZRuplEKPfSujbvglfTXA1nDwn-NgXanwOeg5UnHqpjfM4FmdUPdefQ0r4QwRwdi-TBdw",
|
12
|
+
"@appIconUrl" : "http://flurry.cachefly.net/ad/ad_image/2076_80x80.png",
|
13
|
+
"@appName" : "Gossip Fix",
|
14
|
+
"@appPrice" : "99",
|
15
|
+
"@description" : "Gossip Fix is what you need to easily view the most popular celebrity news sites today! Find yourself constantly checking gossip sites for the latest news? Gossip Fix is fast, simple, and easy to use. \r\n \r\n Easily switch from one feed to the other, view the story with pictures, send the stories to friends, view the full story online, all in one simple app!",
|
16
|
+
"@publisherName" : "Charlie Elliott"
|
17
|
+
},
|
18
|
+
{ "@actionUrl" : "http://ad.flurry.com/manualGetIPhoneApp.do?v=1&e=NbFlSUx_hIy9wRbs80vmsfVV6Uf3V-pPbLBcQeA_fMifgUjI-Xn2J7ELuER93UkF8AIl9RRt944wN1vwRcPcJIDDhll0biCSnIE7FZpUnMajqRxMd5eaiuX75jJTMDbLe_tHoukH0qWmd08nA28KNQ1fDvgS_gyaq1EflSRtF7bbsWHweRZRuplEKPfSujbvglfTXA1nDwn-NgXanwOeg5UnHqpjfM4FmdUPdefQ0r4QwRwdi-TBdw",
|
19
|
+
"@appIconUrl" : "http://flurry.cachefly.net/ad/ad_image/880_80x80.png",
|
20
|
+
"@appName" : "ART Jigsaw Puzzles. Van Gogh",
|
21
|
+
"@appPrice" : "199",
|
22
|
+
"@description" : "Jigsaw Puzzle with 40 masterpieces by Van Gogh. Enjoy great classical music while playing. Post your achievements on facebook and twitter. Send puzzles to challenge your friends.\r\n \r\n Features summary:\r\n ✓ Fullscreen jigsaw puzzle \r\n ✓ 3 levels: Easy (4x7 28 pieces), Medium (6x9 54 pieces) and Hard (7x10 70 pieces)\r\n ✓ 40 Masterpieces by Van Gogh\r\n ✓ Biography of Van Gogh\r\n ✓ My Gallery - gorgeous gallery of collected paintings\r\n ✓ Openfeint enabled: get achievements and send puzzles as challenges\r\n ✓ Charming classical background music\r\n ✓ Game state saved on exit or incoming call\r\n \r\n More educational applications by ADS Software Group:\r\n ✓ ART - Pocket Art Gallery. Great Artists. Slideshow. Quiz\r\n ✓ Classical Music - 50 Famous compositions and Quiz\r\n ✓ World Countries. Factbook, Quiz and Flag games\r\n ✓ JS Bach - Classical Music Collection and Quiz.\r\n ✓ LiveTrivia - realtime multiplayer trivia & chat\r\n \r\n Please also check our other ART Jigsaw puzzles - Michelangelo, Da Vinci and Rafaello.",
|
23
|
+
"@publisherName" : "ADS Software Group, Inc."
|
24
|
+
},
|
25
|
+
{ "@actionUrl" : "http://ad.flurry.com/manualGetIPhoneApp.do?v=1&e=8jB8gex8SXLX7X4GNbPcp7tHKU19JIjZbVS7gXuaqHb9SMkFnZQ7GUIcJWFxWslAKWFDJRCidAnzWhAEZG58VAiBgta3aOrpxBTsHAgwzZHqHr8hwbw4p0WxHL_AZSJnC4Xm3snrURgTMpY_6wZKgLtrFoyh5EMWVFVq2bOMds-3zgj7pKzti5ZWkVvvhzOJUEjBSf2ZQbPsgDTmMoMgmiLUA60I0t4BTMp3v8aw2sOoNfwGv1F4sw",
|
26
|
+
"@appIconUrl" : "http://flurry.cachefly.net/ad/ad_image/11083_80x80.png",
|
27
|
+
"@appName" : "travelmeter",
|
28
|
+
"@appPrice" : "0",
|
29
|
+
"@description" : "Are we there yet? Travelmeter graphically shows the progress of your journey between two cities. The car, train, or boat advances as you get closer. The road, rails, or water moves depending on your speed. \r\n\r\nNOTE: Continued use of GPS running in the background can dramatically decrease battery life. Background operation is turned off by default and controlled on the 'settings' screen of the app.\r\n\r\n* Worldwide reverse geocoder: type in a city and it will look it up, no dropping pins on maps!\r\n\r\n* Alarm: notifies you when you get close.\r\n\r\n* Very little data usage: works in places with poor coverage, no waiting for maps to load. \r\n\r\n* Twitter: announce the progress of your journey on twitter.\r\n\r\n* Distances and direction.\r\n\r\n* Speed and movement.",
|
30
|
+
"@publisherName" : "Caffeine Fish LLC"
|
31
|
+
},
|
32
|
+
{ "@actionUrl" : "http://ad.flurry.com/manualGetIPhoneApp.do?v=1&e=smNGyuWFlDP9okUCGcBQrbtHKU19JIjZbVS7gXuaqHb9SMkFnZQ7GUIcJWFxWslAKWFDJRCidAnzWhAEZG58VAiBgta3aOrpxBTsHAgwzZHqHr8hwbw4p0WxHL_AZSJnC4Xm3snrURgTMpY_6wZKgLtrFoyh5EMWVFVq2bOMds-3zgj7pKzti5ZWkVvvhzOJUEjBSf2ZQbPsgDTmMoMgmiLUA60I0t4BTMp3v8aw2sOoNfwGv1F4sw",
|
33
|
+
"@appIconUrl" : "http://flurry.cachefly.net/ad/ad_image/11396_80x80.jpg",
|
34
|
+
"@appName" : "Poker Dawgs",
|
35
|
+
"@appPrice" : "0",
|
36
|
+
"@description" : "Poker Dawgs provides a simple and unique combination of strategy, luck, scoring, competition and rewards.\r\nPoker Dawgs is an entirely new way to play video poker and the only game in the app store with a Ladder scoring system. Poker Dawgs provides a new level of strategy with wild cards, random score multipliers for each card dealt and a pick your own multiplier bonus round. Poker Dawgs also offers rewards for playing. Each time you play you will accumulate “Dawg Treats” that can be redeemed in the In-App “Pet Store” for more credits. Accumulate enough “Dawg Treats” and you can redeem them for a Gift Card through our web site. For our Top Dawg Players, you can use your video poker skills to win Gift Cards by scoring a million points or more on any game of 1,000 credits or higher. You can play for free by coming back every day to collect your random daily bonus of credits. You can earn more credits by achieving the high score of the day or the month. The higher you score the more credits you will collect from other players. All credits earned in Poker Dawgs can also be used to play our other app called “Dawgs”.\r\nHow the Ladder Scoring System Works:\r\nYour total score will be added up from all 5 hands and submitted once the final hand is complete. If your score qualifies for the leader board, your name and score will be displayed accordingly in the 8 slot 'Ladder' for everyone to see; you will also start accumulating credits while your name remains on the 'Ladder' and other people play. Every time you play, 10% of the credits you used to play will be distributed to each slot on the “Ladder”. For example, if you play the 100 credit game, 10 credits are added to each person currently on the “Ladder”. If you currently occupy 3 slots on the “Ladder” and someone else plays, 10 credits will be added to each of your 3 slots.\r\n\r\nThe 'Ladder' system contains 8 slots and an instant jackpot. Each slot represents a score range, as illustrated below:\r\nSlot 1: 300,000+\r\nSlot 2: 200,000 - 299,999\r\nSlot 3: 150,000 - 199,999\r\nSlot 4: 100,000 - 149,999\r\nSlot 5: 50,000 - 99,999\r\nSlot 6: 25,000 - 49,999\r\nSlot 7: 10,000 - 24,999\r\nSlot 8: 5,000 - 9,999\r\n\r\nIf your total score falls within these ranges your name will occupy the appropriate slot on the “Ladder” which also slides the previous occupant of your slot down 1 slot. For example, if you finish a play with a score of 25,000 and player 'David' was in slot 7 while player 'John' was in slot 8, you will take over slot 7 which pushes 'David' to slot 8 and 'John' is knocked off the “Ladder”. At this point, 'John' collects however many credits he accumulated while on the “Ladder”. Your name will remain in slot 7, collecting credits, until someone else scores 25,000 or more. The obvious goal is to score high enough to get into slot 1. The higher your slot, the longer you will stay on the “Ladder” and the more credits you will collect when you are finally knocked off the “Ladder”.\r\n\r\nThe Jackpot is an instant win of credits or a Gift Card, and you will also take over slot 1! You need to score 1,000,000 or more to win the jackpot for any game.",
|
37
|
+
"@publisherName" : "Travis croxford"
|
38
|
+
}
|
39
|
+
]
|
40
|
+
}
|
@@ -0,0 +1,13 @@
|
|
1
|
+
{
|
2
|
+
"@type":"Recommendations",
|
3
|
+
"@version":"1.0",
|
4
|
+
"@generatedDate":"1/9/13 6:55 AM",
|
5
|
+
"recommendation":{
|
6
|
+
"@actionUrl" : "http://ad.flurry.com/manualGetIPhoneApp.do?v=1&e=NsuJZSsBlxdPFL3Zi4IK97tHKU19JIjZbVS7gXuaqHb9SMkFnZQ7GUIcJWFxWslAKWFDJRCidAnzWhAEZG58VAiBgta3aOrpxBTsHAgwzZHqHr8hwbw4p0WxHL_AZSJnC4Xm3snrURgTMpY_6wZKgLtrFoyh5EMWVFVq2bOMds-3zgj7pKzti5ZWkVvvhzOJUEjBSf2ZQbPsgDTmMoMgmiLUA60I0t4BTMp3v8aw2sOoNfwGv1F4sw",
|
7
|
+
"@appIconUrl" : "http://flurry.cachefly.net/ad/ad_image/5275_80x80.png",
|
8
|
+
"@appName" : "System Status Lite - device activity monitor app",
|
9
|
+
"@appPrice" : "112",
|
10
|
+
"@description" : "System Status Lite is a lite version of System Status - the ultimate application for monitoring and optimizing your device's performance. The lite version monitors your device's basic parameters such as battery level, disk usage, CPU load and connections.<br /><br />GENERAL FEATURES<br />- Full iOS 4 multitasking support<br />- Universal binary - full support of all iPhone, iPod Touch and iPad models<br />- Graphics optimized both for retina and older displays for pixel perfect graphics<br />- Well-arranged user interface so the most frequently used information available immediately<br />- Use of color coding for indicators and graphs to improve readability <br />- Real time updates of the display once the monitored parameters change<br />- Detailed help<br /><br />BATTERY MONITORING<br />- Graphical battery level display<br />- Battery state monitoring (discharging, charging, full)<br /><br />DISK MONITORING<br />- Used and free disk capacity monitoring<br /><br />CPU MONITORING<br />- CPU usage updated in real time<br />- Average load over the last 1, 5 and 15 minutes<br /><br />OPERATING SYSTEM <br />- System boot time and uptime<br /><br />CELL AND NETWORK MONITORING<br />- 3G and Wi-Fi network connection information<br />- IP and MAC addresses of the current connection<br />- Carrier information such as network provider and MCC/MNC codes<br />- External IP address<br /><br />Facebook fan page: www.facebook.com/SystemStatusApp<br /><br />Full version YouTube video: http://youtu.be/0n9DduGUGyk<br /><br />▶ Search the store for 'System Status' to see the extra features the full version offers ◀",
|
11
|
+
"@publisherName" : "Jiri Techet"
|
12
|
+
}
|
13
|
+
}
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require_relative "test_helper"
|
2
|
+
|
3
|
+
class FlurryHarvest::MockClientTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def setup
|
6
|
+
FlurryHarvest::MockClient.any_instance.stubs(:log)
|
7
|
+
@mock_client = FlurryHarvest::MockClient.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_initialize
|
11
|
+
mock_client =
|
12
|
+
FlurryHarvest::MockClient.new(
|
13
|
+
:debug_mode => "debug_mode"
|
14
|
+
)
|
15
|
+
|
16
|
+
assert_equal("debug_mode", mock_client.debug_mode)
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_fetch_offers
|
20
|
+
offers = @mock_client.fetch_offers
|
21
|
+
assert_equal(5, offers.length)
|
22
|
+
|
23
|
+
offer = offers.first
|
24
|
+
assert_equal("http://ad.flurry.com/manualGetIPhoneApp.do?v=1&e=NsuJZSsBlxdPFL3Zi4IK97tHKU19JIjZbVS7gXuaqHb9SMkFnZQ7GUIcJWFxWslAKWFDJRCidAnzWhAEZG58VAiBgta3aOrpxBTsHAgwzZHqHr8hwbw4p0WxHL_AZSJnC4Xm3snrURgTMpY_6wZKgLtrFoyh5EMWVFVq2bOMds-3zgj7pKzti5ZWkVvvhzOJUEjBSf2ZQbPsgDTmMoMgmiLUA60I0t4BTMp3v8aw2sOoNfwGv1F4sw", offer.link)
|
25
|
+
assert_equal("f0359fc7fd864408ca29fdf1bb0c26fe", offer.id)
|
26
|
+
assert_equal(nil, offer.credits)
|
27
|
+
assert_equal(1.12, offer.price)
|
28
|
+
assert_equal("System Status Lite - device activity monitor app", offer.title)
|
29
|
+
end
|
30
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: flurry_harvest
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.3
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Tom Meinlschmidt
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-05-08 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: fakeweb
|
16
|
+
requirement: &70213290695120 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - =
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.3.0
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70213290695120
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: mocha
|
27
|
+
requirement: &70213290694620 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - =
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 0.13.1
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70213290694620
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: delorean
|
38
|
+
requirement: &70213290694160 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - =
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 2.1.0
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70213290694160
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: json
|
49
|
+
requirement: &70213290693700 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 1.7.7
|
55
|
+
type: :runtime
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70213290693700
|
58
|
+
description: Used to fetch and communcate with Flurry service
|
59
|
+
email:
|
60
|
+
- tom@meinlschmidt.org
|
61
|
+
executables: []
|
62
|
+
extensions: []
|
63
|
+
extra_rdoc_files: []
|
64
|
+
files:
|
65
|
+
- .gitignore
|
66
|
+
- .rvmrc.example
|
67
|
+
- Gemfile
|
68
|
+
- LICENSE.txt
|
69
|
+
- README.md
|
70
|
+
- Rakefile
|
71
|
+
- flurry_harvest.gemspec
|
72
|
+
- lib/flurry_harvest.rb
|
73
|
+
- lib/flurry_harvest/client.rb
|
74
|
+
- lib/flurry_harvest/exceptions.rb
|
75
|
+
- lib/flurry_harvest/feed.rb
|
76
|
+
- lib/flurry_harvest/mock_client.rb
|
77
|
+
- lib/flurry_harvest/offer.rb
|
78
|
+
- lib/flurry_harvest/version.rb
|
79
|
+
- test/client_test.rb
|
80
|
+
- test/fixtures/data.json
|
81
|
+
- test/fixtures/data_only_one_element.json
|
82
|
+
- test/mock_client_test.rb
|
83
|
+
- test/test_helper.rb
|
84
|
+
homepage: ''
|
85
|
+
licenses: []
|
86
|
+
post_install_message:
|
87
|
+
rdoc_options: []
|
88
|
+
require_paths:
|
89
|
+
- lib
|
90
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
91
|
+
none: false
|
92
|
+
requirements:
|
93
|
+
- - ! '>='
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
requirements: []
|
103
|
+
rubyforge_project:
|
104
|
+
rubygems_version: 1.8.15
|
105
|
+
signing_key:
|
106
|
+
specification_version: 3
|
107
|
+
summary: Flurre offers, AppCircle etc
|
108
|
+
test_files:
|
109
|
+
- test/client_test.rb
|
110
|
+
- test/fixtures/data.json
|
111
|
+
- test/fixtures/data_only_one_element.json
|
112
|
+
- test/mock_client_test.rb
|
113
|
+
- test/test_helper.rb
|