google-content-api 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ config/google_content_api.yml
19
+ .DS_Store
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --format nested --color
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
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,3 @@
1
+ module GoogleContentApi
2
+ VERSION = "0.1.2"
3
+ 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
@@ -0,0 +1,10 @@
1
+ require 'webmock/rspec'
2
+ require ::File.expand_path("../lib/google_content_api", File.dirname(__FILE__))
3
+
4
+ RSpec.configure do |config|
5
+ config.order = 'random'
6
+ end
7
+
8
+ def fake_token
9
+ "foobarbaz"
10
+ end
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