market_bot 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,19 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ gem "typhoeus", "~> 0.2.4"
7
+ gem "nokogiri", "~> 1.5.0"
8
+
9
+ # Add dependencies to develop your gem here.
10
+ # Include everything needed to run rake, tests, features, etc.
11
+ group :development do
12
+ gem "rspec", "~> 2.3.0"
13
+ gem "bundler", "~> 1.0.0"
14
+ gem "jeweler", "~> 1.6.4"
15
+ gem "rcov", ">= 0"
16
+ gem "ZenTest", "~> 4.6.2"
17
+ gem "ruby-debug19", ">= 0.11.6", :require => "ruby-debug"
18
+ gem "rdoc", ">= 3.9.4"
19
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,54 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ ZenTest (4.6.2)
5
+ archive-tar-minitar (0.5.2)
6
+ columnize (0.3.4)
7
+ diff-lcs (1.1.3)
8
+ git (1.2.5)
9
+ jeweler (1.6.4)
10
+ bundler (~> 1.0)
11
+ git (>= 1.2.5)
12
+ rake
13
+ linecache19 (0.5.12)
14
+ ruby_core_source (>= 0.1.4)
15
+ mime-types (1.16)
16
+ nokogiri (1.5.0)
17
+ rake (0.9.2)
18
+ rcov (0.9.10)
19
+ rdoc (3.9.4)
20
+ rspec (2.3.0)
21
+ rspec-core (~> 2.3.0)
22
+ rspec-expectations (~> 2.3.0)
23
+ rspec-mocks (~> 2.3.0)
24
+ rspec-core (2.3.1)
25
+ rspec-expectations (2.3.0)
26
+ diff-lcs (~> 1.1.2)
27
+ rspec-mocks (2.3.0)
28
+ ruby-debug-base19 (0.11.25)
29
+ columnize (>= 0.3.1)
30
+ linecache19 (>= 0.5.11)
31
+ ruby_core_source (>= 0.1.4)
32
+ ruby-debug19 (0.11.6)
33
+ columnize (>= 0.3.1)
34
+ linecache19 (>= 0.5.11)
35
+ ruby-debug-base19 (>= 0.11.19)
36
+ ruby_core_source (0.1.5)
37
+ archive-tar-minitar (>= 0.5.2)
38
+ typhoeus (0.2.4)
39
+ mime-types
40
+ mime-types
41
+
42
+ PLATFORMS
43
+ ruby
44
+
45
+ DEPENDENCIES
46
+ ZenTest (~> 4.6.2)
47
+ bundler (~> 1.0.0)
48
+ jeweler (~> 1.6.4)
49
+ nokogiri (~> 1.5.0)
50
+ rcov
51
+ rdoc (>= 3.9.4)
52
+ rspec (~> 2.3.0)
53
+ ruby-debug19 (>= 0.11.6)
54
+ typhoeus (~> 0.2.4)
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Chad Remesch
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/NOTES.txt ADDED
@@ -0,0 +1,61 @@
1
+ URLs:
2
+ - id can be one of the below IDs or an application ID which usually starts with com.
3
+ - category is optional and works with a subset of the below IDs.
4
+ https://market.android.com/details?id=&cat=
5
+ https://market.android.com/details?id=com.angrymobgames.muffinknightfree
6
+
7
+ Data Models:
8
+ Android::Leaderboard
9
+ Android::Leaderboard::Stat
10
+ Android::App
11
+ Android::App::Stat
12
+
13
+ IDs:
14
+ apps_topselling_paid
15
+ apps_topselling_free
16
+ apps_topselling_new_free
17
+ apps_topselling_new_paid
18
+ apps_editors_choice
19
+ apps_topselling_paid_game
20
+ apps_movers_shakers
21
+ apps_featured
22
+ apps_tablet_featured
23
+ apps_topgrossing
24
+
25
+ Categories:
26
+ GAME
27
+ ARCADE
28
+ BRAIN
29
+ CARDS
30
+ CASUAL
31
+ GAME_WALLPAPER
32
+ RACING
33
+ SPORTS_GAMES
34
+ GAME_WIDGETS
35
+ APPLICATION
36
+ BOOKS_AND_REFERENCE
37
+ BUSINESS
38
+ COMICS
39
+ COMMUNICATION
40
+ EDUCATION
41
+ ENTERTAINMENT
42
+ FINANCE
43
+ HEALTH_AND_FITNESS
44
+ LIBRARIES_AND_DEMO
45
+ LIFESTYLE
46
+ APP_WALLPAPER
47
+ MEDIA_AND_VIDEO
48
+ MEDICAL
49
+ MUSIC_AND_AUDIO
50
+ NEWS_AND_MAGAZINES
51
+ PERSONALIZATION
52
+ PHOTOGRAPHY
53
+ PRODUCTIVITY
54
+ SHOPPING
55
+ SOCIAL
56
+ SPORTS
57
+ TOOLS
58
+ TRANSPORTATION
59
+ TRAVEL_AND_LOCAL
60
+ WEATHER
61
+ APP_WIDGETS
data/README.markdown ADDED
@@ -0,0 +1,68 @@
1
+ # Market Bot - High performance Ruby scraper for Google's Android Market
2
+
3
+ Market Bot is a high performance Ruby scraper for Google's Android Market with a simple to use API. It is built on top of Nokogiri and Typhoeus.
4
+
5
+ ## Dependencies
6
+
7
+ * Nokogiri
8
+ * Typhoeus
9
+
10
+ ## Installation
11
+
12
+ gem install market_bot
13
+
14
+ ## Usage Examples (using batch API for downloading)
15
+
16
+ require 'rubygems'
17
+ require 'market_bot'
18
+
19
+ # Create a hydra object with 20 http workers.
20
+ hydra = Typhoeus::Hydra.new(:max_concurrency => 20)
21
+
22
+ # Download/parse the leaderboard.
23
+ lb = MarketBot::Android::Leaderboard.new(:apps_topselling_paid, :arcade, :hydra => hydra)
24
+ lb.enqueue_update
25
+ hydra.run
26
+
27
+ # Download/parse the details for the first and last entries of the leaderboard.
28
+ first_app = MarketBot::Android::App.new(lb.results.first[:market_id], :hydra => hydra)
29
+ last_app = MarketBot::Android::App.new(lb.results.last[:market_id], :hydra => hydra)
30
+ first_app.enqueue_update
31
+ last_app.enqueue_update
32
+ hydra.run
33
+
34
+ puts "First place app (#{first_app.title}) price: #{first_app.price}"
35
+ puts "Last place app (#{last_app.title}) price: #{last_app.price}"
36
+
37
+ ## Benchmarks
38
+
39
+ Below are fairly typical results using 20 http workers (Typhoeus::Hydra.new(:max_concurrency => 20). The first benchmark downloads/parses a leaderboard containing 504 entries. The second benchmark takes those 504 entries, converts them to MarketBot::Android::App objects and then downloads/parses their details. Note that 20 workers are used to prevent DOSing the market servers. Higher values may work, but I don't recommend it.
40
+
41
+ $ rake benchmark:android
42
+ ----------------------------------------------------
43
+ Benchmark Leaderboard: Top Selling Paid Apps
44
+ ----------------------------------------------------
45
+ app count: 504
46
+ time: 3.16 seconds
47
+
48
+ ----------------------------------------------------
49
+ Benchmark Apps: top Selling Paid Apps
50
+ ----------------------------------------------------
51
+ app count: 504
52
+ time: 35.685 seconds
53
+
54
+ ## Contributing to Market Bot
55
+
56
+ 1. Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
57
+ 2. Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
58
+ 3. Fork the project.
59
+ 4. Start a feature/bugfix branch.
60
+ 5. Commit and push until you are happy with your contribution.
61
+ 6. Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
62
+ 7. Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
63
+
64
+ ## Copyright
65
+
66
+ Copyright (c) 2011 Chad Remesch. See LICENSE.txt for
67
+ further details.
68
+
data/Rakefile ADDED
@@ -0,0 +1,114 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "market_bot"
18
+ gem.homepage = "http://github.com/chadrem/market_bot"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{Market Bot: High performance Ruby scraper for Google's Android Market}
21
+ gem.description = %Q{Market Bot is a high performance Ruby scraper for Google's Android Market with a simple to use API. It is built on top of Nokogiri and Typhoeus.}
22
+ gem.email = "chad@remesch.com"
23
+ gem.authors = ["Chad Remesch"]
24
+ # dependencies defined in Gemfile
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
27
+
28
+ require 'rspec/core'
29
+ require 'rspec/core/rake_task'
30
+ RSpec::Core::RakeTask.new(:spec) do |spec|
31
+ spec.pattern = FileList['spec/**/*_spec.rb']
32
+ end
33
+
34
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
35
+ spec.pattern = 'spec/**/*_spec.rb'
36
+ spec.rcov = true
37
+ end
38
+
39
+ task :default => :spec
40
+
41
+ require 'rdoc/task'
42
+ RDoc::Task.new do |rdoc|
43
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
44
+
45
+ rdoc.rdoc_dir = 'rdoc'
46
+ rdoc.title = "market_bot #{version}"
47
+ rdoc.rdoc_files.include('README*')
48
+ rdoc.rdoc_files.include('lib/**/*.rb')
49
+ end
50
+
51
+ require 'typhoeus'
52
+
53
+ namespace :spec do
54
+ namespace :data do
55
+ def download(url, save_path)
56
+ resp = Typhoeus::Request.get(url)
57
+ File.open(save_path, "w") do |file|
58
+ file.puts(resp.body)
59
+ end
60
+ end
61
+
62
+ desc 'download the latest test data used by the specs'
63
+ task :update do
64
+ download('https://market.android.com/details?id=com.bluefroggaming.popdat',
65
+ File.join(File.dirname(__FILE__), 'spec', 'market_bot', 'android', 'data', 'app_1.txt'))
66
+
67
+ download('https://market.android.com/details?id=apps_topselling_paid',
68
+ File.join(File.dirname(__FILE__), 'spec', 'market_bot', 'android', 'data', 'leaderboard-apps_topselling_paid-page1.txt'))
69
+
70
+ download('https://market.android.com/details?id=apps_topselling_paid&start=24&num=24',
71
+ File.join(File.dirname(__FILE__), 'spec', 'market_bot', 'android', 'data', 'leaderboard-apps_topselling_paid-page2.txt'))
72
+
73
+ download('https://market.android.com/details?id=apps_topselling_paid&start=48&num=24',
74
+ File.join(File.dirname(__FILE__), 'spec', 'market_bot', 'android', 'data', 'leaderboard-apps_topselling_paid-page3.txt'))
75
+
76
+ download('https://market.android.com/details?id=apps_topselling_paid&cat=ARCADE&start=1000&num=24',
77
+ File.join(File.dirname(__FILE__), 'spec', 'market_bot', 'android', 'data', 'leaderboard-apps_topselling_paid-page4.txt'))
78
+ end
79
+ end
80
+ end
81
+
82
+ namespace :benchmark do
83
+ task :android do
84
+ require 'market_bot'
85
+ require 'benchmark'
86
+
87
+ hydra = Typhoeus::Hydra.new(:max_concurrency => 20)
88
+
89
+ leaderboard = nil
90
+ leaderboard_ms = Benchmark.realtime {
91
+ leaderboard = MarketBot::Android::Leaderboard.new(:apps_topselling_paid, nil, :hydra => hydra)
92
+ leaderboard.update
93
+ }
94
+
95
+ puts '----------------------------------------------------'
96
+ puts 'Benchmark Leaderboard: Top Selling Paid Apps'
97
+ puts '----------------------------------------------------'
98
+ puts "app count: #{leaderboard.results.length}"
99
+ puts "time: #{leaderboard_ms.round(3)} seconds"
100
+ puts
101
+
102
+ apps = nil
103
+ apps_ms = Benchmark.realtime {
104
+ apps = leaderboard.results.map{ |r| MarketBot::Android::App.new(r[:market_id], :hydra => hydra).enqueue_update }
105
+ hydra.run
106
+ }
107
+
108
+ puts '----------------------------------------------------'
109
+ puts 'Benchmark Apps: top Selling Paid Apps'
110
+ puts '----------------------------------------------------'
111
+ puts "app count: #{apps.length}"
112
+ puts "time: #{apps_ms.round(3)} seconds"
113
+ end
114
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,71 @@
1
+ module MarketBot
2
+ module Android
3
+
4
+ class App
5
+ MARKET_ATTRIBUTES = [:title, :rating, :updated, :current_version, :requires_android,
6
+ :category, :installs, :size, :price, :content_rating, :description]
7
+
8
+ attr_reader :app_id
9
+ attr_reader *MARKET_ATTRIBUTES
10
+ attr_reader :hydra
11
+
12
+ def self.parse(html)
13
+ result = {}
14
+
15
+ doc = Nokogiri::HTML(html)
16
+
17
+ result[:title] = doc.title.gsub(/ - Android Market$/, '')
18
+ result[:rating] = doc.css('.doc-metadata').first.elements[2].elements[4].text[6].to_i
19
+ result[:updated] = doc.css('.doc-metadata').first.elements[2].elements[6].text
20
+ result[:current_version] = doc.css('.doc-metadata').first.elements[2].elements[8].text
21
+ result[:requires_android] = doc.css('.doc-metadata').first.elements[2].elements[10].text
22
+ result[:category] = doc.css('.doc-metadata').first.elements[2].elements[12].text
23
+ result[:installs] = doc.css('.doc-metadata').first.elements[2].elements[14].children.first.text
24
+ result[:size] = doc.css('.doc-metadata').first.elements[2].elements[16].text
25
+ result[:price] = doc.css('.doc-metadata').first.elements[2].elements[18].text
26
+ result[:content_rating] = doc.css('.doc-metadata').first.elements[2].elements[20].text
27
+ result[:description] = doc.css('#doc-original-text').first.text
28
+
29
+ result
30
+ end
31
+
32
+ def initialize(app_id, options={})
33
+ @app_id = app_id
34
+ @hydra = options[:hydra] || Typhoeus::Hydra.hydra
35
+ end
36
+
37
+ def market_url
38
+ "https://market.android.com/details?id=#{@app_id}"
39
+ end
40
+
41
+ def update
42
+ resp = Typhoeus::Request.get(market_url)
43
+ result = App.parse(resp.body)
44
+ update_callback(result)
45
+
46
+ self
47
+ end
48
+
49
+ def enqueue_update
50
+ request = Typhoeus::Request.new(market_url)
51
+ request.on_complete do |response|
52
+ result = App.parse(response.body)
53
+ update_callback(result)
54
+ end
55
+ hydra.queue(request)
56
+
57
+ self
58
+ end
59
+
60
+ private
61
+ def update_callback(result)
62
+ MARKET_ATTRIBUTES.each do |a|
63
+ attr_name = "@#{a}"
64
+ attr_value = result[a]
65
+ instance_variable_set(attr_name, attr_value)
66
+ end
67
+ end
68
+ end
69
+
70
+ end
71
+ end
@@ -0,0 +1,59 @@
1
+ module MarketBot
2
+ module Android
3
+
4
+ class Leaderboard
5
+ IDENTIFIERS = [
6
+ :apps_editors_choice,
7
+ :apps_featured,
8
+ :apps_movers_shakers,
9
+ :apps_tablet_featured,
10
+ :apps_topgrossing,
11
+ :apps_topselling_free,
12
+ :apps_topselling_new_free,
13
+ :apps_topselling_new_paid,
14
+ :apps_topselling_paid,
15
+ :apps_topselling_paid_game
16
+ ]
17
+
18
+ CATEGORIES = [
19
+ :application,
20
+ :app_wallpaper,
21
+ :app_widgets,
22
+ :arcade,
23
+ :books_and_reference,
24
+ :brain,
25
+ :business,
26
+ :cards,
27
+ :casual,
28
+ :comics,
29
+ :communication,
30
+ :education,
31
+ :entertainment,
32
+ :finance,
33
+ :game,
34
+ :game_wallpaper,
35
+ :game_widgets,
36
+ :health_and_fitness,
37
+ :libraries_and_demo,
38
+ :lifestyle,
39
+ :media_and_video,
40
+ :medical,
41
+ :music_and_audio,
42
+ :news_and_magazines,
43
+ :personalization,
44
+ :photography,
45
+ :productivity,
46
+ :racing,
47
+ :shopping,
48
+ :social,
49
+ :sports,
50
+ :sports_games,
51
+ :tools,
52
+ :transportation,
53
+ :travel_and_local,
54
+ :weather
55
+ ]
56
+ end
57
+
58
+ end
59
+ end
@@ -0,0 +1,112 @@
1
+ module MarketBot
2
+ module Android
3
+
4
+ class Leaderboard
5
+ attr_reader :identifier, :category
6
+ attr_reader :hydra
7
+
8
+ def self.parse(html)
9
+ results = []
10
+ doc = Nokogiri::HTML(html)
11
+
12
+ doc.css('.snippet').each do |snippet_node|
13
+ result = {}
14
+
15
+ details_node = snippet_node.css('.details')
16
+
17
+ unless snippet_node.css('.ratings').empty?
18
+ stars_text = snippet_node.css('.ratings').first.attributes['title'].value
19
+ result[:stars] = /Rating: (.+) stars .*/.match(stars_text)[1].to_i
20
+ else
21
+ result[:stars] = nil
22
+ end
23
+
24
+ result[:title] = details_node.css('.title').first.attributes['title'].to_s
25
+ result[:price_usd] = details_node.css('.buy-button-price').children.first.text.gsub(' Buy', '')
26
+ result[:developer] = details_node.css('.attribution').children.first.text
27
+ result[:market_id] = details_node.css('.title').first.attributes['href'].to_s.gsub('/details?id=', '').gsub(/&feature=.*$/, '')
28
+ result[:market_url] = "https://market.android.com/details?id=#{result[:market_id]}"
29
+
30
+ result[:price_usd] = '$0.00' if result[:price_usd] == 'Install'
31
+
32
+ results << result
33
+ end
34
+
35
+ results
36
+ end
37
+
38
+ def initialize(identifier, category=nil, options={})
39
+ @identifier = identifier
40
+ @category = category
41
+ @hydra = options[:hydra] || Typhoeus::Hydra.hydra
42
+ @parsed_results = []
43
+ end
44
+
45
+ def market_urls(options={})
46
+ results = []
47
+
48
+ min_page = options[:min_page] || 1
49
+ max_page = options[:max_page] || 25
50
+
51
+ (min_page..max_page).each do |page|
52
+ start_val = (page - 1) * 24
53
+
54
+ url = 'https://market.android.com/details?id=' + identifier.to_s
55
+ url << "&cat=#{category.to_s.upcase}" if category
56
+ url << "&start=#{start_val}"
57
+ url << "&num=24"
58
+
59
+ results << url
60
+ end
61
+
62
+ results
63
+ end
64
+
65
+ def enqueue_update(options={})
66
+ min_rank = options[:min_rank] || 1
67
+ max_rank = options[:max_rank] || 500
68
+
69
+ min_page = rank_to_page(min_rank)
70
+ max_page = rank_to_page(max_rank)
71
+
72
+ @parsed_results = []
73
+
74
+ urls = market_urls(:min_page => min_page, :max_page => max_page)
75
+ urls.each_index{ |i| process_page(urls[i], i+1) }
76
+
77
+ self
78
+ end
79
+
80
+ def update(options={})
81
+ enqueue_update(options)
82
+ @hydra.run
83
+
84
+ self
85
+ end
86
+
87
+ def rank_to_page(rank)
88
+ ((rank - 1) / 24) + 1
89
+ end
90
+
91
+ def results
92
+ raise 'Results do not exist yet.' unless @parsed_results
93
+ @parsed_results.reject{ |page| page.nil? || page.empty? }.flatten
94
+ end
95
+
96
+ private
97
+ def process_page(url, page_num)
98
+ request = Typhoeus::Request.new(url)
99
+ request.on_complete do |response|
100
+ result = Leaderboard.parse(response.body)
101
+ update_callback(result, page_num)
102
+ end
103
+ @hydra.queue(request)
104
+ end
105
+
106
+ def update_callback(result, page)
107
+ @parsed_results[page] = result
108
+ end
109
+ end
110
+
111
+ end
112
+ end
data/lib/market_bot.rb ADDED
@@ -0,0 +1,7 @@
1
+ require 'typhoeus'
2
+ require 'nokogiri'
3
+
4
+ require 'market_bot/android/app'
5
+ require 'market_bot/android/leaderboard/constants'
6
+ require 'market_bot/android/leaderboard'
7
+
@@ -0,0 +1,84 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '../../../spec_helper')
2
+
3
+ include MarketBot::Android
4
+
5
+ test_id = 'com.bluefroggaming.popdat'
6
+ test_src_data = read_file(File.dirname(__FILE__), 'data', 'app_1.txt')
7
+
8
+ def check_getters(app)
9
+ it 'should populate the getters' do
10
+ app.title.should == 'Pop Dat'
11
+ app.rating.should == 4
12
+ app.updated.should == 'August 27, 2011'
13
+ app.current_version.should == '1.0'
14
+ app.requires_android.should == '2.2 and up'
15
+ app.category.should == 'Arcade & Action'
16
+ app.installs.should == '50 - 100'
17
+ app.size.should == '18M'
18
+ app.price.should == 'Free'
19
+ app.content_rating.should == 'Everyone'
20
+ app.description.should =~ /^Experience the next level of chain/
21
+ end
22
+ end
23
+
24
+ describe 'App' do
25
+ context 'Construction' do
26
+ it 'should copy the app_id param' do
27
+ app = App.new(test_id)
28
+ app.app_id.should == test_id
29
+ end
30
+
31
+ it 'should copy optional params' do
32
+ hydra = Typhoeus::Hydra.new
33
+ app = App.new(test_id, :hydra => hydra)
34
+ app.hydra.should equal(hydra)
35
+ end
36
+ end
37
+
38
+ it 'should generate market URLs' do
39
+ App.new(test_id).market_url.should == "https://market.android.com/details?id=#{test_id}"
40
+ end
41
+
42
+ context 'Parsing' do
43
+ it 'should populate a hash with the correct keys/values' do
44
+ result = App.parse(test_src_data)
45
+
46
+ result[:title].should == 'Pop Dat'
47
+ result[:rating].should == 4
48
+ result[:updated].should == 'August 27, 2011'
49
+ result[:current_version].should == '1.0'
50
+ result[:requires_android].should == '2.2 and up'
51
+ result[:category].should == 'Arcade & Action'
52
+ result[:installs].should == '50 - 100'
53
+ result[:size].should == '18M'
54
+ result[:price].should == 'Free'
55
+ result[:content_rating].should == 'Everyone'
56
+ result[:description].should =~ /^Experience the next level of chain/
57
+
58
+ result
59
+ end
60
+ end
61
+
62
+ context 'Updating' do
63
+ response = Typhoeus::Response.new(:code => 200, :headers => '', :body => test_src_data)
64
+
65
+ context 'Quick API' do
66
+ app = App.new(test_id)
67
+ hydra = Typhoeus::Hydra.hydra
68
+ hydra.stub(:get, app.market_url).and_return(response)
69
+
70
+ app.update
71
+ check_getters(app)
72
+ end
73
+
74
+ context 'Batch API' do
75
+ hydra = Typhoeus::Hydra.new
76
+ app = App.new(test_id, :hydra => hydra)
77
+ hydra.stub(:get, app.market_url).and_return(response)
78
+
79
+ app.enqueue_update
80
+ hydra.run
81
+ check_getters(app)
82
+ end
83
+ end
84
+ end