freshtrack 0.6.0 → 0.7.0
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 +7 -0
- data/lib/freshbooks/extensions/base_object.rb +16 -6
- data/lib/freshbooks/extensions/time_entry.rb +3 -1
- data/lib/freshtrack.rb +17 -8
- data/lib/freshtrack/time_collectors/punchy_template.rb +11 -8
- data/lib/freshtrack/version.rb +1 -1
- data/spec/freshbooks/base_object_spec.rb +34 -5
- data/spec/freshtrack_spec.rb +301 -68
- data/spec/time_collectors/one_inch_punch_spec.rb +9 -0
- data/spec/time_collectors/punch_spec.rb +9 -0
- metadata +4 -4
data/History.txt
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
== 0.7.0 2012-03-30
|
2
|
+
|
3
|
+
* 1 minor enhancement:
|
4
|
+
* Tracking time is now idempotent. If a time entry already exists for a date/project/task combination, that time entry will be updated rather than a new entry created.
|
5
|
+
* 1 bugfix:
|
6
|
+
* Tracking time for a project when you're punched in no longer raises an exception. Time entries that aren't completed (not punched out) are now ignored.
|
7
|
+
|
1
8
|
== 0.6.0 2012-03-30
|
2
9
|
|
3
10
|
* 1 minor enhancement:
|
@@ -9,16 +9,17 @@ module FreshBooks
|
|
9
9
|
when '1' : true
|
10
10
|
end
|
11
11
|
end
|
12
|
-
|
12
|
+
|
13
13
|
def to_xml
|
14
14
|
# The root element is the elem name
|
15
15
|
root = Element.new elem_name
|
16
|
-
|
16
|
+
|
17
17
|
# Add each BaseObject member to the root elem
|
18
|
-
|
19
|
-
|
18
|
+
|
19
|
+
self.included_members.each do |field_name|
|
20
|
+
|
20
21
|
value = self.send(field_name)
|
21
|
-
|
22
|
+
|
22
23
|
if value.is_a?(Array)
|
23
24
|
node = root.add_element(field_name)
|
24
25
|
value.each { |array_elem| node.add_element(array_elem.to_xml) }
|
@@ -28,10 +29,19 @@ module FreshBooks
|
|
28
29
|
end
|
29
30
|
root
|
30
31
|
end
|
31
|
-
|
32
|
+
|
32
33
|
# The root element is the class name, downcased (and underscored if there is any CamelCase)
|
33
34
|
def elem_name
|
34
35
|
elem_name = self.class.to_s.split('::').last.gsub(/([a-z])([A-Z])/, '\1_\2').downcase
|
35
36
|
end
|
37
|
+
|
38
|
+
def included_members
|
39
|
+
members - excluded_members
|
40
|
+
end
|
41
|
+
|
42
|
+
def excluded_members
|
43
|
+
return [] unless self.class.const_defined?(:EXCLUDE_XML_ATTRIBUTES)
|
44
|
+
self.class.const_get(:EXCLUDE_XML_ATTRIBUTES)
|
45
|
+
end
|
36
46
|
end
|
37
47
|
end
|
@@ -6,7 +6,9 @@ module FreshBooks
|
|
6
6
|
'time_entry_id' => Fixnum, 'project_id' => Fixnum, 'task_id' => Fixnum,
|
7
7
|
'hours' => Float, 'date' => Date, 'billed' => :boolean
|
8
8
|
}
|
9
|
-
|
9
|
+
|
10
|
+
EXCLUDE_XML_ATTRIBUTES = %w[billed]
|
11
|
+
|
10
12
|
class << self
|
11
13
|
def get(time_entry_id)
|
12
14
|
resp = FreshBooks.call_api('time_entry.get', 'time_entry_id' => time_entry_id)
|
data/lib/freshtrack.rb
CHANGED
@@ -52,22 +52,31 @@ module Freshtrack
|
|
52
52
|
|
53
53
|
def track(project_name, options = {})
|
54
54
|
data = get_data(project_name, options)
|
55
|
+
tracked = get_tracked_data(data)
|
55
56
|
data.each do |entry_data|
|
56
|
-
|
57
|
+
tracked_entry = tracked.detect { |t| t.date == entry_data['date'] }
|
58
|
+
create_entry(entry_data, tracked_entry)
|
57
59
|
end
|
58
60
|
end
|
59
|
-
|
60
|
-
def
|
61
|
-
|
62
|
-
|
61
|
+
|
62
|
+
def get_tracked_data(data)
|
63
|
+
return [] if data.empty?
|
64
|
+
dates = data.collect { |d| d['date'] }
|
65
|
+
FreshBooks::TimeEntry.list('project_id' => project.project_id, 'task_id' => task.task_id, 'date_from' => dates.min, 'date_to' => dates.max)
|
66
|
+
end
|
67
|
+
|
68
|
+
def create_entry(entry_data, time_entry = FreshBooks::TimeEntry.new)
|
69
|
+
time_entry ||= FreshBooks::TimeEntry.new
|
70
|
+
|
63
71
|
time_entry.project_id = project.project_id
|
64
72
|
time_entry.task_id = task.task_id
|
65
73
|
time_entry.date = entry_data['date']
|
66
74
|
time_entry.hours = entry_data['hours']
|
67
75
|
time_entry.notes = entry_data['notes']
|
68
|
-
|
69
|
-
|
70
|
-
|
76
|
+
|
77
|
+
method = time_entry.time_entry_id ? :update : :create
|
78
|
+
result = time_entry.send(method)
|
79
|
+
|
71
80
|
if result
|
72
81
|
true
|
73
82
|
else
|
@@ -2,26 +2,29 @@ module Freshtrack
|
|
2
2
|
module TimeCollector
|
3
3
|
module PunchyTemplate
|
4
4
|
attr_reader :options
|
5
|
-
|
5
|
+
|
6
6
|
def initialize(options = {})
|
7
7
|
@options = options
|
8
8
|
end
|
9
|
-
|
9
|
+
|
10
10
|
def condense_time_data(time_data)
|
11
11
|
date_data = times_to_dates(time_data)
|
12
12
|
group_date_data(date_data)
|
13
13
|
end
|
14
|
-
|
14
|
+
|
15
15
|
def times_to_dates(time_data)
|
16
|
-
time_data.
|
16
|
+
time_data.collect do |td|
|
17
17
|
punch_in = td.delete('in')
|
18
18
|
punch_out = td.delete('out')
|
19
19
|
|
20
|
-
|
21
|
-
|
22
|
-
|
20
|
+
if punch_out
|
21
|
+
td['date'] = punch_in.to_date
|
22
|
+
td['hours'] = (punch_out - punch_in).secs_to_hours
|
23
|
+
td
|
24
|
+
end
|
25
|
+
end.compact
|
23
26
|
end
|
24
|
-
|
27
|
+
|
25
28
|
def group_date_data(date_data)
|
26
29
|
separator = '-' * 20
|
27
30
|
grouped = date_data.group_by { |x| x['date'] }
|
data/lib/freshtrack/version.rb
CHANGED
@@ -2,6 +2,7 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper.rb')
|
|
2
2
|
|
3
3
|
Thing = FreshBooks::BaseObject.new(:attr)
|
4
4
|
ThingDeal = FreshBooks::BaseObject.new(:attr)
|
5
|
+
OtherThing = FreshBooks::BaseObject.new(:attr, :other, :more, :stuff)
|
5
6
|
|
6
7
|
describe FreshBooks::BaseObject do
|
7
8
|
it 'should have a mapping function for Date' do
|
@@ -42,28 +43,56 @@ describe FreshBooks::BaseObject do
|
|
42
43
|
@func.call(@val).should == true
|
43
44
|
end
|
44
45
|
end
|
45
|
-
|
46
|
+
|
46
47
|
describe 'converting an instance to XML' do
|
47
48
|
before do
|
48
49
|
@thing = Thing.new
|
49
50
|
end
|
50
|
-
|
51
|
+
|
51
52
|
it 'should use the elem name' do
|
52
53
|
@thing.should.receive(:elem_name)
|
53
54
|
@thing.to_xml
|
54
55
|
end
|
56
|
+
|
57
|
+
describe 'selective attribute inclusion' do
|
58
|
+
before do
|
59
|
+
@other_thing = OtherThing.new
|
60
|
+
@other_thing.attr = 'blah'
|
61
|
+
@other_thing.other = 'something'
|
62
|
+
@other_thing.more = 'things'
|
63
|
+
@other_thing.stuff = 'this'
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'should include all attributes' do
|
67
|
+
xml = @other_thing.to_xml.to_s
|
68
|
+
xml.should.match(Regexp.new('<attr>blah</attr>'))
|
69
|
+
xml.should.match(Regexp.new('<other>something</other>'))
|
70
|
+
xml.should.match(Regexp.new('<more>things</more>'))
|
71
|
+
xml.should.match(Regexp.new('<stuff>this</stuff>'))
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'should remove attributes marked for exclusion' do
|
75
|
+
OtherThing::EXCLUDE_XML_ATTRIBUTES = %w[other stuff]
|
76
|
+
|
77
|
+
xml = @other_thing.to_xml.to_s
|
78
|
+
xml.should.match(Regexp.new('<attr>blah</attr>'))
|
79
|
+
xml.should.not.match(Regexp.new('<other>something</other>'))
|
80
|
+
xml.should.match(Regexp.new('<more>things</more>'))
|
81
|
+
xml.should.not.match(Regexp.new('<stuff>this</stuff>'))
|
82
|
+
end
|
83
|
+
end
|
55
84
|
end
|
56
|
-
|
85
|
+
|
57
86
|
describe 'getting the elem name' do
|
58
87
|
before do
|
59
88
|
@thing = Thing.new
|
60
89
|
@thing_deal = ThingDeal.new
|
61
90
|
end
|
62
|
-
|
91
|
+
|
63
92
|
it 'should be the class name, downcased' do
|
64
93
|
@thing.elem_name.should == 'thing'
|
65
94
|
end
|
66
|
-
|
95
|
+
|
67
96
|
it 'should be underscored if the class name has CamelCase' do
|
68
97
|
@thing_deal.elem_name.should == 'thing_deal'
|
69
98
|
end
|
data/spec/freshtrack_spec.rb
CHANGED
@@ -355,33 +355,35 @@ describe Freshtrack do
|
|
355
355
|
lambda { Freshtrack.collector }.should.raise(LoadError)
|
356
356
|
end
|
357
357
|
end
|
358
|
-
|
358
|
+
|
359
359
|
describe 'tracking time' do
|
360
360
|
before do
|
361
361
|
@project_name = :proj
|
362
362
|
@data = []
|
363
|
+
@tracked = []
|
363
364
|
Freshtrack.stub!(:get_data).and_return(@data)
|
365
|
+
Freshtrack.stub!(:get_tracked_data).and_return(@tracked)
|
364
366
|
end
|
365
|
-
|
367
|
+
|
366
368
|
it 'should require an argument' do
|
367
369
|
lambda { Freshtrack.track }.should.raise(ArgumentError)
|
368
370
|
end
|
369
|
-
|
371
|
+
|
370
372
|
it 'should accept an argument' do
|
371
373
|
lambda { Freshtrack.track(@project_name) }.should.not.raise(ArgumentError)
|
372
374
|
end
|
373
|
-
|
375
|
+
|
374
376
|
it 'should accept options' do
|
375
377
|
lambda { Freshtrack.track(@project_name, :before => Time.now) }.should.not.raise(ArgumentError)
|
376
378
|
end
|
377
|
-
|
379
|
+
|
378
380
|
it 'should get data for supplied project' do
|
379
381
|
Freshtrack.should.receive(:get_data).and_return(@data) do |project, _|
|
380
382
|
project.should == @project_name
|
381
383
|
end
|
382
384
|
Freshtrack.track(@project_name)
|
383
385
|
end
|
384
|
-
|
386
|
+
|
385
387
|
it 'should pass options on when getting data' do
|
386
388
|
options = { :after => Time.now - 12345 }
|
387
389
|
Freshtrack.should.receive(:get_data).and_return(@data) do |project, opts|
|
@@ -390,7 +392,7 @@ describe Freshtrack do
|
|
390
392
|
end
|
391
393
|
Freshtrack.track(@project_name, options)
|
392
394
|
end
|
393
|
-
|
395
|
+
|
394
396
|
it 'should default options to an empty hash' do
|
395
397
|
Freshtrack.should.receive(:get_data).and_return(@data) do |project, opts|
|
396
398
|
project.should == @project_name
|
@@ -398,112 +400,343 @@ describe Freshtrack do
|
|
398
400
|
end
|
399
401
|
Freshtrack.track(@project_name)
|
400
402
|
end
|
401
|
-
|
402
|
-
it 'should
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
403
|
+
|
404
|
+
it 'should get already-tracked time, passing the data along' do
|
405
|
+
Freshtrack.should.receive(:get_tracked_data).with(@data)
|
406
|
+
Freshtrack.track(@project_name)
|
407
|
+
end
|
408
|
+
|
409
|
+
it 'should create entries for project data, matching them with the already-tracked time by date' do
|
410
|
+
data = Array.new(5) { |i| { 'date' => Date.today - i*2 } }
|
411
|
+
tracked = Array.new(5) { |i| te = FreshBooks::TimeEntry.new; te.date = Date.today - i*3; te }
|
412
|
+
Freshtrack.stub!(:get_data).and_return(data)
|
413
|
+
Freshtrack.stub!(:get_tracked_data).and_return(tracked)
|
414
|
+
|
415
|
+
matches = {
|
416
|
+
data[0] => tracked[0],
|
417
|
+
data[1] => nil,
|
418
|
+
data[2] => nil,
|
419
|
+
data[3] => tracked[2],
|
420
|
+
data[4] => nil
|
421
|
+
}
|
422
|
+
|
423
|
+
matches.each do |data, entry|
|
424
|
+
Freshtrack.should.receive(:create_entry).with(data, entry)
|
407
425
|
end
|
408
426
|
Freshtrack.track(@project_name)
|
409
427
|
end
|
410
428
|
end
|
411
|
-
|
429
|
+
|
430
|
+
describe 'getting already-tracked time' do
|
431
|
+
before do
|
432
|
+
@data = [{ 'date' => Date.today - 3 }]
|
433
|
+
@tracked = []
|
434
|
+
FreshBooks::TimeEntry.stub!(:list).and_return(@tracked)
|
435
|
+
|
436
|
+
@project = mock('project', :project_id => mock('project id'))
|
437
|
+
@task = mock('task', :task_id => mock('task id'))
|
438
|
+
Freshtrack.stub!(:project).and_return(@project)
|
439
|
+
Freshtrack.stub!(:task).and_return(@task)
|
440
|
+
end
|
441
|
+
|
442
|
+
it 'should accept data' do
|
443
|
+
lambda { Freshtrack.get_tracked_data(@data) }.should.not.raise(ArgumentError)
|
444
|
+
end
|
445
|
+
|
446
|
+
it 'should require data' do
|
447
|
+
lambda { Freshtrack.get_tracked_data }.should.raise(ArgumentError)
|
448
|
+
end
|
449
|
+
|
450
|
+
it 'should get a list of time entries based upon the data' do
|
451
|
+
data = Array.new(3) { |i| { 'date' => Date.today - i*3 } } + Array.new(3) { |i| { 'date' => Date.today - 5 + i*2 } }
|
452
|
+
data_options = { 'project_id' => @project.project_id, 'task_id' => @task.task_id }
|
453
|
+
data_options['date_from'] = data.collect { |d| d['date'] }.min
|
454
|
+
data_options['date_to'] = data.collect { |d| d['date'] }.max
|
455
|
+
|
456
|
+
FreshBooks::TimeEntry.should.receive(:list).with(data_options)
|
457
|
+
Freshtrack.get_tracked_data(data)
|
458
|
+
end
|
459
|
+
|
460
|
+
it 'should return the list of time entries' do
|
461
|
+
entries = Object.new
|
462
|
+
FreshBooks::TimeEntry.stub!(:list).and_return(entries)
|
463
|
+
Freshtrack.get_tracked_data(@data).should == entries
|
464
|
+
end
|
465
|
+
|
466
|
+
it 'should not bother getting data for empty input' do
|
467
|
+
FreshBooks::TimeEntry.should.receive(:list).never
|
468
|
+
Freshtrack.get_tracked_data([])
|
469
|
+
end
|
470
|
+
|
471
|
+
it 'should return an empty list for empty input' do
|
472
|
+
Freshtrack.get_tracked_data([]).should == []
|
473
|
+
end
|
474
|
+
end
|
475
|
+
|
412
476
|
describe 'creating an entry' do
|
413
477
|
before do
|
414
478
|
@date = Date.today - 3
|
415
479
|
@hours = 5.67
|
416
480
|
@notes = 'notes for the time entry'
|
417
481
|
@entry_data = { 'date' => @date, 'hours' => @hours, 'notes' => @notes }
|
418
|
-
@time_entry =
|
482
|
+
@time_entry = FreshBooks::TimeEntry.new
|
483
|
+
@time_entry.stub!(:create)
|
484
|
+
@time_entry.stub!(:update)
|
419
485
|
FreshBooks::TimeEntry.stub!(:new).and_return(@time_entry)
|
420
|
-
|
486
|
+
|
421
487
|
@project = mock('project', :project_id => mock('project id'))
|
422
488
|
@task = mock('task', :task_id => mock('task id'))
|
423
489
|
Freshtrack.stub!(:project).and_return(@project)
|
424
490
|
Freshtrack.stub!(:task).and_return(@task)
|
425
|
-
|
491
|
+
|
426
492
|
STDERR.stub!(:puts)
|
427
493
|
end
|
428
|
-
|
494
|
+
|
429
495
|
it 'should require an argument' do
|
430
496
|
lambda { Freshtrack.create_entry }.should.raise(ArgumentError)
|
431
497
|
end
|
432
|
-
|
498
|
+
|
433
499
|
it 'should accept an argument' do
|
434
500
|
lambda { Freshtrack.create_entry(@entry_data) }.should.not.raise(ArgumentError)
|
435
501
|
end
|
436
|
-
|
437
|
-
it 'should
|
438
|
-
|
439
|
-
Freshtrack.create_entry(@entry_data)
|
502
|
+
|
503
|
+
it 'should accept a time entry argument' do
|
504
|
+
lambda { Freshtrack.create_entry(@entry_data, @time_entry) }.should.not.raise(ArgumentError)
|
440
505
|
end
|
441
|
-
|
442
|
-
describe '
|
443
|
-
it 'should
|
444
|
-
|
506
|
+
|
507
|
+
describe 'when given no time entry argument' do
|
508
|
+
it 'should instantiate a new time entry' do
|
509
|
+
FreshBooks::TimeEntry.should.receive(:new).and_return(@time_entry)
|
445
510
|
Freshtrack.create_entry(@entry_data)
|
446
511
|
end
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
512
|
+
|
513
|
+
describe 'with the time entry instance' do
|
514
|
+
it 'should set the project' do
|
515
|
+
@time_entry.should.receive(:project_id=).with(@project.project_id)
|
516
|
+
Freshtrack.create_entry(@entry_data)
|
517
|
+
end
|
518
|
+
|
519
|
+
it 'should set the task' do
|
520
|
+
@time_entry.should.receive(:task_id=).with(@task.task_id)
|
521
|
+
Freshtrack.create_entry(@entry_data)
|
522
|
+
end
|
523
|
+
|
524
|
+
it 'should set the date' do
|
525
|
+
@time_entry.should.receive(:date=).with(@date)
|
526
|
+
Freshtrack.create_entry(@entry_data)
|
527
|
+
end
|
528
|
+
|
529
|
+
it 'should set the hours' do
|
530
|
+
@time_entry.should.receive(:hours=).with(@hours)
|
531
|
+
Freshtrack.create_entry(@entry_data)
|
532
|
+
end
|
533
|
+
|
534
|
+
it 'should set the notes' do
|
535
|
+
@time_entry.should.receive(:notes=).with(@notes)
|
536
|
+
Freshtrack.create_entry(@entry_data)
|
537
|
+
end
|
451
538
|
end
|
452
|
-
|
453
|
-
it 'should
|
454
|
-
@time_entry.should.receive(:
|
539
|
+
|
540
|
+
it 'should create the time entry' do
|
541
|
+
@time_entry.should.receive(:create)
|
455
542
|
Freshtrack.create_entry(@entry_data)
|
456
543
|
end
|
457
|
-
|
458
|
-
it 'should
|
459
|
-
@time_entry.should.receive(:
|
544
|
+
|
545
|
+
it 'should not update the time entry' do
|
546
|
+
@time_entry.should.receive(:update).never
|
460
547
|
Freshtrack.create_entry(@entry_data)
|
461
548
|
end
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
549
|
+
|
550
|
+
describe 'successfully' do
|
551
|
+
before do
|
552
|
+
@time_entry.stub!(:create).and_return(5)
|
553
|
+
end
|
554
|
+
|
555
|
+
it 'should be silent' do
|
556
|
+
STDERR.should.receive(:puts).never
|
557
|
+
Freshtrack.create_entry(@entry_data)
|
558
|
+
end
|
559
|
+
|
560
|
+
it 'should return true' do
|
561
|
+
Freshtrack.create_entry(@entry_data).should == true
|
562
|
+
end
|
563
|
+
end
|
564
|
+
|
565
|
+
describe 'unsuccessfully' do
|
566
|
+
before do
|
567
|
+
@time_entry.stub!(:create).and_return(nil)
|
568
|
+
end
|
569
|
+
|
570
|
+
it 'should output an indication' do
|
571
|
+
STDERR.should.receive(:puts) do |arg|
|
572
|
+
arg.should.match(/#{@date.to_s}/)
|
573
|
+
end
|
574
|
+
Freshtrack.create_entry(@entry_data)
|
575
|
+
end
|
576
|
+
|
577
|
+
it 'should return nil' do
|
578
|
+
Freshtrack.create_entry(@entry_data).should.be.nil
|
579
|
+
end
|
466
580
|
end
|
467
581
|
end
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
describe 'successfully' do
|
475
|
-
before do
|
476
|
-
@time_entry.stub!(:create).and_return(5)
|
582
|
+
|
583
|
+
describe 'when given a nil time entry argument' do
|
584
|
+
it 'should instantiate a new time entry' do
|
585
|
+
FreshBooks::TimeEntry.should.receive(:new).and_return(@time_entry)
|
586
|
+
Freshtrack.create_entry(@entry_data, nil)
|
477
587
|
end
|
478
|
-
|
479
|
-
|
480
|
-
|
588
|
+
|
589
|
+
describe 'with the time entry instance' do
|
590
|
+
it 'should set the project' do
|
591
|
+
@time_entry.should.receive(:project_id=).with(@project.project_id)
|
592
|
+
Freshtrack.create_entry(@entry_data, nil)
|
593
|
+
end
|
594
|
+
|
595
|
+
it 'should set the task' do
|
596
|
+
@time_entry.should.receive(:task_id=).with(@task.task_id)
|
597
|
+
Freshtrack.create_entry(@entry_data, nil)
|
598
|
+
end
|
599
|
+
|
600
|
+
it 'should set the date' do
|
601
|
+
@time_entry.should.receive(:date=).with(@date)
|
602
|
+
Freshtrack.create_entry(@entry_data, nil)
|
603
|
+
end
|
604
|
+
|
605
|
+
it 'should set the hours' do
|
606
|
+
@time_entry.should.receive(:hours=).with(@hours)
|
607
|
+
Freshtrack.create_entry(@entry_data, nil)
|
608
|
+
end
|
609
|
+
|
610
|
+
it 'should set the notes' do
|
611
|
+
@time_entry.should.receive(:notes=).with(@notes)
|
612
|
+
Freshtrack.create_entry(@entry_data, nil)
|
613
|
+
end
|
614
|
+
end
|
615
|
+
|
616
|
+
it 'should create the time entry' do
|
617
|
+
@time_entry.should.receive(:create)
|
618
|
+
Freshtrack.create_entry(@entry_data, nil)
|
619
|
+
end
|
620
|
+
|
621
|
+
it 'should not update the time entry' do
|
622
|
+
@time_entry.should.receive(:update).never
|
481
623
|
Freshtrack.create_entry(@entry_data)
|
482
624
|
end
|
483
|
-
|
484
|
-
|
485
|
-
|
625
|
+
|
626
|
+
describe 'successfully' do
|
627
|
+
before do
|
628
|
+
@time_entry.stub!(:create).and_return(5)
|
629
|
+
end
|
630
|
+
|
631
|
+
it 'should be silent' do
|
632
|
+
STDERR.should.receive(:puts).never
|
633
|
+
Freshtrack.create_entry(@entry_data, nil)
|
634
|
+
end
|
635
|
+
|
636
|
+
it 'should return true' do
|
637
|
+
Freshtrack.create_entry(@entry_data, nil).should == true
|
638
|
+
end
|
639
|
+
end
|
640
|
+
|
641
|
+
describe 'unsuccessfully' do
|
642
|
+
before do
|
643
|
+
@time_entry.stub!(:create).and_return(nil)
|
644
|
+
end
|
645
|
+
|
646
|
+
it 'should output an indication' do
|
647
|
+
STDERR.should.receive(:puts) do |arg|
|
648
|
+
arg.should.match(/#{@date.to_s}/)
|
649
|
+
end
|
650
|
+
Freshtrack.create_entry(@entry_data, nil)
|
651
|
+
end
|
652
|
+
|
653
|
+
it 'should return nil' do
|
654
|
+
Freshtrack.create_entry(@entry_data, nil).should.be.nil
|
655
|
+
end
|
486
656
|
end
|
487
657
|
end
|
488
|
-
|
489
|
-
describe '
|
658
|
+
|
659
|
+
describe 'when given an existing time entry argument' do
|
490
660
|
before do
|
491
|
-
@time_entry.
|
661
|
+
@time_entry.time_entry_id = 293
|
492
662
|
end
|
493
|
-
|
494
|
-
it 'should
|
495
|
-
|
496
|
-
|
663
|
+
|
664
|
+
it 'should not instantiate a new time entry' do
|
665
|
+
FreshBooks::TimeEntry.should.receive(:new).never
|
666
|
+
Freshtrack.create_entry(@entry_data, @time_entry)
|
667
|
+
end
|
668
|
+
|
669
|
+
describe 'with the time entry instance' do
|
670
|
+
it 'should set the project' do
|
671
|
+
@time_entry.should.receive(:project_id=).with(@project.project_id)
|
672
|
+
Freshtrack.create_entry(@entry_data, @time_entry)
|
673
|
+
end
|
674
|
+
|
675
|
+
it 'should set the task' do
|
676
|
+
@time_entry.should.receive(:task_id=).with(@task.task_id)
|
677
|
+
Freshtrack.create_entry(@entry_data, @time_entry)
|
678
|
+
end
|
679
|
+
|
680
|
+
it 'should set the date' do
|
681
|
+
@time_entry.should.receive(:date=).with(@date)
|
682
|
+
Freshtrack.create_entry(@entry_data, @time_entry)
|
683
|
+
end
|
684
|
+
|
685
|
+
it 'should set the hours' do
|
686
|
+
@time_entry.should.receive(:hours=).with(@hours)
|
687
|
+
Freshtrack.create_entry(@entry_data, @time_entry)
|
688
|
+
end
|
689
|
+
|
690
|
+
it 'should set the notes' do
|
691
|
+
@time_entry.should.receive(:notes=).with(@notes)
|
692
|
+
Freshtrack.create_entry(@entry_data, @time_entry)
|
497
693
|
end
|
498
|
-
Freshtrack.create_entry(@entry_data)
|
499
694
|
end
|
500
|
-
|
501
|
-
it 'should
|
502
|
-
|
695
|
+
|
696
|
+
it 'should update the time entry' do
|
697
|
+
@time_entry.should.receive(:update)
|
698
|
+
Freshtrack.create_entry(@entry_data, @time_entry)
|
699
|
+
end
|
700
|
+
|
701
|
+
it 'should not create the time entry' do
|
702
|
+
@time_entry.should.receive(:create).never
|
703
|
+
Freshtrack.create_entry(@entry_data, @time_entry)
|
704
|
+
end
|
705
|
+
|
706
|
+
describe 'successfully' do
|
707
|
+
before do
|
708
|
+
@time_entry.stub!(:update).and_return(true)
|
709
|
+
end
|
710
|
+
|
711
|
+
it 'should be silent' do
|
712
|
+
STDERR.should.receive(:puts).never
|
713
|
+
Freshtrack.create_entry(@entry_data, @time_entry)
|
714
|
+
end
|
715
|
+
|
716
|
+
it 'should return true' do
|
717
|
+
Freshtrack.create_entry(@entry_data, @time_entry).should == true
|
718
|
+
end
|
719
|
+
end
|
720
|
+
|
721
|
+
describe 'unsuccessfully' do
|
722
|
+
before do
|
723
|
+
@time_entry.stub!(:update).and_return(false)
|
724
|
+
end
|
725
|
+
|
726
|
+
it 'should output an indication' do
|
727
|
+
STDERR.should.receive(:puts) do |arg|
|
728
|
+
arg.should.match(/#{@date.to_s}/)
|
729
|
+
end
|
730
|
+
Freshtrack.create_entry(@entry_data, @time_entry)
|
731
|
+
end
|
732
|
+
|
733
|
+
it 'should return nil' do
|
734
|
+
Freshtrack.create_entry(@entry_data, @time_entry).should.be.nil
|
735
|
+
end
|
503
736
|
end
|
504
737
|
end
|
505
738
|
end
|
506
|
-
|
739
|
+
|
507
740
|
it 'should list open invoices' do
|
508
741
|
Freshtrack.should.respond_to(:open_invoices)
|
509
742
|
end
|
@@ -178,6 +178,15 @@ describe Freshtrack::TimeCollector::OneInchPunch do
|
|
178
178
|
result = result.first
|
179
179
|
result['hours'].should == 1.5
|
180
180
|
end
|
181
|
+
|
182
|
+
it 'should ignore any time data that is not finished' do
|
183
|
+
@time_data.push({ 'in' => Time.local(2012, 1, 25, 6, 25, 0), 'out' => Time.local(2012, 1, 25, 7, 55, 0) })
|
184
|
+
@time_data.push({ 'in' => Time.local(2012, 3, 25, 6, 25, 0)})
|
185
|
+
|
186
|
+
result = @collector.times_to_dates(@time_data)
|
187
|
+
result.length.should == 1
|
188
|
+
result.first['hours'].should == 1.5
|
189
|
+
end
|
181
190
|
end
|
182
191
|
|
183
192
|
it 'should group date data' do
|
@@ -224,6 +224,15 @@ describe Freshtrack::TimeCollector::Punch do
|
|
224
224
|
result = result.first
|
225
225
|
result['hours'].should == 1.5
|
226
226
|
end
|
227
|
+
|
228
|
+
it 'should ignore any time data that is not finished' do
|
229
|
+
@time_data.push({ 'in' => Time.local(2012, 1, 25, 6, 25, 0), 'out' => Time.local(2012, 1, 25, 7, 55, 0) })
|
230
|
+
@time_data.push({ 'in' => Time.local(2012, 3, 25, 6, 25, 0)})
|
231
|
+
|
232
|
+
result = @collector.times_to_dates(@time_data)
|
233
|
+
result.length.should == 1
|
234
|
+
result.first['hours'].should == 1.5
|
235
|
+
end
|
227
236
|
end
|
228
237
|
|
229
238
|
it 'should group date data' do
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: freshtrack
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 3
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
8
|
+
- 7
|
9
9
|
- 0
|
10
|
-
version: 0.
|
10
|
+
version: 0.7.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Yossef Mendelssohn
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2012-
|
18
|
+
date: 2012-04-11 00:00:00 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
name: bacon
|