google-content-api 0.1.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.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.rspec +1 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +44 -0
- data/Rakefile +1 -0
- data/config/google_content_api.example +10 -0
- data/google-content-api.gemspec +28 -0
- data/lib/google_content_api.rb +33 -0
- data/lib/google_content_api/authorization.rb +36 -0
- data/lib/google_content_api/product.rb +152 -0
- data/lib/google_content_api/sub_account.rb +95 -0
- data/lib/google_content_api/version.rb +3 -0
- data/spec/google_content_api/authorization_spec.rb +31 -0
- data/spec/google_content_api/product_spec.rb +75 -0
- data/spec/google_content_api/sub_account_spec.rb +130 -0
- data/spec/google_content_api_spec.rb +14 -0
- data/spec/spec_helper.rb +10 -0
- metadata +151 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 7befcf58ddc6c724cdb03706d3f388bf034e9c59
|
4
|
+
data.tar.gz: 2485b540d118c17240864510667805f8d2857e2a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 158b953cf148e4cb71441067821f0b2eabf523b67983d35bf003fde06937545bb1e14d9b6c4eccd633ec636b96eeabed0edab7ea97ef09800b81b9285c0779e2
|
7
|
+
data.tar.gz: 74dd48bfad2e3da6c4c75a6bd3c307683924b032c064b163f95e118c9f021b35dfc5a4db9443d7cb62a902393fdc7831289362d7282372fcd800b9cb8469a77a
|
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--format nested --color
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 DaWanda GmbH
|
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,44 @@
|
|
1
|
+
# Google Content API
|
2
|
+
|
3
|
+
Gem for interacting with [Google's Content API for Shopping](https://developers.google.com/shopping-content/).
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'google-content-api'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install google-content-api
|
18
|
+
|
19
|
+
## Example Usage
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
GoogleContentApi::SubAccount.get_all
|
23
|
+
# Get all sub accounts for the user_id specified in conf/google_content_api.yml
|
24
|
+
# Note: _must_ be a multi-user google account id
|
25
|
+
|
26
|
+
GoogleContentApi::SubAccount.create(title, adult_content, attributes)
|
27
|
+
# Create a new subaccount with _title_.
|
28
|
+
# Use attributes to include 'description', 'link', 'internal_id',
|
29
|
+
# 'reviews_url' or 'adwords_accounts'.
|
30
|
+
|
31
|
+
GoogleContentApi::SubAccount.delete(sub_account_id)
|
32
|
+
# Deletes a sub account.
|
33
|
+
|
34
|
+
GoogleContentApi::Product.create_products(sub_account_id, products, dry_run)
|
35
|
+
# Creates products for sub_account_id using batch request. dry_run defaults to false (used for debugging)
|
36
|
+
```
|
37
|
+
|
38
|
+
## Contributing
|
39
|
+
|
40
|
+
1. Fork it
|
41
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
42
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
43
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
44
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,10 @@
|
|
1
|
+
client_id: "....apps.googleusercontent.com"
|
2
|
+
content_api_scope: "https://www.googleapis.com/auth/structuredcontent"
|
3
|
+
client_secret: "..."
|
4
|
+
refresh_token: "<refresh token for continuous interactions>"
|
5
|
+
redirect_uri: "https://example.com/oauth2callback"
|
6
|
+
user_id: "<your user id>"
|
7
|
+
application_name: "<your app name>" # Not required
|
8
|
+
application_version: "0.0.0" # Not required
|
9
|
+
feed_urls:
|
10
|
+
managed_accounts: "https://content.googleapis.com/content/v1/#{USER_ID}/managedaccounts"
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'google_content_api/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "google-content-api"
|
8
|
+
spec.version = GoogleContentApi::VERSION
|
9
|
+
spec.authors = ["DaWanda GmbH"]
|
10
|
+
spec.email = ["amir@dawanda.com"]
|
11
|
+
spec.description = %q{Gem for interacting with Google's Content API.}
|
12
|
+
spec.summary = %q{Gem for interacting with Google's Content API.}
|
13
|
+
spec.homepage = "https://github.com/dawanda/google-content-api"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "rspec", "~> 2.13"
|
24
|
+
spec.add_development_dependency "webmock", "~> 1.11.0"
|
25
|
+
spec.add_dependency "google-api-client", "~> 0.4"
|
26
|
+
spec.add_dependency "nokogiri", "~> 1.5"
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'nokogiri'
|
3
|
+
require 'google/api_client'
|
4
|
+
|
5
|
+
require 'google_content_api/version'
|
6
|
+
require 'google_content_api/authorization'
|
7
|
+
require 'google_content_api/sub_account'
|
8
|
+
require 'google_content_api/product'
|
9
|
+
|
10
|
+
module GoogleContentApi
|
11
|
+
def self.config(options = {})
|
12
|
+
@@config ||= YAML.load( File.read(options[:config_file] || "config/google_content_api.yml") )
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.urls(type, account_id, options = {})
|
16
|
+
base_url = "https://content.googleapis.com/content/v1/#{account_id}"
|
17
|
+
url = case type
|
18
|
+
when "managed_accounts"
|
19
|
+
"#{base_url}/managedaccounts"
|
20
|
+
when "products"
|
21
|
+
"#{base_url}/items/products/schema/batch?warnings"
|
22
|
+
when "item", "product"
|
23
|
+
raise "must supply language, country and item id" \
|
24
|
+
if options[:language].nil? || options[:country].nil? || options[:item_id].nil?
|
25
|
+
|
26
|
+
"#{base_url}/items/products/generic/online:#{options[:language].downcase}:#{options[:country].upcase}:#{options[:item_id]}?warnings"
|
27
|
+
else
|
28
|
+
raise "unknown zone"
|
29
|
+
end
|
30
|
+
|
31
|
+
options[:dry_run] ? url + "&dry-run" : url
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module GoogleContentApi
|
2
|
+
|
3
|
+
class Authorization
|
4
|
+
class << self
|
5
|
+
|
6
|
+
def fetch_token
|
7
|
+
@@client ||= Google::APIClient.new(
|
8
|
+
:application_name => GoogleContentApi.config["application_name"],
|
9
|
+
:application_version => GoogleContentApi.config["application_version"]
|
10
|
+
)
|
11
|
+
|
12
|
+
@@client.authorization.client_id = GoogleContentApi.config["client_id"]
|
13
|
+
@@client.authorization.client_secret = GoogleContentApi.config["client_secret"]
|
14
|
+
@@client.authorization.redirect_uri = GoogleContentApi.config["redirect_uri"]
|
15
|
+
@@client.authorization.refresh_token = GoogleContentApi.config["refresh_token"]
|
16
|
+
@@client.authorization.scope = GoogleContentApi.config["content_api_scope"]
|
17
|
+
|
18
|
+
refresh_token
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
def refresh_token
|
23
|
+
@@token_date ||= nil
|
24
|
+
time_now = Time.now
|
25
|
+
if @@token_date.nil? || (@@token_date + 120 < time_now)
|
26
|
+
@@client.authorization.fetch_access_token!
|
27
|
+
@@token_date = time_now
|
28
|
+
end
|
29
|
+
@@client.authorization.access_token
|
30
|
+
end
|
31
|
+
# p.s - we can consider using:
|
32
|
+
# client.authorization.update_token!
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
module GoogleContentApi
|
2
|
+
|
3
|
+
class Product
|
4
|
+
class << self
|
5
|
+
|
6
|
+
def create_products(sub_account_id, products, dry_run = false)
|
7
|
+
token = Authorization.fetch_token
|
8
|
+
products_url = GoogleContentApi.urls("products", sub_account_id, :dry_run => dry_run)
|
9
|
+
xml = create_product_items_batch_xml(products)
|
10
|
+
Faraday.headers = {
|
11
|
+
"Content-Type" => "application/atom+xml",
|
12
|
+
"Authorization" => "AuthSub token=#{token}"
|
13
|
+
}
|
14
|
+
|
15
|
+
response = Faraday.post products_url, xml
|
16
|
+
|
17
|
+
if response.status == 200
|
18
|
+
response
|
19
|
+
else
|
20
|
+
raise "Unable to batch insert products - received status #{response.status}. body: #{response.body}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
alias_method :create_items, :create_products
|
24
|
+
|
25
|
+
def delete(sub_account_id, params)
|
26
|
+
token = Authorization.fetch_token
|
27
|
+
product_url = GoogleContentApi.urls("product", sub_account_id, :language => params[:language], :country => params[:country], :item_id => params[:item_id], :dry_run => params[:dry_run])
|
28
|
+
Faraday.headers = { "Authorization" => "AuthSub token=#{token}" }
|
29
|
+
|
30
|
+
response = Faraday.delete product_url
|
31
|
+
|
32
|
+
if response.status == 200
|
33
|
+
response
|
34
|
+
else
|
35
|
+
raise "Unable to delete product - received status #{response.status}. body: #{response.body}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def create
|
40
|
+
raise "not implemented"
|
41
|
+
end
|
42
|
+
|
43
|
+
def update
|
44
|
+
raise "not implemented"
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
def create_item_xml(item)
|
49
|
+
item[:id] = item_url(item[:id])
|
50
|
+
|
51
|
+
Nokogiri::XML::Builder.new do |xml|
|
52
|
+
xml.entry(
|
53
|
+
'xmlns' => 'http://www.w3.org/2005/Atom',
|
54
|
+
'xmlns:app' => 'http://www.w3.org/2007/app',
|
55
|
+
'xmlns:sc' => 'http://schemas.google.com/structuredcontent/2009',
|
56
|
+
'xmlns:scp' => 'http://schemas.google.com/structuredcontent/2009/products',
|
57
|
+
'xmlns:gd' => 'http://schemas.google.com/g/2005') do
|
58
|
+
add_mandatory_values(xml, item)
|
59
|
+
add_optional_values(xml, item)
|
60
|
+
end
|
61
|
+
end.to_xml
|
62
|
+
end
|
63
|
+
|
64
|
+
def create_product_items_batch_xml(items)
|
65
|
+
Nokogiri::XML::Builder.new do |xml|
|
66
|
+
xml.feed('xmlns' => 'http://www.w3.org/2005/Atom', 'xmlns:batch' => 'http://schemas.google.com/gdata/batch') do
|
67
|
+
items.each do |attributes|
|
68
|
+
xml.entry('xmlns' => 'http://www.w3.org/2005/Atom', 'xmlns:sc' => 'http://schemas.google.com/structuredcontent/2009', 'xmlns:scp' => 'http://schemas.google.com/structuredcontent/2009/products', 'xmlns:app' => 'http://www.w3.org/2007/app') do
|
69
|
+
xml['batch'].operation_(:type => 'INSERT')
|
70
|
+
add_mandatory_values(xml, attributes)
|
71
|
+
add_optional_values(xml, attributes)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end.to_xml
|
76
|
+
end
|
77
|
+
|
78
|
+
def add_mandatory_values(xml, attributes)
|
79
|
+
xml['batch'].id_ attributes[:id]
|
80
|
+
xml['sc'].id_ attributes[:id]
|
81
|
+
xml.title_ attributes[:title]
|
82
|
+
xml.content_ attributes[:description], :type => 'text'
|
83
|
+
xml.link_(:rel => 'alternate', :type => 'text/html', :href => attributes[:link])
|
84
|
+
xml['sc'].image_link_ attributes[:image]
|
85
|
+
xml['sc'].content_language_ attributes[:content_language]
|
86
|
+
xml['sc'].target_country_ attributes[:target_country]
|
87
|
+
xml['scp'].availability_ attributes[:availability]
|
88
|
+
xml['scp'].condition_(attributes[:condition] != 9 ? "new" : "used")
|
89
|
+
xml['scp'].price_ attributes[:price], :unit => attributes[:currency]
|
90
|
+
end
|
91
|
+
|
92
|
+
def add_optional_values(xml, attributes)
|
93
|
+
if attributes[:expiration_date]
|
94
|
+
xml['sc'].expiration_date_ attributes[:expiration_date]
|
95
|
+
end
|
96
|
+
if attributes[:adult]
|
97
|
+
xml['sc'].adult_ attributes[:adult]
|
98
|
+
end
|
99
|
+
if attributes[:additional_images]
|
100
|
+
attributes[:additional_images].each { |image_link| xml['sc'].additional_image_link_ }
|
101
|
+
end
|
102
|
+
if attributes[:product_type]
|
103
|
+
xml['scp'].product_type_ attributes[:product_type]
|
104
|
+
end
|
105
|
+
if attributes[:google_product_category]
|
106
|
+
xml['scp'].google_product_category_ attributes[:google_product_category]
|
107
|
+
end
|
108
|
+
if attributes[:brand]
|
109
|
+
xml['scp'].brand_ attributes[:brand]
|
110
|
+
end
|
111
|
+
if attributes[:mpn]
|
112
|
+
xml['scp'].mpn_ attributes[:mpn]
|
113
|
+
end
|
114
|
+
if attributes[:adwords_grouping]
|
115
|
+
xml['scp'].adwords_grouping_ attributes[:adwords_grouping]
|
116
|
+
end
|
117
|
+
if attributes[:adwords_labels]
|
118
|
+
xml['scp'].adwords_labels_ attributes[:adwords_labels]
|
119
|
+
end
|
120
|
+
if attributes[:adwords_redirect]
|
121
|
+
xml['scp'].adwords_redirect_ attributes[:adwords_redirect]
|
122
|
+
end
|
123
|
+
if attributes[:shipping]
|
124
|
+
xml['scp'].shipping_ do
|
125
|
+
xml['scp'].shipping_country_ attributes[:shipping][:shipping_country]
|
126
|
+
xml['scp'].shipping_price_ attributes[:shipping][:shipping_price], :unit => attributes[:currency]
|
127
|
+
xml['scp'].shipping_service_ attributes[:shipping][:shipping_service]
|
128
|
+
end
|
129
|
+
end
|
130
|
+
if attributes[:size]
|
131
|
+
xml['scp'].size_ attributes[:size]
|
132
|
+
end
|
133
|
+
if attributes[:gender]
|
134
|
+
xml['scp'].gender_ attributes[:gender]
|
135
|
+
end
|
136
|
+
if attributes[:age_group]
|
137
|
+
xml['scp'].age_group_ attributes[:age_group]
|
138
|
+
end
|
139
|
+
if attributes.has_key?(:identifier_exists)
|
140
|
+
xml['scp'].identifier_exists_ attributes[:identifier_exists]
|
141
|
+
end
|
142
|
+
if attributes[:unit_pricing_base_measure] && attributes[:unit]
|
143
|
+
xml['scp'].unit_pricing_base_measure_ attributes[:unit_pricing_base_measure], :unit => attributes[:unit]
|
144
|
+
end
|
145
|
+
if attributes[:unit_pricing_measure] && attributes[:unit]
|
146
|
+
xml['scp'].unit_pricing_measure_ attributes[:unit_pricing_measure], :unit => attributes[:unit]
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module GoogleContentApi
|
2
|
+
|
3
|
+
class SubAccount
|
4
|
+
class << self
|
5
|
+
|
6
|
+
def get_all
|
7
|
+
token = Authorization.fetch_token
|
8
|
+
sub_accounts_url = GoogleContentApi.urls("managed_accounts", google_user_id)
|
9
|
+
Faraday.headers = {
|
10
|
+
"Content-Type" => "application/atom+xml",
|
11
|
+
"Authorization" => "AuthSub token=#{token}"
|
12
|
+
}
|
13
|
+
|
14
|
+
response = Faraday.get sub_accounts_url
|
15
|
+
|
16
|
+
if response.status == 200
|
17
|
+
response
|
18
|
+
else
|
19
|
+
raise "request unsuccessful - received status #{response.status}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def create(title, adult_content = false, attributes = {})
|
24
|
+
prepare_request_params(title, adult_content, attributes)
|
25
|
+
response = Faraday.post @sub_accounts_url, @xml
|
26
|
+
|
27
|
+
if response.status == 201
|
28
|
+
response
|
29
|
+
else
|
30
|
+
raise "Unable to create sub account - received status #{response.status}. body: #{response.body}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def update(title, google_id, adult_content = false, attributes = {})
|
35
|
+
prepare_request_params(title, adult_content, attributes)
|
36
|
+
response = Faraday.put "#{@sub_accounts_url}/#{google_id}", @xml
|
37
|
+
|
38
|
+
if response.status == 200
|
39
|
+
response
|
40
|
+
else
|
41
|
+
raise "Unable to update sub account - received status #{response.status}. body: #{response.body}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def delete(google_id)
|
46
|
+
token = Authorization.fetch_token
|
47
|
+
sub_account_url = GoogleContentApi.urls("managed_accounts", google_user_id)
|
48
|
+
Faraday.headers = { "Authorization" => "AuthSub token=#{token}" }
|
49
|
+
response = Faraday.delete "#{sub_account_url}/#{google_id}"
|
50
|
+
|
51
|
+
if response.status == 200
|
52
|
+
response
|
53
|
+
else
|
54
|
+
raise "Unable to delete sub account - received status #{response.status}. body: #{response.body}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
def prepare_request_params(title, adult_content = false, attributes = {})
|
60
|
+
@token = Authorization.fetch_token
|
61
|
+
@sub_accounts_url = GoogleContentApi.urls("managed_accounts", google_user_id)
|
62
|
+
@xml = create_xml(title, adult_content, attributes)
|
63
|
+
Faraday.headers = set_headers(@token, @xml.length)
|
64
|
+
end
|
65
|
+
|
66
|
+
def set_headers(token, length = 0)
|
67
|
+
{
|
68
|
+
"Content-Type" => "application/atom+xml",
|
69
|
+
"Content-Length" => length.to_s,
|
70
|
+
"Authorization" => "AuthSub token=#{token}"
|
71
|
+
}
|
72
|
+
end
|
73
|
+
|
74
|
+
def create_xml(title, adult_content = false, attributes = {})
|
75
|
+
adult_content = !adult_content ? "no" : "yes"
|
76
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
77
|
+
xml.entry('xmlns' => 'http://www.w3.org/2005/Atom', 'xmlns:sc' => 'http://schemas.google.com/structuredcontent/2009') do
|
78
|
+
xml.title_ title
|
79
|
+
xml['sc'].adult_content adult_content
|
80
|
+
xml.content_ attributes[:description] if attributes[:description]
|
81
|
+
xml.link_(:rel => 'alternate', :type => 'text/html', :href => attributes[:link]) if attributes[:link]
|
82
|
+
xml['sc'].internal_id_ attributes[:internal_id] if attributes[:internal_id]
|
83
|
+
xml['sc'].reviews_url_ attributes[:reviews_url] if attributes[:reviews_url]
|
84
|
+
xml['sc'].adwords_accounts_ attributes[:adwords_accounts] if attributes[:adwords_accounts]
|
85
|
+
end
|
86
|
+
end.to_xml
|
87
|
+
end
|
88
|
+
|
89
|
+
def google_user_id
|
90
|
+
GoogleContentApi.config["user_id"]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GoogleContentApi::Authorization do
|
4
|
+
subject { GoogleContentApi::Authorization }
|
5
|
+
|
6
|
+
describe ".refresh_token" do
|
7
|
+
context "when 2 minutes didn't pass" do
|
8
|
+
it "doesn't call fetch_access_token!" do
|
9
|
+
subject.send :class_variable_set, :@@token_date, Time.now
|
10
|
+
Signet::OAuth2::Client.any_instance.should_not_receive(:fetch_access_token!)
|
11
|
+
subject.fetch_token
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
context "when 2 minutes have passed" do
|
16
|
+
it "calls fetch_access_token!" do
|
17
|
+
subject.send :class_variable_set, :@@token_date, Time.now - 121
|
18
|
+
Signet::OAuth2::Client.any_instance.should_receive(:fetch_access_token!)
|
19
|
+
subject.fetch_token
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe ".fetch_token" do
|
25
|
+
it "always returns a token" do
|
26
|
+
Signet::OAuth2::Client.any_instance.stub(:fetch_access_token!)
|
27
|
+
Signet::OAuth2::Client.any_instance.stub(:access_token).and_return(fake_token)
|
28
|
+
2.times { subject.fetch_token.should == fake_token }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
describe GoogleContentApi::Product do
|
5
|
+
subject { GoogleContentApi::Product }
|
6
|
+
it { should respond_to(:create_products) }
|
7
|
+
let(:sub_account_id) { "9898988" }
|
8
|
+
let(:product_id) { "123123" }
|
9
|
+
let(:dry_run) { true }
|
10
|
+
let(:successful_response) { OpenStruct.new(:status => 200) }
|
11
|
+
let(:product_attributes) {
|
12
|
+
{
|
13
|
+
:id => "x",
|
14
|
+
:title => "Title",
|
15
|
+
:description => "desc",
|
16
|
+
:link => "http://testing.is/fun",
|
17
|
+
:image => "http://testing.is/fun.jpg",
|
18
|
+
:content_language => "en",
|
19
|
+
:target_country => "US",
|
20
|
+
:currency => "USD",
|
21
|
+
:price => 0.99,
|
22
|
+
:condition => "new"
|
23
|
+
}
|
24
|
+
}
|
25
|
+
|
26
|
+
describe ".create_products" do
|
27
|
+
it "status == 200" do
|
28
|
+
GoogleContentApi::Authorization.should_receive(:fetch_token).once.and_return(fake_token)
|
29
|
+
stub_request(:post, GoogleContentApi.urls("products", sub_account_id, :dry_run => dry_run)).
|
30
|
+
with(:headers => {
|
31
|
+
'Content-Type' => 'application/atom+xml',
|
32
|
+
'Authorization' => "AuthSub token=#{fake_token}"
|
33
|
+
}).to_return(:status => 200)
|
34
|
+
|
35
|
+
subject.create_products(sub_account_id, [], dry_run).status.should == 200
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe ".delete" do
|
40
|
+
it "status == 200" do
|
41
|
+
GoogleContentApi::Authorization.stub(:fetch_token).and_return(fake_token)
|
42
|
+
Faraday.stub(:delete).and_return(successful_response)
|
43
|
+
subject.delete(sub_account_id, :language => "de", :country => "de", :item_id => product_id).status.should == 200
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "private" do
|
48
|
+
describe ".create_product_items_batch_xml" do
|
49
|
+
it "creates an xml with all given product attributes" do
|
50
|
+
result_xml = subject.send(:create_product_items_batch_xml, [product_attributes])
|
51
|
+
|
52
|
+
result_xml.should match 'xmlns:batch="http://schemas.google.com/gdata/batch">'
|
53
|
+
result_xml.should match 'xmlns:scp="http://schemas.google.com/structuredcontent/2009/products"'
|
54
|
+
product_attributes.each { |attribute, value| result_xml.should match /#{value}/ }
|
55
|
+
end
|
56
|
+
|
57
|
+
it "calls .add_optional_values" do
|
58
|
+
GoogleContentApi::Authorization.stub(:fetch_token).and_return(fake_token)
|
59
|
+
Faraday.stub(:post).and_return(successful_response)
|
60
|
+
|
61
|
+
subject.should_receive(:add_optional_values).once
|
62
|
+
subject.create_products(sub_account_id, [product_attributes], dry_run)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe ".add_optional_values" do
|
67
|
+
let(:unit) { "g" }
|
68
|
+
let(:unit_pricing_measure) { 100 }
|
69
|
+
it "creates the xml with the given optional values" do
|
70
|
+
result_xml = subject.send(:create_product_items_batch_xml, [product_attributes.merge(:unit => unit, :unit_pricing_measure => unit_pricing_measure)])
|
71
|
+
result_xml.should match "<scp:unit_pricing_measure unit=\"#{unit}\">#{unit_pricing_measure}</scp:unit_pricing_measure>"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GoogleContentApi::SubAccount do
|
4
|
+
subject { GoogleContentApi::SubAccount }
|
5
|
+
let(:user_id) { GoogleContentApi.config["user_id"] }
|
6
|
+
let(:example_create_xml) { %Q|<?xml version=\"1.0\"?>\n<entry xmlns:sc=\"http://schemas.google.com/structuredcontent/2009\" xmlns=\"http://www.w3.org/2005/Atom\">\n <title>#{sub_account_name}</title>\n <sc:adult_content>no</sc:adult_content>\n</entry>\n| }
|
7
|
+
let(:sub_account_name) { "test account" }
|
8
|
+
|
9
|
+
it { should respond_to(:create) }
|
10
|
+
it { should respond_to(:get_all) }
|
11
|
+
it { should respond_to(:delete) }
|
12
|
+
|
13
|
+
describe ".create" do
|
14
|
+
it "creates a sub account" do
|
15
|
+
subject.should_receive(:create_xml).
|
16
|
+
once.with(sub_account_name, false, {}).and_return(example_create_xml)
|
17
|
+
GoogleContentApi::Authorization.should_receive(:fetch_token).once.and_return(fake_token)
|
18
|
+
|
19
|
+
stub_request(:post, GoogleContentApi.urls("managed_accounts", user_id)).
|
20
|
+
with(
|
21
|
+
:body => example_create_xml,
|
22
|
+
:headers => {
|
23
|
+
'Accept'=>'*/*',
|
24
|
+
'Authorization' => "AuthSub token=#{fake_token}",
|
25
|
+
'Content-Length' => example_create_xml.length.to_s,
|
26
|
+
'Content-Type' => 'application/atom+xml'
|
27
|
+
}).to_return(
|
28
|
+
:status => 201,
|
29
|
+
:body => "<entry xmlns='...'>stuff</entry>")
|
30
|
+
|
31
|
+
response = subject.create(sub_account_name, false)
|
32
|
+
response.status.should == 201
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe ".update" do
|
37
|
+
let(:existing_google_id) { "123456" }
|
38
|
+
it "updates a sub account" do
|
39
|
+
subject.should_receive(:create_xml).
|
40
|
+
once.with(sub_account_name, false, {}).and_return(example_create_xml)
|
41
|
+
GoogleContentApi::Authorization.should_receive(:fetch_token).once.and_return(fake_token)
|
42
|
+
|
43
|
+
url = GoogleContentApi.urls("managed_accounts", user_id)
|
44
|
+
stub_request(:put, "#{url}/#{existing_google_id}").
|
45
|
+
with(
|
46
|
+
:body => example_create_xml,
|
47
|
+
:headers => {
|
48
|
+
'Accept'=>'*/*',
|
49
|
+
'Authorization' => "AuthSub token=#{fake_token}",
|
50
|
+
'Content-Length' => example_create_xml.length.to_s,
|
51
|
+
'Content-Type' => 'application/atom+xml'
|
52
|
+
}).to_return(
|
53
|
+
:status => 200,
|
54
|
+
:body => "<entry xmlns='...'>stuff</entry>")
|
55
|
+
|
56
|
+
response = subject.update(sub_account_name, existing_google_id, false)
|
57
|
+
response.status.should == 200
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe ".get_all" do
|
62
|
+
before(:each) do
|
63
|
+
GoogleContentApi::Authorization.should_receive(:fetch_token).once.and_return(fake_token)
|
64
|
+
end
|
65
|
+
|
66
|
+
context "when status == 200" do
|
67
|
+
it "returns the response" do
|
68
|
+
|
69
|
+
stub_request(:get, GoogleContentApi.urls("managed_accounts", user_id)).
|
70
|
+
with(:headers => {
|
71
|
+
'Accept'=>'*/*',
|
72
|
+
'Authorization' => "AuthSub token=#{fake_token}",
|
73
|
+
'Content-Type' => 'application/atom+xml'
|
74
|
+
}).to_return(
|
75
|
+
:status => 200,
|
76
|
+
:body => "<entry xmlns='...'>blah blah</entry>")
|
77
|
+
|
78
|
+
response = subject.get_all
|
79
|
+
response.status.should == 200
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
context "when status != 200" do
|
84
|
+
it "raises an error" do
|
85
|
+
stub_request(:get, GoogleContentApi.urls("managed_accounts", user_id)).
|
86
|
+
with(:headers => {
|
87
|
+
'Accept'=>'*/*',
|
88
|
+
'Authorization' => "AuthSub token=#{fake_token}",
|
89
|
+
'Content-Type' => 'application/atom+xml'
|
90
|
+
}).to_return(
|
91
|
+
:status => 400,
|
92
|
+
:body => "<entry xmlns='...'>blah blah</entry>")
|
93
|
+
|
94
|
+
expect { subject.get_all }.to raise_error
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe ".delete" do
|
100
|
+
let(:sub_account_id) { "555555" }
|
101
|
+
let(:delete_url) { GoogleContentApi.urls("managed_accounts", user_id) + "/#{sub_account_id}" }
|
102
|
+
|
103
|
+
context "when status == 200" do
|
104
|
+
it "returns the response" do
|
105
|
+
GoogleContentApi::Authorization.should_receive(:fetch_token).once.and_return(fake_token)
|
106
|
+
Faraday.should_receive(:delete).
|
107
|
+
with(delete_url).
|
108
|
+
and_return( double("response", :status => 200) )
|
109
|
+
|
110
|
+
response = subject.delete sub_account_id
|
111
|
+
response.status.should == 200
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
context "when status != 200" do
|
116
|
+
let(:example_delete_error_xml) { %Q|<?xml version='1.0' encoding='UTF-8'?><errors xmlns='http://schemas.google.com/g/2005'><error><domain>GData</domain><code>ResourceNotFoundException</code><internalReason>Managed account 15794381 not found</internalReason></error></errors>| }
|
117
|
+
|
118
|
+
it "raises error" do
|
119
|
+
GoogleContentApi::Authorization.should_receive(:fetch_token).once.and_return(fake_token)
|
120
|
+
Faraday.should_receive(:delete).
|
121
|
+
with(delete_url).
|
122
|
+
and_return( double("response",
|
123
|
+
:status => 404,
|
124
|
+
:body => example_delete_error_xml) )
|
125
|
+
|
126
|
+
expect { subject.delete(sub_account_id) }.to raise_error
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GoogleContentApi do
|
4
|
+
it { should respond_to(:config) }
|
5
|
+
it { should respond_to(:urls) }
|
6
|
+
|
7
|
+
it "should have a config" do
|
8
|
+
subject.config.should be_kind_of(Hash)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "has a version" do
|
12
|
+
GoogleContentApi::VERSION.should_not be_nil
|
13
|
+
end
|
14
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: google-content-api
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- DaWanda GmbH
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-11-29 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '2.13'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2.13'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: webmock
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.11.0
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 1.11.0
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: google-api-client
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ~>
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.4'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ~>
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.4'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: nokogiri
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ~>
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '1.5'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ~>
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '1.5'
|
97
|
+
description: Gem for interacting with Google's Content API.
|
98
|
+
email:
|
99
|
+
- amir@dawanda.com
|
100
|
+
executables: []
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- .gitignore
|
105
|
+
- .rspec
|
106
|
+
- Gemfile
|
107
|
+
- LICENSE.txt
|
108
|
+
- README.md
|
109
|
+
- Rakefile
|
110
|
+
- config/google_content_api.example
|
111
|
+
- google-content-api.gemspec
|
112
|
+
- lib/google_content_api.rb
|
113
|
+
- lib/google_content_api/authorization.rb
|
114
|
+
- lib/google_content_api/product.rb
|
115
|
+
- lib/google_content_api/sub_account.rb
|
116
|
+
- lib/google_content_api/version.rb
|
117
|
+
- spec/google_content_api/authorization_spec.rb
|
118
|
+
- spec/google_content_api/product_spec.rb
|
119
|
+
- spec/google_content_api/sub_account_spec.rb
|
120
|
+
- spec/google_content_api_spec.rb
|
121
|
+
- spec/spec_helper.rb
|
122
|
+
homepage: https://github.com/dawanda/google-content-api
|
123
|
+
licenses:
|
124
|
+
- MIT
|
125
|
+
metadata: {}
|
126
|
+
post_install_message:
|
127
|
+
rdoc_options: []
|
128
|
+
require_paths:
|
129
|
+
- lib
|
130
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
131
|
+
requirements:
|
132
|
+
- - '>='
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
version: '0'
|
135
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
136
|
+
requirements:
|
137
|
+
- - '>='
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
version: '0'
|
140
|
+
requirements: []
|
141
|
+
rubyforge_project:
|
142
|
+
rubygems_version: 2.1.1
|
143
|
+
signing_key:
|
144
|
+
specification_version: 4
|
145
|
+
summary: Gem for interacting with Google's Content API.
|
146
|
+
test_files:
|
147
|
+
- spec/google_content_api/authorization_spec.rb
|
148
|
+
- spec/google_content_api/product_spec.rb
|
149
|
+
- spec/google_content_api/sub_account_spec.rb
|
150
|
+
- spec/google_content_api_spec.rb
|
151
|
+
- spec/spec_helper.rb
|