harvested 0.3.3 → 0.4.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.
Files changed (109) hide show
  1. data/Gemfile +15 -0
  2. data/HISTORY +11 -0
  3. data/README.md +14 -6
  4. data/Rakefile +13 -31
  5. data/TODO +2 -0
  6. data/VERSION +1 -1
  7. data/examples/user_assignments.rb +1 -1
  8. data/harvested.gemspec +114 -126
  9. data/lib/ext/array.rb +52 -0
  10. data/lib/ext/date.rb +9 -0
  11. data/lib/ext/hash.rb +17 -0
  12. data/lib/ext/time.rb +5 -0
  13. data/lib/harvest/api/account.rb +8 -1
  14. data/lib/harvest/api/base.rb +32 -10
  15. data/lib/harvest/api/contacts.rb +1 -1
  16. data/lib/harvest/api/expenses.rb +3 -4
  17. data/lib/harvest/api/invoice_categories.rb +26 -0
  18. data/lib/harvest/api/invoices.rb +16 -0
  19. data/lib/harvest/api/projects.rb +2 -10
  20. data/lib/harvest/api/reports.rb +13 -16
  21. data/lib/harvest/api/task_assignments.rb +8 -6
  22. data/lib/harvest/api/tasks.rb +1 -1
  23. data/lib/harvest/api/time.rb +13 -13
  24. data/lib/harvest/api/user_assignments.rb +7 -5
  25. data/lib/harvest/base.rb +9 -1
  26. data/lib/harvest/behavior/activatable.rb +2 -2
  27. data/lib/harvest/behavior/crud.rb +15 -13
  28. data/lib/harvest/client.rb +18 -13
  29. data/lib/harvest/contact.rb +13 -11
  30. data/lib/harvest/errors.rb +6 -4
  31. data/lib/harvest/expense.rb +40 -14
  32. data/lib/harvest/expense_category.rb +10 -9
  33. data/lib/harvest/hardy_client.rb +1 -1
  34. data/lib/harvest/invoice.rb +103 -0
  35. data/lib/harvest/invoice_category.rb +18 -0
  36. data/lib/harvest/line_item.rb +12 -0
  37. data/lib/harvest/model.rb +120 -0
  38. data/lib/harvest/project.rb +55 -26
  39. data/lib/harvest/rate_limit_status.rb +9 -8
  40. data/lib/harvest/task.rb +17 -14
  41. data/lib/harvest/task_assignment.rb +27 -22
  42. data/lib/harvest/time_entry.rb +32 -30
  43. data/lib/harvest/user.rb +46 -22
  44. data/lib/harvest/user_assignment.rb +24 -17
  45. data/lib/harvested.rb +12 -5
  46. data/spec/functional/account_spec.rb +17 -0
  47. data/spec/functional/clients_spec.rb +58 -0
  48. data/spec/functional/errors_spec.rb +22 -0
  49. data/spec/functional/expenses_spec.rb +84 -0
  50. data/spec/functional/hardy_client_spec.rb +33 -0
  51. data/spec/functional/invoice_spec.rb +67 -0
  52. data/spec/functional/project_spec.rb +50 -0
  53. data/spec/functional/reporting_spec.rb +80 -0
  54. data/spec/functional/tasks_spec.rb +88 -0
  55. data/spec/functional/time_tracking_spec.rb +53 -0
  56. data/spec/functional/users_spec.rb +102 -0
  57. data/spec/harvest/base_spec.rb +1 -1
  58. data/spec/harvest/credentials_spec.rb +1 -1
  59. data/spec/harvest/expense_category_spec.rb +5 -0
  60. data/spec/harvest/expense_spec.rb +8 -5
  61. data/spec/harvest/invoice_spec.rb +47 -0
  62. data/spec/harvest/project_spec.rb +11 -0
  63. data/spec/harvest/task_assignment_spec.rb +4 -4
  64. data/spec/harvest/task_spec.rb +7 -0
  65. data/spec/harvest/time_entry_spec.rb +11 -10
  66. data/spec/harvest/user_assignment_spec.rb +3 -3
  67. data/spec/harvest/user_spec.rb +3 -1
  68. data/spec/spec_helper.rb +37 -6
  69. data/{features → spec}/support/harvest_credentials.example.yml +0 -1
  70. data/spec/support/harvested_helpers.rb +44 -0
  71. data/spec/support/json_examples.rb +11 -0
  72. data/spec/test_rubies +5 -0
  73. metadata +109 -85
  74. data/.gitignore +0 -28
  75. data/features/account.feature +0 -7
  76. data/features/client_contacts.feature +0 -23
  77. data/features/clients.feature +0 -29
  78. data/features/errors.feature +0 -25
  79. data/features/expense_categories.feature +0 -21
  80. data/features/expenses.feature +0 -55
  81. data/features/hardy_client.feature +0 -40
  82. data/features/projects.feature +0 -39
  83. data/features/reporting.feature +0 -72
  84. data/features/step_definitions/account_steps.rb +0 -7
  85. data/features/step_definitions/assignment_steps.rb +0 -100
  86. data/features/step_definitions/contact_steps.rb +0 -11
  87. data/features/step_definitions/debug_steps.rb +0 -3
  88. data/features/step_definitions/error_steps.rb +0 -113
  89. data/features/step_definitions/expenses_steps.rb +0 -46
  90. data/features/step_definitions/harvest_steps.rb +0 -8
  91. data/features/step_definitions/model_steps.rb +0 -90
  92. data/features/step_definitions/people_steps.rb +0 -4
  93. data/features/step_definitions/report_steps.rb +0 -91
  94. data/features/step_definitions/time_entry_steps.rb +0 -40
  95. data/features/support/env.rb +0 -37
  96. data/features/support/error_helpers.rb +0 -18
  97. data/features/support/fixtures/empty_clients.xml +0 -2
  98. data/features/support/fixtures/over_limit.xml +0 -8
  99. data/features/support/fixtures/receipt.png +0 -0
  100. data/features/support/fixtures/under_limit.xml +0 -8
  101. data/features/support/harvest_helpers.rb +0 -11
  102. data/features/support/inflections.rb +0 -9
  103. data/features/task_assignment.feature +0 -69
  104. data/features/tasks.feature +0 -25
  105. data/features/time_tracking.feature +0 -29
  106. data/features/user_assignments.feature +0 -33
  107. data/features/users.feature +0 -55
  108. data/lib/harvest/base_model.rb +0 -73
  109. data/spec/spec.default.opts +0 -1
