badbill 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/lib/badbill.rb ADDED
@@ -0,0 +1,152 @@
1
+ # encoding: utf-8
2
+
3
+ require 'yajl/json_gem'
4
+ require 'faraday_middleware'
5
+ require 'hashie/mash'
6
+
7
+ require_relative 'badbill/resource'
8
+ require_relative 'badbill/forward_methods'
9
+ require_relative 'badbill/base_resource'
10
+
11
+ require_relative 'badbill/client'
12
+ require_relative 'badbill/invoice'
13
+
14
+ # Handles the connection and requests to the Billomat API.
15
+ #
16
+ # This class can be used for direct API access and is used for connections from
17
+ # resource classes.
18
+ #
19
+ # If a API resource is not yet implemented as a Ruby class, easy access is possible here.
20
+ #
21
+ # Examples:
22
+ #
23
+ # billo = BadBill.new 'ruby', '1234568'
24
+ # # => #<BadBill:0x00000002825710 ...>
25
+ # billo.get 'clients'
26
+ # # => {"clients"=>{"client"=>[...]}}
27
+ class BadBill
28
+ VERSION = '0.0.1'
29
+
30
+ # Reject any not allowed HTTP method.
31
+ class NotAllowedException < Exception; end
32
+ # Fail if no global connection is set.
33
+ class NoConnection < Exception; end
34
+
35
+ # The API url used for all connections.
36
+ API_URL = 'http%s://%s.billomat.net/'
37
+ # Allowed HTTP methods.
38
+ ALLOWED_METHODS = [:get, :post, :put, :delete]
39
+
40
+ # Create new Billomat connection.
41
+ #
42
+ # @param [String] billomat_id Used as your BillomatID
43
+ # @param [String] api_key API Key used to authenticate to the API.
44
+ # @param [Boolean] ssl Wether to use SSL or not (only possible for paying customers)
45
+ def initialize billomat_id, api_key, ssl=false
46
+ @billomat_id = billomat_id
47
+ @api_key = api_key
48
+ @ssl = ssl
49
+ @http_adapter = connection
50
+
51
+ BadBill.connection = self
52
+ end
53
+
54
+ # Assign global BadBill connection object.
55
+ #
56
+ # @param [BadBill] connection The connection object.
57
+ def self.connection= connection
58
+ @connection = connection
59
+ end
60
+
61
+ # Get the global connection object.
62
+ #
63
+ # @return [BadBill, nil] The global connection object or nil if not set.
64
+ def self.connection
65
+ @connection
66
+ end
67
+
68
+ # Call the specified resource.
69
+ #
70
+ # It sets the X-BillomatApiKey header, the Content-Type header and the Accept header.
71
+ #
72
+ # @param [String] resource The String resource name (gets prepended with _/api/_).
73
+ # @param [String,Integer] id The ID for the resource.
74
+ # @param [Hash] options All parameters for this request.
75
+ # Exact parameters depend on the resource.
76
+ # @param [Symbol] method One of ALLOWED_METHODS.
77
+ #
78
+ # @return [Hashie::Mash] The response body.
79
+ # On error the return value only includes the key :error.
80
+ def call resource, id='', options=nil, method=:get
81
+ raise NotAllowedException.new("#{method.inspect} is not allowed. Use one of [:#{ALLOWED_METHODS*', :'}]") unless ALLOWED_METHODS.include?(method)
82
+
83
+ if id.kind_of? Hash
84
+ options = id
85
+ id = ''
86
+ end
87
+
88
+ #no_accept = options.delete :no_accept
89
+ @http_adapter.__send__(method) { |req|
90
+ if method == :get && options && !options.empty?
91
+ req.url "/api/#{resource}/#{id}", options
92
+ else
93
+ req.url "/api/#{resource}/#{id}"
94
+ end
95
+ req.headers['X-BillomatApiKey'] = @api_key
96
+ req.headers['Accept'] = 'application/json'
97
+ req.headers['Content-Type'] = 'application/json' if [:post, :put].include?(method)
98
+ req.body = options if method != :get && options && !options.empty?
99
+ }.body
100
+ rescue Faraday::Error::ClientError => error
101
+ Hashie::Mash.new :error => error
102
+ end
103
+
104
+ # Send a GET request.
105
+ #
106
+ # @param (see #call)
107
+ # @return (see #call)
108
+ def get resource, id='', options=nil
109
+ call resource, id, options, :get
110
+ end
111
+
112
+ # Send a POST request.
113
+ #
114
+ # @param (see #call)
115
+ # @return (see #call)
116
+ def post resource, id='', options=nil
117
+ call resource, id, options, :post
118
+ end
119
+
120
+ # Send a PUT request.
121
+ #
122
+ # @param (see #call)
123
+ # @return (see #call)
124
+ def put resource, id='', options=nil
125
+ call resource, id, options, :put
126
+ end
127
+
128
+ # Send a DELETE request.
129
+ #
130
+ # @param (see #call)
131
+ # @return (see #call)
132
+ def delete resource, id='', options=nil
133
+ call resource, id, options, :delete
134
+ end
135
+
136
+ private
137
+ # Creates a new Faraday connection object.
138
+ #
139
+ # @return [Faraday] new connection object with url and response handling set.
140
+ def connection
141
+ @connection ||= Faraday.new(API_URL % [@ssl ? 's' : '', @billomat_id]) do |conn|
142
+ conn.request :json
143
+
144
+ conn.response :mashify
145
+ conn.response :json, :content_type => /\bjson$/
146
+ #conn.response :logger
147
+ conn.response :raise_error
148
+ conn.adapter :net_http
149
+ conn.options[:timeout] = 2
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,12 @@
1
+ # encoding: utf-8
2
+
3
+ require_relative '../helper'
4
+
5
+ describe BadBill::BaseResource do
6
+ it "can't set a new ID" do
7
+ b = BadBill::BaseResource.new 42, {}
8
+ b.id.should == 42
9
+ lambda{ b.id = 23 }.should raise_error(NoMethodError)
10
+ b.id.should == 42
11
+ end
12
+ end
@@ -0,0 +1,98 @@
1
+ # encoding: utf-8
2
+
3
+ require_relative '../helper'
4
+
5
+ describe BadBill::Client do
6
+ before :all do
7
+ @badbill = BadBill.new "ruby", "12345"
8
+ end
9
+
10
+ it "fetches all clients" do
11
+ stub = stub_request(:get, "ruby.billomat.net/api/clients/").
12
+ with(:headers => {'Accept' => 'application/json'}).
13
+ to_return(:body => '{"clients":{"client":[{"id":1}]}}',
14
+ :headers => {'Content-Type' => 'application/json'})
15
+
16
+ resp = BadBill::Client.all
17
+ stub.should have_been_requested
18
+
19
+ resp.size.should == 1
20
+ resp.first.id.should == 1
21
+ end
22
+
23
+ it "fetches info about myself" do
24
+ stub = stub_request(:get, "ruby.billomat.net/api/clients/myself").
25
+ with(:headers => {'Accept' => 'application/json'}).
26
+ to_return(:body => '{"client":{"id":1}}',
27
+ :headers => {'Content-Type' => 'application/json'})
28
+
29
+ resp = BadBill::Client.myself
30
+ stub.should have_been_requested
31
+
32
+ resp.id.should == 1
33
+ resp.myself?.should == true
34
+ end
35
+
36
+ it "fetches info about another client" do
37
+ stub = stub_request(:get, "ruby.billomat.net/api/clients/1").
38
+ with(:headers => {'Accept' => 'application/json'}).
39
+ to_return(:body => '{"client":{"id":1}}',
40
+ :headers => {'Content-Type' => 'application/json'})
41
+
42
+ resp = BadBill::Client.find 1
43
+ stub.should have_been_requested
44
+
45
+ resp.id.should == 1
46
+ resp.myself?.should == false
47
+ end
48
+
49
+ it "saves changes to client data" do
50
+ body = { client: {name: "old name", "id" => 1} }
51
+ stub_request(:get, "ruby.billomat.net/api/clients/1").
52
+ with(:headers => {'Accept' => 'application/json'}).
53
+ to_return(:body => body,
54
+ :headers => {'Content-Type' => 'application/json'})
55
+
56
+ client = BadBill::Client.find 1
57
+
58
+ body = { client: {name: "new name", "id" => 1} }
59
+ stub = stub_request(:put, "ruby.billomat.net/api/clients/1").
60
+ with(:headers => {'Accept' => 'application/json'}, :body => body).
61
+ to_return(:status => 200)
62
+
63
+ client.name.should == "old name"
64
+ client.name = "new name"
65
+ client.save.should == true
66
+ stub.should have_been_requested
67
+ end
68
+
69
+ it "creates a new client with given data" do
70
+ body = {
71
+ "client" => {
72
+ "name" => "Musterfirma",
73
+ "salutation" => "Herr",
74
+ "first_name" => "Max",
75
+ "last_name" => "Muster",
76
+ "street" => "Musterstraße 123",
77
+ "zip" => "12345",
78
+ "city" => "Musterstadt",
79
+ "state" => "Bundesland",
80
+ "country_code" => "DE",
81
+ }
82
+ }
83
+
84
+ body_return = { "client" => body["client"].dup }
85
+ body_return["client"]["id"] = 42
86
+ body_return["client"]["created"] = "2007-12-13T12:12:00+01:00"
87
+
88
+ stub = stub_request(:post, "ruby.billomat.net/api/clients/").
89
+ with(:body => body.to_json, :headers => {'Accept' => 'application/json',
90
+ 'Content-Type'=>'application/json'}).
91
+ to_return(:status => 201, :body => body_return,
92
+ :headers => {'Content-Type' => 'application/json'})
93
+
94
+ client = BadBill::Client.create body["client"]
95
+ stub.should have_been_requested
96
+ client.id.should == 42
97
+ end
98
+ end
@@ -0,0 +1,228 @@
1
+ # encoding: utf-8
2
+
3
+ require_relative '../helper'
4
+
5
+ describe BadBill::Invoice do
6
+ before :all do
7
+ @badbill = BadBill.new "ruby", "12345"
8
+ end
9
+
10
+ it "fetches all invoices" do
11
+ stub = stub_request(:get, "ruby.billomat.net/api/invoices/").
12
+ with(:headers => {'Accept' => 'application/json'}).
13
+ to_return(:body => '{"invoices":{"invoice":[{"id":1}]}}',
14
+ :headers => {'Content-Type' => 'application/json'})
15
+
16
+ resp = BadBill::Invoice.all
17
+ stub.should have_been_requested
18
+
19
+ resp.size.should == 1
20
+ resp.first.id.should == 1
21
+ end
22
+
23
+ it "fetches info about an invoice" do
24
+ stub = stub_request(:get, "ruby.billomat.net/api/invoices/1").
25
+ with(:headers => {'Accept' => 'application/json'}).
26
+ to_return(:body => '{"invoice":{"id":1}}',
27
+ :headers => {'Content-Type' => 'application/json'})
28
+
29
+ resp = BadBill::Invoice.find 1
30
+ stub.should have_been_requested
31
+
32
+ resp.id.should == 1
33
+ end
34
+
35
+ context "existing invoice" do
36
+ before :each do
37
+ stub_request(:get, "ruby.billomat.net/api/invoices/1").
38
+ with(:headers => {'Accept' => 'application/json'}).
39
+ to_return(:body => '{"invoice":{"id":1,"status":"DRAFT"}}',
40
+ :headers => {'Content-Type' => 'application/json'})
41
+
42
+ @invoice = BadBill::Invoice.find 1
43
+ end
44
+
45
+ it "marks invoice as complete" do
46
+ stub = stub_request(:put, "ruby.billomat.net/api/invoices/1/complete").
47
+ with(:headers => {'Accept' => 'application/json'}).
48
+ to_return(:status => 200)
49
+
50
+ @invoice.complete.should == true
51
+ stub.should have_been_requested
52
+ end
53
+
54
+ it "cancels an invoice" do
55
+ stub = stub_request(:put, "ruby.billomat.net/api/invoices/1/cancel").
56
+ with(:headers => {'Accept' => 'application/json'}).
57
+ to_return(:status => 200)
58
+
59
+ @invoice.cancel.should == true
60
+ stub.should have_been_requested
61
+ end
62
+
63
+ it "deletes an invoice" do
64
+ stub = stub_request(:delete, "ruby.billomat.net/api/invoices/1").
65
+ with(:headers => {'Accept' => 'application/json'}).
66
+ to_return(:status => 200)
67
+
68
+ @invoice.delete.should == true
69
+ stub.should have_been_requested
70
+ end
71
+
72
+ it "fetches the pdf" do
73
+ body = {
74
+ "pdf" => {
75
+ "id" => 1,
76
+ "created" => "2009-09-02T12 =>04 =>15+02 =>00",
77
+ "invoice_id" => 1,
78
+ "filename" => "invoice_1.pdf",
79
+ "mimetype" => "application/pdf",
80
+ "filesize" => "1",
81
+ "base64file" => "foobar"
82
+ }
83
+ }
84
+
85
+ stub = stub_request(:get, "ruby.billomat.net/api/invoices/1/pdf").
86
+ with(:headers => {'Accept' => 'application/json'}).
87
+ to_return(:body => body, :headers => {
88
+ 'Content-Type' => 'application/json'
89
+ })
90
+
91
+ pdf = @invoice.pdf
92
+
93
+ stub.should have_been_requested
94
+ pdf.id.should == 1
95
+ pdf.invoice_id == 1
96
+ end
97
+
98
+ it "uploads a signature" do
99
+ body = {"signature" => {"base64file" => "cnVieQ==\n"}}
100
+ stub = stub_request(:put, "ruby.billomat.net/api/invoices/1/upload-signature").
101
+ with(:headers => {'Accept' => 'application/json'}, :body => body).
102
+ to_return(:status => 200)
103
+
104
+ @invoice.status = "OPEN"
105
+ file_content = StringIO.new "ruby"
106
+ resp = @invoice.upload_signature file_content
107
+ resp.should == true
108
+
109
+ stub.should have_been_requested
110
+ end
111
+
112
+ context "sending invoice as mail" do
113
+ it "sends an invoice via mail" do
114
+ body = {
115
+ "email" => {
116
+ "from" => "sender@test.example",
117
+ "recipients" => {
118
+ "to" => "recv@test.example",
119
+ "cc" => "mail@test.example"
120
+ },
121
+ "subject" => "subject",
122
+ "body" => "body",
123
+ "filename" => "invoice",
124
+ "attachments" => {
125
+ "attachment" => {
126
+ "filename" => "more.pdf",
127
+ "mimetype" => "application/pdf",
128
+ "base64file" => "foobar"
129
+ }
130
+ }
131
+ }
132
+ }
133
+
134
+ stub = stub_request(:post, "ruby.billomat.net/api/invoices/1/email").
135
+ with(:headers => {'Accept' => 'application/json'}, :body => body).
136
+ to_return(:headers => {
137
+ 'Content-Type' => 'application/json'
138
+ })
139
+
140
+ resp = @invoice.email "recv@test.example", "sender@test.example",
141
+ "subject", "body", {
142
+ recipients: { cc: "mail@test.example" },
143
+ filename: "invoice",
144
+ attachments: {
145
+ attachment: {
146
+ filename: "more.pdf",
147
+ mimetype: "application/pdf",
148
+ base64file: "foobar"
149
+ }
150
+ }
151
+ }
152
+
153
+ stub.should have_been_requested
154
+ resp.should == true
155
+ end
156
+
157
+ it "sends an invoice with only basic information" do
158
+ body = {
159
+ "email" => {
160
+ "recipients" => {
161
+ "to" => "recv@test.example",
162
+ "cc" => "mail@test.example",
163
+ }
164
+ }
165
+ }
166
+
167
+ stub = stub_request(:post, "ruby.billomat.net/api/invoices/1/email").
168
+ with(:headers => {'Accept' => 'application/json'}, :body => body).
169
+ to_return(:headers => {
170
+ 'Content-Type' => 'application/json'
171
+ })
172
+
173
+ resp = @invoice.email "recv@test.example", {recipients: { cc: "mail@test.example" }}
174
+
175
+ stub.should have_been_requested
176
+ resp.should == true
177
+ end
178
+
179
+ it "sends an invoice with basic information and from" do
180
+ body = {
181
+ "email" => {
182
+ "recipients" => {
183
+ "to" => "recv@test.example",
184
+ "cc" => "mail@test.example",
185
+ },
186
+ "from" => "sender@test.example"
187
+ }
188
+ }
189
+
190
+ stub = stub_request(:post, "ruby.billomat.net/api/invoices/1/email").
191
+ with(:headers => {'Accept' => 'application/json'}, :body => body).
192
+ to_return(:headers => {
193
+ 'Content-Type' => 'application/json'
194
+ })
195
+
196
+ resp = @invoice.email "recv@test.example", "sender@test.example", {recipients: { cc: "mail@test.example" }}
197
+
198
+ stub.should have_been_requested
199
+ resp.should == true
200
+ end
201
+
202
+ it "sends an invoice with basic information, from and subject" do
203
+ body = {
204
+ "email" => {
205
+ "recipients" => {
206
+ "to" => "recv@test.example",
207
+ "cc" => "mail@test.example",
208
+ },
209
+ "from" => "sender@test.example",
210
+ "subject" => "subject"
211
+ }
212
+ }
213
+
214
+ stub = stub_request(:post, "ruby.billomat.net/api/invoices/1/email").
215
+ with(:headers => {'Accept' => 'application/json'}, :body => body).
216
+ to_return(:headers => {
217
+ 'Content-Type' => 'application/json'
218
+ })
219
+
220
+ resp = @invoice.email "recv@test.example", "sender@test.example", "subject",
221
+ {recipients: { cc: "mail@test.example" }}
222
+
223
+ stub.should have_been_requested
224
+ resp.should == true
225
+ end
226
+ end
227
+ end
228
+ end
@@ -0,0 +1,89 @@
1
+ # encoding: utf-8
2
+
3
+ require_relative 'helper'
4
+
5
+ describe BadBill do
6
+ it "creates new global connection object" do
7
+ BadBill.connection.should be_nil
8
+ b = BadBill.new "ruby", "12345"
9
+ BadBill.connection.should_not be_nil
10
+ end
11
+
12
+ context "sending requests" do
13
+ before :each do
14
+ @badbill = BadBill.new "ruby", "12345"
15
+ end
16
+
17
+ it "sends a specified request to the API" do
18
+ stub = stub_request(:get, "ruby.billomat.net/api/").
19
+ with(:headers => {'Accept' => /application\/json/}).
20
+ to_return(:body => "{}")
21
+
22
+ @badbill.call ''
23
+ stub.should have_been_requested
24
+ end
25
+
26
+ it "sends a GET request to the API" do
27
+ stub = stub_request(:get, "ruby.billomat.net/api/").
28
+ with(:headers => {'X-BillomatApiKey' => '12345',
29
+ 'Accept' => /application\/json/}).
30
+ to_return(:body => "{}")
31
+
32
+ @badbill.get ''
33
+ stub.should have_been_requested
34
+ end
35
+
36
+ it "sends a GET request with parameters to the API" do
37
+ stub = stub_request(:get, "ruby.billomat.net/api/resource/?option=foobar").
38
+ with(:headers => {'X-BillomatApiKey' => '12345',
39
+ 'Accept' => /application\/json/}).
40
+ to_return(:body => "{}")
41
+
42
+ @badbill.get 'resource', option: "foobar"
43
+ stub.should have_been_requested
44
+ end
45
+
46
+ it "sends a POST request to the API" do
47
+ stub = stub_request(:post, "ruby.billomat.net/api/").
48
+ with(:headers => {'X-BillomatApiKey' => '12345',
49
+ 'Accept' => /application\/json/,
50
+ 'Content-Type' => /application\/json/}).
51
+ to_return(:body => "{}")
52
+
53
+ @badbill.post ''
54
+ stub.should have_been_requested
55
+ end
56
+
57
+ it "sends a PUT request to the API" do
58
+ stub = stub_request(:put, "ruby.billomat.net/api/").
59
+ with(:headers => {'X-BillomatApiKey' => '12345',
60
+ 'Accept' => /application\/json/,
61
+ 'Content-Type' => /application\/json/}).
62
+ to_return(:body => "{}")
63
+
64
+ @badbill.put ''
65
+ stub.should have_been_requested
66
+ end
67
+
68
+ it "sends a DELETE request to the API" do
69
+ stub = stub_request(:delete, "ruby.billomat.net/api/").
70
+ with(:headers => {'X-BillomatApiKey' => '12345',
71
+ 'Accept' => /application\/json/}).
72
+ to_return(:body => "{}")
73
+
74
+ @badbill.delete ''
75
+ stub.should have_been_requested
76
+ end
77
+
78
+ it "returns an error object on HTTP errors" do
79
+ stub = stub_request(:get, "ruby.billomat.net/api/resource/").
80
+ with(:headers => {'X-BillomatApiKey' => '12345',
81
+ 'Accept' => /application\/json/}).
82
+ to_return(:status => 404)
83
+
84
+ resp = @badbill.get 'resource'
85
+ resp.error.kind_of?(Faraday::Error::ClientError).should == true
86
+ stub.should have_been_requested
87
+ end
88
+ end
89
+ end
data/spec/helper.rb ADDED
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+
3
+ if ENV['COVERAGE']
4
+ require 'simplecov'
5
+
6
+ SimpleCov.formatter = Class.new do
7
+ def format(result)
8
+ SimpleCov::Formatter::HTMLFormatter.new.format(result) unless ENV['CI']
9
+ File.open('coverage/covered_percent', 'w') do |f|
10
+ f.puts result.source_files.covered_percent.to_i
11
+ end
12
+ end
13
+ end
14
+
15
+ SimpleCov.start
16
+ end
17
+
18
+ require 'rspec'
19
+ require 'webmock/rspec'
20
+
21
+ here = File.expand_path(File.dirname(__FILE__) + '/..')
22
+ $LOAD_PATH.unshift here+'/lib'
23
+
24
+ require 'badbill'