billtrap 0.0.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.
- data/.gitignore +21 -0
- data/Gemfile +2 -0
- data/LICENSE +22 -0
- data/README.md +218 -0
- data/Rakefile +21 -0
- data/billtrap.gemspec +35 -0
- data/bin/bt +12 -0
- data/bin/dev_b +6 -0
- data/lib/billtrap.rb +56 -0
- data/lib/billtrap/adapters.rb +12 -0
- data/lib/billtrap/adapters/ooffice.rb +27 -0
- data/lib/billtrap/cli.rb +73 -0
- data/lib/billtrap/cmd/client.rb +50 -0
- data/lib/billtrap/cmd/configure.rb +8 -0
- data/lib/billtrap/cmd/entry.rb +45 -0
- data/lib/billtrap/cmd/export.rb +32 -0
- data/lib/billtrap/cmd/import.rb +47 -0
- data/lib/billtrap/cmd/in.rb +16 -0
- data/lib/billtrap/cmd/new.rb +28 -0
- data/lib/billtrap/cmd/payment.rb +45 -0
- data/lib/billtrap/cmd/set.rb +46 -0
- data/lib/billtrap/cmd/show.rb +87 -0
- data/lib/billtrap/cmd/usage.rb +80 -0
- data/lib/billtrap/config.rb +68 -0
- data/lib/billtrap/helpers.rb +17 -0
- data/lib/billtrap/models.rb +239 -0
- data/lib/billtrap/version.rb +3 -0
- data/lib/serenity/LICENSE +22 -0
- data/lib/serenity/serenity.rb +9 -0
- data/lib/serenity/serenity/debug.rb +19 -0
- data/lib/serenity/serenity/escape_xml.rb +18 -0
- data/lib/serenity/serenity/generator.rb +18 -0
- data/lib/serenity/serenity/line.rb +68 -0
- data/lib/serenity/serenity/node_type.rb +7 -0
- data/lib/serenity/serenity/odteruby.rb +90 -0
- data/lib/serenity/serenity/template.rb +31 -0
- data/lib/serenity/serenity/xml_reader.rb +31 -0
- data/migrations/001_base.rb +48 -0
- data/spec/billtrap_spec.rb +283 -0
- metadata +285 -0
@@ -0,0 +1,19 @@
|
|
1
|
+
module Serenity
|
2
|
+
module Debug
|
3
|
+
def debug?
|
4
|
+
false
|
5
|
+
end
|
6
|
+
|
7
|
+
def debug_file_path
|
8
|
+
File.join(debug_dir, debug_file_name)
|
9
|
+
end
|
10
|
+
|
11
|
+
def debug_file_name
|
12
|
+
"serenity_debug_#{rand(100)}.rb"
|
13
|
+
end
|
14
|
+
|
15
|
+
def debug_dir
|
16
|
+
File.join(File.dirname(__FILE__), '..', '..', 'debug')
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class String
|
2
|
+
def escape_xml
|
3
|
+
mgsub!([[/&/, '&'], [/</, '<'], [/>/, '>']])
|
4
|
+
end
|
5
|
+
|
6
|
+
def convert_newlines
|
7
|
+
gsub!("\n", '<text:line-break/>')
|
8
|
+
self
|
9
|
+
end
|
10
|
+
|
11
|
+
def mgsub!(key_value_pairs=[].freeze)
|
12
|
+
regexp_fragments = key_value_pairs.collect { |k,v| k }
|
13
|
+
gsub!(Regexp.union(*regexp_fragments)) do |match|
|
14
|
+
key_value_pairs.detect{|k,v| k =~ match}[1]
|
15
|
+
end
|
16
|
+
self
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Serenity
|
2
|
+
module Generator
|
3
|
+
def render_odt template_path, output_path = output_name(template_path)
|
4
|
+
template = Template.new template_path, output_path
|
5
|
+
template.process binding
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def output_name input
|
11
|
+
if input =~ /(.+)\.odt\Z/
|
12
|
+
"#{$1}_output.odt"
|
13
|
+
else
|
14
|
+
"#{input}_output.odt"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Serenity
|
2
|
+
class Line
|
3
|
+
attr_reader :text
|
4
|
+
|
5
|
+
def initialize text
|
6
|
+
@text = text
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_s
|
10
|
+
@text
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.text txt
|
14
|
+
TextLine.new txt
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.code txt
|
18
|
+
CodeLine.new txt
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.string txt
|
22
|
+
StringLine.new txt
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.literal txt
|
26
|
+
LiteralLine.new txt
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
class TextLine < Line
|
32
|
+
def to_buf
|
33
|
+
" _buf << '" << escape_text(@text) << "';"
|
34
|
+
end
|
35
|
+
|
36
|
+
def escape_text text
|
37
|
+
text.gsub(/['\\]/, '\\\\\&')
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class CodeLine < Line
|
42
|
+
def to_buf
|
43
|
+
escape_code(@text) << ';'
|
44
|
+
end
|
45
|
+
|
46
|
+
def escape_code code
|
47
|
+
code.mgsub! [[/'/, "'"], [/>/, '>'], [/</, '<'], [/"/, '"'], [/&/, '&']]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class StringLine < CodeLine
|
52
|
+
def to_buf
|
53
|
+
" _buf << (" << escape_code(@text) << ").to_s.escape_xml.convert_newlines;"
|
54
|
+
end
|
55
|
+
|
56
|
+
def convert_newlines text
|
57
|
+
text.gsub("First line", '<text:line-break>')
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class LiteralLine < CodeLine
|
62
|
+
def to_buf
|
63
|
+
" _buf << (" << escape_code(@text) << ").to_s;"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module Serenity
|
2
|
+
class OdtEruby
|
3
|
+
include Debug
|
4
|
+
|
5
|
+
EMBEDDED_PATTERN = /\{%([=%]+)?(.*?)-?%\}/m
|
6
|
+
|
7
|
+
def initialize template
|
8
|
+
@src = convert template
|
9
|
+
if debug?
|
10
|
+
File.open(debug_file_path, 'w') do |f|
|
11
|
+
f << @src
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def evaluate context
|
17
|
+
eval(@src, context)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def convert template
|
23
|
+
src = "_buf = '';"
|
24
|
+
buffer = []
|
25
|
+
buffer_next = []
|
26
|
+
|
27
|
+
template.each_node do |node, type|
|
28
|
+
if !buffer_next.empty?
|
29
|
+
if is_matching_pair?(buffer.last, node)
|
30
|
+
buffer.pop
|
31
|
+
next
|
32
|
+
elsif is_nonpair_tag? node
|
33
|
+
next
|
34
|
+
else
|
35
|
+
buffer << buffer_next
|
36
|
+
buffer.flatten!
|
37
|
+
buffer_next = []
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
if type == NodeType::CONTROL
|
42
|
+
buffer_next = process_instruction(node)
|
43
|
+
else
|
44
|
+
buffer << process_instruction(node)
|
45
|
+
buffer.flatten!
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
buffer.each { |line| src << line.to_buf }
|
50
|
+
src << "\n_buf.to_s\n"
|
51
|
+
end
|
52
|
+
|
53
|
+
def process_instruction text
|
54
|
+
#text = text.strip
|
55
|
+
pos = 0
|
56
|
+
src = []
|
57
|
+
|
58
|
+
text.scan(EMBEDDED_PATTERN) do |indicator, code|
|
59
|
+
m = Regexp.last_match
|
60
|
+
middle = text[pos...m.begin(0)]
|
61
|
+
pos = m.end(0)
|
62
|
+
src << Line.text(middle) unless middle.empty?
|
63
|
+
|
64
|
+
if !indicator # <% %>
|
65
|
+
src << Line.code(code)
|
66
|
+
elsif indicator == '=' # <%= %>
|
67
|
+
src << Line.string(code)
|
68
|
+
elsif indicator == '%' # <%% %>
|
69
|
+
src << Line.literal(code)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
rest = pos == 0 ? text : text[pos..-1]
|
74
|
+
|
75
|
+
src << Line.text(rest) unless rest.nil? or rest.empty?
|
76
|
+
src
|
77
|
+
end
|
78
|
+
|
79
|
+
def is_nonpair_tag? tag
|
80
|
+
tag =~ /<.+?\/>/
|
81
|
+
end
|
82
|
+
|
83
|
+
def is_matching_pair? open, close
|
84
|
+
open = open.to_s.strip
|
85
|
+
close = close.to_s.strip
|
86
|
+
|
87
|
+
close == "</#{open[1, close.length - 3]}>"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'zip/zip'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
module Serenity
|
5
|
+
class Template
|
6
|
+
attr_accessor :template
|
7
|
+
|
8
|
+
def initialize(template, output)
|
9
|
+
FileUtils.cp(template, output)
|
10
|
+
@template = output
|
11
|
+
end
|
12
|
+
|
13
|
+
def process context
|
14
|
+
tmpfiles = []
|
15
|
+
Zip::ZipFile.open(@template) do |zipfile|
|
16
|
+
%w(content.xml styles.xml).each do |xml_file|
|
17
|
+
content = zipfile.read(xml_file)
|
18
|
+
odteruby = OdtEruby.new(XmlReader.new(content))
|
19
|
+
out = odteruby.evaluate(context)
|
20
|
+
out.force_encoding Encoding.default_external.to_s
|
21
|
+
|
22
|
+
tmpfiles << (file = Tempfile.new("serenity"))
|
23
|
+
file << out
|
24
|
+
file.close
|
25
|
+
|
26
|
+
zipfile.replace(xml_file, file.path)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Serenity
|
2
|
+
class XmlReader
|
3
|
+
|
4
|
+
def initialize src
|
5
|
+
@src = src.force_encoding("UTF-8")
|
6
|
+
end
|
7
|
+
|
8
|
+
def each_node
|
9
|
+
last_match_pos = 0
|
10
|
+
|
11
|
+
@src.scan(/<.*?>/) do |node|
|
12
|
+
m = Regexp.last_match
|
13
|
+
if m.begin(0) > last_match_pos
|
14
|
+
text = @src[last_match_pos...m.begin(0)]
|
15
|
+
yield text, node_type(text) if text.gsub(/\s+/, '') != ''
|
16
|
+
end
|
17
|
+
|
18
|
+
last_match_pos = m.end(0)
|
19
|
+
yield node, NodeType::TAG
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def node_type text
|
24
|
+
if text =~ /\s*\{%[^=#].+?%\}\s*/
|
25
|
+
NodeType::CONTROL
|
26
|
+
else
|
27
|
+
NodeType::TEMPLATE
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
Sequel.migration do
|
2
|
+
change do
|
3
|
+
# Create clients table
|
4
|
+
create_table(:clients) do
|
5
|
+
primary_key :id
|
6
|
+
String :firstname
|
7
|
+
String :surname
|
8
|
+
String :company
|
9
|
+
String :address , :text => true
|
10
|
+
String :mail
|
11
|
+
Integer :rate
|
12
|
+
String :currency
|
13
|
+
end
|
14
|
+
|
15
|
+
create_table(:invoices) do
|
16
|
+
primary_key :id
|
17
|
+
foreign_key :client_id, :clients
|
18
|
+
String :name
|
19
|
+
Date :created
|
20
|
+
Date :sent
|
21
|
+
String :attributes, :text => true, :default => '{}'
|
22
|
+
end
|
23
|
+
|
24
|
+
create_table(:invoice_entries) do
|
25
|
+
primary_key :id
|
26
|
+
foreign_key :invoice_id, :invoices
|
27
|
+
String :title, :null => false
|
28
|
+
String :notes, :text => true
|
29
|
+
Date :date
|
30
|
+
String :unit, :size => 10
|
31
|
+
Float :count
|
32
|
+
Integer :cents
|
33
|
+
end
|
34
|
+
|
35
|
+
create_table(:payments) do
|
36
|
+
primary_key :id
|
37
|
+
foreign_key :invoice_id, :invoices
|
38
|
+
Integer :cents
|
39
|
+
String :note, :text => true
|
40
|
+
end
|
41
|
+
|
42
|
+
create_table(:meta) do
|
43
|
+
primary_key :id
|
44
|
+
String :key
|
45
|
+
String :value
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,283 @@
|
|
1
|
+
SPEC_RUNNING = true
|
2
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'billtrap'))
|
3
|
+
require 'rspec'
|
4
|
+
require 'fakefs/safe'
|
5
|
+
|
6
|
+
module BillTrap::StubConfig
|
7
|
+
def with_stubbed_config options = {}
|
8
|
+
defaults = BillTrap::Config.defaults.dup
|
9
|
+
BillTrap::Config.stub(:[]).and_return do |k|
|
10
|
+
defaults.merge(options)[k]
|
11
|
+
end
|
12
|
+
yield if block_given?
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
module Helpers
|
18
|
+
def invoke command
|
19
|
+
BillTrap::CLI.args = command.shellsplit
|
20
|
+
BillTrap::CLI.invoke
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_client_verified c
|
24
|
+
$stdin.should_receive(:gets).and_return(c[:firstname])
|
25
|
+
$stdin.should_receive(:gets).and_return(c[:surname])
|
26
|
+
$stdin.should_receive(:gets).and_return(c[:company])
|
27
|
+
$stdin.should_receive(:gets).and_return(c[:address])
|
28
|
+
$stdin.should_receive(:gets).and_return(c[:mail])
|
29
|
+
$stdin.should_receive(:gets).and_return(c[:rate])
|
30
|
+
$stdin.should_receive(:gets).and_return(c[:currency])
|
31
|
+
invoke 'client --add'
|
32
|
+
|
33
|
+
expect($stdout.string).to include "Client #{c[:firstname]} #{c[:surname]} was created with id"
|
34
|
+
end
|
35
|
+
|
36
|
+
def add_entry_verified e
|
37
|
+
$stdin.should_receive(:gets).and_return(e[:title])
|
38
|
+
$stdin.should_receive(:gets).and_return(e[:date])
|
39
|
+
$stdin.should_receive(:gets).and_return(e[:unit])
|
40
|
+
$stdin.should_receive(:gets).and_return(e[:count])
|
41
|
+
$stdin.should_receive(:gets).and_return(e[:price])
|
42
|
+
$stdin.should_receive(:gets).and_return(e[:notes])
|
43
|
+
invoke 'entry --add'
|
44
|
+
|
45
|
+
expect($stdout.string).to match(/Added entry \(#\d+\) to current invoice/)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
RSpec.configure do |config|
|
50
|
+
config.include Helpers
|
51
|
+
config.around(:each) do |example|
|
52
|
+
# Make DBs rollback all changes
|
53
|
+
Sequel.transaction([BillTrap::DB, BillTrap::TT_DB], :rollback=>:always) do
|
54
|
+
example.run
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe BillTrap do
|
60
|
+
include BillTrap::StubConfig
|
61
|
+
before do
|
62
|
+
with_stubbed_config
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
before :each do
|
67
|
+
$stdout = StringIO.new
|
68
|
+
$stdin = StringIO.new
|
69
|
+
$stderr = StringIO.new
|
70
|
+
end
|
71
|
+
|
72
|
+
describe 'CLI' do
|
73
|
+
describe 'with no argument given' do
|
74
|
+
it "should display usage and exit" do
|
75
|
+
expect(lambda { invoke '' }).to raise_error SystemExit
|
76
|
+
expect($stdout.string).to include "Usage: bt COMMAND"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe 'with an invalid subcommand' do
|
82
|
+
it "should output an error" do
|
83
|
+
invoke 'foozbar'
|
84
|
+
expect($stderr.string).to include 'Error: Invalid command "foozbar"'
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe 'configure' do
|
89
|
+
it "should write a config file" do
|
90
|
+
FakeFS do
|
91
|
+
billtrap_home = ENV['BILLTRAP_HOME'] || File.join(ENV['HOME'], '.billtrap')
|
92
|
+
FileUtils.mkdir_p(billtrap_home)
|
93
|
+
config_file = BillTrap::Config::CONFIG_PATH
|
94
|
+
FileUtils.rm(config_file) if File.exist? config_file
|
95
|
+
expect(File.exist?(config_file)).to be_false
|
96
|
+
invoke "configure"
|
97
|
+
expect(File.exist?(config_file)).to be_true
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should display the path to the config file" do
|
102
|
+
FakeFS do
|
103
|
+
invoke "configure"
|
104
|
+
expect($stdout.string).to eq "Config file written to: \"#{ENV['HOME']}/.billtrap/billtrap.yml\"\n"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
describe 'client' do
|
110
|
+
it 'should allow to add and remove clients' do
|
111
|
+
|
112
|
+
add_client_verified(
|
113
|
+
:firstname => 'John',
|
114
|
+
:surname => 'Doe',
|
115
|
+
:company => 'Doemasters Inc.',
|
116
|
+
:address => "Somestreet xyz\n 12345Sometown",
|
117
|
+
:mail => 'jdoe@example.com',
|
118
|
+
:rate => '25',
|
119
|
+
:currency => 'EUR'
|
120
|
+
)
|
121
|
+
|
122
|
+
expect(BillTrap::Client.all.size).to eq 1
|
123
|
+
expect(BillTrap::Client[1].name).to eq 'John Doe'
|
124
|
+
expect(BillTrap::Client[1].company).to eq'Doemasters Inc.'
|
125
|
+
|
126
|
+
# Assume client doesn't confirm deletion
|
127
|
+
$stdin.should_receive(:gets).and_return('n')
|
128
|
+
invoke 'client --delete 1'
|
129
|
+
expect(BillTrap::Client.all.size).to eq 1
|
130
|
+
|
131
|
+
$stdin.should_receive(:gets).and_return('y')
|
132
|
+
invoke 'client --delete 1'
|
133
|
+
expect(BillTrap::Client.all.size).to eq 0
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
describe 'entry' do
|
138
|
+
it 'should allow to add and delete new invoice entries' do
|
139
|
+
|
140
|
+
invoke 'new'
|
141
|
+
add_entry_verified(
|
142
|
+
:title => 'Entry1',
|
143
|
+
:date => '2013-04-10',
|
144
|
+
:count => '2',
|
145
|
+
:price => '25'
|
146
|
+
)
|
147
|
+
|
148
|
+
expect(BillTrap::InvoiceEntry.all.size).to eq 1
|
149
|
+
expect(BillTrap::Invoice.current.total).to eq Money.new('5000', 'USD')
|
150
|
+
|
151
|
+
add_entry_verified(
|
152
|
+
:title => 'Entry2',
|
153
|
+
:date => '2013-04-11',
|
154
|
+
:count => '2',
|
155
|
+
:price => '12.51'
|
156
|
+
)
|
157
|
+
|
158
|
+
expect(BillTrap::InvoiceEntry[1].title).to eq 'Entry1'
|
159
|
+
expect(BillTrap::InvoiceEntry[2].title).to eq 'Entry2'
|
160
|
+
expect(BillTrap::InvoiceEntry[1].invoice_id).to eq 1
|
161
|
+
expect(BillTrap::InvoiceEntry[2].invoice_id).to eq 1
|
162
|
+
expect(BillTrap::Invoice.current.total).to eq Money.new('7502', 'USD')
|
163
|
+
|
164
|
+
$stdin.should_receive(:gets).and_return('n')
|
165
|
+
invoke 'entry --delete 1'
|
166
|
+
expect(BillTrap::InvoiceEntry.all.size).to eq 2
|
167
|
+
|
168
|
+
$stdin.should_receive(:gets).and_return('y')
|
169
|
+
invoke 'entry --delete 1'
|
170
|
+
expect(BillTrap::InvoiceEntry[1]).to eq nil
|
171
|
+
expect(BillTrap::InvoiceEntry.all.size).to eq 1
|
172
|
+
expect(BillTrap::Invoice.current.total).to eq Money.new('2502', 'USD')
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
describe 'in' do
|
177
|
+
it 'should switch active invoices' do
|
178
|
+
invoke 'new --name "Some important project"'
|
179
|
+
invoke 'new --name "Support"'
|
180
|
+
|
181
|
+
expect(BillTrap::Invoice.all.size).to eq 2
|
182
|
+
expect(BillTrap::Invoice.current.name).to eq 'Support'
|
183
|
+
expect(BillTrap::Invoice.current.id).to eq 2
|
184
|
+
|
185
|
+
invoke 'in 1'
|
186
|
+
expect(BillTrap::Invoice.current.name).to eq 'Some important project'
|
187
|
+
expect(BillTrap::Invoice.current.id).to eq 1
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
describe 'new' do
|
192
|
+
it 'should allow to set date and client' do
|
193
|
+
add_client_verified(
|
194
|
+
:firstname => 'John',
|
195
|
+
:surname => 'Doe',
|
196
|
+
:company => 'Doemasters Inc.',
|
197
|
+
:address => "Somestreet xyz\n 12345Sometown",
|
198
|
+
:mail => 'jdoe@example.com',
|
199
|
+
:rate => '25',
|
200
|
+
:currency => 'EUR'
|
201
|
+
)
|
202
|
+
|
203
|
+
# Setting by id
|
204
|
+
invoke 'new --client 1'
|
205
|
+
expect(BillTrap::Invoice.current.client.name).to eq 'John Doe'
|
206
|
+
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
describe 'set' do
|
211
|
+
it 'should allow to set client for current invoice' do
|
212
|
+
add_client_verified(
|
213
|
+
:firstname => 'John',
|
214
|
+
:surname => 'Doe',
|
215
|
+
:company => 'Doemasters Inc.',
|
216
|
+
:address => "Somestreet xyz\n 12345Sometown",
|
217
|
+
:mail => 'jdoe@example.com',
|
218
|
+
:rate => '25',
|
219
|
+
:currency => 'EUR'
|
220
|
+
)
|
221
|
+
|
222
|
+
expect(BillTrap::Client.all.size).to eq 1
|
223
|
+
expect(BillTrap::Client[1].name).to eq 'John Doe'
|
224
|
+
|
225
|
+
# Create invoice
|
226
|
+
$stdout = StringIO.new
|
227
|
+
invoke 'new'
|
228
|
+
expect($stdout.string).to include "Created invoice #1"
|
229
|
+
expect(BillTrap::Invoice.current[:id]).to eq 1
|
230
|
+
invoke 'set client 1'
|
231
|
+
|
232
|
+
expect($stdout.string).to include 'SET client to John Doe (#1)'
|
233
|
+
|
234
|
+
# Expect warning output
|
235
|
+
invoke 'set client 2'
|
236
|
+
expect($stderr.string).to include "Error: Can't find Client with id '2'"
|
237
|
+
expect(BillTrap::Invoice.current.client.name).to eq 'John Doe'
|
238
|
+
end
|
239
|
+
|
240
|
+
it 'should set the current invoice name' do
|
241
|
+
invoke 'new --name "Foobar"'
|
242
|
+
expect(BillTrap::Invoice.current.name).to eq 'Foobar'
|
243
|
+
|
244
|
+
invoke 'set name "Important project"'
|
245
|
+
expect($stdout.string).to include "SET name to 'Important project'"
|
246
|
+
expect(BillTrap::Invoice.current.name).to eq 'Important project'
|
247
|
+
end
|
248
|
+
|
249
|
+
it 'should set the current invoice date' do
|
250
|
+
invoke 'new'
|
251
|
+
expect(BillTrap::Invoice.current.created).to eq Date.today
|
252
|
+
|
253
|
+
invoke 'set date 2013-04-10'
|
254
|
+
expect(BillTrap::Invoice.current.created).to eq Date.parse('2013-04-10')
|
255
|
+
|
256
|
+
# with no args sets to today
|
257
|
+
invoke 'set date'
|
258
|
+
expect(BillTrap::Invoice.current.created).to eq Date.today
|
259
|
+
end
|
260
|
+
|
261
|
+
it 'should set the current invoice sent date' do
|
262
|
+
invoke 'new'
|
263
|
+
expect(BillTrap::Invoice.current.sent).to eq nil
|
264
|
+
|
265
|
+
invoke 'set sent 2013-04-10'
|
266
|
+
expect(BillTrap::Invoice.current.sent).to eq Date.parse('2013-04-10')
|
267
|
+
|
268
|
+
invoke 'set sent'
|
269
|
+
expect(BillTrap::Invoice.current.sent).to eq nil
|
270
|
+
end
|
271
|
+
|
272
|
+
it 'should set arbitrary attributes' do
|
273
|
+
invoke 'new'
|
274
|
+
expect(BillTrap::Invoice.current.sent).to eq nil
|
275
|
+
|
276
|
+
invoke 'set foo bar'
|
277
|
+
expect(BillTrap::Invoice.current.attributes['foo']).to eq 'bar'
|
278
|
+
|
279
|
+
invoke 'set foo notbar'
|
280
|
+
expect(BillTrap::Invoice.current.attributes['foo']).to eq 'notbar'
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|