harvested 0.3.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 (87) hide show
  1. data/.gitignore +23 -0
  2. data/HISTORY +16 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +67 -0
  5. data/Rakefile +52 -0
  6. data/VERSION +1 -0
  7. data/examples/basics.rb +35 -0
  8. data/examples/clear_account.rb +28 -0
  9. data/examples/task_assignments.rb +27 -0
  10. data/examples/user_assignments.rb +24 -0
  11. data/features/account.feature +7 -0
  12. data/features/client_contacts.feature +23 -0
  13. data/features/clients.feature +29 -0
  14. data/features/errors.feature +25 -0
  15. data/features/expense_categories.feature +21 -0
  16. data/features/expenses.feature +55 -0
  17. data/features/hardy_client.feature +40 -0
  18. data/features/projects.feature +39 -0
  19. data/features/reporting.feature +72 -0
  20. data/features/step_definitions/account_steps.rb +7 -0
  21. data/features/step_definitions/assignment_steps.rb +100 -0
  22. data/features/step_definitions/contact_steps.rb +11 -0
  23. data/features/step_definitions/debug_steps.rb +3 -0
  24. data/features/step_definitions/error_steps.rb +113 -0
  25. data/features/step_definitions/expenses_steps.rb +46 -0
  26. data/features/step_definitions/harvest_steps.rb +8 -0
  27. data/features/step_definitions/model_steps.rb +90 -0
  28. data/features/step_definitions/people_steps.rb +4 -0
  29. data/features/step_definitions/report_steps.rb +91 -0
  30. data/features/step_definitions/time_entry_steps.rb +40 -0
  31. data/features/support/env.rb +37 -0
  32. data/features/support/error_helpers.rb +18 -0
  33. data/features/support/fixtures/empty_clients.xml +2 -0
  34. data/features/support/fixtures/over_limit.xml +8 -0
  35. data/features/support/fixtures/receipt.png +0 -0
  36. data/features/support/fixtures/under_limit.xml +8 -0
  37. data/features/support/harvest_credentials.example.yml +4 -0
  38. data/features/support/harvest_helpers.rb +11 -0
  39. data/features/support/inflections.rb +9 -0
  40. data/features/task_assignment.feature +69 -0
  41. data/features/tasks.feature +25 -0
  42. data/features/time_tracking.feature +29 -0
  43. data/features/user_assignments.feature +33 -0
  44. data/features/users.feature +55 -0
  45. data/lib/harvest/api/account.rb +10 -0
  46. data/lib/harvest/api/base.rb +42 -0
  47. data/lib/harvest/api/clients.rb +10 -0
  48. data/lib/harvest/api/contacts.rb +19 -0
  49. data/lib/harvest/api/expense_categories.rb +9 -0
  50. data/lib/harvest/api/expenses.rb +28 -0
  51. data/lib/harvest/api/projects.rb +39 -0
  52. data/lib/harvest/api/reports.rb +39 -0
  53. data/lib/harvest/api/task_assignments.rb +32 -0
  54. data/lib/harvest/api/tasks.rb +9 -0
  55. data/lib/harvest/api/time.rb +32 -0
  56. data/lib/harvest/api/user_assignments.rb +32 -0
  57. data/lib/harvest/api/users.rb +15 -0
  58. data/lib/harvest/base.rb +59 -0
  59. data/lib/harvest/base_model.rb +34 -0
  60. data/lib/harvest/behavior/activatable.rb +21 -0
  61. data/lib/harvest/behavior/crud.rb +31 -0
  62. data/lib/harvest/client.rb +18 -0
  63. data/lib/harvest/contact.rb +16 -0
  64. data/lib/harvest/credentials.rb +21 -0
  65. data/lib/harvest/errors.rb +23 -0
  66. data/lib/harvest/expense.rb +19 -0
  67. data/lib/harvest/expense_category.rb +18 -0
  68. data/lib/harvest/hardy_client.rb +80 -0
  69. data/lib/harvest/project.rb +22 -0
  70. data/lib/harvest/rate_limit_status.rb +16 -0
  71. data/lib/harvest/task.rb +20 -0
  72. data/lib/harvest/task_assignment.rb +34 -0
  73. data/lib/harvest/time_entry.rb +41 -0
  74. data/lib/harvest/timezones.rb +150 -0
  75. data/lib/harvest/user.rb +40 -0
  76. data/lib/harvest/user_assignment.rb +34 -0
  77. data/lib/harvested.rb +31 -0
  78. data/spec/harvest/base_spec.rb +9 -0
  79. data/spec/harvest/credentials_spec.rb +22 -0
  80. data/spec/harvest/expense_spec.rb +15 -0
  81. data/spec/harvest/task_assignment_spec.rb +10 -0
  82. data/spec/harvest/time_entry_spec.rb +22 -0
  83. data/spec/harvest/user_assignment_spec.rb +10 -0
  84. data/spec/harvest/user_spec.rb +32 -0
  85. data/spec/spec.default.opts +1 -0
  86. data/spec/spec_helper.rb +10 -0
  87. metadata +243 -0
