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 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