picasa 0.5.1 → 0.5.2

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/README.md CHANGED
@@ -24,14 +24,21 @@ client.photo.create("album_id", file_path: "path/to/my-photo.png")
24
24
  # => Picasa::Presenter::Photo
25
25
  ```
26
26
 
27
- If password is specified, all requests will be authenticated.
28
- This affect results to contain private data, however it can be controlled by `access` parameter.
27
+ ### Authentication
29
28
 
30
- ## Caveats
29
+ When request is authenticated, response will contain private data, however this can be controlled by `access` parameter.
31
30
 
32
- Currently picasa wont work with `ox` xml parser.
33
- Using `rexml` parser wont return `etag` attribute properly.
34
- I recommend to use `libxml` or `nokogiri`.
31
+ You can authenticate by specifing password:
32
+
33
+ ```ruby
34
+ client = Picasa::Client.new(user_id: "some.user@gmail.com", password: "secret")
35
+ ```
36
+
37
+ Or by setting custom authorization header, i.e. taken from OAuth authentication:
38
+
39
+ ```ruby
40
+ client = Picasa::Client.new(user_id: "some.user@gmail.com", authorization_header: "OAuth token")
41
+ ```
35
42
 
36
43
  ## Extra
37
44
 
@@ -51,6 +58,8 @@ And then use it (it will create album taking title from folder name and upload a
51
58
 
52
59
  ```
53
60
  GOOGLE_USER_ID=your.email@gmail.com GOOGLE_PASSWORD=secret thor picasa_uploader:upload_all path-to-folder-with-photos
61
+ # Without specifing password
62
+ GOOGLE_USER_ID=your.email@gmail.com GOOGLE_AUTHORIZATION_HEADER="GoogleLogin auth=token" thor picasa_uploader:upload_all path-to-folder-with-photos
54
63
  ```
55
64
 
56
65
  If your upload was somehow interrupted, you can resume it by adding `--continue` option:
@@ -59,6 +68,16 @@ If your upload was somehow interrupted, you can resume it by adding `--continue`
59
68
  GOOGLE_USER_ID=your.email@gmail.com GOOGLE_PASSWORD=secret thor picasa_uploader:upload_all --continue path-to-folder-with-photos
60
69
  ```
61
70
 
