alexa 0.3.1 → 0.4.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.
Files changed (38) hide show
  1. data/.travis.yml +4 -5
  2. data/Gemfile +1 -0
  3. data/README.md +76 -21
  4. data/alexa.gemspec +1 -9
  5. data/lib/alexa.rb +11 -22
  6. data/lib/alexa/api/category_browse.rb +62 -0
  7. data/lib/alexa/api/category_listings.rb +66 -0
  8. data/lib/alexa/api/sites_linking_in.rb +41 -0
  9. data/lib/alexa/api/traffic_history.rb +45 -0
  10. data/lib/alexa/api/url_info.rb +108 -0
  11. data/lib/alexa/client.rb +30 -0
  12. data/lib/alexa/connection.rb +86 -0
  13. data/lib/alexa/exceptions.rb +17 -0
  14. data/lib/alexa/utils.rb +19 -0
  15. data/lib/alexa/version.rb +1 -1
  16. data/test/api/category_browse_test.rb +27 -0
  17. data/test/api/category_listings_test.rb +19 -0
  18. data/test/api/sites_linking_in_test.rb +23 -0
  19. data/test/api/traffic_history_test.rb +24 -0
  20. data/test/api/url_info_test.rb +112 -0
  21. data/test/client_test.rb +65 -0
  22. data/test/connection_test.rb +37 -0
  23. data/test/fixtures/bad_request.txt +8 -0
  24. data/test/fixtures/category_browse/card_games.txt +480 -0
  25. data/test/fixtures/category_listings/card_games.txt +138 -0
  26. data/test/fixtures/sites_linking_in/github_count_3.txt +24 -0
  27. data/test/fixtures/traffic_history/alexa_example.txt +37 -0
  28. data/test/fixtures/traffic_history/github_range_2.txt +8 -0
  29. data/test/fixtures/{custom-response-group.txt → url_info/custom-response-group.txt} +0 -0
  30. data/test/fixtures/{empty_group.txt → url_info/empty_group.txt} +0 -0
  31. data/test/fixtures/{github_full.txt → url_info/github_full.txt} +0 -0
  32. data/test/fixtures/{github_rank.txt → url_info/github_rank.txt} +0 -0
  33. data/test/helper.rb +6 -8
  34. data/test/utils_test.rb +54 -0
  35. metadata +33 -23
  36. data/lib/alexa/url_info.rb +0 -109
  37. data/test/config_test.rb +0 -35
  38. data/test/url_info_test.rb +0 -152