@@ -1,8 +1,15 @@
1
- require 'happymapper'
2
1
  require 'httparty'
3
2
  require 'base64'
4
- require 'builder'
5
3
  require 'delegate'
4
+ require 'hashie'
5
+ require 'json'
6
+ require 'time'
7
+ require 'csv'
8
+
9
+ require 'ext/array'
10
+ require 'ext/hash'
11
+ require 'ext/date'
12
+ require 'ext/time'
6
13
 
7
14
  require 'harvest/credentials'
8
15
  require 'harvest/errors'
@@ -12,11 +19,11 @@ require 'harvest/timezones'
12
19
  require 'harvest/base'
13
20
 
14
21
  %w(crud activatable).each {|a| require "harvest/behavior/#{a}"}
15
- %w(base_model client contact project task user rate_limit_status task_assignment user_assignment expense_category expense time_entry).each {|a| require "harvest/#{a}"}
16
- %w(base account clients contacts projects tasks users task_assignments user_assignments expense_categories expenses time reports).each {|a| require "harvest/api/#{a}"}
22
+ %w(model client contact project task user rate_limit_status task_assignment user_assignment expense_category expense time_entry invoice_category line_item invoice).each {|a| require "harvest/#{a}"}
23
+ %w(base account clients contacts projects tasks users task_assignments user_assignments expense_categories expenses time reports invoice_categories invoices).each {|a| require "harvest/api/#{a}"}
17
24
 
18
25
  module Harvest
19
- VERSION = "0.3.1".freeze
26
+ VERSION = File.read(File.expand_path(File.join(File.dirname(__FILE__), '..', 'VERSION'))).strip
20
27
 
21
28
  class << self
