billtrap 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|