sox 0.0.1
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/.gitignore +4 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +22 -0
- data/LICENSE +22 -0
- data/README.md +91 -0
- data/Rakefile +26 -0
- data/lib/sox.rb +9 -0
- data/lib/sox/client.rb +24 -0
- data/lib/sox/proxy.rb +11 -0
- data/lib/sox/request_options.rb +40 -0
- data/lib/sox/restful_operation.rb +21 -0
- data/lib/sox/version.rb +4 -0
- data/lib/sox/xml_parser.rb +64 -0
- data/sox.gemspec +21 -0
- data/spec/_spec_helper.rb +6 -0
- data/spec/fixtures/all_clients_successful_response.xml +14 -0
- data/spec/fixtures/all_projects_successful_response.xml +45 -0
- data/spec/lib/sox/client_spec.rb +36 -0
- data/spec/lib/sox/clients_operations_spec.rb +28 -0
- data/spec/lib/sox/projects_operations_spec.rb +27 -0
- data/spec/lib/sox/request_options_spec.rb +21 -0
- data/spec/lib/sox/xml_parser_spec.rb +163 -0
- metadata +147 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
sox (0.0.1)
|
|
5
|
+
bubble-wrap (~> 1.3.0)
|
|
6
|
+
|
|
7
|
+
GEM
|
|
8
|
+
remote: https://rubygems.org/
|
|
9
|
+
specs:
|
|
10
|
+
bacon (1.2.0)
|
|
11
|
+
bubble-wrap (1.3.0)
|
|
12
|
+
rake (10.1.0)
|
|
13
|
+
webstub (0.6.1)
|
|
14
|
+
|
|
15
|
+
PLATFORMS
|
|
16
|
+
ruby
|
|
17
|
+
|
|
18
|
+
DEPENDENCIES
|
|
19
|
+
bacon
|
|
20
|
+
rake
|
|
21
|
+
sox!
|
|
22
|
+
webstub
|
data/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Copyright (c) 2013, Brilliant Fantastic, LLC.
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
|
4
|
+
obtaining a copy of this software and associated documentation
|
|
5
|
+
files (the "Software"), to deal in the Software without
|
|
6
|
+
restriction, including without limitation the rights to use,
|
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
copies of the Software, and to permit persons to whom the
|
|
9
|
+
Software is furnished to do so, subject to the following
|
|
10
|
+
conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be
|
|
13
|
+
included in all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
Sox
|
|
2
|
+
====
|
|
3
|
+
|
|
4
|
+
A RubyMotion wrapper for the Freshbooks API.
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
Add this to your RubyMotion application's `Gemfile`:
|
|
9
|
+
|
|
10
|
+
```ruby
|
|
11
|
+
gem 'sox'
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
And then execute:
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
bundle
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Then add this to your RubyMotion application's `Rakefile`:
|
|
21
|
+
|
|
22
|
+
```ruby
|
|
23
|
+
require 'sox'
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
All requests can go through the `Sox::Client` object. To create a client object, you pass it your authorization credentials.
|
|
29
|
+
Currenly Sox only supports basic authentication using your Freshbooks API token but we there is a plan to add OAuth support.
|
|
30
|
+
|
|
31
|
+
You can retrieve your Freshbooks API token from the 'My Account' page on Freshbooks.
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
@client = Sox::Client 'your-freshbooks-subdomain', 'api-token'
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
You can then use your client object to perform the CRUD operations on Freshbooks.
|
|
38
|
+
|
|
39
|
+
```ruby
|
|
40
|
+
projects = @client.projects.all
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Sox will convert the XML that is returned from the Freshbooks API into modern day hashes. Yay!
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
puts projects[:response][:projects][:project][0][:client_id]
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## TODO
|
|
50
|
+
|
|
51
|
+
This is a very early release. All the base classes and supporting classes should be in place to easily add the additional proxies and API calls (specifically find, update, create, and delete).
|
|
52
|
+
|
|
53
|
+
* Currently only setup for OSX
|
|
54
|
+
* Add integration tests for real API data
|
|
55
|
+
* Support for OAuth authentication
|
|
56
|
+
|
|
57
|
+
* Clients (create, update, get, delete)
|
|
58
|
+
* Projects (create, update, get, delete)
|
|
59
|
+
* Categories (create, update, get, delete, list)
|
|
60
|
+
* Estimates (create, update, get, delete, list, sendByEmail)
|
|
61
|
+
* Categories (create, update, get, delete, list)
|
|
62
|
+
* Expenses (create, update, get, delete, list)
|
|
63
|
+
* Gateways (list)
|
|
64
|
+
* Invoices (create, update, get, delete, list, sendByEmail, sendbySnailMail)
|
|
65
|
+
* Invoice Items (add, delete, update)
|
|
66
|
+
* Items (create, update, get, delete, list)
|
|
67
|
+
* Languages (list)
|
|
68
|
+
* Payments (create, update, get, delete, list)
|
|
69
|
+
* Receipts (create, update, get, delete)
|
|
70
|
+
* Recurring (create, update, get, delete, list)
|
|
71
|
+
* Recurring Items (add, delete, update)
|
|
72
|
+
* Staff (current, get, list)
|
|
73
|
+
* Tasks (create, update, get, delete)
|
|
74
|
+
* Taxes (create, update, get, delete)
|
|
75
|
+
* Time entries (create, update, get, delete)
|
|
76
|
+
|
|
77
|
+
## Contributing
|
|
78
|
+
|
|
79
|
+
1. Fork it
|
|
80
|
+
1. Run `bundle` to get the Gem dependency
|
|
81
|
+
1. Run `rake spec` to run all of the tests to ensure they pass
|
|
82
|
+
1. Create your feature branch (`git checkout -b my-awesome-sauce`)
|
|
83
|
+
1. Write your specs
|
|
84
|
+
1. Code!
|
|
85
|
+
1. Commit your changes (`git commit -am 'Added some awesome sauce'`)
|
|
86
|
+
1. Push your new branch (`git push origin my-awesome-sauce`)
|
|
87
|
+
1. Create a pull request (`hub pull-request -b brilliantfantastic:master -h yourrepo:my-awesome-sauce`)
|
|
88
|
+
|
|
89
|
+
## License
|
|
90
|
+
|
|
91
|
+
MIT. See [LICENSE](LICENSE)
|
data/Rakefile
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
$:.unshift("/Library/RubyMotion/lib")
|
|
2
|
+
|
|
3
|
+
#if ENV['osx']
|
|
4
|
+
require 'motion/project/template/osx'
|
|
5
|
+
#else
|
|
6
|
+
# require 'motion/project/template/ios'
|
|
7
|
+
#end
|
|
8
|
+
|
|
9
|
+
require 'bundler'
|
|
10
|
+
require 'bubble-wrap'
|
|
11
|
+
require 'webstub'
|
|
12
|
+
|
|
13
|
+
Bundler.setup
|
|
14
|
+
Bundler.require
|
|
15
|
+
|
|
16
|
+
Bundler::GemHelper.install_tasks
|
|
17
|
+
|
|
18
|
+
namespace :spec do
|
|
19
|
+
task :lib do
|
|
20
|
+
sh "bacon #{Dir.glob("spec/lib/**/*_spec.rb").join(' ')}"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
task :motion => 'spec'
|
|
24
|
+
|
|
25
|
+
task :all => [:lib, :motion]
|
|
26
|
+
end
|
data/lib/sox.rb
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
unless defined?(Motion::Project::Config)
|
|
2
|
+
raise "This file must be required within a RubyMotion project Rakefile."
|
|
3
|
+
end
|
|
4
|
+
|
|
5
|
+
Motion::Project::App.setup do |app|
|
|
6
|
+
Dir.glob(File.join(File.dirname(__FILE__), 'sox/*.rb')).each do |file|
|
|
7
|
+
app.files.unshift(file)
|
|
8
|
+
end
|
|
9
|
+
end
|
data/lib/sox/client.rb
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module Sox
|
|
2
|
+
class Client
|
|
3
|
+
include Sox::Proxy
|
|
4
|
+
|
|
5
|
+
attr_reader :base_url
|
|
6
|
+
|
|
7
|
+
def initialize(subdomain, api_token)
|
|
8
|
+
@base_url = "https://#{subdomain}.freshbooks.com/api/#{Sox::API_VERSION}/xml-in"
|
|
9
|
+
@api_token = api_token
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def auth
|
|
13
|
+
{ username: @api_token, password: 'X' }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def clients
|
|
17
|
+
proxy(:client)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def projects
|
|
21
|
+
proxy(:project)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
data/lib/sox/proxy.rb
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module Sox
|
|
2
|
+
class RequestOptions
|
|
3
|
+
def request
|
|
4
|
+
generate_xml
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def initialize(method, options={})
|
|
8
|
+
@method = method
|
|
9
|
+
@options = options
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def generate_xml
|
|
15
|
+
root = NSXMLElement.alloc.initWithName 'request'
|
|
16
|
+
root.addAttribute NSXMLNode.attributeWithName('method', stringValue: @method)
|
|
17
|
+
|
|
18
|
+
# Add all the options if there are any
|
|
19
|
+
@options.each { |key, value| generate_xml_node root, key, value }
|
|
20
|
+
|
|
21
|
+
document = NSXMLDocument.alloc.initWithRootElement root
|
|
22
|
+
data = document.XMLDataWithOptions NSXMLDocumentTidyXML
|
|
23
|
+
NSString.alloc.initWithData(data, encoding: NSUTF8StringEncoding)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def generate_xml_node(element, key, value)
|
|
27
|
+
if value.is_a? Hash
|
|
28
|
+
parent = NSXMLElement.alloc.initWithName(key)
|
|
29
|
+
value.each do |k, v|
|
|
30
|
+
el = generate_xml_node parent, k, v
|
|
31
|
+
element.addChild el
|
|
32
|
+
end
|
|
33
|
+
else
|
|
34
|
+
el = NSXMLElement.alloc.initWithName(key, stringValue: value.to_s)
|
|
35
|
+
element.addChild el
|
|
36
|
+
end
|
|
37
|
+
element
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Sox
|
|
2
|
+
class RestfulOperation
|
|
3
|
+
attr_reader :prefix
|
|
4
|
+
|
|
5
|
+
def initialize(base_url, auth, prefix)
|
|
6
|
+
@base_url = base_url
|
|
7
|
+
@auth = auth
|
|
8
|
+
@prefix = prefix
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def all(options={}, &block)
|
|
12
|
+
payload = RequestOptions.new("#{@prefix}.list", options).request
|
|
13
|
+
opts = { credentials: @auth }
|
|
14
|
+
opts.merge!({ payload: payload })
|
|
15
|
+
BW::HTTP.post(@base_url, opts) do |response|
|
|
16
|
+
XML::Parser.new(response.body).parse { |hash| block.call hash }
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
data/lib/sox/version.rb
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
module Sox
|
|
2
|
+
module XML
|
|
3
|
+
class ParserError < StandardError; end
|
|
4
|
+
|
|
5
|
+
class Parser
|
|
6
|
+
def initialize(data)
|
|
7
|
+
@data = data
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def parse(&block)
|
|
11
|
+
@callback = block
|
|
12
|
+
parse_data @data
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def parserDidStartDocument(parser)
|
|
16
|
+
@result = [{}]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def parser(parser, didStartElement:element, namespaceURI:uri, qualifiedName:name, attributes:attrs)
|
|
20
|
+
element = element.to_sym
|
|
21
|
+
parent = @result.last
|
|
22
|
+
|
|
23
|
+
child = {}
|
|
24
|
+
attrs.each { |k, v| child[k.to_sym] = v } if attrs
|
|
25
|
+
|
|
26
|
+
existing = parent[element]
|
|
27
|
+
|
|
28
|
+
if existing
|
|
29
|
+
values = existing.is_a?(Array) ? existing << child : [existing, child]
|
|
30
|
+
parent[element] = values
|
|
31
|
+
else
|
|
32
|
+
parent[element] = child
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
@result << child
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def parser(parser, didEndElement:element, namespaceURI:uri, qualifiedName:name)
|
|
39
|
+
@result.pop
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def parser(parser, foundCharacters:string)
|
|
43
|
+
@result.last[:data] = string.strip
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def parserDidEndDocument(parser)
|
|
47
|
+
@callback.call(@result.first) if @callback
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def parser(parser, parseErrorOccurred:parse_error)
|
|
51
|
+
raise ParserError.new(parse_error.localizedDescription)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def parse_data(data)
|
|
57
|
+
parser = NSXMLParser.alloc.initWithData(data)
|
|
58
|
+
parser.shouldProcessNamespaces = true
|
|
59
|
+
parser.delegate = self
|
|
60
|
+
parser.parse
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
data/sox.gemspec
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
require File.expand_path('../lib/sox/version', __FILE__)
|
|
3
|
+
|
|
4
|
+
Gem::Specification.new do |gem|
|
|
5
|
+
gem.authors = ['Jamie Wright', 'Keith Thompson']
|
|
6
|
+
gem.email = ['jamie@brilliantfantastic.com', 'keith@brilliantfantastic.com']
|
|
7
|
+
gem.description = 'A RubyMotion wrapper for the Freshbooks API'
|
|
8
|
+
gem.summary = 'A RubyMotion wrapper for the Freshbooks API'
|
|
9
|
+
|
|
10
|
+
gem.files = `git ls-files`.split($\)
|
|
11
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|lib_spec|features)/})
|
|
12
|
+
gem.name = 'sox'
|
|
13
|
+
gem.require_paths = ['lib']
|
|
14
|
+
gem.version = Sox::VERSION
|
|
15
|
+
|
|
16
|
+
gem.add_dependency 'bubble-wrap', '~> 1.3.0'
|
|
17
|
+
|
|
18
|
+
gem.add_development_dependency 'webstub'
|
|
19
|
+
gem.add_development_dependency 'bacon'
|
|
20
|
+
gem.add_development_dependency 'rake'
|
|
21
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<response xmlns="http://www.freshbooks.com/api/" status="ok">
|
|
2
|
+
<clients page="1" per_page="25" pages="1" total="2">
|
|
3
|
+
<client>
|
|
4
|
+
<client_id>1</client_id>
|
|
5
|
+
<first_name>Jane</first_name>
|
|
6
|
+
<last_name>Doe</last_name>
|
|
7
|
+
</client>
|
|
8
|
+
<client>
|
|
9
|
+
<client_id>2</client_id>
|
|
10
|
+
<first_name>John</first_name>
|
|
11
|
+
<last_name>Doe</last_name>
|
|
12
|
+
</client>
|
|
13
|
+
</clients>
|
|
14
|
+
</response>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<response xmlns="http://www.freshbooks.com/api/" status="ok">
|
|
2
|
+
<projects page="1" per_page="15" pages="1" total="2">
|
|
3
|
+
<project>
|
|
4
|
+
<project_id>6</project_id>
|
|
5
|
+
<name>Super Fun Project</name>
|
|
6
|
+
<description></description>
|
|
7
|
+
<rate>11000</rate>
|
|
8
|
+
<bill_method>flat-rate</bill_method>
|
|
9
|
+
<client_id>119</client_id>
|
|
10
|
+
<hour_budget>100</hour_budget>
|
|
11
|
+
<tasks>
|
|
12
|
+
<task>
|
|
13
|
+
<task_id>5</task_id>
|
|
14
|
+
</task>
|
|
15
|
+
<task>
|
|
16
|
+
<task_id>12</task_id>
|
|
17
|
+
</task>
|
|
18
|
+
<task>
|
|
19
|
+
<task_id>6</task_id>
|
|
20
|
+
<rate>25</rate>
|
|
21
|
+
</task>
|
|
22
|
+
<task>
|
|
23
|
+
<task_id>7</task_id>
|
|
24
|
+
<rate>35</rate>
|
|
25
|
+
</task>
|
|
26
|
+
</tasks>
|
|
27
|
+
<staff>
|
|
28
|
+
<staff>
|
|
29
|
+
<staff_id>1</staff_id>
|
|
30
|
+
</staff>
|
|
31
|
+
<staff>
|
|
32
|
+
<staff_id>2</staff_id>
|
|
33
|
+
</staff>
|
|
34
|
+
<staff>
|
|
35
|
+
<staff_id>3</staff_id>
|
|
36
|
+
</staff>
|
|
37
|
+
<staff_id deprecated="true">1</staff_id>
|
|
38
|
+
<staff_id deprecated="true">2</staff_id>
|
|
39
|
+
<staff_id deprecated="true">3</staff_id>
|
|
40
|
+
</staff>
|
|
41
|
+
</project>
|
|
42
|
+
<project>
|
|
43
|
+
</project>
|
|
44
|
+
</projects>
|
|
45
|
+
</response>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
describe Sox::Client do
|
|
2
|
+
before do
|
|
3
|
+
@subdomain = 'fake'
|
|
4
|
+
@api_token = '1234'
|
|
5
|
+
@client = Sox::Client.new @subdomain, @api_token
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
describe '#initialize' do
|
|
9
|
+
it 'sets the base url' do
|
|
10
|
+
@client.base_url.should == "https://#{@subdomain}.freshbooks.com/api/#{Sox::API_VERSION}/xml-in"
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
describe '#auth' do
|
|
15
|
+
it 'sets the auth header using basic auth' do
|
|
16
|
+
@client.auth.should == { username: @api_token, password: 'X' }
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
describe '#clients' do
|
|
21
|
+
it 'returns a proxy object' do
|
|
22
|
+
@client.clients.prefix.should == :client
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
describe '#projects' do
|
|
27
|
+
it 'returns a proxy object' do
|
|
28
|
+
@client.projects.prefix.should == :project
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it 'can have multiple proxies' do
|
|
33
|
+
@client.clients.prefix.should == :client
|
|
34
|
+
@client.projects.prefix.should == :project
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
describe 'Freshbooks API clients operations' do
|
|
2
|
+
extend WebStub::SpecHelpers
|
|
3
|
+
extend SpecHelper
|
|
4
|
+
|
|
5
|
+
before do
|
|
6
|
+
disable_network_access!
|
|
7
|
+
@response = load_fixture('all_clients_successful_response.xml')
|
|
8
|
+
@client = Sox::Client.new 'fake', '12345'
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it 'can fetch all of the clients' do
|
|
12
|
+
stub_request(:post, @client.base_url).to_return body: @response, content_type: 'application/xml'
|
|
13
|
+
|
|
14
|
+
@client.clients.all do |response|
|
|
15
|
+
@response = response
|
|
16
|
+
resume
|
|
17
|
+
end
|
|
18
|
+
wait_max 1.0 do
|
|
19
|
+
response = @response[:response]
|
|
20
|
+
response[:status].should == 'ok'
|
|
21
|
+
response[:clients][:total].should == '2'
|
|
22
|
+
response[:clients][:client][0][:client_id][:data].should == '1'
|
|
23
|
+
response[:clients][:client][0][:first_name][:data].should == 'Jane'
|
|
24
|
+
response[:clients][:client][1][:client_id][:data].should == '2'
|
|
25
|
+
response[:clients][:client][1][:first_name][:data].should == 'John'
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
describe 'Freshbooks API projects operations' do
|
|
2
|
+
extend WebStub::SpecHelpers
|
|
3
|
+
extend SpecHelper
|
|
4
|
+
|
|
5
|
+
before do
|
|
6
|
+
disable_network_access!
|
|
7
|
+
@response = load_fixture('all_projects_successful_response.xml')
|
|
8
|
+
@client = Sox::Client.new 'fake', '12345'
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it 'can fetch all of the projects' do
|
|
12
|
+
stub_request(:post, @client.base_url).to_return body: @response, content_type: 'application/xml'
|
|
13
|
+
|
|
14
|
+
@client.projects.all do |response|
|
|
15
|
+
@response = response
|
|
16
|
+
resume
|
|
17
|
+
end
|
|
18
|
+
wait_max 1.0 do
|
|
19
|
+
response = @response[:response]
|
|
20
|
+
response[:status].should == 'ok'
|
|
21
|
+
response[:projects][:total].should == '2'
|
|
22
|
+
response[:projects][:project][0][:project_id][:data].should == '6'
|
|
23
|
+
response[:projects][:project][0][:name][:data].should == 'Super Fun Project'
|
|
24
|
+
response[:projects][:project][0][:client_id][:data].should == '119'
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
describe Sox::RequestOptions do
|
|
2
|
+
it 'specifies the request method in the xml' do
|
|
3
|
+
request = Sox::RequestOptions.new('client.list').request
|
|
4
|
+
request.should == '<request method="client.list"></request>'
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
it 'converts a simple hash to xml' do
|
|
8
|
+
request = Sox::RequestOptions.new('client.list', { client_id: 1 }).request
|
|
9
|
+
request.should == '<request method="client.list"><client_id>1</client_id></request>'
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it 'converts an embedded hash to xml' do
|
|
13
|
+
request = Sox::RequestOptions.new('client.list', { client: { client_id: 1 } }).request
|
|
14
|
+
request.should == '<request method="client.list"><client><client_id>1</client_id></client></request>'
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it 'converts a super duper embedded hash to xml' do
|
|
18
|
+
request = Sox::RequestOptions.new('client.list', { client: { address: { street: '123 Main St.' } } }).request
|
|
19
|
+
request.should == '<request method="client.list"><client><address><street>123 Main St.</street></address></client></request>'
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
describe Sox::XML::Parser do
|
|
2
|
+
describe '#parse' do
|
|
3
|
+
describe 'with a simple single element' do
|
|
4
|
+
before do
|
|
5
|
+
document = "<element></element>"
|
|
6
|
+
@parser = Sox::XML::Parser.new(document.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true))
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
it 'yields to the block once parsing has finished' do
|
|
10
|
+
hash = nil
|
|
11
|
+
@parser.parse { |hsh| hash = hsh }
|
|
12
|
+
hash.should != nil
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it 'creates a hash representing the document' do
|
|
16
|
+
hash = nil
|
|
17
|
+
@parser.parse { |hsh| hash = hsh }
|
|
18
|
+
hash.should == { element: {} }
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
describe 'with a simple single element and data' do
|
|
23
|
+
before do
|
|
24
|
+
document = "<element>value</element>"
|
|
25
|
+
@parser = Sox::XML::Parser.new(document.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true))
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it 'creates a hash representing the document' do
|
|
29
|
+
hash = nil
|
|
30
|
+
@parser.parse { |hsh| hash = hsh }
|
|
31
|
+
hash.should == { element: { data: 'value' } }
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
describe 'with a simple single element and an attribute' do
|
|
36
|
+
before do
|
|
37
|
+
document = "<element attribute=\"attribute one\"></element>"
|
|
38
|
+
@parser = Sox::XML::Parser.new(document.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true))
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it 'creates a hash representing the document' do
|
|
42
|
+
hash = nil
|
|
43
|
+
@parser.parse { |hsh| hash = hsh }
|
|
44
|
+
hash.should == { element: { attribute: 'attribute one' } }
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
describe 'with a simple single element and multiple attribute' do
|
|
49
|
+
before do
|
|
50
|
+
document = "<element attribute_one=\"attribute one\" attribute_two=\"attribute two\"></element>"
|
|
51
|
+
@parser = Sox::XML::Parser.new(document.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true))
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it 'creates a hash representing the document' do
|
|
55
|
+
hash = nil
|
|
56
|
+
@parser.parse { |hsh| hash = hsh }
|
|
57
|
+
hash.should == { element: { attribute_one: 'attribute one', attribute_two: 'attribute two' } }
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
describe 'with a simple single element, multiple attribute, and a value' do
|
|
62
|
+
before do
|
|
63
|
+
document = "<element attribute_one=\"attribute one\" attribute_two=\"attribute two\">value</element>"
|
|
64
|
+
@parser = Sox::XML::Parser.new(document.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true))
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it 'creates a hash representing the document' do
|
|
68
|
+
hash = nil
|
|
69
|
+
@parser.parse { |hsh| hash = hsh }
|
|
70
|
+
hash.should == { element: { attribute_one: 'attribute one', attribute_two: 'attribute two', data: 'value' } }
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
describe 'with a simple embedded element' do
|
|
75
|
+
before do
|
|
76
|
+
document = "<element_one><element_two></element_two></element_one>"
|
|
77
|
+
@parser = Sox::XML::Parser.new(document.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true))
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
it 'creates a hash representing the document' do
|
|
81
|
+
hash = nil
|
|
82
|
+
@parser.parse { |hsh| hash = hsh }
|
|
83
|
+
hash.should == { element_one: { element_two: {} } }
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
describe 'with a simple embedded element and a value' do
|
|
88
|
+
before do
|
|
89
|
+
document = "<element_one><element_two>value two</element_two></element_one>"
|
|
90
|
+
@parser = Sox::XML::Parser.new(document.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true))
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
it 'creates a hash representing the document' do
|
|
94
|
+
hash = nil
|
|
95
|
+
@parser.parse { |hsh| hash = hsh }
|
|
96
|
+
hash.should == { element_one: { element_two: { data: 'value two' } } }
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
describe 'with a simple embedded element, a value, and some attributes' do
|
|
101
|
+
before do
|
|
102
|
+
document = "<element_one attribute_one=\"attribute one\"><element_two attribute_two=\"attribute two\">value two</element_two></element_one>"
|
|
103
|
+
@parser = Sox::XML::Parser.new(document.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true))
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
it 'creates a hash representing the document' do
|
|
107
|
+
hash = nil
|
|
108
|
+
@parser.parse { |hsh| hash = hsh }
|
|
109
|
+
hash.should == { element_one: { attribute_one: 'attribute one', element_two: { attribute_two: 'attribute two', data: 'value two' } } }
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
describe 'with multiple child elements' do
|
|
114
|
+
before do
|
|
115
|
+
document = "<element_one><element_two>value two</element_two><element_three>value three</element_three></element_one>"
|
|
116
|
+
@parser = Sox::XML::Parser.new(document.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true))
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
it 'creates a hash representing the document' do
|
|
120
|
+
hash = nil
|
|
121
|
+
@parser.parse { |hsh| hash = hsh }
|
|
122
|
+
hash.should == { element_one: { element_two: { data: 'value two' }, element_three: { data: 'value three' } } }
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
describe 'with an array of child elements' do
|
|
127
|
+
before do
|
|
128
|
+
document = "<parent><children><child></child><child></child></children></parent>"
|
|
129
|
+
@parser = Sox::XML::Parser.new(document.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true))
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
it 'creates a hash representing the document' do
|
|
133
|
+
hash = nil
|
|
134
|
+
@parser.parse { |hsh| hash = hsh }
|
|
135
|
+
hash.should == { parent: { children: { child: [{}, {}]} } }
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
describe 'with an array with arrays of child elements' do
|
|
140
|
+
before do
|
|
141
|
+
document = "<parent><children><child></child><child><toys><toy></toy></toys></child></children></parent>"
|
|
142
|
+
@parser = Sox::XML::Parser.new(document.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true))
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
it 'creates a hash representing the document' do
|
|
146
|
+
hash = nil
|
|
147
|
+
@parser.parse { |hsh| hash = hsh }
|
|
148
|
+
hash.should == { parent: { children: { child: [{}, { toys: { toy: {} } } ] } } }
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
describe 'with an invalid xml document' do
|
|
153
|
+
before do
|
|
154
|
+
document = '<element value></element>'
|
|
155
|
+
@parser = Sox::XML::Parser.new(document.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true))
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
it 'raises a parser error' do
|
|
159
|
+
should.raise(Sox::XML::ParserError) { @parser.parse }
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: sox
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
prerelease:
|
|
6
|
+
platform: ruby
|
|
7
|
+
authors:
|
|
8
|
+
- Jamie Wright
|
|
9
|
+
- Keith Thompson
|
|
10
|
+
autorequire:
|
|
11
|
+
bindir: bin
|
|
12
|
+
cert_chain: []
|
|
13
|
+
date: 2013-07-26 00:00:00.000000000 Z
|
|
14
|
+
dependencies:
|
|
15
|
+
- !ruby/object:Gem::Dependency
|
|
16
|
+
name: bubble-wrap
|
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
|
18
|
+
none: false
|
|
19
|
+
requirements:
|
|
20
|
+
- - ~>
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: 1.3.0
|
|
23
|
+
type: :runtime
|
|
24
|
+
prerelease: false
|
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
26
|
+
none: false
|
|
27
|
+
requirements:
|
|
28
|
+
- - ~>
|
|
29
|
+
- !ruby/object:Gem::Version
|
|
30
|
+
version: 1.3.0
|
|
31
|
+
- !ruby/object:Gem::Dependency
|
|
32
|
+
name: webstub
|
|
33
|
+
requirement: !ruby/object:Gem::Requirement
|
|
34
|
+
none: false
|
|
35
|
+
requirements:
|
|
36
|
+
- - ! '>='
|
|
37
|
+
- !ruby/object:Gem::Version
|
|
38
|
+
version: '0'
|
|
39
|
+
type: :development
|
|
40
|
+
prerelease: false
|
|
41
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
42
|
+
none: false
|
|
43
|
+
requirements:
|
|
44
|
+
- - ! '>='
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0'
|
|
47
|
+
- !ruby/object:Gem::Dependency
|
|
48
|
+
name: bacon
|
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
|
50
|
+
none: false
|
|
51
|
+
requirements:
|
|
52
|
+
- - ! '>='
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '0'
|
|
55
|
+
type: :development
|
|
56
|
+
prerelease: false
|
|
57
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
58
|
+
none: false
|
|
59
|
+
requirements:
|
|
60
|
+
- - ! '>='
|
|
61
|
+
- !ruby/object:Gem::Version
|
|
62
|
+
version: '0'
|
|
63
|
+
- !ruby/object:Gem::Dependency
|
|
64
|
+
name: rake
|
|
65
|
+
requirement: !ruby/object:Gem::Requirement
|
|
66
|
+
none: false
|
|
67
|
+
requirements:
|
|
68
|
+
- - ! '>='
|
|
69
|
+
- !ruby/object:Gem::Version
|
|
70
|
+
version: '0'
|
|
71
|
+
type: :development
|
|
72
|
+
prerelease: false
|
|
73
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
74
|
+
none: false
|
|
75
|
+
requirements:
|
|
76
|
+
- - ! '>='
|
|
77
|
+
- !ruby/object:Gem::Version
|
|
78
|
+
version: '0'
|
|
79
|
+
description: A RubyMotion wrapper for the Freshbooks API
|
|
80
|
+
email:
|
|
81
|
+
- jamie@brilliantfantastic.com
|
|
82
|
+
- keith@brilliantfantastic.com
|
|
83
|
+
executables: []
|
|
84
|
+
extensions: []
|
|
85
|
+
extra_rdoc_files: []
|
|
86
|
+
files:
|
|
87
|
+
- .gitignore
|
|
88
|
+
- Gemfile
|
|
89
|
+
- Gemfile.lock
|
|
90
|
+
- LICENSE
|
|
91
|
+
- README.md
|
|
92
|
+
- Rakefile
|
|
93
|
+
- lib/sox.rb
|
|
94
|
+
- lib/sox/client.rb
|
|
95
|
+
- lib/sox/proxy.rb
|
|
96
|
+
- lib/sox/request_options.rb
|
|
97
|
+
- lib/sox/restful_operation.rb
|
|
98
|
+
- lib/sox/version.rb
|
|
99
|
+
- lib/sox/xml_parser.rb
|
|
100
|
+
- sox.gemspec
|
|
101
|
+
- spec/_spec_helper.rb
|
|
102
|
+
- spec/fixtures/all_clients_successful_response.xml
|
|
103
|
+
- spec/fixtures/all_projects_successful_response.xml
|
|
104
|
+
- spec/lib/sox/client_spec.rb
|
|
105
|
+
- spec/lib/sox/clients_operations_spec.rb
|
|
106
|
+
- spec/lib/sox/projects_operations_spec.rb
|
|
107
|
+
- spec/lib/sox/request_options_spec.rb
|
|
108
|
+
- spec/lib/sox/xml_parser_spec.rb
|
|
109
|
+
homepage:
|
|
110
|
+
licenses: []
|
|
111
|
+
post_install_message:
|
|
112
|
+
rdoc_options: []
|
|
113
|
+
require_paths:
|
|
114
|
+
- lib
|
|
115
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
116
|
+
none: false
|
|
117
|
+
requirements:
|
|
118
|
+
- - ! '>='
|
|
119
|
+
- !ruby/object:Gem::Version
|
|
120
|
+
version: '0'
|
|
121
|
+
segments:
|
|
122
|
+
- 0
|
|
123
|
+
hash: 2815329898089102049
|
|
124
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
125
|
+
none: false
|
|
126
|
+
requirements:
|
|
127
|
+
- - ! '>='
|
|
128
|
+
- !ruby/object:Gem::Version
|
|
129
|
+
version: '0'
|
|
130
|
+
segments:
|
|
131
|
+
- 0
|
|
132
|
+
hash: 2815329898089102049
|
|
133
|
+
requirements: []
|
|
134
|
+
rubyforge_project:
|
|
135
|
+
rubygems_version: 1.8.23
|
|
136
|
+
signing_key:
|
|
137
|
+
specification_version: 3
|
|
138
|
+
summary: A RubyMotion wrapper for the Freshbooks API
|
|
139
|
+
test_files:
|
|
140
|
+
- spec/_spec_helper.rb
|
|
141
|
+
- spec/fixtures/all_clients_successful_response.xml
|
|
142
|
+
- spec/fixtures/all_projects_successful_response.xml
|
|
143
|
+
- spec/lib/sox/client_spec.rb
|
|
144
|
+
- spec/lib/sox/clients_operations_spec.rb
|
|
145
|
+
- spec/lib/sox/projects_operations_spec.rb
|
|
146
|
+
- spec/lib/sox/request_options_spec.rb
|
|
147
|
+
- spec/lib/sox/xml_parser_spec.rb
|