71
+ If you run out of quota and want to resize images to fit Picasa free storage limits, you might be interested in Thor task for [that job](https://github.com/morgoth/ripper#usage)
72
+
73
+ ## Caveats
74
+
75
+ Currently picasa wont work with `ox` xml parser.
76
+
77
+ Using `rexml` parser wont return `etag` attribute properly.
78
+
79
+ I recommend to use `libxml` or `nokogiri`.
80
+
62
81
  ## Continuous Integration
63
82
  [![Build Status](https://secure.travis-ci.org/morgoth/picasa.png)](http://travis-ci.org/morgoth/picasa)
64
83
 
@@ -1,4 +1,5 @@
1
1
  require "picasa"
2
+ require "thread"
2
3
 
3
4
  # Temporary requirement
4
5
  MultiXml.parser = :libxml
@@ -7,7 +8,8 @@ class PicasaUploader < Thor
7
8
  include Thor::Actions
8
9
 
9
10
  desc "upload_all DIR", "Uploads all photos from given directory (pass --continue to resume uploading)"
10
- method_options continue: false
11
+ method_option :continue, type: :boolean, default: false, aliases: "-c", desc: "continue aborted upload"
12
+ method_option :threads, type: :numeric, default: 8, aliases: "-t", desc: "specify threads number"
11
13
  def upload_all(dir = File.basename(Dir.getwd))
12
14
  require_credentials
13
15
 
@@ -34,23 +36,34 @@ class PicasaUploader < Thor
34
36
  album = create_album(album_name)
35
37
  end
36
38
 
37
- entries.each do |file|
38
- photos_number += 1
39
- say("Uploading photo #{file} to album #{album.title} - #{photos_number}/#{entries.size}")
40
- create_photo(album, file)
41
- end
42
- say "Finished uploading #{photos_number} photos"
39
+ mutex = Mutex.new
40
+ entries_size = entries.size
41
+ number = 0
42
+ options[:threads].times.map do
43
+ Thread.new(entries) do |files|
44
+ while file = mutex.synchronize { files.pop }
45
+ mutex.synchronize { number += 1 }
46
+ say("Uploading photo #{file} to album #{album.title} - #{number}/#{entries_size}")
47
+ create_photo(album, file)
48
+ end
49
+ end
50
+ end.each(&:join)
51
+ say "Finished uploading #{number} photos"
43
52
  end
44
53
  end
45
54
 
46
55
  no_tasks do
47
56
  def client
48
- @client ||= Picasa::Client.new(user_id: ENV["GOOGLE_USER_ID"], password: ENV["GOOGLE_PASSWORD"])
57
+ @client ||= Picasa::Client.new(user_id: ENV["GOOGLE_USER_ID"],
58
+ authorization_header: ENV["GOOGLE_AUTHORIZATION_HEADER"],
59
+ password: ENV["GOOGLE_PASSWORD"])
49
60
  end
50
61
 
51
62
  def require_credentials
52
- say "You must specify GOOGLE_USER_ID env variable" and exit unless ENV["GOOGLE_USER_ID"]
53
- say "You must specify GOOGLE_PASSWORD env variable" and exit unless ENV["GOOGLE_PASSWORD"]
63
+ say "You must specify GOOGLE_USER_ID env variable" and exit if ENV["GOOGLE_USER_ID"].nil?
64
+ if ENV["GOOGLE_AUTHORIZATION_HEADER"].nil? && ENV["GOOGLE_PASSWORD"].nil?
65
+ say "You must specify GOOGLE_PASSWORD or GOOGLE_AUTHORIZATION_HEADER env variable" and exit
66
+ end
54
67
  end
55
68
 
56
69
  def create_album(title)
@@ -14,9 +14,9 @@ module Picasa
14
14
  # @return [Presenter::AlbumList]
15
15
  def list(options = {})
16
16
  uri = URI.parse("/data/feed/api/user/#{user_id}")
17
- parsed_body = Connection.new(credentials).get(uri.path, options)
17
+ response = Connection.new.get(:path => uri.path, :query => options, :headers => auth_header)
18
18
 
19
- Presenter::AlbumList.new(parsed_body["feed"])
19
+ Presenter::AlbumList.new(MultiXml.parse(response.body)["feed"])
20
20
  end
21
21
 
22
22
  # Returns photo list for given album
@@ -31,9 +31,9 @@ module Picasa
31
31
  # @raise [NotFoundError] raised when album cannot be found
32
32
  def show(album_id, options = {})
33
33
  uri = URI.parse("/data/feed/api/user/#{user_id}/albumid/#{album_id}")
34
- parsed_body = Connection.new(credentials).get(uri.path, options)
34
+ response = Connection.new.get(:path => uri.path, :query => options, :headers => auth_header)
35
35
 
36
- Presenter::Album.new(parsed_body["feed"])
36
+ Presenter::Album.new(MultiXml.parse(response.body)["feed"])
37
37
  end
38
38
 
39
39
  # Creates album
@@ -53,8 +53,9 @@ module Picasa
53
53
 
54
54
  template = Template.new(:new_album, params)
55
55
  uri = URI.parse("/data/feed/api/user/#{user_id}")
56
- parsed_body = Connection.new(credentials).post(uri.path, template.render)
57
- Presenter::Album.new(parsed_body["entry"])
56
+ response = Connection.new.post(:path => uri.path, :body => template.render, :headers => auth_header)
57
+
58
+ Presenter::Album.new(MultiXml.parse(response.body)["entry"])
58
59
  end
59
60
 
60
61
  # Destroys given album
@@ -67,9 +68,9 @@ module Picasa
67
68
  # @raise [NotFoundError] raised when album cannot be found
68
69
  # @raise [PreconditionFailedError] raised when ETag does not match
69
70
  def destroy(album_id, options = {})
70
- headers = {"If-Match" => options.fetch(:etag, "*")}
71
+ headers = auth_header.merge({"If-Match" => options.fetch(:etag, "*")})
71
72
  uri = URI.parse("/data/entry/api/user/#{user_id}/albumid/#{album_id}")
72
- Connection.new(credentials).delete(uri.path, headers)
73
+ Connection.new.delete(:path => uri.path, :headers => headers)
73
74
  true
74
75
  end
75
76
  alias :delete :destroy
@@ -1,17 +1,23 @@
1
1
  module Picasa
2
2
  module API
3
3
  class Base
4
- attr_reader :user_id, :credentials
4
+ attr_reader :user_id, :authorization_header
5
5
 
6
6
  # @param [Hash] credentials
7
7
  # @option credentials [String] :user_id google username/email
8
- # @option credentials [String] :password password for given username/email
9
- def initialize(credentials)
8
+ # @option credentials [String] :authorization_header header for authenticating requests
9
+ def initialize(credentials = {})
10
10
  if MultiXml.parser.to_s == "MultiXml::Parsers::Ox"
11
11
  raise StandardError, "MultiXml parser is set to :ox - picasa gem will not work with it currently, use one of: :libxml, :nokogiri, :rexml"
12
12
  end
13
13
  @user_id = credentials.fetch(:user_id)
14
- @credentials = credentials
14
+ @authorization_header = credentials[:authorization_header]
15
+ end
16
+
17
+ def auth_header
18
+ {}.tap do |header|
19
+ header["Authorization"] = authorization_header if authorization_header
20
+ end
15
21
  end
16
22
  end
17
23
  end
@@ -20,11 +20,12 @@ module Picasa
20
20
  params[:content_type] ||= (file && file.content_type) || raise(ArgumentError.new("content_type must be specified"))
21
21
 
22
22
  template = Template.new(:new_photo, params)
23
- headers = {"Content-Type" => "multipart/related; boundary=\"#{params[:boundary]}\""}
23
+ headers = auth_header.merge({"Content-Type" => "multipart/related; boundary=\"#{params[:boundary]}\""})
24
24
 
25
25
  uri = URI.parse("/data/feed/api/user/#{user_id}/albumid/#{album_id}")
26
- parsed_body = Connection.new(credentials).post(uri.path, template.render, headers)
27
- Presenter::Photo.new(parsed_body["entry"])
26
+ response = Connection.new.post(:path => uri.path, :body => template.render, :headers => headers)
27
+
28
+ Presenter::Photo.new(MultiXml.parse(response.body)["entry"])
28
29
  end
29
30
 
30
31
  # Destroys given photo
@@ -38,9 +39,9 @@ module Picasa
38
39
  # @raise [NotFoundError] raised when album or photo cannot be found
39
40
  # @raise [PreconditionFailedError] raised when ETag does not match
40
41
  def destroy(album_id, photo_id, options = {})
41
- headers = {"If-Match" => options.fetch(:etag, "*")}
42
+ headers = auth_header.merge({"If-Match" => options.fetch(:etag, "*")})
42
43
  uri = URI.parse("/data/entry/api/user/#{user_id}/albumid/#{album_id}/photoid/#{photo_id}")
43
- Connection.new(credentials).delete(uri.path, headers)
44
+ Connection.new.delete(:path => uri.path, :headers => headers)
44
45
  true
45
46
  end
46
47
  alias :delete :destroy
@@ -11,8 +11,8 @@ module Picasa
11
11
  #
12
12
  # @return [Presenter::TagList]
13
13
  def list(options = {})
14
- album_id = options[:album_id]
15
- photo_id = options[:photo_id]
14
+ album_id = options.delete(:album_id)
15
+ photo_id = options.delete(:photo_id)
16
16
  raise(ArgumentError, "You must specify album_id when providing photo_id") if photo_id && !album_id
17
17
 
18
18
  path = "/data/feed/api/user/#{user_id}"
@@ -20,8 +20,9 @@ module Picasa
20
20
  path << "/photoid/#{photo_id}" if photo_id
21
21
 
22
22
  uri = URI.parse(path)
23
- parsed_body = Connection.new(credentials).get(uri.path, options.merge(:kind => "tag"))
24
- Presenter::TagList.new(parsed_body["feed"])
23
+ response = Connection.new.get(:path => uri.path, :query => options.merge(:kind => "tag"), :headers => auth_header)
24
+
25
+ Presenter::TagList.new(MultiXml.parse(response.body)["feed"])
25
26
  end
26
27
 
27
28
  # Creates a tag for a photo.
@@ -42,8 +43,9 @@ module Picasa
42
43
  template = Template.new("new_tag", params)
43
44
 
44
45
  uri = URI.parse(path)
45
- parsed_body = Connection.new(credentials).post(uri.path, template.render)
46
- Presenter::Tag.new(parsed_body["entry"])
46
+ response = Connection.new.post(:path => uri.path, :body => template.render, :headers => auth_header)
47
+
48
+ Presenter::Tag.new(MultiXml.parse(response.body)["entry"])
47
49
  end
48
50
  end
49
51
  end
@@ -1,13 +1,15 @@
1
1
  module Picasa
2
2
  class Client
3
- attr_reader :credentials
3
+ attr_reader :user_id, :password, :authorization_header
4
4
 
5
5
  # @param [Hash] credentials
6
6
  # @option credentials [String] :user_id google username/email
7
7
  # @option credentials [String] :password password for given username/email
8
+ # @option credentials [String] :authorization_header custom authorization header (i.e. taken from OAuth2)
8
9
  def initialize(credentials = {})
9
- credentials[:user_id] || raise(ArgumentError, "You must specify user_id")
10
- @credentials = credentials
10
+ @user_id = credentials[:user_id] || raise(ArgumentError, "You must specify user_id")
11
+ @password = credentials[:password]
12
+ @authorization_header = credentials[:authorization_header]
11
13
  end
12
14
 
13
15
  # @return [API::Album]
@@ -18,6 +20,7 @@ module Picasa
18
20
  # album_list.title
19
21
  # # => "My album"
20
22
  def album
23
+ authenticate if authenticates?
21
24
  API::Album.new(credentials)
22
25
  end
23
26
 
@@ -29,6 +32,7 @@ module Picasa
29
32
  # photo.id
30
33
  # # => "4322232322421"
31
34
  def photo
35
+ authenticate if authenticates?
32
36
  API::Photo.new(credentials)
33
37
  end
34
38
 
@@ -40,7 +44,45 @@ module Picasa
40
44
  # tag_list.title
41
45
  # # => "holidays"
42
46
  def tag
47
+ authenticate if authenticates?
43
48
  API::Tag.new(credentials)
44
49
  end
50
+
51
+ # @return [String]
52
+ def authenticate
53
+ response = Connection.new.post(
54
+ :host => API_AUTH_URL,
55
+ :headers => {"Content-Type" => "application/x-www-form-urlencoded"},
56
+ :path => "/accounts/ClientLogin",
57
+ :body => Utils.inline_query(
58
+ "accountType" => "HOSTED_OR_GOOGLE",
59
+ "Email" => user_id,
60
+ "Passwd" => password,
61
+ "service" => "lh2",
62
+ "source" => "ruby-gem-v#{VERSION}"
63
+ )
64
+ )
65
+
66
+ key = extract_auth_key(response.body)
67
+ @authorization_header = "GoogleLogin auth=#{key}"
68
+ end
69
+
70
+ private
71
+
72
+ def authenticates?
73
+ password && authorization_header.nil?
74
+ end
75
+
76
+ def credentials
77
+ {:user_id => user_id}.tap do |credentials|
78
+ credentials[:authorization_header] = authorization_header if authorization_header
79
+ end
80
+ end
81
+
82
+ def extract_auth_key(data)
83
+ response = data.split("\n").map { |v| v.split("=") }
84
+ params = Hash[response]
85
+ params["Auth"]
86
+ end
45
87
  end
46
88
  end
@@ -4,13 +4,6 @@ require "uri"
4
4
 
5
5
  module Picasa
6
6
  class Connection
7
- attr_reader :user_id, :password
8
-
9
- def initialize(credentials = {})
10
- @user_id = credentials.fetch(:user_id)
11
- @password = credentials.fetch(:password, nil)
12
- end
13
-
14
7
  def http(url = API_URL)
15
8
  uri = URI.parse(url)
16
9
  http = Net::HTTP.new(uri.host, uri.port)
@@ -18,40 +11,41 @@ module Picasa
18
11
  http
19
12
  end
20
13
 
21
- def get(path, params = {})
22
- authenticate if auth?
23
-
24
- path = path_with_params(path, params)
25
- request = Net::HTTP::Get.new(path, headers)
26
- response = handle_response(http.request(request))
27
- MultiXml.parse(response.body)
14
+ # @param [Hash] params request arguments
15
+ # @option params [String] :host host of request
16
+ # @option params [String] :path request path
17
+ # @option params [String] :body request body (for POST)
18
+ # @option params [String] :query request url query
19
+ # @option params [String] :headers request headers
20
+ def get(params = {})
21
+ params[:headers] ||= {}
22
+ params[:query] ||= {}
23
+ params[:host] ||= API_URL
24
+
25
+ path = path_with_query(params[:path], params[:query])
26
+ request = Net::HTTP::Get.new(path, headers.merge(params[:headers]))
27
+ handle_response(http(params[:host]).request(request))
28
28
  end
29
29
 
30
- def post(path, body, custom_headers = {})
31
- authenticate if auth?
30
+ def post(params = {})
31
+ params[:headers] ||= {}
32
+ params[:host] ||= API_URL
32
33
 
33
- request = Net::HTTP::Post.new(path, headers.merge(custom_headers))
34
- request.body = body
35
- response = handle_response(http.request(request))
36
- MultiXml.parse(response.body)
34
+ request = Net::HTTP::Post.new(params[:path], headers.merge(params[:headers]))
35
+ request.body = params[:body]
36
+ handle_response(http(params[:host]).request(request))
37
37
  end
38
38
 
39
- def delete(path, custom_headers = {})
40
- authenticate if auth?
39
+ def delete(params = {})
40
+ params[:headers] ||= {}
41
+ params[:host] ||= API_URL
41
42
 
42
- request = Net::HTTP::Delete.new(path, headers.merge(custom_headers))
43
- handle_response(http.request(request))
43
+ request = Net::HTTP::Delete.new(params[:path], headers.merge(params[:headers]))
44
+ handle_response(http(params[:host]).request(request))
44
45
  end
45
46
 
46
- def inline_params(params)
47
- params.map do |key, value|
48
- dasherized = key.to_s.gsub("_", "-")
49
- "#{CGI.escape(dasherized)}=#{CGI.escape(value.to_s)}"
50
- end.join("&")
51
- end
52
-
53
- def path_with_params(path, params = {})
54
- path = path + "?" + inline_params(params) unless params.empty?
47
+ def path_with_query(path, query = {})
48
+ path = path + "?" + Utils.inline_query(query) unless query.empty?
55
49
  URI.parse(path).to_s
56
50
  end
57
51
 
@@ -74,38 +68,10 @@ module Picasa
74
68
 
75
69
  def headers
76
70
  {
77
- "User-Agent" => client_name,
71
+ "User-Agent" => "ruby-gem-v#{VERSION}",
78
72
  "GData-Version" => API_VERSION,
79
73
  "Content-Type" => "application/atom+xml"
80
- }.tap do |headers|
81
- headers["Authorization"] = "GoogleLogin auth=#{@auth_key}" if @auth_key
82
- end
83
- end
84
-
85
- def auth?
86
- !password.nil?
87
- end
88
-
89
- def authenticate
90
- data = inline_params({"accountType" => "HOSTED_OR_GOOGLE",
91
- "Email" => user_id,
92
- "Passwd" => password,
93
- "service" => "lh2",
94
- "source" => client_name})
95
-
96
- response = handle_response(http(API_AUTH_URL).post("/accounts/ClientLogin", data))
97
-
98
- @auth_key = extract_auth_key(response.body)
99
- end
100
-
101
- def extract_auth_key(data)
102
- response = data.split("\n").map { |v| v.split("=") }
103
- params = Hash[response]
104
- params["Auth"]
105
- end
106
-
107
- def client_name
108
- "ruby-gem-v#{Picasa::VERSION}"
74
+ }
109
75
  end
110
76
  end
111
77
  end
@@ -39,6 +39,13 @@ module Picasa
39
39
  end
40
40
  end
41
41
 
42
- module_function :safe_retrieve, :array_wrap, :map_to_integer, :map_to_boolean, :map_to_date
42
+ def inline_query(query)
43
+ query.map do |key, value|
44
+ dasherized = key.to_s.gsub("_", "-")
45
+ "#{CGI.escape(dasherized)}=#{CGI.escape(value.to_s)}"
46
+ end.join("&")
47
+ end
48
+
49
+ module_function :safe_retrieve, :array_wrap, :map_to_integer, :map_to_boolean, :map_to_date, :inline_query
43
50
  end
44
51
  end
@@ -1,3 +1,3 @@
1
1
  module Picasa
2
- VERSION = "0.5.1"
2
+ VERSION = "0.5.2"
3
3
  end
@@ -1,14 +1,35 @@
1
1
  require "helper"
2
2
 
3
3
  describe Picasa::Client do
4
- it "has credentials" do
5
- client = Picasa::Client.new(:user_id => "john.doe")
6
- assert_equal({:user_id => "john.doe"}, client.credentials)
7
- end
8
-
9
4
  it "raises ArgumentError when user_id is missing" do
10
5
  assert_raises(Picasa::ArgumentError, /user_id/) do
11
6
  Picasa::Client.new
12
7
  end
13
8
  end
9
+
10
+ it "allows to assign custom authorization header" do
11
+ client = Picasa::Client.new(:user_id => "john.doe", :authorization_header => "OAuth token")
12
+ assert_equal "OAuth token", client.authorization_header
13
+ end
14
+
15
+ describe "#authenticate" do
16
+ it "successfully authenticates" do
17
+ client = Picasa::Client.new(:user_id => "john.doe@domain.com", :password => "secret")
18
+
19
+ stub_request(:post, "https://www.google.com/accounts/ClientLogin").to_return(fixture("auth/success.txt"))
20
+
21
+ client.authenticate
22
+ refute_nil client.authorization_header
23
+ end
24
+
25
+ it "raises an ForbiddenError when authentication failed" do
26
+ client = Picasa::Client.new(:user_id => "john.doe@domain.com", :password => "invalid")
27
+
28
+ stub_request(:post, "https://www.google.com/accounts/ClientLogin").to_return(fixture("exceptions/forbidden.txt"))
29
+
30
+ assert_raises(Picasa::ForbiddenError) do
31
+ client.authenticate
32
+ end
33
+ end
34
+ end
14
35
  end
@@ -1,86 +1,42 @@
1
- # -*- encoding: utf-8 -*-
2
1
  require "helper"
3
2
 
4
3
  describe Picasa::Connection do
5
4
  before do
6
- @connection = Picasa::Connection.new(:user_id => "joe.doe")
5
+ @connection = Picasa::Connection.new
7
6
  end
8
7
 
9
- describe "#inline_params" do
10
- it "converts params to inline style" do
11
- params = @connection.inline_params({:alt => "json", :kind => "photo"})
12
- # make ruby 1.8 tests pass
13
- assert_equal "alt=json", params.split("&").sort[0]
14
- assert_equal "kind=photo", params.split("&").sort[1]
15
- end
16
-
17
- it "changes param keys underscore to dash" do
18
- params = @connection.inline_params({:max_results => 10})
19
- assert_equal "max-results=10", params
20
- end
21
-
22
- it "escapes values" do
23
- params = @connection.inline_params({:kind => "żółć"})
24
- assert_equal "kind=%C5%BC%C3%B3%C5%82%C4%87", params
25
- end
26
- end
27
-
28
- describe "#path_with_params" do
8
+ describe "#path_with_query" do
29
9
  it "returns path when no params provided" do
30
- path = @connection.path_with_params("/data/feed/api")
10
+ path = @connection.path_with_query("/data/feed/api")
31
11
  assert_equal "/data/feed/api", path
32
12
  end
33
13
 
34
14
  it "adds params to path" do
35
- path = @connection.path_with_params("/data/feed/api", {:q => "bomb"})
15
+ path = @connection.path_with_query("/data/feed/api", {:q => "bomb"})
36
16
  assert_equal "/data/feed/api?q=bomb", path
37
17
  end
38
18
  end
39
19
 
40
20
  it "raises NotFound exception when 404 returned" do
41
- connection = Picasa::Connection.new(:user_id => "john.doe@domain.com")
42
- uri = URI.parse("/data/feed/api/user/#{connection.user_id}/albumid/non-existing")
21
+ connection = Picasa::Connection.new
22
+ uri = URI.parse("/data/feed/api/user/some.user/albumid/non-existing")
43
23
 
44
24
  stub_request(:get, "https://picasaweb.google.com" + uri.path).to_return(fixture("exceptions/not_found.txt"))
45
25
 
46
26
  assert_raises Picasa::NotFoundError, "Invalid entity id: non-existing" do
47
- connection.get(uri.path)
27
+ connection.get(:path => uri.path)
48
28
  end
49
29
  end
50
30
 
51
31
  it "raises PreconditionFailed exception when 412 returned" do
52
- connection = Picasa::Connection.new(:user_id => "john.doe@domain.com", :password => "secret")
53
- uri = URI.parse("/data/feed/api/user/#{connection.user_id}/albumid/123")
32
+ connection = Picasa::Connection.new
33
+ uri = URI.parse("/data/feed/api/user/some.user/albumid/123")
54
34
 
55
35
  stub_request(:post, "https://www.google.com/accounts/ClientLogin").to_return(fixture("auth/success.txt"))
56
36
  stub_request(:delete, "https://picasaweb.google.com" + uri.path).to_return(fixture("exceptions/precondition_failed.txt"))
57
37
 
58
38
  assert_raises Picasa::PreconditionFailedError, "Mismatch: etags = [oldetag], version = [7]" do
59
- connection.delete(uri.path, {"If-Match" => "oldetag"})
60
- end
61
- end
62
-
63
- describe "authentication" do
64
- it "successfully authenticates" do
65
- connection = Picasa::Connection.new(:user_id => "john.doe@domain.com", :password => "secret")
66
- uri = URI.parse("/data/feed/api/user/#{connection.user_id}")
67
-
68
- stub_request(:post, "https://www.google.com/accounts/ClientLogin").to_return(fixture("auth/success.txt"))
69
- stub_request(:get, "https://picasaweb.google.com/data/feed/api/user/john.doe@domain.com").to_return(fixture("album/album-list.txt"))
70
-
71
- connection.expects(:authenticate).returns(:result)
72
- refute_nil connection.get(uri.path)
73
- end
74
-
75
- it "raises an ResponseError when authentication failed" do
76
- connection = Picasa::Connection.new(:user_id => "john.doe@domain.com", :password => "secret")
77
- uri = URI.parse("/data/feed/api/user/#{connection.user_id}")
78
-
79
- stub_request(:post, "https://www.google.com/accounts/ClientLogin").to_return(fixture("exceptions/forbidden.txt"))
80
-
81
- assert_raises(Picasa::ForbiddenError) do
82
- connection.get(uri.path)
83
- end
39
+ connection.delete(:path => uri.path, :headers => {"If-Match" => "oldetag"})
84
40
  end
85
41
  end
86
42
  end
@@ -15,7 +15,7 @@ class MiniTest::Unit::TestCase
15
15
  end
16
16
 
17
17
  def image_path(filename)
18
- ::File.join("test", "fixtures", filename)
18
+ File.join("test", "fixtures", filename)
19
19
  end
20
20
 
21
21
  # Recording response is as simple as writing in terminal:
@@ -1,3 +1,4 @@
1
+ # -*- encoding: utf-8 -*-
1
2
  require "helper"
2
3
 
3
4
  describe Picasa::Utils do
@@ -81,4 +82,23 @@ describe Picasa::Utils do
81
82
  assert_nil Picasa::Utils.map_to_boolean("truthy")
82
83
  end
83
84
  end
85
+
86
+ describe "#inline_query" do
87
+ it "converts params to inline style" do
88
+ params = Picasa::Utils.inline_query({:alt => "json", :kind => "photo"})
89
+ # make ruby 1.8 tests pass
90
+ assert_equal "alt=json", params.split("&").sort[0]
91
+ assert_equal "kind=photo", params.split("&").sort[1]
92
+ end
93
+
94
+ it "changes param keys underscore to dash" do
95
+ params = Picasa::Utils.inline_query({:max_results => 10})
96
+ assert_equal "max-results=10", params
97
+ end
98
+
99
+ it "escapes values" do
100
+ params = Picasa::Utils.inline_query({:kind => "żółć"})
101
+ assert_equal "kind=%C5%BC%C3%B3%C5%82%C4%87", params
102
+ end
103
+ end
84
104
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: picasa
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.5.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-09-15 00:00:00.000000000 Z
12
+ date: 2012-09-16 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: multi_xml