harvested 0.3.0

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