@@ -0,0 +1,30 @@
1
+ module Alexa
2
+ class Client
3
+ attr_reader :access_key_id, :secret_access_key
4
+
5
+ def initialize(configuration = {})
6
+ @access_key_id = configuration[:access_key_id] || raise(ArgumentError.new("You must specify access_key_id"))
7
+ @secret_access_key = configuration[:secret_access_key] || raise(ArgumentError.new("You must specify secret_access_key"))
8
+ end
9
+
10
+ def category_browse(arguments = {})
11
+ API::CategoryBrowse.new(:access_key_id => access_key_id, :secret_access_key => secret_access_key).fetch(arguments)
12
+ end
13
+
14
+ def category_listings(arguments = {})
15
+ API::CategoryListings.new(:access_key_id => access_key_id, :secret_access_key => secret_access_key).fetch(arguments)
16
+ end
17
+
18
+ def sites_linking_in(arguments = {})
19
+ API::SitesLinkingIn.new(:access_key_id => access_key_id, :secret_access_key => secret_access_key).fetch(arguments)
20
+ end
21
+
22
+ def traffic_history(arguments = {})
23
+ API::TrafficHistory.new(:access_key_id => access_key_id, :secret_access_key => secret_access_key).fetch(arguments)
24
+ end
25
+
26
+ def url_info(arguments = {})
27
+ API::UrlInfo.new(:access_key_id => access_key_id, :secret_access_key => secret_access_key).fetch(arguments)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,86 @@
1
+ require "cgi"
2
+ require "base64"
3
+ require "openssl"
4
+ require "digest/sha1"
5
+ require "net/http"
6
+ require "time"
7
+
8
+ module Alexa
9
+ class Connection
10
+ attr_accessor :secret_access_key, :access_key_id
11
+ attr_writer :params
12
+
13
+ def initialize(credentials = {})
14
+ self.secret_access_key = credentials.fetch(:secret_access_key)
15
+ self.access_key_id = credentials.fetch(:access_key_id)
16
+ end
17
+
18
+ def params
19
+ @params ||= {}
20
+ end
21
+
22
+ def get(params = {})
23
+ self.params = params
24
+ encode handle_response(request).body
25
+ end
26
+
27
+ def handle_response(response)
28
+ case response.code.to_i
29
+ when 200...300
30
+ response
31
+ when 300...600
32
+ if response.body.nil?
33
+ raise ResponseError.new(nil, response)
34
+ else
35
+ xml = MultiXml.parse(response.body)
36
+ message = xml["Response"]["Errors"]["Error"]["Message"]
37
+ raise ResponseError.new(message, response)
38
+ end
39
+ else
40
+ raise ResponseError.new("Unknown code: #{respnse.code}", response)
41
+ end
42
+ end
43
+
44
+ def request
45
+ Net::HTTP.get_response(uri)
46
+ end
47
+
48
+ def timestamp
49
+ @timestamp ||= Time::now.utc.strftime("%Y-%m-%dT%H:%M:%S.000Z")
50
+ end
51
+
52
+ def signature
53
+ Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new("sha256"), secret_access_key, sign)).strip
54
+ end
55
+
56
+ def uri
57
+ URI.parse("http://#{Alexa::API_HOST}/?" + query + "&Signature=" + CGI::escape(signature))
58
+ end
59
+
60
+ def default_params
61
+ {
62
+ "AWSAccessKeyId" => access_key_id,
63
+ "SignatureMethod" => "HmacSHA256",
64
+ "SignatureVersion" => "2",
65
+ "Timestamp" => timestamp,
66
+ "Version" => Alexa::API_VERSION
67
+ }
68
+ end
69
+
70
+ def sign
71
+ "GET\n" + Alexa::API_HOST + "\n/\n" + query
72
+ end
73
+
74
+ def query
75
+ default_params.merge(params).map { |key, value| "#{key}=#{CGI::escape(value.to_s)}" }.sort.join("&")
76
+ end
77
+
78
+ def encode(string)
79
+ if "muflon".respond_to?(:force_encoding)
80
+ string.force_encoding(Encoding::UTF_8)
81
+ else
82
+ string
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,17 @@
1
+ module Alexa
2
+ # All Alexa exceptions can be cought by rescuing:
3
+ # Alexa::StandardError
4
+ #
5
+ class StandardError < StandardError; end
6
+
7
+ class ArgumentError < StandardError; end
8
+
9
+ class ResponseError < StandardError
10
+ attr_reader :response
11
+
12
+ def initialize(message, response)
13
+ @response = response
14
+ super(message)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,19 @@
1
+ module Alexa
2
+ module Utils
3
+ def safe_retrieve(hash, *keys)
4
+ return if !hash.kind_of?(Hash) || !hash.has_key?(keys.first)
5
+
6
+ if keys.size == 1
7
+ hash[keys.first]
8
+ elsif keys.size > 1
9
+ Alexa::Utils.safe_retrieve(hash[keys.first], *keys[1..-1])
10
+ end
11
+ end
12
+
13
+ def camelize(string)
14
+ string.split("_").map { |w| w.capitalize }.join
15
+ end
16
+
17
+ module_function :safe_retrieve, :camelize
18
+ end
19
+ end
data/lib/alexa/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Alexa
2
- VERSION = "0.3.1"
2
+ VERSION = "0.4.0"
3
3
  end
