harvested 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +23 -0
- data/HISTORY +16 -0
- data/MIT-LICENSE +20 -0
- data/README.md +67 -0
- data/Rakefile +52 -0
- data/VERSION +1 -0
- data/examples/basics.rb +35 -0
- data/examples/clear_account.rb +28 -0
- data/examples/task_assignments.rb +27 -0
- data/examples/user_assignments.rb +24 -0
- data/features/account.feature +7 -0
- data/features/client_contacts.feature +23 -0
- data/features/clients.feature +29 -0
- data/features/errors.feature +25 -0
- data/features/expense_categories.feature +21 -0
- data/features/expenses.feature +55 -0
- data/features/hardy_client.feature +40 -0
- data/features/projects.feature +39 -0
- data/features/reporting.feature +72 -0
- data/features/step_definitions/account_steps.rb +7 -0
- data/features/step_definitions/assignment_steps.rb +100 -0
- data/features/step_definitions/contact_steps.rb +11 -0
- data/features/step_definitions/debug_steps.rb +3 -0
- data/features/step_definitions/error_steps.rb +113 -0
- data/features/step_definitions/expenses_steps.rb +46 -0
- data/features/step_definitions/harvest_steps.rb +8 -0
- data/features/step_definitions/model_steps.rb +90 -0
- data/features/step_definitions/people_steps.rb +4 -0
- data/features/step_definitions/report_steps.rb +91 -0
- data/features/step_definitions/time_entry_steps.rb +40 -0
- data/features/support/env.rb +37 -0
- data/features/support/error_helpers.rb +18 -0
- data/features/support/fixtures/empty_clients.xml +2 -0
- data/features/support/fixtures/over_limit.xml +8 -0
- data/features/support/fixtures/receipt.png +0 -0
- data/features/support/fixtures/under_limit.xml +8 -0
- data/features/support/harvest_credentials.example.yml +4 -0
- data/features/support/harvest_helpers.rb +11 -0
- data/features/support/inflections.rb +9 -0
- data/features/task_assignment.feature +69 -0
- data/features/tasks.feature +25 -0
- data/features/time_tracking.feature +29 -0
- data/features/user_assignments.feature +33 -0
- data/features/users.feature +55 -0
- data/lib/harvest/api/account.rb +10 -0
- data/lib/harvest/api/base.rb +42 -0
- data/lib/harvest/api/clients.rb +10 -0
- data/lib/harvest/api/contacts.rb +19 -0
- data/lib/harvest/api/expense_categories.rb +9 -0
- data/lib/harvest/api/expenses.rb +28 -0
- data/lib/harvest/api/projects.rb +39 -0
- data/lib/harvest/api/reports.rb +39 -0
- data/lib/harvest/api/task_assignments.rb +32 -0
- data/lib/harvest/api/tasks.rb +9 -0
- data/lib/harvest/api/time.rb +32 -0
- data/lib/harvest/api/user_assignments.rb +32 -0
- data/lib/harvest/api/users.rb +15 -0
- data/lib/harvest/base.rb +59 -0
- data/lib/harvest/base_model.rb +34 -0
- data/lib/harvest/behavior/activatable.rb +21 -0
- data/lib/harvest/behavior/crud.rb +31 -0
- data/lib/harvest/client.rb +18 -0
- data/lib/harvest/contact.rb +16 -0
- data/lib/harvest/credentials.rb +21 -0
- data/lib/harvest/errors.rb +23 -0
- data/lib/harvest/expense.rb +19 -0
- data/lib/harvest/expense_category.rb +18 -0
- data/lib/harvest/hardy_client.rb +80 -0
- data/lib/harvest/project.rb +22 -0
- data/lib/harvest/rate_limit_status.rb +16 -0
- data/lib/harvest/task.rb +20 -0
- data/lib/harvest/task_assignment.rb +34 -0
- data/lib/harvest/time_entry.rb +41 -0
- data/lib/harvest/timezones.rb +150 -0
- data/lib/harvest/user.rb +40 -0
- data/lib/harvest/user_assignment.rb +34 -0
- data/lib/harvested.rb +31 -0
- data/spec/harvest/base_spec.rb +9 -0
- data/spec/harvest/credentials_spec.rb +22 -0
- data/spec/harvest/expense_spec.rb +15 -0
- data/spec/harvest/task_assignment_spec.rb +10 -0
- data/spec/harvest/time_entry_spec.rb +22 -0
- data/spec/harvest/user_assignment_spec.rb +10 -0
- data/spec/harvest/user_spec.rb +32 -0
- data/spec/spec.default.opts +1 -0
- data/spec/spec_helper.rb +10 -0
- 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,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,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
|