social-avatar-proxy 0.0.1
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.
- data/.gitignore +18 -0
- data/.rvmrc +12 -0
- data/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +58 -0
- data/Rakefile +13 -0
- data/lib/social-avatar-proxy.rb +1 -0
- data/lib/social_avatar_proxy/app.rb +76 -0
- data/lib/social_avatar_proxy/avatar.rb +58 -0
- data/lib/social_avatar_proxy/facebook_avatar.rb +13 -0
- data/lib/social_avatar_proxy/remote_file_resolver.rb +49 -0
- data/lib/social_avatar_proxy/twitter_avatar.rb +15 -0
- data/lib/social_avatar_proxy/version.rb +3 -0
- data/lib/social_avatar_proxy.rb +5 -0
- data/social-avatar-proxy.gemspec +28 -0
- data/spec/social_avatar_proxy/app_spec.rb +54 -0
- data/spec/social_avatar_proxy/avatar_spec.rb +99 -0
- data/spec/social_avatar_proxy/facebook_avatar_spec.rb +39 -0
- data/spec/social_avatar_proxy/remote_file_resolver_spec.rb +66 -0
- data/spec/social_avatar_proxy/twitter_avatar_spec.rb +39 -0
- data/spec/spec_helper.rb +9 -0
- data/spec/support/env.rb +8 -0
- metadata +162 -0
data/.gitignore
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
if ! rvm list | grep -q ruby-1.9.3-p194 ; then
|
4
|
+
rvm install 1.9.3-p194
|
5
|
+
fi
|
6
|
+
|
7
|
+
rvm 1.9.3-p194@platformq_social_avatar_proxy --create
|
8
|
+
|
9
|
+
if ! gem list | grep -q bundler ; then
|
10
|
+
gem install --no-ri --no-rdoc bundler
|
11
|
+
bundle install --without production
|
12
|
+
fi
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Platform Q LLC
|
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,58 @@
|
|
1
|
+
# SocialAvatarProxy
|
2
|
+
|
3
|
+
This gem acts as a proxy for avatars on Twitter & Facebook.
|
4
|
+
|
5
|
+
[![Build Status][2]][1]
|
6
|
+
|
7
|
+
[1]: http://travis-ci.org/platformq/social-avatar-proxy
|
8
|
+
[2]: https://secure.travis-ci.org/platformq/social-avatar-proxy.png?branch=master
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
Add this line to your application's Gemfile:
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
gem "social-avatar-proxy"
|
16
|
+
```
|
17
|
+
|
18
|
+
And then execute:
|
19
|
+
|
20
|
+
$ bundle
|
21
|
+
|
22
|
+
Or install it yourself as:
|
23
|
+
|
24
|
+
$ gem install social-avatar-proxy
|
25
|
+
|
26
|
+
## Usage
|
27
|
+
|
28
|
+
This usage is based on Rails, however it should be easily adaptable for other languages:
|
29
|
+
|
30
|
+
In your `config/routes.rb` file:
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
mount SocialAvatarProxy::App, at: "/avatars"
|
34
|
+
```
|
35
|
+
|
36
|
+
In your views:
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
# for a Twitter user, by username:
|
40
|
+
image_tag(twitter_avatar_path("username"))
|
41
|
+
# by ID:
|
42
|
+
image_tag(twitter_avatar_path(12345))
|
43
|
+
|
44
|
+
# for a Facebook user, by username:
|
45
|
+
image_tag(facebook_avatar_path("username"))
|
46
|
+
# by ID:
|
47
|
+
image_tag(facebook_avatar_path(12345))
|
48
|
+
```
|
49
|
+
|
50
|
+
The above helper methods are provided by the `SocialAvatarProxy::Paths` module.
|
51
|
+
|
52
|
+
## Contributing
|
53
|
+
|
54
|
+
1. Fork it
|
55
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
56
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
57
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
58
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require "bundler/gem_tasks"
|
3
|
+
|
4
|
+
require "rspec/core/rake_task"
|
5
|
+
|
6
|
+
desc "Runs all the specs"
|
7
|
+
task default: %w(spec)
|
8
|
+
|
9
|
+
desc "Run specs"
|
10
|
+
RSpec::Core::RakeTask.new do |t|
|
11
|
+
t.pattern = "./spec/**/*_spec.rb" # don't need this, it's default.
|
12
|
+
# Put spec opts in a file named .rspec in root
|
13
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require "social_avatar_proxy"
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require "social_avatar_proxy/facebook_avatar"
|
2
|
+
require "social_avatar_proxy/twitter_avatar"
|
3
|
+
require "rack"
|
4
|
+
|
5
|
+
module SocialAvatarProxy
|
6
|
+
class App
|
7
|
+
def self.call(env, options = {})
|
8
|
+
new(env, options).response.finish
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :options
|
12
|
+
|
13
|
+
def initialize(env, options = {})
|
14
|
+
@request = Rack::Request.new(env)
|
15
|
+
@options = {
|
16
|
+
expires: 86400, # 1 day from now
|
17
|
+
cache_control: {
|
18
|
+
max_age: 86400, # store for 1 day, after that re-request
|
19
|
+
max_stale: 86400, # allow cache to be a day stale
|
20
|
+
public: true # allow proxy caching
|
21
|
+
}
|
22
|
+
}.merge(options)
|
23
|
+
end
|
24
|
+
|
25
|
+
def not_found
|
26
|
+
Rack::Response.new("Not Found", 404)
|
27
|
+
end
|
28
|
+
|
29
|
+
def response
|
30
|
+
# ensure this is a valid avatar request
|
31
|
+
unless @request.path =~ /^\/(facebook|twitter)\/([\w\-\.]+)(\.(jpe?g|png|gif|bmp))?$/i
|
32
|
+
return not_found
|
33
|
+
end
|
34
|
+
# load the avatar
|
35
|
+
avatar = load_avatar($1, $2)
|
36
|
+
# if it exists
|
37
|
+
if avatar.exist?
|
38
|
+
# render the avatar to the response
|
39
|
+
Rack::Response.new do |response|
|
40
|
+
# set the last modified header
|
41
|
+
response["Last-Modified"] = avatar.last_modified
|
42
|
+
# set the content type header
|
43
|
+
response["Content-Type"] = avatar.content_type
|
44
|
+
# if we want to expire in a set time, calculate the header
|
45
|
+
if options[:expires]
|
46
|
+
response["Expires"] = (Time.now + options[:expires]).httpdate
|
47
|
+
end
|
48
|
+
# if we want to set cache control settings
|
49
|
+
if cc = options[:cache_control]
|
50
|
+
directives = []
|
51
|
+
directives << "no-cache" if cc[:no_cache]
|
52
|
+
directives << "max-stale=#{cc[:max_stale]}" if cc[:max_stale]
|
53
|
+
directives << "max-age=#{cc[:max_age]}" if cc[:max_age]
|
54
|
+
directives << cc[:public] ? "public" : "private"
|
55
|
+
response["Cache-Control"] = directives.join("; ")
|
56
|
+
end
|
57
|
+
# set the data
|
58
|
+
response.write(avatar.body)
|
59
|
+
end
|
60
|
+
# if the avatar doesn't exist
|
61
|
+
else
|
62
|
+
not_found
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
def load_avatar(service, id)
|
68
|
+
# titleize the service name
|
69
|
+
service = service.gsub(/[\_\-]/, " ").gsub(/\b([a-z])/) do |match|
|
70
|
+
match.upcase
|
71
|
+
end
|
72
|
+
# pass it onto the
|
73
|
+
SocialAvatarProxy.const_get("#{service}Avatar").new(id)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require "social_avatar_proxy/remote_file_resolver"
|
2
|
+
|
3
|
+
module SocialAvatarProxy
|
4
|
+
class Avatar
|
5
|
+
attr_reader :identifier
|
6
|
+
|
7
|
+
def initialize(identifier)
|
8
|
+
@identifier = identifier
|
9
|
+
end
|
10
|
+
|
11
|
+
# downloads the avatar from the remote server
|
12
|
+
def body
|
13
|
+
response.body
|
14
|
+
end
|
15
|
+
|
16
|
+
# returns whether the avatar returns a successful response
|
17
|
+
def exist?
|
18
|
+
@exists ||= begin
|
19
|
+
# ensure the response is success/redirect
|
20
|
+
response.code.to_i.between?(200, 399)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
alias_method :exists?, :exist?
|
24
|
+
|
25
|
+
# returns the last modified timestamp
|
26
|
+
def last_modified
|
27
|
+
@last_modified ||= begin
|
28
|
+
response["last-modified"] &&
|
29
|
+
Time.parse(response["last-modified"]) or
|
30
|
+
Time.now
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# returns the content type of the file
|
35
|
+
def content_type
|
36
|
+
@content_type ||= begin
|
37
|
+
response["content-type"] ||
|
38
|
+
"image/jpeg"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# returns whether the avatar has changed
|
43
|
+
def modified_since?(timestamp)
|
44
|
+
last_modified > timestamp
|
45
|
+
end
|
46
|
+
|
47
|
+
# in the base Avatar class, we'll use the identifier as the URL
|
48
|
+
def remote_url
|
49
|
+
identifier
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
# request the remote file
|
54
|
+
def response
|
55
|
+
@response ||= RemoteFileResolver.new(remote_url).resolve
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require "social_avatar_proxy/avatar"
|
2
|
+
|
3
|
+
module SocialAvatarProxy
|
4
|
+
class FacebookAvatar < Avatar
|
5
|
+
def remote_url
|
6
|
+
if identifier =~ /^[\w\-\.]+$/i
|
7
|
+
"https://graph.facebook.com/#{identifier}/picture"
|
8
|
+
else
|
9
|
+
raise RuntimeError, "Identifier contains invalid characters"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require "net/https"
|
2
|
+
require "net/http"
|
3
|
+
require "uri"
|
4
|
+
require "timeout"
|
5
|
+
|
6
|
+
module SocialAvatarProxy
|
7
|
+
class TooManyRedirects < StandardError; end
|
8
|
+
class TimeoutError < Timeout::Error; end
|
9
|
+
|
10
|
+
class RemoteFileResolver
|
11
|
+
def initialize(url, limit = 5)
|
12
|
+
# timeout if we have no redirects left
|
13
|
+
raise TooManyRedirects if limit <= 0
|
14
|
+
# store the limit and URL
|
15
|
+
@url, @redirect_limit = url, limit
|
16
|
+
end
|
17
|
+
|
18
|
+
def resolve
|
19
|
+
response = Timeout::timeout(30) do
|
20
|
+
perform_request
|
21
|
+
end
|
22
|
+
|
23
|
+
# if this is a redirect
|
24
|
+
if response.kind_of?(Net::HTTPRedirection)
|
25
|
+
# follow the redirect
|
26
|
+
resolver = self.class.new(response["location"], @redirect_limit - 1)
|
27
|
+
resolver.resolve
|
28
|
+
# if this is not a redirect
|
29
|
+
else
|
30
|
+
# return the response
|
31
|
+
response
|
32
|
+
end
|
33
|
+
rescue Timeout::Error => e
|
34
|
+
raise TimeoutError, e.message, e.backtrace
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
def uri
|
39
|
+
URI.parse(@url)
|
40
|
+
end
|
41
|
+
|
42
|
+
def perform_request
|
43
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
44
|
+
http.use_ssl = uri.scheme == "https"
|
45
|
+
request = Net::HTTP::Get.new(uri.request_uri)
|
46
|
+
http.request(request)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require "social_avatar_proxy/avatar"
|
2
|
+
|
3
|
+
module SocialAvatarProxy
|
4
|
+
class TwitterAvatar < Avatar
|
5
|
+
def remote_url
|
6
|
+
if identifier =~ /^\d+$/
|
7
|
+
"http://api.twitter.com/1/users/profile_image?user_id=#{identifier}&size=original"
|
8
|
+
elsif identifier =~ /^[\w\-\.]+$/i
|
9
|
+
"http://api.twitter.com/1/users/profile_image?screen_name=#{identifier}&size=original"
|
10
|
+
else
|
11
|
+
raise RuntimeError, "Identifier contains invalid characters"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
|
5
|
+
require "social_avatar_proxy/version"
|
6
|
+
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.name = "social-avatar-proxy"
|
9
|
+
s.version = SocialAvatarProxy::VERSION
|
10
|
+
s.authors = ["Ryan Townsend"]
|
11
|
+
s.email = ["ryan@ryantownsend.co.uk"]
|
12
|
+
s.description = %q{This gem acts as a proxy for avatars on Twitter & Facebook.}
|
13
|
+
s.summary = s.description
|
14
|
+
s.homepage = "https://github.com/platformq/social-avatar-proxy"
|
15
|
+
s.license = "MIT"
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split($/)
|
18
|
+
s.executables = s.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
19
|
+
s.test_files = s.files.grep(%r{^(spec|features)/})
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
|
22
|
+
s.add_dependency "rack"
|
23
|
+
|
24
|
+
s.add_development_dependency "rake"
|
25
|
+
s.add_development_dependency "rspec"
|
26
|
+
s.add_development_dependency "simplecov"
|
27
|
+
s.add_development_dependency "foreman"
|
28
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "rack"
|
3
|
+
|
4
|
+
describe SocialAvatarProxy::App do
|
5
|
+
subject do
|
6
|
+
app = SocialAvatarProxy::App
|
7
|
+
Rack::MockRequest.new(app)
|
8
|
+
end
|
9
|
+
|
10
|
+
let(:successful_response) do
|
11
|
+
response = Net::HTTPSuccess.new("1.1", 200, "Success")
|
12
|
+
response.stub(:stream_check).and_return(true)
|
13
|
+
response.stub(:read_body).and_return("data")
|
14
|
+
response
|
15
|
+
end
|
16
|
+
|
17
|
+
let(:not_found_response) do
|
18
|
+
Net::HTTPNotFound.new("1.1", 404, "Not Found")
|
19
|
+
end
|
20
|
+
|
21
|
+
let(:response) { nil }
|
22
|
+
|
23
|
+
before(:each) do
|
24
|
+
klass = SocialAvatarProxy::Avatar
|
25
|
+
klass.any_instance.stub(:response).and_return(response)
|
26
|
+
end
|
27
|
+
|
28
|
+
context "given an invalid path" do
|
29
|
+
it "should return a 404 response" do
|
30
|
+
path = "/unknown"
|
31
|
+
expect(subject.get(path).status).to eq(404)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context "given a valid path" do
|
36
|
+
context "that finds an existing avatar" do
|
37
|
+
let(:response) { successful_response }
|
38
|
+
|
39
|
+
it "should return a 200 response" do
|
40
|
+
path = "/facebook/61413673"
|
41
|
+
expect(subject.get(path).status).to eq(200)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context "that fails to find an avatar" do
|
46
|
+
let(:response) { not_found_response }
|
47
|
+
|
48
|
+
it "should return a 404 response" do
|
49
|
+
path = "/facebook/someRandomUserThatDoesntExist"
|
50
|
+
expect(subject.get(path).status).to eq(404)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe SocialAvatarProxy::Avatar do
|
4
|
+
subject do
|
5
|
+
SocialAvatarProxy::Avatar.new(id)
|
6
|
+
end
|
7
|
+
|
8
|
+
let(:id) { "http://www.example.com/" }
|
9
|
+
|
10
|
+
let(:response) { nil }
|
11
|
+
|
12
|
+
let(:successful_response) do
|
13
|
+
response = Net::HTTPSuccess.new("1.1", 200, "Success")
|
14
|
+
response.stub(:stream_check).and_return(true)
|
15
|
+
response.stub(:read_body).and_return("data")
|
16
|
+
response
|
17
|
+
end
|
18
|
+
|
19
|
+
before(:each) do
|
20
|
+
klass = SocialAvatarProxy::RemoteFileResolver
|
21
|
+
klass.any_instance.stub(:resolve).and_return(response)
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "#remote_url" do
|
25
|
+
it "should return the identifier" do
|
26
|
+
expect(subject.remote_url).to eq(id)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context "with a valid response" do
|
31
|
+
let(:response) { successful_response }
|
32
|
+
|
33
|
+
describe "#exist?" do
|
34
|
+
it "should be true" do
|
35
|
+
expect(subject.exist?).to be_true
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "#body" do
|
40
|
+
it "should be able to download the body" do
|
41
|
+
expect(subject.body).to eq "data"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "#content_type" do
|
46
|
+
context "with a content-type header" do
|
47
|
+
before(:each) do
|
48
|
+
response.stub(:[]).with("content-type").and_return("image/png")
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should return the header value" do
|
52
|
+
expect(subject.content_type).to eq("image/png")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context "with a content-type header" do
|
57
|
+
it "should return image/jpeg as the default" do
|
58
|
+
expect(subject.content_type).to eq("image/jpeg")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context "with a last-modified header" do
|
64
|
+
before(:each) do
|
65
|
+
response.stub(:[]).with("last-modified").and_return(timestamp.httpdate)
|
66
|
+
end
|
67
|
+
|
68
|
+
let(:timestamp) { Time.now - 86400 }
|
69
|
+
|
70
|
+
describe "#last_modified" do
|
71
|
+
it "should parse the header and return a DateTime object" do
|
72
|
+
expect(subject.last_modified.to_i).to eq(timestamp.to_i)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe "#modified_since?" do
|
77
|
+
context "given a date older than the timestamp" do
|
78
|
+
it "should return true" do
|
79
|
+
expect(subject.modified_since?(timestamp - 86400)).to be_true
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
context "given a date newer than the timestamp" do
|
84
|
+
it "should return true" do
|
85
|
+
expect(subject.modified_since?(Time.now)).to be_false
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
context "with no last-modified header" do
|
92
|
+
describe "#last_modified" do
|
93
|
+
it "should return the current time" do
|
94
|
+
expect(subject.last_modified.to_i).to eq(Time.now.to_i)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe SocialAvatarProxy::FacebookAvatar do
|
4
|
+
subject do
|
5
|
+
SocialAvatarProxy::FacebookAvatar.new(id)
|
6
|
+
end
|
7
|
+
|
8
|
+
context "with a valid user ID" do
|
9
|
+
let(:id) { "61413673" }
|
10
|
+
|
11
|
+
describe "#remote_url" do
|
12
|
+
it "should return a valid URL" do
|
13
|
+
expected = "https://graph.facebook.com/#{id}/picture"
|
14
|
+
expect(subject.remote_url).to eq(expected)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context "with a valid user name" do
|
20
|
+
let(:id) { "ryandtownsend" }
|
21
|
+
|
22
|
+
describe "#remote_url" do
|
23
|
+
it "should return a valid URL" do
|
24
|
+
expected = "https://graph.facebook.com/#{id}/picture"
|
25
|
+
expect(subject.remote_url).to eq(expected)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context "with an invalid identifier" do
|
31
|
+
let(:id) { "$omeInvalidN@me" }
|
32
|
+
|
33
|
+
describe "#remote_url" do
|
34
|
+
it "should raise an error" do
|
35
|
+
expect { subject.remote_url }.to raise_error(RuntimeError)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe SocialAvatarProxy::RemoteFileResolver do
|
4
|
+
subject do
|
5
|
+
SocialAvatarProxy::RemoteFileResolver.new(url, limit)
|
6
|
+
end
|
7
|
+
|
8
|
+
let(:url) { "http://www.example.com/" }
|
9
|
+
|
10
|
+
let(:limit) { 5 }
|
11
|
+
|
12
|
+
let(:redirect_response) do
|
13
|
+
response = Net::HTTPRedirection.new("1.1", 301, "Moved")
|
14
|
+
response["location"] = url
|
15
|
+
response
|
16
|
+
end
|
17
|
+
|
18
|
+
let(:successful_response) do
|
19
|
+
response = Net::HTTPSuccess.new("1.1", 200, "Success")
|
20
|
+
response.body = "data"
|
21
|
+
response
|
22
|
+
end
|
23
|
+
|
24
|
+
context "when the response redirects permanently" do
|
25
|
+
before(:each) do
|
26
|
+
subject.class.any_instance.stub(:perform_request).and_return(redirect_response)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should throw an exception" do
|
30
|
+
expect {
|
31
|
+
subject.resolve
|
32
|
+
}.to raise_error(SocialAvatarProxy::TooManyRedirects)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context "when the response redirects less times than the limit" do
|
37
|
+
let(:redirect_count) { limit - 1 }
|
38
|
+
let(:responses) do
|
39
|
+
set = (1..redirect_count).to_a.map { redirect_response }
|
40
|
+
set << successful_response
|
41
|
+
set
|
42
|
+
end
|
43
|
+
|
44
|
+
before(:each) do
|
45
|
+
subject.class.any_instance.stub(:perform_request).and_return do
|
46
|
+
responses.delete_at(0)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should return the successful response" do
|
51
|
+
expect(subject.resolve).to eq(successful_response)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context "when the response times out" do
|
56
|
+
before(:each) do
|
57
|
+
subject.should_receive(:perform_request).once.and_raise(Timeout::Error)
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should throw an exception" do
|
61
|
+
expect {
|
62
|
+
subject.resolve
|
63
|
+
}.to raise_error(SocialAvatarProxy::TimeoutError)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe SocialAvatarProxy::TwitterAvatar do
|
4
|
+
subject do
|
5
|
+
SocialAvatarProxy::TwitterAvatar.new(id)
|
6
|
+
end
|
7
|
+
|
8
|
+
context "with a valid user ID" do
|
9
|
+
let(:id) { "2202971" }
|
10
|
+
|
11
|
+
describe "#remote_url" do
|
12
|
+
it "should return a valid URL" do
|
13
|
+
expected = "http://api.twitter.com/1/users/profile_image?user_id=#{id}&size=original"
|
14
|
+
expect(subject.remote_url).to eq(expected)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context "with a valid user name" do
|
20
|
+
let(:id) { "RyanTownsend" }
|
21
|
+
|
22
|
+
describe "#remote_url" do
|
23
|
+
it "should return a valid URL" do
|
24
|
+
expected = "http://api.twitter.com/1/users/profile_image?screen_name=#{id}&size=original"
|
25
|
+
expect(subject.remote_url).to eq(expected)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context "with an invalid identifier" do
|
31
|
+
let(:id) { "$omeInvalidN@me" }
|
32
|
+
|
33
|
+
describe "#remote_url" do
|
34
|
+
it "should raise an error" do
|
35
|
+
expect { subject.remote_url }.to raise_error(RuntimeError)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/spec/spec_helper.rb
ADDED
data/spec/support/env.rb
ADDED
metadata
ADDED
@@ -0,0 +1,162 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: social-avatar-proxy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Ryan Townsend
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-10-02 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rack
|
16
|
+
requirement: !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: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rspec
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: simplecov
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: foreman
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
description: This gem acts as a proxy for avatars on Twitter & Facebook.
|
95
|
+
email:
|
96
|
+
- ryan@ryantownsend.co.uk
|
97
|
+
executables: []
|
98
|
+
extensions: []
|
99
|
+
extra_rdoc_files: []
|
100
|
+
files:
|
101
|
+
- .gitignore
|
102
|
+
- .rvmrc
|
103
|
+
- .travis.yml
|
104
|
+
- Gemfile
|
105
|
+
- LICENSE.txt
|
106
|
+
- README.md
|
107
|
+
- Rakefile
|
108
|
+
- lib/social-avatar-proxy.rb
|
109
|
+
- lib/social_avatar_proxy.rb
|
110
|
+
- lib/social_avatar_proxy/app.rb
|
111
|
+
- lib/social_avatar_proxy/avatar.rb
|
112
|
+
- lib/social_avatar_proxy/facebook_avatar.rb
|
113
|
+
- lib/social_avatar_proxy/remote_file_resolver.rb
|
114
|
+
- lib/social_avatar_proxy/twitter_avatar.rb
|
115
|
+
- lib/social_avatar_proxy/version.rb
|
116
|
+
- social-avatar-proxy.gemspec
|
117
|
+
- spec/social_avatar_proxy/app_spec.rb
|
118
|
+
- spec/social_avatar_proxy/avatar_spec.rb
|
119
|
+
- spec/social_avatar_proxy/facebook_avatar_spec.rb
|
120
|
+
- spec/social_avatar_proxy/remote_file_resolver_spec.rb
|
121
|
+
- spec/social_avatar_proxy/twitter_avatar_spec.rb
|
122
|
+
- spec/spec_helper.rb
|
123
|
+
- spec/support/env.rb
|
124
|
+
homepage: https://github.com/platformq/social-avatar-proxy
|
125
|
+
licenses:
|
126
|
+
- MIT
|
127
|
+
post_install_message:
|
128
|
+
rdoc_options: []
|
129
|
+
require_paths:
|
130
|
+
- lib
|
131
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
132
|
+
none: false
|
133
|
+
requirements:
|
134
|
+
- - ! '>='
|
135
|
+
- !ruby/object:Gem::Version
|
136
|
+
version: '0'
|
137
|
+
segments:
|
138
|
+
- 0
|
139
|
+
hash: -3304292752345164634
|
140
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
141
|
+
none: false
|
142
|
+
requirements:
|
143
|
+
- - ! '>='
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
segments:
|
147
|
+
- 0
|
148
|
+
hash: -3304292752345164634
|
149
|
+
requirements: []
|
150
|
+
rubyforge_project:
|
151
|
+
rubygems_version: 1.8.24
|
152
|
+
signing_key:
|
153
|
+
specification_version: 3
|
154
|
+
summary: This gem acts as a proxy for avatars on Twitter & Facebook.
|
155
|
+
test_files:
|
156
|
+
- spec/social_avatar_proxy/app_spec.rb
|
157
|
+
- spec/social_avatar_proxy/avatar_spec.rb
|
158
|
+
- spec/social_avatar_proxy/facebook_avatar_spec.rb
|
159
|
+
- spec/social_avatar_proxy/remote_file_resolver_spec.rb
|
160
|
+
- spec/social_avatar_proxy/twitter_avatar_spec.rb
|
161
|
+
- spec/spec_helper.rb
|
162
|
+
- spec/support/env.rb
|