22
29
 
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'account information' do
4
+ it 'returns the rate limit when requested' do
5
+ cassette('account1') do
6
+ status = harvest.account.rate_limit_status
7
+ status.max_calls.should == 100
8
+ end
9
+ end
10
+
11
+ it 'returns the result of whoami' do
12
+ cassette('account2') do
13
+ user = harvest.account.who_am_i
14
+ user.email.should == credentials["username"]
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,58 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'harvest clients' do
4
+ it 'allows adding, updating and removing clients' do
5
+ cassette("client") do
6
+ client = harvest.clients.create(
7
+ "name" => "Joe's Steam Cleaning",
8
+ "details" => "Building API Widgets across the country"
9
+ )
10
+ client.name.should == "Joe's Steam Cleaning"
11
+
12
+ client.name = "Joe and Frank's Steam Cleaning"
13
+ client = harvest.clients.update(client)
14
+ client.name.should == "Joe and Frank's Steam Cleaning"
15
+
16
+ harvest.clients.delete(client)
17
+ harvest.clients.all.select {|p| p.name == "Joe and Frank's Steam Cleaning"}.should == []
18
+ end
19
+ end
20
+
21
+ it 'allows activating and deactivating clients' do
22
+ cassette("client2") do
23
+ client = harvest.clients.create(
24
+ "name" => "Joe's Steam Cleaning",
25
+ "details" => "Building API Widgets across the country"
26
+ )
27
+ client.should be_active
28
+
29
+ client = harvest.clients.deactivate(client)
30
+ client.should_not be_active
31
+
32
+ client = harvest.clients.activate(client)
33
+ client.should be_active
34
+ end
35
+ end
36
+
37
+ context "contacts" do
38
+ it "allows adding, updating, and removing contacts" do
39
+ cassette("client3") do
40
+ client = harvest.clients.create(
41
+ "name" => "Joe's Steam Cleaning",
42
+ "details" => "Building API Widgets across the country"
43
+ )
44
+ contact = harvest.contacts.create(
45
+ "client_id" => client.id,
46
+ "email" => "jane@doe.com",
47
+ "first_name" => "Jane",
48
+ "last_name" => "Doe"
49
+ )
50
+ contact.client_id.should == client.id
51
+ contact.email.should == "jane@doe.com"
52
+
53
+ harvest.contacts.delete(contact)
54
+ harvest.contacts.all.select {|e| e.email == "jane@doe.com" }.should == []
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'harvest errors' do
4
+ before { WebMock.disable_net_connect! }
5
+
6
+ it "wraps errors" do
7
+ stub_request(:get, /\/clients/).to_return({:status => ['400', 'Bad Request']}, {:body => "[]", :status => 200})
8
+ expect { harvest.clients.all }.to raise_error(Harvest::BadRequest)
9
+
10
+ stub_request(:get, /\/clients/).to_return({:status => ['404', 'Not Found']}, {:body => "[]", :status => 200})
11
+ expect { harvest.clients.all }.to raise_error(Harvest::NotFound)
12
+
13
+ stub_request(:get, /\/clients/).to_return({:status => ['500', 'Server Error']}, {:body => "[]", :status => 200})
14
+ expect { harvest.clients.all }.to raise_error(Harvest::ServerError)
15
+
16
+ stub_request(:get, /\/clients/).to_return({:status => ['502', 'Bad Gateway']}, {:body => "[]", :status => 200})
17
+ expect { harvest.clients.all }.to raise_error(Harvest::Unavailable)
18
+
19
+ stub_request(:get, /\/clients/).to_return({:status => ['503', 'Rate Limited']}, {:body => "[]", :status => 200})
20
+ expect { harvest.clients.all }.to raise_error(Harvest::RateLimited)
21
+ end
22
+ end
@@ -0,0 +1,84 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'harvest expenses' do
4
+ context 'categories' do
5
+ it 'allows adding, updating and removing categories' do
6
+ cassette("expense_category1") do
7
+ category = harvest.expense_categories.create(
8
+ "name" => "Mileage",
9
+ "unit_price" => 100,
10
+ "unit_name" => "mile"
11
+ )
12
+ category.name.should == "Mileage"
13
+
14
+ category.name = "Travel"
15
+ category = harvest.expense_categories.update(category)
16
+ category.name.should == "Travel"
17
+
18
+ harvest.expense_categories.delete(category)
19
+ harvest.expense_categories.all.select{|e| e.name == "Travel" }.should == []
20
+ end
21
+ end
22
+ end
23
+
24
+
25
+ it "allows adding, updating, and removing expenses" do
26
+ cassette("expenses2") do
27
+ category = harvest.expense_categories.create(
28
+ "name" => "Something Deductible",
29
+ "unit_price" => 100,
30
+ "unit_name" => "deduction"
31
+ )
32
+
33
+ client = harvest.clients.create(
34
+ "name" => "Tom's Butcher",
35
+ "details" => "Building API Widgets across the country"
36
+ )
37
+
38
+ project = harvest.projects.create(
39
+ "name" => "Expensing Project",
40
+ "active" => true,
41
+ "notes" => "project to test the api",
42
+ "client_id" => client.id
43
+ )
44
+
45
+ expense = harvest.expenses.create(
46
+ "notes" => "Drive to Chicago",
47
+ "total_cost" => 75.0,
48
+ "spent_at" => Time.utc(2009, 12, 28),
49
+ "expense_category_id" => category.id,
50
+ "project_id" => project.id
51
+ )
52
+ expense.notes.should == "Drive to Chicago"
53
+
54
+ expense.notes = "Off to Chicago"
55
+ expense = harvest.expenses.update(expense)
56
+ expense.notes.should == "Off to Chicago"
57
+
58
+ harvest.expenses.delete(expense)
59
+ harvest.expenses.all(Time.utc(2009, 12, 28)).select {|e| e.notes == "Off to Chicago"}.should == []
60
+ end
61
+ end
62
+ end
63
+
64
+ # Copied from feature
65
+ # @wip
66
+ # Scenario: Attaching a receipt to an Expense
67
+ # Given I am using the credentials from "./support/harvest_credentials.yml"
68
+ # When I create an expense category with the following:
69
+ # | name | Mileage |
70
+ # | unit_name | Miles |
71
+ # | unit_price | 0.485 |
72
+ # Then there should be an expense category "Mileage"
73
+ # When I create a client with the following:
74
+ # | name | Expense Client |
75
+ # When I create a project for the client "Expense Client" with the following:
76
+ # | name | Test Project |
77
+ # When I create an expense for the project "Test Project" with the category "Mileage" with the following:
78
+ # | notes | Drive to Chicago |
79
+ # | total_cost | 75.00 |
80
+ # | spent_at | 12/28/2009 |
81
+ # Then there should be an expense "Drive to Chicago" on "12/28/2009"
82
+ # When I attach the receipt "./support/fixtures/receipt.png" to the expense "Drive to Chicago" on "12/28/2009"
83
+ # Then there should be a receipt "./support/fixtures/receipt.png" attached to the expense "Drive to Chicago" on "12/28/2009"
84
+
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'harvest hardy client' do
4
+ before do
5
+ WebMock.disable_net_connect!
6
+ @time = Time.now
7
+ end
8
+
9
+ it "waits the alloted time out when over rate limit" do
10
+ stub_request(:get, /\/clients/).to_return({:status => ['503', 'Rate Limited'], :headers => {"Retry-After" => "5"}}, {:body => "[]", :status => 200})
11
+ hardy_harvest.clients.all
12
+ Time.now.should be_within(10).of(@time)
13
+ end
14
+
15
+ it "waits a default time when over the rate limit" do
16
+ stub_request(:get, /\/clients/).to_return({:status => ['503', 'Rate Limited']}, {:body => "[]", :status => 200})
17
+ hardy_harvest.clients.all
18
+ Time.now.should be_within(20).of(@time)
19
+ end
20
+
21
+ it "retries after known errors" do
22
+ stub_request(:get, /\/rate_limit_status/).to_return({:body => '{"lockout_seconds":2,"last_access_at":"2011-06-18T17:13:22+00:00","max_calls":100,"count":1,"timeframe_limit":15}'})
23
+
24
+ stub_request(:get, /\/clients/).to_return({:status => ['502', 'Bad Gateway']}).times(2).then.to_return({:body => "[]", :status => 200})
25
+ hardy_harvest.clients.all.should == []
26
+
27
+ stub_request(:get, /\/clients/).to_raise(Net::HTTPError.new("custom error", "")).times(2).then.to_return({:body => "[]", :status => 200})
28
+ hardy_harvest.clients.all.should == []
29
+
30
+ stub_request(:get, /\/clients/).to_return({:status => ['502', 'Bad Gateway']}).times(6).then.to_return({:body => "[]", :status => 200})
31
+ expect { hardy_harvest.clients.all }.to raise_error(Harvest::Unavailable)
32
+ end
33
+ end
@@ -0,0 +1,67 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'harvest invoices' do
4
+ it 'allows adding, updating and removing categories' do
5
+ cassette('invoice1') do
6
+ cat = harvest.invoice_categories.create("name" => "New Category")
7
+ cat.name.should == "New Category"
8
+
9
+ cat.name = "Updated Category"
10
+ cat = harvest.invoice_categories.update(cat)
11
+ cat.name.should == "Updated Category"
12
+
13
+ harvest.invoice_categories.delete(cat)
14
+ harvest.invoice_categories.all.select {|c| c.name == "Updated Category" }.should == []
15
+ end
16
+ end
17
+
18
+ it 'allows adding, updating and removing invoices' do
19
+ pending "having trouble following the API docs at http://www.getharvest.com/api/invoices"
20
+
21
+ cassette('invoice2') do
22
+ client = harvest.clients.create("name" => "Frannie's Factory")
23
+ project = harvest.projects.create("name" => "Invoiced Project1", "client_id" => client.id)
24
+
25
+ # invoice = harvest.invoices.create(
26
+ invoice = Harvest::Invoice.new(
27
+ "subject" => "Invoice for Frannie's Widgets",
28
+ "client_id" => client.id,
29
+ "issued_at" => "2011-03-31",
30
+ "due_at" => "2011-03-31",
31
+ "due_at_human_format" => "upon receipt",
32
+
33
+ "currency" => "United States Dollars - USD",
34
+ "number" => 1000,
35
+ "notes" => "Some notes go here",
36
+ "period_end" => "2011-03-31",
37
+ "period_start" => "2011-02-26",
38
+ "kind" => "free_form"
39
+ # "state" => "draft",
40
+ # "purchase_order" => nil,
41
+ # "tax" => nil,
42
+ # "tax2" => nil,
43
+ # "kind" => "free_form",
44
+ # "import_hours" => "no",
45
+ # "import_expenses" => "no"
46
+ # "line_items" => [Harvest::LineItem.new("kind" => "Service", "description" => "One item", "quantity" => 200, "unit_price" => "12.00")]
47
+ )
48
+ p invoice.to_json
49
+ invoice = harvest.invoices.create(invoice)
50
+
51
+ invoice.subject.should == "Invoice for Frannie's Widgets"
52
+ invoice.amount.should == "2400.0"
53
+ invoice.line_items.size.should == 1
54
+
55
+ invoice.subject = "Updated Invoice for Frannie"
56
+ invoice.line_items << Harvest::LineItem.new("kind" => "Service", "description" => "Two item", "quantity" => 10, "unit_price" => "2.00", "amount" => "20.0")
57
+
58
+ invoice = harvest.invoices.update(invoice)
59
+ invoice.subject.should == "Updated Invoice for Frannie"
60
+ invoice.amount.should == "2420.0"
61
+ invoice.line_items.size.should == 2
62
+
63
+ harvest.invoices.delete(invoice)
64
+ harvest.invoices.all.select {|p| p.number == "1000"}.should == []
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'harvest projects' do
4
+ it 'allows adding, updating and removing projects' do
5
+ cassette('project1') do
6
+ client = harvest.clients.create(
7
+ "name" => "Joe's Steam Cleaning",
8
+ "details" => "Building API Widgets across the country"
9
+ )
10
+
11
+ project = harvest.projects.create(
12
+ "name" => "Test Project",
13
+ "active" => true,
14
+ "notes" => "project to test the api",
15
+ "client_id" => client.id
16
+ )
17
+ project.name.should == "Test Project"
18
+
19
+ project.name = "Updated Project"
20
+ project = harvest.projects.update(project)
21
+ project.name.should == "Updated Project"
22
+
23
+ harvest.projects.delete(project)
24
+ harvest.projects.all.select {|p| p.name == "Updated Project"}.should == []
25
+ end
26
+ end
27
+
28
+ it 'allows activating and deactivating clients' do
29
+ cassette('project2') do
30
+ client = harvest.clients.create(
31
+ "name" => "Joe's Steam Cleaning",
32
+ "details" => "Building API Widgets across the country"
33
+ )
34
+
35
+ project = harvest.projects.create(
36
+ "name" => "Test Project",
37
+ "active" => true,
38
+ "notes" => "project to test the api",
39
+ "client_id" => client.id
40
+ )
41
+ project.should be_active
42
+
43
+ project = harvest.projects.deactivate(project)
44
+ project.should_not be_active
45
+
46
+ project = harvest.projects.activate(project)
47
+ project.should be_active
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,80 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'harvest reporting' do
4
+ it 'allows project and people entry reporting' do
5
+ cassette("reports1") do
6
+ user = harvest.users.create(
7
+ "email" => "jane@example.com",
8
+ "first_name" => "Jane",
9
+ "last_name" => "Doe",
10
+ "password" => "secure"
11
+ )
12
+ client = harvest.clients.create("name" => "Tim's Dry Cleaning")
13
+ project1 = harvest.projects.create("name" => "Reporting Project1", "client_id" => client.id)
14
+ project2 = harvest.projects.create("name" => "Reporting Project2", "client_id" => client.id)
15
+ task1 = harvest.tasks.create("name" => "A billable task", "default_hourly_rate" => 120, "billable_by_default" => true)
16
+ task2 = harvest.tasks.create("name" => "A non billable task", "billable_by_default" => false)
17
+
18
+ harvest.task_assignments.create("project" => project1, "task" => task1)
19
+ harvest.task_assignments.create("project" => project2, "task" => task2)
20
+ harvest.user_assignments.create("project" => project1, "user" => user)
21
+ harvest.user_assignments.create("project" => project2, "user" => user)
22
+
23
+ entry1 = harvest.time.create("notes" => "billable entry1", "hours" => 3, "spent_at" => "12/28/2009", "task_id" => task1.id, "project_id" => project1.id, "of_user" => user.id)
24
+ entry2 = harvest.time.create("notes" => "non billable entry2", "hours" => 6, "spent_at" => "12/28/2009", "task_id" => task2.id, "project_id" => project2.id, "of_user" => user.id)
25
+
26
+ harvest.reports.time_by_project(project1, Time.utc(2009, 12, 20), Time.utc(2009,12,30)).first.should == entry1
27
+
28
+ harvest.reports.time_by_project(project1, Time.utc(2009, 12, 20), Time.utc(2009,12,30), :user => user).first.should == entry1
29
+
30
+ harvest.reports.time_by_project(project2, Time.utc(2009, 12, 20), Time.utc(2009,12,30), :user => user).first.should == entry2
31
+
32
+ harvest.reports.time_by_project(project2, Time.utc(2009, 12, 20), Time.utc(2009,12,30), :billable => true).should == []
33
+ harvest.reports.time_by_project(project2, Time.utc(2009, 12, 20), Time.utc(2009,12,30), :billable => false).first.should == entry2
34
+
35
+ harvest.reports.time_by_user(user, Time.utc(2009, 12, 20), Time.utc(2009,12,30)).map(&:id).should == [entry1, entry2].map(&:id)
36
+
37
+ harvest.reports.time_by_user(user, Time.utc(2009, 12, 20), Time.utc(2009,12,30), :project => project1).first.should == entry1
38
+
39
+ harvest.reports.time_by_user(user, Time.utc(2009, 12, 20), Time.utc(2009,12,30), :project => project2).first.should == entry2
40
+
41
+ harvest.reports.time_by_user(user, Time.utc(2009, 12, 20), Time.utc(2009,12,30), :billable => true).first.should == entry1
42
+ harvest.reports.time_by_user(user, Time.utc(2009, 12, 20), Time.utc(2009,12,30), :billable => false).first.should == entry2
43
+
44
+ client2 = harvest.clients.create("name" => "Phil's Sandwich Shop")
45
+ harvest.reports.projects_by_client(client).map(&:id).to_set.should == [project1, project2].map(&:id).to_set
46
+ harvest.reports.projects_by_client(client2).should == []
47
+ end
48
+ end
49
+
50
+ it 'allows expense reporting' do
51
+ cassette("reports2") do
52
+ user = harvest.users.create(
53
+ "email" => "simon@example.com",
54
+ "first_name" => "Simon",
55
+ "last_name" => "Stir",
56
+ "password" => "secure"
57
+ )
58
+
59
+ category = harvest.expense_categories.create("name" => "Reporting category", "unit_price" => 100, "unit_name" => "deduction")
60
+ client = harvest.clients.create("name" => "Philip's Butcher")
61
+ project = harvest.projects.create("name" => "Expense Reporting Project", "client_id" => client.id)
62
+ harvest.user_assignments.create("project" => project, "user" => user)
63
+
64
+ expense = harvest.expenses.create(
65
+ "notes" => "Drive to Chicago",
66
+ "total_cost" => 75.0,
67
+ "spent_at" => Time.utc(2009, 12, 28),
68
+ "expense_category_id" => category.id,
69
+ "project_id" => project.id,
70
+ "user_id" => user.id
71
+ )
72
+
73
+ harvest.reports.expenses_by_user(user, Time.utc(2009, 12, 20), Time.utc(2009,12,30)).first.should == expense
74
+
75
+ my_user = harvest.users.all.detect {|u| u.email == credentials["username"]}
76
+ my_user.should_not be_nil
77
+ harvest.reports.expenses_by_user(my_user, Time.utc(2009, 12, 20), Time.utc(2009,12,30)).should == []
78
+ end
79
+ end
80
+ end