ffmike-rshoeboxed 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/History.txt ADDED
@@ -0,0 +1,4 @@
1
+ == 0.0.1 2009-01-12
2
+
3
+ * 1 major enhancement:
4
+ * Initial release
data/Manifest.txt ADDED
@@ -0,0 +1,30 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.rdoc
4
+ Rakefile
5
+ lib/rshoeboxed.rb
6
+ lib/rshoeboxed/category.rb
7
+ lib/rshoeboxed/connection.rb
8
+ lib/rshoeboxed/list_proxy.rb
9
+ lib/rshoeboxed/parse_error.rb
10
+ lib/rshoeboxed/receipt.rb
11
+ script/console
12
+ script/destroy
13
+ script/generate
14
+ test/fixtures/category_request.xml
15
+ test/fixtures/category_response.xml
16
+ test/fixtures/receipt_info_request.xml
17
+ test/fixtures/receipt_info_response.xml
18
+ test/fixtures/receipt_request.xml
19
+ test/fixtures/receipt_response.xml
20
+ test/fixtures/receipt_response_bad_credentials.xml
21
+ test/fixtures/receipt_response_internal_error.xml
22
+ test/fixtures/receipt_response_restricted_ip.xml
23
+ test/fixtures/receipt_response_unknown_api_call.xml
24
+ test/fixtures/receipt_response_xml_validation.xml
25
+ test/fixtures/receipt_with_category_id_request.xml
26
+ test/test_category.rb
27
+ test/test_connection.rb
28
+ test/test_helper.rb
29
+ test/test_list_proxy.rb
30
+ test/test_receipt.rb
data/README.rdoc ADDED
@@ -0,0 +1,74 @@
1
+ = rshoeboxed
2
+
3
+ Master repo:
4
+
5
+ * http://github.com/bcurren/rshoeboxed
6
+
7
+ == DESCRIPTION:
8
+
9
+ Ruby wrapper for the Shoeboxed API.
10
+
11
+ == SYNOPSIS:
12
+
13
+ === Generate authentication url
14
+
15
+ Use the following code to generate a url to send a user to to authenticate. The first parameter is the
16
+ AppName provided by Shoeboxed. Shoeboxed will redirect back to the return url once a user successfully
17
+ authenticates. When the user is redirected back to the return_url, a token will be provided in the url
18
+ for you to store for future API calls.
19
+
20
+ RShoeboxed::Connection.authentication_url("Outright", "http://example.com")
21
+
22
+ === Get a list of all receipts
23
+
24
+ The api_token is provided by Shoeboxed when you setup your API account. The user_token is retrieved by
25
+ sending a user to authentication_url so they can log in to Shoeboxed. On success, they will be redirected
26
+ to the return_url with the user_token.
27
+
28
+ connection = RShoeboxed::Connection.new("api_token", "user_token")
29
+ receipts = connection.get_receipt_call(Date.new(2008, 1, 1), Date.new(2008, 12, 29))
30
+
31
+ === Get a particular receipt
32
+
33
+ connection = RShoeboxed::Connection.new("api_token", "user_token")
34
+ receipt = connection.get_receipt_info_call("2342442424")
35
+
36
+ === Get a list of all categories
37
+
38
+ connection = RShoeboxed::Connection.new("api_token", "user_token")
39
+ categories = connection.get_category_call
40
+
41
+ === Get a list of all business cards
42
+
43
+ connection = RShoeboxed::Connection.new("api_token", "user_token")
44
+ cards = connection.get_business_card_call(200,1)
45
+
46
+
47
+ == INSTALL:
48
+
49
+ * sudo gem install bcurren_rshoeboxed
50
+
51
+ == LICENSE:
52
+
53
+ (The MIT License)
54
+
55
+ Copyright (c) 2009 Ben Curren
56
+
57
+ Permission is hereby granted, free of charge, to any person obtaining
58
+ a copy of this software and associated documentation files (the
59
+ 'Software'), to deal in the Software without restriction, including
60
+ without limitation the rights to use, copy, modify, merge, publish,
61
+ distribute, sublicense, and/or sell copies of the Software, and to
62
+ permit persons to whom the Software is furnished to do so, subject to
63
+ the following conditions:
64
+
65
+ The above copyright notice and this permission notice shall be
66
+ included in all copies or substantial portions of the Software.
67
+
68
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
69
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
70
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
71
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
72
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
73
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
74
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,28 @@
1
+ %w[rubygems rake rake/clean fileutils newgem rubigen].each { |f| require f }
2
+ require File.dirname(__FILE__) + '/lib/rshoeboxed'
3
+
4
+ # Generate all the Rake tasks
5
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
6
+ $hoe = Hoe.new('rshoeboxed', RShoeboxed::VERSION) do |p|
7
+ p.developer('Ben Curren', 'ben@gobootstrap.com')
8
+ p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
9
+ p.rubyforge_name = p.name # TODO this is default value
10
+ p.extra_deps = [
11
+ ['builder','>= 2.1.2'],
12
+ ]
13
+ p.extra_dev_deps = [
14
+ ['newgem', ">= #{::Newgem::VERSION}"],
15
+ ['mocha', ">= 0.9.4"]
16
+ ]
17
+
18
+ p.clean_globs |= %w[**/.DS_Store tmp *.log]
19
+ path = (p.rubyforge_name == p.name) ? p.rubyforge_name : "\#{p.rubyforge_name}/\#{p.name}"
20
+ p.remote_rdoc_dir = File.join(path.gsub(/^#{p.rubyforge_name}\/?/,''), 'rdoc')
21
+ p.rsync_args = '-av --delete --ignore-errors'
22
+ end
23
+
24
+ require 'newgem/tasks' # load /tasks/*.rake
25
+ Dir['tasks/**/*.rake'].each { |t| load t }
26
+
27
+ # TODO - want other tests/tasks run by default? Add them to the list
28
+ # task :default => [:spec, :features]
@@ -0,0 +1,26 @@
1
+ require 'rexml/document'
2
+
3
+ module RShoeboxed
4
+ class Category
5
+ attr_accessor :id, :name
6
+
7
+ def self.parse(xml)
8
+ document = REXML::Document.new(xml)
9
+ document.elements.collect("//Category") do |category_element|
10
+ category = Category.new
11
+ begin
12
+ category.id = category_element.attributes["id"]
13
+ category.name = category_element.attributes["name"]
14
+ rescue => e
15
+ raise RShoeboxed::ParseError.new(e, category_element.to_s, "Error parsing category.")
16
+ end
17
+ category
18
+ end
19
+ end
20
+
21
+ def ==(category)
22
+ self.id == category.id && self.name == category.name
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,234 @@
1
+ gem "builder"
2
+ require "builder"
3
+ require "cgi"
4
+ require "net/https"
5
+ require 'rexml/document'
6
+ require 'logger'
7
+ require 'iconv'
8
+
9
+ module RShoeboxed
10
+ class Error < StandardError; end;
11
+ class UnknownAPICallError < Error; end;
12
+ class InternalError < Error; end;
13
+ class AuthenticationError < Error; end;
14
+ class RestrictedIPError < Error; end;
15
+ class XMLValidationError < Error; end;
16
+
17
+ class Connection
18
+ attr_accessor :api_key, :user_token
19
+
20
+ API_SERVER = "app.shoeboxed.com"
21
+ API_PATH = "/ws/api.htm"
22
+ API_URL = "https://" + API_SERVER + API_PATH
23
+
24
+ @@logger = Logger.new(STDOUT)
25
+ def logger
26
+ @@logger
27
+ end
28
+
29
+ def self.log_level=(level)
30
+ @@logger.level = level
31
+ end
32
+ self.log_level = Logger::WARN
33
+
34
+ # Generates a url for you to obtain the user token. This url with take the user to the Shoeboxed authentication
35
+ # page. After the user successfully authenticates, they will be redirected to the app_url with the token and uname
36
+ # as parameters.
37
+ #
38
+ # app_name - string that contains the name of the app that is calling the API
39
+ # app_url - url that Shoeboxed will redirect to after a successful authentication
40
+ # app_params - option hash or array of strings containing params that will be passed back to app_url
41
+ def self.authentication_url(app_name, app_url, app_params={})
42
+ API_URL + "?" + encode_params(
43
+ [[:appname, app_name],
44
+ [:appurl, app_url],
45
+ [:apparams, encode_params(app_params)],
46
+ [:SignIn, 1]]
47
+ )
48
+ end
49
+
50
+
51
+ # The initialization method takes in two params:
52
+ # api_key -
53
+ # user_token (generated after validating from the authentication url)
54
+ def initialize(api_key, user_token)
55
+ @api_key = api_key
56
+ @user_token = user_token
57
+ end
58
+
59
+ def get_receipt_info_call(id)
60
+ request = build_receipt_info_request(id)
61
+ response = post_xml(request)
62
+
63
+ receipts = Receipt.parse(response)
64
+ receipts ? receipts.first : nil
65
+ end
66
+
67
+ # Note: the result_count can only be 50, 100, or 200
68
+ def get_receipt_call(start_date, end_date, options = {})
69
+ options = {
70
+ :use_sell_date => false,
71
+ :per_page => 50,
72
+ :current_page => 1
73
+ }.merge(options)
74
+
75
+ request = build_receipt_request(start_date, end_date, options)
76
+ response = post_xml(request)
77
+
78
+ receipts = Receipt.parse(response)
79
+ wrap_array_with_pagination(receipts, response, options[:current_page], options[:per_page])
80
+ end
81
+
82
+ def get_category_call
83
+ request = build_category_request
84
+ response = post_xml(request)
85
+
86
+ Category.parse(response)
87
+ end
88
+
89
+ def get_business_card_call(options = {})
90
+ request = build_business_card_request(options)
91
+ response = post_xml(request)
92
+
93
+ BusinessCard.parse(response)
94
+ end
95
+
96
+ private
97
+
98
+ def wrap_array_with_pagination(receipts, response, current_page, per_page)
99
+ document = REXML::Document.new(response)
100
+ counts = document.elements.collect("//Receipts") { |element| element.attributes["count"] || 0 }
101
+
102
+ ListProxy.new(receipts, counts.first, current_page, per_page)
103
+ end
104
+
105
+ def date_to_s(date)
106
+ my_date = date
107
+ if !date.kind_of?(String)
108
+ my_date = date.strftime("%Y-%m-%dT%H:%M:%S")
109
+ end
110
+ my_date
111
+ end
112
+
113
+ def self.encode_params(params)
114
+ # If hash, turn to array and give it alpha ordering
115
+ if params.kind_of? Hash
116
+ params = params.to_a
117
+ end
118
+
119
+ encoded_params = params.collect do |name, value|
120
+ "#{CGI.escape(name.to_s)}=#{CGI.escape(value.to_s)}"
121
+ end
122
+ encoded_params.join("&")
123
+ end
124
+
125
+
126
+ def post_xml(body)
127
+ connection = Net::HTTP.new(API_SERVER, 443)
128
+ connection.use_ssl = true
129
+ connection.verify_mode = OpenSSL::SSL::VERIFY_NONE
130
+
131
+ request = Net::HTTP::Post.new(API_PATH)
132
+ request.set_form_data({'xml'=>body})
133
+
134
+ result = connection.start { |http| http.request(request) }
135
+
136
+ # Convert to UTF-8, shoeboxed encodes with ISO-8859-1
137
+ result_body = Iconv.conv('UTF-8', 'ISO-8859-1', result.body)
138
+
139
+ if logger.debug?
140
+ logger.debug "Request:"
141
+ logger.debug body
142
+ logger.debug "Response:"
143
+ logger.debug result_body
144
+ end
145
+
146
+ check_for_api_error(result_body)
147
+ end
148
+
149
+ def check_for_api_error(body)
150
+ document = REXML::Document.new(body)
151
+ root = document.root
152
+ puts root.inspect
153
+ if root && root.name == "Error"
154
+ description = root.attributes["description"]
155
+
156
+ case root.attributes["code"]
157
+ when "1"
158
+ raise AuthenticationError.new(description)
159
+ when "2"
160
+ raise UnknownAPICallError.new(description)
161
+ when "3"
162
+ raise RestrictedIPError.new(description)
163
+ when "4"
164
+ raise XMLValidationError.new(description)
165
+ when "5"
166
+ raise InternalError.new(description)
167
+ end
168
+ end
169
+
170
+ body
171
+ end
172
+
173
+ def build_receipt_request(start_date, end_date, options)
174
+ xml = Builder::XmlMarkup.new
175
+ xml.instruct!
176
+ xml.Request(:xmlns => "urn:sbx:apis:SbxBaseComponents") do |xml|
177
+ append_credentials(xml)
178
+ xml.GetReceiptCall do |xml|
179
+ xml.ReceiptFilter do |xml|
180
+ xml.Results(options[:per_page])
181
+ xml.PageNo(options[:current_page])
182
+ xml.DateStart(date_to_s(start_date))
183
+ xml.DateEnd(date_to_s(end_date))
184
+ xml.UseSellDate(options[:use_sell_date])
185
+ xml.Category(options[:category_id]) if options[:category_id]
186
+ end
187
+ end
188
+ end
189
+ end
190
+
191
+ def build_category_request
192
+ xml = Builder::XmlMarkup.new
193
+ xml.instruct!
194
+ xml.Request(:xmlns => "urn:sbx:apis:SbxBaseComponents") do |xml|
195
+ append_credentials(xml)
196
+ xml.GetCategoryCall
197
+ end
198
+ end
199
+
200
+ def build_business_card_request(options)
201
+ xml = Builder::XmlMarkup.new
202
+ xml.instruct!
203
+ xml.Request(:xmlns => "urn:sbx:apis:SbxBaseComponents") do |xml|
204
+ append_credentials(xml)
205
+ xml.GetBusinessCardCall do |xml|
206
+ xml.BusinessCardFilter do |xml|
207
+ xml.Results(options[:per_page])
208
+ xml.PageNo(options[:current_page])
209
+ end
210
+ end
211
+ end
212
+ end
213
+
214
+ def build_receipt_info_request(id)
215
+ xml = Builder::XmlMarkup.new
216
+ xml.instruct!
217
+ xml.Request(:xmlns => "urn:sbx:apis:SbxBaseComponents") do |xml|
218
+ append_credentials(xml)
219
+ xml.GetReceiptInfoCall do |xml|
220
+ xml.ReceiptFilter do |xml|
221
+ xml.ReceiptId(id)
222
+ end
223
+ end
224
+ end
225
+ end
226
+
227
+ def append_credentials(xml)
228
+ xml.RequesterCredentials do |xml|
229
+ xml.ApiUserToken(@api_key)
230
+ xml.SbxUserToken(@user_token)
231
+ end
232
+ end
233
+ end
234
+ end
@@ -0,0 +1,25 @@
1
+ module RShoeboxed
2
+ class ListProxy
3
+ attr_reader :current_page, :per_page, :total
4
+
5
+ def initialize(array, total, current_page, per_page)
6
+ @array = array
7
+
8
+ @total = total.to_i
9
+ @current_page = current_page.to_i
10
+ @per_page = per_page.to_i
11
+ end
12
+
13
+ def pages
14
+ pages = total / per_page
15
+ if total % per_page != 0
16
+ pages += 1
17
+ end
18
+ pages
19
+ end
20
+
21
+ def method_missing(method, *args, &block)
22
+ @array.send(method, *args, &block)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,19 @@
1
+ module RShoeboxed
2
+ class ParseError < StandardError
3
+ attr_accessor :original_error, :xml
4
+
5
+ def initialize(original_error, xml, msg = nil)
6
+ @original_error = original_error
7
+ @xml = xml
8
+ super(msg)
9
+ end
10
+
11
+ def to_s
12
+ message = super
13
+
14
+ "Original Error: #{original_error.to_s}\n" +
15
+ "XML: #{xml.to_s}\n" +
16
+ "Message: #{message}\n"
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,62 @@
1
+ require 'bigdecimal'
2
+ require 'date'
3
+ require 'rexml/document'
4
+
5
+ module RShoeboxed
6
+ class Receipt
7
+ attr_accessor :id, :store, :image_url, :categories
8
+ attr_reader :sell_date, :created_date, :modified_date, :total
9
+
10
+ def self.parse(xml)
11
+ document = REXML::Document.new(xml)
12
+ document.elements.collect("//Receipt") do |receipt_element|
13
+ receipt = Receipt.new
14
+ begin
15
+ receipt.id = receipt_element.attributes["id"]
16
+ receipt.store = receipt_element.attributes["store"]
17
+ receipt.sell_date = receipt_element.attributes["selldate"]
18
+ receipt.created_date = receipt_element.attributes["createdDate"]
19
+ receipt.modified_date = receipt_element.attributes["modifiedDate"]
20
+ receipt.total = receipt_element.attributes["total"]
21
+ receipt.image_url = receipt_element.attributes["imgurl"]
22
+
23
+ # Get the categories elements and have Category parse them
24
+ category_element = receipt_element.elements["Categories"]
25
+ receipt.categories = category_element ? Category.parse(category_element.to_s) : []
26
+ rescue => e
27
+ raise RShoeboxed::ParseError.new(e, receipt_element.to_s, "Error parsing receipt.")
28
+ end
29
+ receipt
30
+ end
31
+ end
32
+
33
+ def total=(total)
34
+ total.gsub!(/[^\d|.]/, "") if total.is_a?(String)
35
+ total = BigDecimal.new(total) unless total.is_a?(BigDecimal)
36
+
37
+ @total = total
38
+ end
39
+
40
+ def sell_date=(date)
41
+ date = Date.parse(date) if date.is_a?(String)
42
+ @sell_date = date
43
+ end
44
+
45
+ def created_date=(date)
46
+ date = Date.parse(date) if date.is_a?(String)
47
+ @created_date = date
48
+ end
49
+
50
+ def modified_date=(date)
51
+ date = Date.parse(date) if date.is_a?(String)
52
+ @modified_date = date
53
+ end
54
+
55
+ def ==(receipt)
56
+ self.id == receipt.id && self.store == receipt.store && self.image_url == receipt.image_url &&
57
+ self.categories == receipt.categories && self.sell_date == receipt.sell_date &&
58
+ self.modified_date == receipt.modified_date && self.created_date == receipt.created_date &&
59
+ self.total == receipt.total
60
+ end
61
+ end
62
+ end
data/lib/rshoeboxed.rb ADDED
@@ -0,0 +1,14 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ require 'rubygems'
5
+ require 'rshoeboxed/receipt'
6
+ require 'rshoeboxed/category'
7
+ require 'rshoeboxed/business_card'
8
+ require 'rshoeboxed/connection'
9
+ require 'rshoeboxed/list_proxy'
10
+ require 'rshoeboxed/parse_error'
11
+
12
+ module RShoeboxed
13
+ VERSION = '0.0.6'
14
+ end
data/script/console ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+ # File: script/console
3
+ irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
4
+
5
+ libs = " -r irb/completion"
6
+ # Perhaps use a console_lib to store any extra methods I may want available in the cosole
7
+ # libs << " -r #{File.dirname(__FILE__) + '/../lib/console_lib/console_logger.rb'}"
8
+ libs << " -r #{File.dirname(__FILE__) + '/../lib/rshoeboxed.rb'}"
9
+ puts "Loading rshoeboxed gem"
10
+ exec "#{irb} #{libs} --simple-prompt"
data/script/destroy ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/destroy'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Destroy.new.run(ARGV)
data/script/generate ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/generate'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Generate.new.run(ARGV)
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <Request xmlns="urn:sbx:apis:SbxBaseComponents">
3
+ <RequesterCredentials>
4
+ <ApiUserToken>api_key</ApiUserToken>
5
+ <SbxUserToken>user_token</SbxUserToken>
6
+ </RequesterCredentials>
7
+ <GetCategoryCall/>
8
+ </Request>
@@ -0,0 +1,7 @@
1
+ <GetCategoryCallResponse>
2
+ <Categories>
3
+ <Category id="1" name="Category 1"/>
4
+ <Category id="2" name="Category 2"/>
5
+ <Category id="3" name="Category 3"/>
6
+ </Categories>
7
+ </GetCategoryCallResponse>
@@ -0,0 +1,12 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <Request xmlns="urn:sbx:apis:SbxBaseComponents">
3
+ <RequesterCredentials>
4
+ <ApiUserToken>api_key</ApiUserToken>
5
+ <SbxUserToken>user_token</SbxUserToken>
6
+ </RequesterCredentials>
7
+ <GetReceiptInfoCall>
8
+ <ReceiptFilter>
9
+ <ReceiptId>1</ReceiptId>
10
+ </ReceiptFilter>
11
+ </GetReceiptInfoCall>
12
+ </Request>
@@ -0,0 +1,5 @@
1
+ <Receipt store="Morgan Imports" id="1" total="$1,929.00" selldate="5/12/2008" createdDate="4/12/2008" modifiedDate="4/20/2008" imgurl="http://www.shoeboxed.com/receipt.jpeg">
2
+ <Categories>
3
+ <Category name="Category 1" id="1"/>
4
+ </Categories>
5
+ </Receipt>
@@ -0,0 +1,16 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <Request xmlns="urn:sbx:apis:SbxBaseComponents">
3
+ <RequesterCredentials>
4
+ <ApiUserToken>api_key</ApiUserToken>
5
+ <SbxUserToken>user_token</SbxUserToken>
6
+ </RequesterCredentials>
7
+ <GetReceiptCall>
8
+ <ReceiptFilter>
9
+ <Results>50</Results>
10
+ <PageNo>1</PageNo>
11
+ <DateStart>2008-01-01T00:00:00</DateStart>
12
+ <DateEnd>2009-01-01T00:00:00</DateEnd>
13
+ <UseSellDate>false</UseSellDate>
14
+ </ReceiptFilter>
15
+ </GetReceiptCall>
16
+ </Request>
@@ -0,0 +1,14 @@
1
+ <GetReceiptCallResponse>
2
+ <Receipts count="2">
3
+ <Receipt store="Great Plains Trust Company" id="23984923842" total="$3,378.30" selldate="5/12/2008" createdDate="4/12/2008" modifiedDate="4/20/2008" imgurl="http://www.shoeboxed.com/receipt1.jpeg">
4
+ <Categories>
5
+ <Category name="Category 1" id="1"/>
6
+ </Categories>
7
+ </Receipt>
8
+ <Receipt store="RadioShack" id="39239293" total="$3.51" selldate="5/12/2008" createdDate="4/12/2008" modifiedDate="4/20/2008" imgurl="http://www.shoeboxed.com/receipt2.jpeg">
9
+ <Categories>
10
+ <Category name="Category 2" id="2"/>
11
+ </Categories>
12
+ </Receipt>
13
+ </Receipts>
14
+ </GetReceiptCallResponse>
@@ -0,0 +1 @@
1
+ <Error code="1" description="Bad credentials" />
@@ -0,0 +1 @@
1
+ <Error code="5" description="Internal Error" />
@@ -0,0 +1 @@
1
+ <Error code="3" description="Restricted IP" />
@@ -0,0 +1 @@
1
+ <Error code="2" description="Unknown API Call" />
@@ -0,0 +1 @@
1
+ <Error code="4" description="XML Validation" />
@@ -0,0 +1,17 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <Request xmlns="urn:sbx:apis:SbxBaseComponents">
3
+ <RequesterCredentials>
4
+ <ApiUserToken>api_key</ApiUserToken>
5
+ <SbxUserToken>user_token</SbxUserToken>
6
+ </RequesterCredentials>
7
+ <GetReceiptCall>
8
+ <ReceiptFilter>
9
+ <Results>50</Results>
10
+ <PageNo>1</PageNo>
11
+ <DateStart>2008-01-01T00:00:00</DateStart>
12
+ <DateEnd>2009-01-01T00:00:00</DateEnd>
13
+ <UseSellDate>true</UseSellDate>
14
+ <Category>10</Category>
15
+ </ReceiptFilter>
16
+ </GetReceiptCall>
17
+ </Request>
@@ -0,0 +1,31 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+
3
+ class TestCategory < Test::Unit::TestCase
4
+ include RShoeboxed
5
+
6
+ def setup
7
+ end
8
+
9
+ def test_initialize_parse_xml
10
+ response = fixture_xml_content("category_response")
11
+ categories = Category.parse(response)
12
+ assert_equal 3, categories.size
13
+
14
+ categories.each_with_index do |category, i|
15
+ category_id = (i+1).to_s
16
+ assert_equal category_id, category.id
17
+ assert_equal "Category #{category_id}", category.name
18
+ end
19
+ end
20
+
21
+ def test_category__accessors
22
+ category = Category.new
23
+ assert category
24
+
25
+ category.name = "Category 1"
26
+ assert_equal "Category 1", category.name
27
+
28
+ category.id = "1"
29
+ assert_equal "1", category.id
30
+ end
31
+ end
@@ -0,0 +1,177 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+
3
+ class TestConnection < Test::Unit::TestCase
4
+ include RShoeboxed
5
+
6
+ def setup
7
+ @conn = Connection.new("api_key", "user_token")
8
+
9
+ @category1 = Category.new
10
+ @category1.id = "1"
11
+ @category1.name = "Category 1"
12
+
13
+ @category2 = Category.new
14
+ @category2.id = "2"
15
+ @category2.name = "Category 2"
16
+ end
17
+
18
+ def test_authenticate__connection_failures
19
+ assert true
20
+ end
21
+
22
+ def test_authentication_url__success_with_options
23
+ assert_equal "https://app.shoeboxed.com/ws/api.htm?appname=Bootstrap&appurl=http%3A%2F%2Fexample.com&apparams=test%3D1%26test2%3D1&SignIn=1",
24
+ Connection.authentication_url("Bootstrap", "http://example.com", [[:test, 1], [:test2, 1]])
25
+ end
26
+
27
+
28
+ def test_authentication_url__success_no_options
29
+ assert_equal "https://app.shoeboxed.com/ws/api.htm?appname=Bootstrap&appurl=http%3A%2F%2Fexample.com&apparams=&SignIn=1",
30
+ Connection.authentication_url("Bootstrap", "http://example.com")
31
+ end
32
+
33
+ def test_connection__instantiate_object
34
+ assert_equal "api_key", @conn.api_key
35
+ assert_equal "user_token", @conn.user_token
36
+ end
37
+
38
+
39
+ def test_get_receipt_info_call__success_getting_one_receipt
40
+ request = fixture_xml_content("receipt_info_request")
41
+ response = fixture_xml_content("receipt_info_response")
42
+
43
+ conn = Connection.new("api_key", "user_token")
44
+ conn.expects(:post_xml).with(request).returns(response)
45
+
46
+ receipt = conn.get_receipt_info_call('1')
47
+ assert_not_nil receipt
48
+ assert_equal "1", receipt.id
49
+ assert_equal "Morgan Imports", receipt.store
50
+ assert_equal Date.new(2008, 5, 12), receipt.sell_date
51
+ assert_equal Date.new(2008, 4, 12), receipt.created_date
52
+ assert_equal Date.new(2008, 4, 20), receipt.modified_date
53
+ assert_equal BigDecimal.new("1929.00"), receipt.total
54
+ assert_equal "http://www.shoeboxed.com/receipt.jpeg", receipt.image_url
55
+ assert_equal [@category1], receipt.categories
56
+ end
57
+
58
+ def test_build_receipt_info_call_request
59
+ assert_equal fixture_xml_content("receipt_info_request"), @conn.send(:build_receipt_info_request, "1")
60
+ end
61
+
62
+ def test_get_receipt_call__success_getting_two_receipt
63
+ request = fixture_xml_content("receipt_request")
64
+ response = fixture_xml_content("receipt_response")
65
+
66
+ conn = Connection.new("api_key", "user_token")
67
+ conn.expects(:post_xml).with(request).returns(response)
68
+
69
+ receipts = conn.get_receipt_call(Date.new(2008, 1, 1), Date.new(2009, 1, 1))
70
+ assert_equal 2, receipts.total
71
+ assert_equal 1, receipts.current_page
72
+ assert_equal 50, receipts.per_page
73
+ assert_equal 1, receipts.pages
74
+ assert_equal 2, receipts.size
75
+
76
+ receipt = receipts[0]
77
+ assert_equal "23984923842", receipt.id
78
+ assert_equal "Great Plains Trust Company", receipt.store
79
+ assert_equal Date.new(2008, 5, 12), receipt.sell_date
80
+ assert_equal Date.new(2008, 4, 12), receipt.created_date
81
+ assert_equal Date.new(2008, 4, 20), receipt.modified_date
82
+ assert_equal BigDecimal.new("3378.30"), receipt.total
83
+ assert_equal "http://www.shoeboxed.com/receipt1.jpeg", receipt.image_url
84
+ assert_equal [@category1], receipt.categories
85
+
86
+ receipt = receipts[1]
87
+ assert_equal "39239293", receipt.id
88
+ assert_equal "RadioShack", receipt.store
89
+ assert_equal Date.new(2008, 5, 12), receipt.sell_date
90
+ assert_equal Date.new(2008, 4, 12), receipt.created_date
91
+ assert_equal Date.new(2008, 4, 20), receipt.modified_date
92
+ assert_equal BigDecimal.new("3.51"), receipt.total
93
+ assert_equal "http://www.shoeboxed.com/receipt2.jpeg", receipt.image_url
94
+ assert_equal [@category2], receipt.categories
95
+ end
96
+
97
+ def test_build_receipt_request
98
+ options = {
99
+ :per_page => 50,
100
+ :current_page => 1,
101
+ :use_sell_date => false
102
+ }
103
+ assert_equal fixture_xml_content("receipt_request"),
104
+ @conn.send(:build_receipt_request, Date.new(2008, 1, 1), Date.new(2009, 1, 1), options)
105
+ end
106
+
107
+ def test_build_receipt_request__category_filter
108
+ options = {
109
+ :per_page => 50,
110
+ :current_page => 1,
111
+ :use_sell_date => true,
112
+ :category_id => 10
113
+ }
114
+ assert_equal fixture_xml_content("receipt_with_category_id_request"),
115
+ @conn.send(:build_receipt_request, Date.new(2008, 1, 1), Date.new(2009, 1, 1), options)
116
+ end
117
+
118
+ def test_check_for_api_error__all_error_codes
119
+ assert_api_error_raised("receipt_response_bad_credentials", AuthenticationError, "Bad credentials")
120
+ assert_api_error_raised("receipt_response_unknown_api_call", UnknownAPICallError, "Unknown API Call")
121
+ assert_api_error_raised("receipt_response_restricted_ip", RestrictedIPError, "Restricted IP")
122
+ assert_api_error_raised("receipt_response_xml_validation", XMLValidationError, "XML Validation")
123
+ assert_api_error_raised("receipt_response_unknown_api_call", UnknownAPICallError, "Unknown API Call")
124
+ end
125
+
126
+ def test_check_for_api_error__no_error
127
+ assert_nothing_raised do
128
+ assert_equal "", @conn.send(:check_for_api_error, "")
129
+ end
130
+
131
+ response = fixture_xml_content("receipt_info_response")
132
+ assert_nothing_raised do
133
+ assert_equal response, @conn.send(:check_for_api_error, response)
134
+ end
135
+ end
136
+
137
+ def test_date_to_s
138
+ assert_equal "2009-01-15T00:00:00", @conn.send(:date_to_s, Date.new(2009, 1, 15))
139
+ assert_equal "2010-02-13T05:10:23", @conn.send(:date_to_s, DateTime.new(2010, 2, 13, 5, 10, 23))
140
+ assert_equal "2008-03-14T12:13:14", @conn.send(:date_to_s, Time.utc(2008, 3, 14, 12, 13, 14))
141
+ assert_equal "2008-03-14T12:13:14", @conn.send(:date_to_s, "2008-03-14T12:13:14")
142
+ end
143
+
144
+ def test_build_category_request
145
+ assert_equal fixture_xml_content("category_request"), @conn.send(:build_category_request)
146
+ end
147
+
148
+
149
+ def test_get_category_call
150
+ request = fixture_xml_content("category_request")
151
+ response = fixture_xml_content("category_response")
152
+
153
+ conn = Connection.new("api_key", "user_token")
154
+ conn.expects(:post_xml).with(request).returns(response)
155
+
156
+ categories = conn.get_category_call
157
+ assert_equal 3, categories.size
158
+
159
+ categories.each_with_index do |category, i|
160
+ category_id = (i + 1).to_s
161
+ assert_equal category_id, category.id
162
+ assert_equal "Category #{category_id}", category.name
163
+ end
164
+ end
165
+
166
+ private
167
+
168
+ def assert_api_error_raised(response_fixture, error_type, error_message)
169
+ response = fixture_xml_content(response_fixture)
170
+ begin
171
+ @conn.send(:check_for_api_error, response)
172
+ fail "#{error_type.to_s} was not thrown"
173
+ rescue error_type => e
174
+ assert_equal error_message, e.message
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,20 @@
1
+ require File.dirname(__FILE__) + '/../lib/rshoeboxed'
2
+
3
+ require 'stringio'
4
+ require 'test/unit'
5
+
6
+ gem 'mocha'
7
+ require 'mocha'
8
+
9
+ class Test::Unit::TestCase
10
+ def fixture_xml_content(file_name)
11
+ # Quick way to remove white space and newlines from xml. Makes it easier to compare in tests
12
+ open(File.join(fixture_dir, "#{file_name}.xml"), "r").readlines.inject("") do |contents, line|
13
+ contents + line.strip
14
+ end
15
+ end
16
+
17
+ def fixture_dir
18
+ File.join(File.dirname(__FILE__), "fixtures")
19
+ end
20
+ end
@@ -0,0 +1,19 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+
3
+ class TestConnection < Test::Unit::TestCase
4
+ include RShoeboxed
5
+
6
+ def test_pages
7
+ proxy = ListProxy.new([], 39, 2, 20)
8
+ assert_equal 39, proxy.total
9
+ assert_equal 2, proxy.current_page
10
+ assert_equal 20, proxy.per_page
11
+ assert_equal 2, proxy.pages
12
+
13
+ proxy = ListProxy.new([], 40, 2, 20)
14
+ assert_equal 2, proxy.pages
15
+
16
+ proxy = ListProxy.new([], 0, 1, 20)
17
+ assert_equal 0, proxy.pages
18
+ end
19
+ end
@@ -0,0 +1,58 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+
3
+ class TestReceipt < Test::Unit::TestCase
4
+ include RShoeboxed
5
+
6
+ def setup
7
+ @category1 = Category.new
8
+ @category1.id = "1"
9
+ @category1.name = "Category 1"
10
+ end
11
+
12
+ def test_initialize_parse_xml
13
+ response = fixture_xml_content("receipt_info_response")
14
+ receipts = Receipt.parse(response)
15
+ assert_equal 1, receipts.size
16
+
17
+ receipt = receipts.first
18
+ assert_equal "1", receipt.id
19
+ assert_equal "Morgan Imports", receipt.store
20
+ assert_equal Date.new(2008, 5, 12), receipt.sell_date
21
+ assert_equal Date.new(2008, 4, 12), receipt.created_date
22
+ assert_equal Date.new(2008, 4, 20), receipt.modified_date
23
+ assert_equal BigDecimal.new("1929.00"), receipt.total
24
+ assert_equal "http://www.shoeboxed.com/receipt.jpeg", receipt.image_url
25
+ assert_equal [@category1], receipt.categories
26
+ end
27
+
28
+ def test_receipt__accessors
29
+ receipt = Receipt.new
30
+ assert receipt
31
+
32
+ receipt.store = "Macy's"
33
+ assert_equal "Macy's", receipt.store
34
+
35
+ receipt.id = "1"
36
+ assert_equal "1", receipt.id
37
+
38
+ receipt.total = '$1,000.19'
39
+ assert_equal BigDecimal.new('1000.19'), receipt.total
40
+
41
+ receipt.created_date = '1/2/2001'
42
+ assert_equal Date.parse('1/2/2001'), receipt.created_date
43
+
44
+ receipt.modified_date = '1/3/2001'
45
+ assert_equal Date.parse('1/3/2001'), receipt.modified_date
46
+
47
+ receipt.image_url = "http://www.example.com/one.image"
48
+ assert_equal "http://www.example.com/one.image", receipt.image_url
49
+
50
+ receipt.categories = [@category1]
51
+ assert_equal [@category1], receipt.categories
52
+ end
53
+
54
+ def test_equal
55
+ # lame test but at least execute the code
56
+ assert_equal Receipt.new, Receipt.new
57
+ end
58
+ end
metadata ADDED
@@ -0,0 +1,133 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ffmike-rshoeboxed
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.6
5
+ platform: ruby
6
+ authors:
7
+ - Ben Curren
8
+ - William Chow
9
+ - Mike Gunderloy
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+
14
+ date: 2009-02-13 00:00:00 -08:00
15
+ default_executable:
16
+ dependencies:
17
+ - !ruby/object:Gem::Dependency
18
+ name: builder
19
+ type: :runtime
20
+ version_requirement:
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: 2.1.2
26
+ version:
27
+ - !ruby/object:Gem::Dependency
28
+ name: newgem
29
+ type: :development
30
+ version_requirement:
31
+ version_requirements: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: 1.1.0
36
+ version:
37
+ - !ruby/object:Gem::Dependency
38
+ name: mocha
39
+ type: :development
40
+ version_requirement:
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: 0.9.4
46
+ version:
47
+ - !ruby/object:Gem::Dependency
48
+ name: hoe
49
+ type: :development
50
+ version_requirement:
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: 1.8.0
56
+ version:
57
+ description: Ruby wrapper for the Shoeboxed API.
58
+ email:
59
+ - ben@outright.com
60
+ - will@outright.com
61
+ - MikeG1@larkfarm.com
62
+ executables: []
63
+
64
+ extensions: []
65
+
66
+ extra_rdoc_files:
67
+ - History.txt
68
+ - Manifest.txt
69
+ - README.rdoc
70
+ files:
71
+ - History.txt
72
+ - Manifest.txt
73
+ - README.rdoc
74
+ - Rakefile
75
+ - lib/rshoeboxed.rb
76
+ - lib/rshoeboxed/category.rb
77
+ - lib/rshoeboxed/connection.rb
78
+ - lib/rshoeboxed/list_proxy.rb
79
+ - lib/rshoeboxed/parse_error.rb
80
+ - lib/rshoeboxed/receipt.rb
81
+ - script/console
82
+ - script/destroy
83
+ - script/generate
84
+ - test/fixtures/category_request.xml
85
+ - test/fixtures/category_response.xml
86
+ - test/fixtures/receipt_info_request.xml
87
+ - test/fixtures/receipt_info_response.xml
88
+ - test/fixtures/receipt_request.xml
89
+ - test/fixtures/receipt_response.xml
90
+ - test/fixtures/receipt_response_bad_credentials.xml
91
+ - test/fixtures/receipt_response_internal_error.xml
92
+ - test/fixtures/receipt_response_restricted_ip.xml
93
+ - test/fixtures/receipt_response_unknown_api_call.xml
94
+ - test/fixtures/receipt_response_xml_validation.xml
95
+ - test/fixtures/receipt_with_category_id_request.xml
96
+ - test/test_category.rb
97
+ - test/test_connection.rb
98
+ - test/test_helper.rb
99
+ - test/test_list_proxy.rb
100
+ - test/test_receipt.rb
101
+ has_rdoc: true
102
+ homepage: http://github.com/bcurren/rshoeboxed
103
+ post_install_message:
104
+ rdoc_options:
105
+ - --main
106
+ - README.rdoc
107
+ require_paths:
108
+ - lib
109
+ required_ruby_version: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ version: "0"
114
+ version:
115
+ required_rubygems_version: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ version: "0"
120
+ version:
121
+ requirements: []
122
+
123
+ rubyforge_project: rshoeboxed
124
+ rubygems_version: 1.2.0
125
+ signing_key:
126
+ specification_version: 2
127
+ summary: Ruby wrapper for the Shoeboxed API.
128
+ test_files:
129
+ - test/test_category.rb
130
+ - test/test_connection.rb
131
+ - test/test_helper.rb
132
+ - test/test_list_proxy.rb
133
+ - test/test_receipt.rb