@@ -0,0 +1,40 @@
1
+ @disconnected
2
+ Feature: Hardy Client
3
+
4
+ Background:
5
+ Given I am using the credentials from "./support/harvest_credentials.yml"
6
+ And the rate limit status indicates I'm under my limit
7
+
8
+ Scenario: Hardy Client waiting for Rate Limit resets
9
+ Given the next request will receive a rate limit response with a refresh in 5 seconds
10
+ When I make a request with the hardy client
11
+ Then the hardy client should wait 5 seconds for the rate limit to reset
12
+ And I should be able to make a request again
13
+
14
+ Scenario: Hardy Client waiting for Rate Limit resets
15
+ Given the next request will receive a rate limit response without a refresh set
16
+ When I make a request with the hardy client
17
+ Then the hardy client should wait 16 seconds for the rate limit to reset
18
+ And I should be able to make a request again
19
+
20
+ Scenario: Hardy Client retrying Bad Gateway responses
21
+ Given the next 2 requests will receive a bad gateway response
22
+ When I make a request with the hardy client
23
+ Then no errors should be raised
24
+
25
+ Scenario: Hardy Client retrying HTTP Errors
26
+ Given the next 2 requests will receive an HTTP Error
27
+ When I make a request with the hardy client
28
+ Then no errors should be raised
29
+
30
+ Scenario: Hardy Client stop retrying Bad Gateway responses
31
+ Given the next 6 requests will receive a bad gateway response
32
+ When I make a request with the hardy client
33
+ Then a 502 error should be raised
34
+
35
+ Scenario: Check rate limit before retrying bad gateway requests
36
+ Given the next request will receive a bad gateway response
37
+ And the rate limit status indicates I'm over my limit
38
+ When I make a request with the hardy client
39
+ Then the hardy client should wait 16 seconds for the rate limit to reset
40
+ And I should be able to make a request again
@@ -0,0 +1,39 @@
1
+ @clean
2
+ Feature: Managing Projects
3
+
4
+ Scenario: Adding, Updating, and Removing a Project
5
+ Given I am using the credentials from "./support/harvest_credentials.yml"
6
+ When I create a client with the following:
7
+ | name | Client Projects |
8
+ | details | Building API Widgets across the country |
9
+ When I create a project for the client "Client Projects" with the following:
10
+ | name | Test Project |
11
+ | active | true |
12
+ | notes | project to test the api |
13
+ Then there should be a project "Test Project"
14
+ When I update the project "Test Project" with the following:
15
+ | name | Updated Project |
16
+ | code | new-code |
17
+ | bill_by | Project |
18
+ | hourly_rate | 150 |
19
+ Then the project "Updated Project" should have the following attributes:
20
+ | code | new-code |
21
+ | bill_by | Project |
22
+ | hourly_rate | 150.0 |
23
+ When I delete the project "Updated Project"
24
+ Then there should not be a project "Updated Project"
25
+
26
+ Scenario: Activating and Deactivating a Project
27
+ Given I am using the credentials from "./support/harvest_credentials.yml"
28
+ And I create a client with the following:
29
+ | name | Client Projects |
30
+ | details | Building API Widgets across the country |
31
+ And I create a project for the client "Client Projects" with the following:
32
+ | name | Test Project |
33
+ | active | true |
34
+ | notes | project to test the api |
35
+ Then the project "Test Project" should be activated
36
+ When I deactivate the project "Test Project"
37
+ Then the project "Test Project" should be deactivated
38
+ When I activate the project "Test Project"
39
+ Then the project "Test Project" should be activated
@@ -0,0 +1,72 @@
1
+ @clean
2
+ Feature: Harvest Reporting
3
+
4
+ Background:
5
+ Given I am using the credentials from "./support/harvest_credentials.yml"
6
+ And I create a client with the following:
7
+ | name | Report Client |
8
+ | details | Client to run reports |
9
+ And I create a project for the client "Report Client" with the following:
10
+ | name | Report Project1 |
11
+ And I create a project for the client "Report Client" with the following:
12
+ | name | Report Project2 |
13
+
14
+ Scenario: Time Entry Reporting
15
+ Given I create a task with the following:
16
+ | name | Report Task |
17
+ | billable | true |
18
+ | hourly_rate | 120 |
19
+ And I assign the task "Report Task" to the project "Report Project1"
20
+ And I assign the task "Report Task" to the project "Report Project2"
21
+ And I create a time entry for the project "Report Project1" and the task "Report Task" with the following:
22
+ | notes | project1 time |
23
+ | hours | 2.0 |
24
+ | spent_at | 12/28/2009 |
25
+ And I create a time entry for the project "Report Project2" and the task "Report Task" with the following:
26
+ | notes | project2 time |
27
+ | hours | 2.0 |
28
+ | spent_at | 12/28/2009 |
29
+ When I run a project report for "Report Project1" for "12/20/2009" and "12/30/2009" the following entries should be returned:
30
+ | project1 time |
31
+ When I run a project report for "Report Project1" for "12/20/2009" and "12/30/2009" the following entries should not be returned:
32
+ | project2 time |
33
+ When I run a a project report for "Report Project1" for "12/20/2009" and "12/30/2009" filtered by my user the following entries should be returned:
34
+ | project1 time |
35
+ When I run a a project report for "Report Project1" for "12/20/2009" and "12/30/2009" filtered by my user the following entries should not be returned:
36
+ | project2 time |
37
+ When I run a people report for my user for "12/20/2009" and "12/30/2009" the following entries should be returned:
38
+ | project1 time |
39
+ | project2 time |
40
+
41
+ @wip
42
+ Scenario: People Reports filtered by Project
43
+ Given I create a task with the following:
44
+ | name | Report Task |
45
+ | billable | true |
46
+ | hourly_rate | 120 |
47
+ And I assign the task "Report Task" to the project "Report Project1"
48
+ And I assign the task "Report Task" to the project "Report Project2"
49
+ And I create a time entry for the project "Report Project1" and the task "Report Task" with the following:
50
+ | notes | project1 time |
51
+ | hours | 2.0 |
52
+ | spent_at | 12/28/2009 |
53
+ And I create a time entry for the project "Report Project2" and the task "Report Task" with the following:
54
+ | notes | project2 time |
55
+ | hours | 2.0 |
56
+ | spent_at | 12/28/2009 |
57
+ When I run a people report for my user for "12/20/2009" and "12/30/2009" filtered by the project "Report Project1" the following entries should be returned:
58
+ | project1 time |
59
+ When I run a people report for my user for "12/20/2009" and "12/30/2009" filtered by the project "Report Project1" the following entries should not be returned:
60
+ | project2 time |
61
+
62
+ Scenario: Expenses by People
63
+ Given I create an expense category with the following:
64
+ | name | Conference |
65
+ | unit_name | Ticket |
66
+ | unit_price | 300 |
67
+ And I create an expense for the project "Report Project1" with the category "Conference" with the following:
68
+ | notes | RubyConf |
69
+ | total_cost | 300 |
70
+ | spent_at | 12/28/2009 |
71
+ When I run an expense report for my user for "12/20/2009" and "12/30/2009" the following entries should be returned:
72
+ | RubyConf |
@@ -0,0 +1,7 @@
1
+ When 'I request the current rate limit I should get the following:' do |table|
2
+ rate_limit_status = harvest_api.account.rate_limit_status
3
+ table.rows_hash.each do |key, value|
4
+ rate_limit_status.send(key).to_s.should == value
5
+ end
6
+ end
7
+
@@ -0,0 +1,100 @@
1
+ When 'I create and assign a task "$1" to the project "$2"' do |task_name, project_name|
2
+ project = Then %Q{there should be a project "#{project_name}"}
3
+ p = harvest_api.projects.create_task(project, task_name)
4
+ p.should == project
5
+ end
6
+
7
+ When 'I assign the task "$1" to the project "$2"' do |task_name, project_name|
8
+ task = Then %Q{there should be a task "#{task_name}"}
9
+ project = Then %Q{there should be a project "#{project_name}"}
10
+ assignment = Harvest::TaskAssignment.new(:project => project, :task => task)
11
+ harvest_api.task_assignments.create(assignment)
12
+ end
13
+
14
+ Then 'the task "$1" should be assigned to the project "$2"' do |task_name, project_name|
15
+ task = Then %Q{there should be a task "#{task_name}"}
16
+ project = Then %Q{there should be a project "#{project_name}"}
17
+ assignments = harvest_api.task_assignments.all(project)
18
+ assignment = assignments.detect {|a| a.project_id == project.to_i && a.task_id == task.to_i }
19
+ assignment.should_not be_nil
20
+ assignment
21
+ end
22
+
23
+ Then 'the task "$1" should not be assigned to the project "$2"' do |task_name, project_name|
24
+ task = Then %Q{there should be a task "#{task_name}"}
25
+ project = Then %Q{there should be a project "#{project_name}"}
26
+ assignments = harvest_api.task_assignments.all(project)
27
+ assignment = assignments.detect {|a| a.project_id == project.to_i && a.task_id == task.to_i }
28
+ assignment.should be_nil
29
+ end
30
+
31
+ When 'I update the task "$1" for the project "$2" with the following:' do |task_name, project_name, table|
32
+ assignment = Then %Q{the task "#{task_name}" should be assigned to the project "#{project_name}"}
33
+ assignment.attributes = table.rows_hash
34
+ harvest_api.task_assignments.update(assignment)
35
+ end
36
+
37
+ Then 'the task "$1" for the project "$2" should have the following attributes:' do |task_name, project_name, table|
38
+ assignment = Then %Q{the task "#{task_name}" should be assigned to the project "#{project_name}"}
39
+ table.rows_hash.each do |key, value|
40
+ assignment.send(key).to_s.should == value
41
+ end
42
+ end
43
+
44
+ When 'I remove the task "$1" from the project "$2"' do |task_name, project_name|
45
+ assignment = Then %Q{the task "#{task_name}" should be assigned to the project "#{project_name}"}
46
+ id = harvest_api.task_assignments.delete(assignment)
47
+ id.should == assignment.id
48
+ end
49
+
50
+ When 'I try to remove the task "$1" from the project "$2"' do |task_name, project_name|
51
+ assignment = Then %Q{the task "#{task_name}" should be assigned to the project "#{project_name}"}
52
+ begin
53
+ harvest_api.task_assignments.delete(assignment)
54
+ rescue Harvest::HTTPError => e
55
+ @error = e
56
+ end
57
+ end
58
+
59
+ When 'I assign the user "$1" to the project "$2"' do |email, project_name|
60
+ user = Then %Q{there should be a user "#{email}"}
61
+ project = Then %Q{there should be a project "#{project_name}"}
62
+ assignment = Harvest::UserAssignment.new(:project => project, :user => user)
63
+ harvest_api.user_assignments.create(assignment)
64
+ end
65
+
66
+ When 'I remove the user "$1" from the project "$2"' do |email, project_name|
67
+ assignment = Then %Q{the user "#{email}" should be assigned to the project "#{project_name}"}
68
+ id = harvest_api.user_assignments.delete(assignment)
69
+ id.should == assignment.id
70
+ end
71
+
72
+ Then 'the user "$1" should be assigned to the project "$2"' do |email, project_name|
73
+ user = Then %Q{there should be a user "#{email}"}
74
+ project = Then %Q{there should be a project "#{project_name}"}
75
+ assignments = harvest_api.user_assignments.all(project)
76
+ assignment = assignments.detect {|a| a.project_id == project.to_i && a.user_id == user.to_i }
77
+ assignment.should_not be_nil
78
+ assignment
79
+ end
80
+
81
+ Then 'the user "$1" should not be assigned to the project "$2"' do |email, project_name|
82
+ user = Then %Q{there should be a user "#{email}"}
83
+ project = Then %Q{there should be a project "#{project_name}"}
84
+ assignments = harvest_api.user_assignments.all(project)
85
+ assignment = assignments.detect {|a| a.project_id == project.to_i && a.user_id == user.to_i }
86
+ assignment.should be_nil
87
+ end
88
+
89
+ When 'I update the user "$1" on the project "$2" with the following:' do |email, project_name, table|
90
+ assignment = Then %Q{the user "#{email}" should be assigned to the project "#{project_name}"}
91
+ assignment.attributes = table.rows_hash
92
+ harvest_api.user_assignments.update(assignment)
93
+ end
94
+
95
+ Then 'the user "$1" on the project "$2" should have the following attributes:' do |email, project_name, table|
96
+ assignment = Then %Q{the user "#{email}" should be assigned to the project "#{project_name}"}
97
+ table.rows_hash.each do |key, value|
98
+ assignment.send(key).to_s.should == value
99
+ end
100
+ end
@@ -0,0 +1,11 @@
1
+ Then 'there should be a contact "$1" for the client "$2"' do |email, client_name|
2
+ client = Then %Q{there should be a client "#{client_name}"}
3
+ contact = Then %Q{there should be a contact "#{email}"}
4
+ contact.client_id.should == client.id
5
+
6
+ contacts = harvest_api.contacts.all(client.id)
7
+ contact2 = contacts.detect {|c| c.email == email}
8
+ contact2.should == contact
9
+
10
+ contact
11
+ end
@@ -0,0 +1,3 @@
1
+ Then 'I debug' do
2
+ debugger
3
+ end
@@ -0,0 +1,113 @@
1
+ Given /^the next request will receive a (bad request|not found|bad gateway|server error|rate limit) response$/ do |type|
2
+ statuses = {
3
+ 'bad request' => ['400', 'Bad Request'],
4
+ 'not found' => ['404', 'Not Found'],
5
+ 'bad gateway' => ['502', 'Bad Gateway'],
6
+ 'server error' => ['500', 'Server Error'],
7
+ 'rate limit' => ['503', 'Rake Limited']
8
+ }
9
+
10
+ if status = statuses[type]
11
+ FakeWeb.register_uri(:get, /\/clients/, [
12
+ {:status => status, :times => 1},
13
+ {:body => File.read(File.dirname(__FILE__) + '/../support/fixtures/empty_clients.xml')}
14
+ ])
15
+ else
16
+ pending
17
+ end
18
+ end
19
+
20
+ Given /^the next request will receive a rate limit response with a refresh in (\d+) seconds$/ do |seconds|
21
+ FakeWeb.register_uri(:get, /\/clients/, [
22
+ {:status => ['503', 'Rake Limited'], "Retry-After" => seconds},
23
+ {:body => File.read(File.dirname(__FILE__) + '/../support/fixtures/empty_clients.xml')}
24
+ ])
25
+ end
26
+
27
+ Given 'the next request will receive a rate limit response without a refresh set' do
28
+ FakeWeb.register_uri(:get, /\/clients/, [
29
+ {:status => ['503', 'Rake Limited']},
30
+ {:body => File.read(File.dirname(__FILE__) + '/../support/fixtures/empty_clients.xml')}
31
+ ])
32
+ end
33
+
34
+ When 'I make a request with the standard client' do
35
+ set_time_and_return_and_error do
36
+ standard_api.clients.all
37
+ end
38
+ end
39
+
40
+ When 'I make a request with the hardy client' do
41
+ set_time_and_return_and_error do
42
+ harvest_api.clients.all
43
+ end
44
+ end
45
+
46
+ When /^I make a request with the hardy client with (\d+) max retries$/ do |times|
47
+ set_time_and_return_and_error do
48
+ api = Harvest.hardy_client(@subdomain, @username, @password, :ssl => @ssl, :retry => times.to_i)
49
+ api.clients.all
50
+ end
51
+ end
52
+
53
+ Then /a (\d+) error should be raised/ do |code|
54
+ case code
55
+ when '400'
56
+ @error.should be_a(Harvest::BadRequest)
57
+ when '404'
58
+ @error.should be_a(Harvest::NotFound)
59
+ when '502'
60
+ @error.should be_a(Harvest::Unavailable)
61
+ when '500'
62
+ @error.should be_a(Harvest::ServerError)
63
+ when '503'
64
+ @error.should be_a(Harvest::RateLimited)
65
+ else
66
+ pending
67
+ end
68
+ end
69
+
70
+ Then /the hardy client should wait (\d+) seconds for the rate limit to reset/ do |seconds|
71
+ Time.now.should be_close(@time + seconds.to_i, 2)
72
+ end
73
+
74
+ Then 'I should be able to make a request again' do
75
+ harvest_api.clients.all
76
+ harvest_api.clients.all
77
+ end
78
+
79
+ Given /^the next (\d+) requests will receive a bad gateway response$/ do |times|
80
+ FakeWeb.register_uri(:get, /\/clients/, [
81
+ {:status => ['502', 'Bad Gateway'], :times => times.to_i},
82
+ {:body => File.read(File.dirname(__FILE__) + '/../support/fixtures/empty_clients.xml')}
83
+ ])
84
+ end
85
+
86
+ Given /^the next (\d+) requests will receive a server error response$/ do |times|
87
+ FakeWeb.register_uri(:get, /\/clients/, [
88
+ {:status => ['500', 'Server Error'], :times => times.to_i},
89
+ {:body => File.read(File.dirname(__FILE__) + '/../support/fixtures/empty_clients.xml')}
90
+ ])
91
+ end
92
+
93
+ Given /^the next (\d+) requests will receive an HTTP Error$/ do |times|
94
+ FakeWeb.register_uri(:get, /\/clients/, [
95
+ {:exception => Net::HTTPError, :times => times.to_i},
96
+ {:body => File.read(File.dirname(__FILE__) + '/../support/fixtures/empty_clients.xml')}
97
+ ])
98
+ end
99
+
100
+ Then 'no errors should be raised' do
101
+ @error.should be_nil
102
+ @clients.should == []
103
+ end
104
+
105
+ Given 'the rate limit status indicates I\'m over my limit' do
106
+ over_limit_response = File.read(File.dirname(__FILE__) + '/../support/fixtures/over_limit.xml')
107
+ FakeWeb.register_uri(:get, /\/account\/rate_limit_status/, :body => over_limit_response)
108
+ end
109
+
110
+ Given 'the rate limit status indicates I\'m under my limit' do
111
+ over_limit_response = File.read(File.dirname(__FILE__) + '/../support/fixtures/under_limit.xml')
112
+ FakeWeb.register_uri(:get, /\/account\/rate_limit_status/, :body => over_limit_response)
113
+ end
@@ -0,0 +1,46 @@
1
+ When 'I create an expense for the project "$1" with the category "$2" with the following:' do |project_name, category_name, table|
2
+ project = Then %Q{there should be a project "#{project_name}"}
3
+ category = Then %Q{there should be an expense category "#{category_name}"}
4
+
5
+ expense = Harvest::Expense.new(table.rows_hash.merge("expense_category_id" => category.id, "project_id" => project.id))
6
+ harvest_api.expenses.create(expense)
7
+ end
8
+
9
+ Then 'there should be an expense "$1" on "$2"' do |notes, date|
10
+ expenses = harvest_api.expenses.all(date)
11
+ expense = expenses.detect {|e| e.notes == notes}
12
+ expense.should_not be_nil
13
+ expense
14
+ end
15
+
16
+ Then 'there should not be an expense "$1" on "$2"' do |notes, date|
17
+ expenses = harvest_api.expenses.all(date)
18
+ expense = expenses.detect {|e| e.notes == notes}
19
+ expense.should be_nil
20
+ end
21
+
22
+ When 'I delete the expense "$1" on "$2"' do |notes, date|
23
+ expense = Then %Q{there should be an expense "#{notes}" on "#{date}"}
24
+ id = harvest_api.expenses.delete(expense)
25
+ expense.id.should == id
26
+ end
27
+
28
+ Then 'the expense "$1" on "$2" should have the following attributes:' do |notes, date, table|
29
+ expense = Then %Q{there should be an expense "#{notes}" on "#{date}"}
30
+ table.rows_hash.each do |key, value|
31
+ expense.send(key).to_s.should == value
32
+ end
33
+ end
34
+
35
+ When 'I update the expense "$1" on "$2" with the following:' do |notes, date, table|
36
+ expense = Then %Q{there should be an expense "#{notes}" on "#{date}"}
37
+ expense.attributes = table.rows_hash
38
+ harvest_api.expenses.update(expense)
39
+ end
40
+
41
+ When 'I attach the receipt "$1" to the expense "$2" on "$3"' do |path, notes, date|
42
+ path = "#{File.dirname(__FILE__)}/../#{path}"
43
+ receipt = StringIO.new(File.read(path))
44
+ expense = Then %Q{there should be an expense "#{notes}" on "#{date}"}
45
+ harvest_api.expenses.attach(expense, "receipt.png", receipt)
46
+ end
@@ -0,0 +1,8 @@
1
+ Given 'I am using the credentials from "$1"' do |path|
2
+ raise "You need a credentials file in support/harvest_credentials.yml!!" unless File.exist?("#{File.dirname(__FILE__)}/../#{path}")
3
+ credentials = YAML.load_file("#{File.dirname(__FILE__)}/../#{path}")
4
+ @username = credentials["username"]
5
+ @password = credentials["password"]
6
+ @subdomain = credentials["subdomain"]
7
+ @ssl = credentials["ssl"]
8
+ end