@@ -0,0 +1,27 @@
1
+ require "helper"
2
+
3
+ describe Alexa::API::CategoryBrowse do
4
+ describe "parsing xml" do
5
+ before do
6
+ stub_request(:get, %r{http://awis.amazonaws.com}).to_return(fixture("category_browse/card_games.txt"))
7
+ @category_browse = Alexa::API::CategoryBrowse.new(:access_key_id => "fake", :secret_access_key => "fake")
8
+ @category_browse.fetch(:path => "Top/Games/Card_Games")
9
+ end
10
+
11
+ it "returns categories" do
12
+ assert_equal 8, @category_browse.categories.size
13
+ end
14
+
15
+ it "returns language categories" do
16
+ assert_equal 20, @category_browse.language_categories.size
17
+ end
18
+
19
+ it "returns related categories" do
20
+ assert_equal 8, @category_browse.related_categories.size
21
+ end
22
+
23
+ it "returns letter bars" do
24
+ assert_equal 36, @category_browse.letter_bars.size
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,19 @@
1
+ require "helper"
2
+
3
+ describe Alexa::API::CategoryListings do
4
+ describe "parsing xml" do
5
+ before do
6
+ stub_request(:get, %r{http://awis.amazonaws.com}).to_return(fixture("category_listings/card_games.txt"))
7
+ @category_listings = Alexa::API::CategoryListings.new(:access_key_id => "fake", :secret_access_key => "fake")
8
+ @category_listings.fetch(:path => "Top/Games/Card_Games")
9
+ end
10
+
11
+ it "returns recursive count" do
12
+ assert_equal 1051, @category_listings.recursive_count
13
+ end
14
+
15
+ it "returns listings" do
16
+ assert_equal 20, @category_listings.listings.size
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,23 @@
1
+ require "helper"
2
+
3
+ describe Alexa::API::SitesLinkingIn do
4
+ describe "parsing xml" do
5
+ before do
6
+ stub_request(:get, %r{http://awis.amazonaws.com}).to_return(fixture("sites_linking_in/github_count_3.txt"))
7
+ @sites_linking_in = Alexa::API::SitesLinkingIn.new(:access_key_id => "fake", :secret_access_key => "fake")
8
+ @sites_linking_in.fetch(:url => "github.com", :count => 3)
9
+ end
10
+
11
+ it "returns sites" do
12
+ assert_equal 3, @sites_linking_in.sites.size
13
+ end
14
+
15
+ it "has Title attribute on single site" do
16
+ assert_equal "google.com", @sites_linking_in.sites.first["Title"]
17
+ end
18
+
19
+ it "has Url attribute on single site" do
20
+ assert_equal "code.google.com:80/a/eclipselabs.org/p/m2eclipse-android-integration", @sites_linking_in.sites.first["Url"]
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,24 @@
1
+ require "helper"
2
+
3
+ describe Alexa::API::TrafficHistory do
4
+ it "defaults start to be in range to current time" do
5
+ stub_request(:get, %r{http://awis.amazonaws.com}).to_return(:body => "ok")
6
+ @traffic_history = Alexa::API::TrafficHistory.new(:access_key_id => "fake", :secret_access_key => "fake")
7
+ @traffic_history.fetch(:url => "github.com", :range => 14)
8
+
9
+ # 14 days from now
10
+ assert_in_delta (Time.now - 3600 * 24 * 14).to_i, @traffic_history.start.to_i, 0.001
11
+ end
12
+
13
+ describe "parsing xml" do
14
+ before do
15
+ stub_request(:get, %r{http://awis.amazonaws.com}).to_return(fixture("traffic_history/alexa_example.txt"))
16
+ @traffic_history = Alexa::API::TrafficHistory.new(:access_key_id => "fake", :secret_access_key => "fake")
17
+ @traffic_history.fetch(:url => "amazon.com", :start => "20050101", :range => 31)
18
+ end
19
+
20
+ it "returns data" do
21
+ # assert_equal 1, @traffic_history.data.size
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,112 @@
1
+ require "helper"
2
+
3
+ describe Alexa::API::UrlInfo do
4
+ it "allows to pass single attribute as response_group" do
5
+ stub_request(:get, %r{http://awis.amazonaws.com}).to_return(:body => "ok")
6
+ @url_info = Alexa::API::UrlInfo.new(:access_key_id => "fake", :secret_access_key => "fake")
7
+ @url_info.fetch(:url => "github.com", :response_group => "rank")
8
+
9
+ assert_equal ["rank"], @url_info.response_group
10
+ end
11
+
12
+ describe "parsing xml returned by options rank, links_in_count, site_data" do
13
+ before do
14
+ stub_request(:get, %r{http://awis.amazonaws.com}).to_return(fixture("url_info/custom-response-group.txt"))
15
+ @url_info = Alexa::API::UrlInfo.new(:access_key_id => "fake", :secret_access_key => "fake")
16
+ @url_info.fetch(:url => "github.com", :response_group => ["rank", "links_in_count", "site_data"])
17
+ end
18
+
19
+ it "returns rank" do
20
+ assert_equal 493, @url_info.rank
21
+ end
22
+
23
+ it "returns data url" do
24
+ assert_equal "github.com/", @url_info.data_url
25
+ end
26
+
27
+ it "returns site title" do
28
+ assert_equal "GitHub", @url_info.site_title
29
+ end
30
+
31
+ it "returns site description" do
32
+ expected = "Online project hosting using Git. Includes source-code browser, in-line editing, wikis, and ticketing. Free for public open-source code. Commercial closed source hosting is also available."
33
+
34
+ assert_equal expected, @url_info.site_description
35
+ end
36
+ end
37
+
38
+ describe "with github.com full response group" do
39
+ before do
40
+ stub_request(:get, %r{http://awis.amazonaws.com}).to_return(fixture("url_info/github_full.txt"))
41
+ @url_info = Alexa::API::UrlInfo.new(:access_key_id => "fake", :secret_access_key => "fake")
42
+ @url_info.fetch(:url => "github.com")
43
+ end
44
+
45
+ it "returns rank" do
46
+ assert_equal 551, @url_info.rank
47
+ end
48
+
49
+ it "returns data url" do
50
+ assert_equal "github.com", @url_info.data_url
51
+ end
52
+
53
+ it "returns site title" do
54
+ assert_equal "GitHub", @url_info.site_title
55
+ end
56
+
57
+ it "returns site description" do
58
+ expected = "Online project hosting using Git. Includes source-code browser, in-line editing, wikis, and ticketing. Free for public open-source code. Commercial closed source hosting is also available."
59
+ assert_equal expected, @url_info.site_description
60
+ end
61
+
62
+ it "returns language locale" do
63
+ assert_nil @url_info.language_locale
64
+ end
65
+
66
+ it "returns language encoding" do
67
+ assert_nil @url_info.language_encoding
68
+ end
69
+
70
+ it "returns links in count" do
71
+ assert_equal 43819, @url_info.links_in_count
72
+ end
73
+
74
+ it "returns keywords" do
75
+ assert_nil @url_info.keywords
76
+ end
77
+
78
+ it "returns related links" do
79
+ assert_equal 10, @url_info.related_links.size
80
+ end
81
+
82
+ it "returns speed_median load time" do
83
+ assert_equal 1031, @url_info.speed_median_load_time
84
+ end
85
+
86
+ it "returns speed percentile" do
87
+ assert_equal 68, @url_info.speed_percentile
88
+ end
89
+
90
+ it "returns rank by country" do
91
+ assert_equal 19, @url_info.rank_by_country.size
92
+ end
93
+
94
+ it "returns rank by city" do
95
+ assert_equal 163, @url_info.rank_by_city.size
96
+ end
97
+
98
+ it "returns usage statistics" do
99
+ assert_equal 4, @url_info.usage_statistics.size
100
+ end
101
+ end
102
+
103
+ describe "with github.com rank response group" do
104
+ it "successfuly connects" do
105
+ stub_request(:get, %r{http://awis.amazonaws.com}).to_return(fixture("url_info/github_rank.txt"))
106
+ @url_info = Alexa::API::UrlInfo.new(:access_key_id => "fake", :secret_access_key => "fake")
107
+ @url_info.fetch(:url => "github.com", :response_group => ["rank"])
108
+
109
+ assert_equal 551, @url_info.rank
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,65 @@
1
+ require "helper"
2
+
3
+ describe Alexa::Client do
4
+ it "raises Argument Error when access_key_id not present" do
5
+ assert_raises Alexa::ArgumentError, /access_key_id/ do
6
+ Alexa::Client.new(:secret_access_key => "secret")
7
+ end
8
+ end
9
+
10
+ it "raises Argument Error when secret_access_key not present" do
11
+ assert_raises Alexa::ArgumentError, /secret_access_key/ do
12
+ Alexa::Client.new(:access_key_id => "key")
13
+ end
14
+ end
15
+
16
+ it "fetches UrlInfo results" do
17
+ credentials = {:access_key_id => "key", :secret_access_key => "secret"}
18
+ client = Alexa::Client.new(credentials)
19
+ url_info = stub
20
+ Alexa::API::UrlInfo.expects(:new).with(credentials).returns(url_info)
21
+ url_info.expects(:fetch).with(:url => "github.com")
22
+
23
+ client.url_info(:url => "github.com")
24
+ end
25
+
26
+ it "fetches SitesLinkingIn results" do
27
+ credentials = {:access_key_id => "key", :secret_access_key => "secret"}
28
+ client = Alexa::Client.new(credentials)
29
+ sites_linking_in = stub
30
+ Alexa::API::SitesLinkingIn.expects(:new).with(credentials).returns(sites_linking_in)
31
+ sites_linking_in.expects(:fetch).with(:url => "github.com", :count => 15, :start => 2)
32
+
33
+ client.sites_linking_in(:url => "github.com", :count => 15, :start => 2)
34
+ end
35
+
36
+ it "fetches TrafficHistory results" do
37
+ credentials = {:access_key_id => "key", :secret_access_key => "secret"}
38
+ client = Alexa::Client.new(credentials)
39
+ traffic_history = stub
40
+ Alexa::API::TrafficHistory.expects(:new).with(credentials).returns(traffic_history)
41
+ traffic_history.expects(:fetch).with(:url => "github.com", :range => 15)
42
+
43
+ client.traffic_history(:url => "github.com", :range => 15)
44
+ end
45
+
46
+ it "fetches CategoryBrowse results" do
47
+ credentials = {:access_key_id => "key", :secret_access_key => "secret"}
48
+ client = Alexa::Client.new(credentials)
49
+ category_browse = stub
50
+ Alexa::API::CategoryBrowse.expects(:new).with(credentials).returns(category_browse)
51
+ category_browse.expects(:fetch).with(:path => "Top/Games/Card_Games")
52
+
53
+ client.category_browse(:path => "Top/Games/Card_Games")
54
+ end
55
+
56
+ it "fetches CategoryListings results" do
57
+ credentials = {:access_key_id => "key", :secret_access_key => "secret"}
58
+ client = Alexa::Client.new(credentials)
59
+ category_listings = stub
60
+ Alexa::API::CategoryListings.expects(:new).with(credentials).returns(category_listings)
61
+ category_listings.expects(:fetch).with(:path => "Top/Games/Card_Games")
62
+
63
+ client.category_listings(:path => "Top/Games/Card_Games")
64
+ end
65
+ end
@@ -0,0 +1,37 @@
1
+ require "helper"
2
+
3
+ describe Alexa::Connection do
4
+ it "calculates signature" do
5
+ connection = Alexa::Connection.new(:access_key_id => "fake", :secret_access_key => "fake")
6
+ connection.stubs(:timestamp).returns("2012-08-08T20:58:32.000Z")
7
+
8
+ assert_equal "3uaSV1s7uJUtIDivvM8mzPkNxq+Za8jAFCDnQOvjRH4=", connection.signature
9
+ end
10
+
11
+ it "normalizes non string params value" do
12
+ connection = Alexa::Connection.new(:access_key_id => "fake", :secret_access_key => "fake")
13
+ connection.stubs(:timestamp).returns("2012-08-08T20:58:32.000Z")
14
+ connection.params = {:custom_value => 3}
15
+
16
+ expected = "AWSAccessKeyId=fake&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2012-08-08T20%3A58%3A32.000Z&Version=2005-07-11&custom_value=3"
17
+ assert_equal expected, connection.query
18
+ end
19
+
20
+ it "raises error when unathorized" do
21
+ stub_request(:get, %r{http://awis.amazonaws.com}).to_return(fixture("unathorized.txt"))
22
+ connection = Alexa::Connection.new(:access_key_id => "wrong", :secret_access_key => "wrong")
23
+
24
+ assert_raises Alexa::ResponseError do
25
+ connection.get
26
+ end
27
+ end
28
+
29
+ it "raises error when forbidden" do
30
+ stub_request(:get, %r{http://awis.amazonaws.com}).to_return(fixture("forbidden.txt"))
31
+ connection = Alexa::Connection.new(:access_key_id => "wrong", :secret_access_key => "wrong")
32
+
33
+ assert_raises Alexa::ResponseError do
34
+ connection.get
35
+ end
36
+ end
37
+ end