quilted-harvested 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. data/.document +3 -0
  2. data/.gitignore +25 -0
  3. data/HISTORY +19 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.md +68 -0
  6. data/Rakefile +25 -0
  7. data/VERSION +1 -0
  8. data/examples/basics.rb +35 -0
  9. data/examples/clear_account.rb +28 -0
  10. data/examples/task_assignments.rb +27 -0
  11. data/examples/user_assignments.rb +24 -0
  12. data/features/account.feature +7 -0
  13. data/features/client_contacts.feature +23 -0
  14. data/features/clients.feature +29 -0
  15. data/features/errors.feature +25 -0
  16. data/features/expense_categories.feature +21 -0
  17. data/features/expenses.feature +55 -0
  18. data/features/hardy_client.feature +40 -0
  19. data/features/projects.feature +39 -0
  20. data/features/reporting.feature +72 -0
  21. data/features/step_definitions/account_steps.rb +7 -0
  22. data/features/step_definitions/assignment_steps.rb +100 -0
  23. data/features/step_definitions/contact_steps.rb +11 -0
  24. data/features/step_definitions/debug_steps.rb +3 -0
  25. data/features/step_definitions/error_steps.rb +113 -0
  26. data/features/step_definitions/expenses_steps.rb +46 -0
  27. data/features/step_definitions/harvest_steps.rb +8 -0
  28. data/features/step_definitions/model_steps.rb +90 -0
  29. data/features/step_definitions/people_steps.rb +4 -0
  30. data/features/step_definitions/report_steps.rb +91 -0
  31. data/features/step_definitions/time_entry_steps.rb +40 -0
  32. data/features/support/env.rb +37 -0
  33. data/features/support/error_helpers.rb +18 -0
  34. data/features/support/fixtures/empty_clients.xml +2 -0
  35. data/features/support/fixtures/over_limit.xml +8 -0
  36. data/features/support/fixtures/receipt.png +0 -0
  37. data/features/support/fixtures/under_limit.xml +8 -0
  38. data/features/support/harvest_credentials.example.yml +4 -0
  39. data/features/support/harvest_helpers.rb +11 -0
  40. data/features/support/inflections.rb +9 -0
  41. data/features/task_assignment.feature +69 -0
  42. data/features/tasks.feature +25 -0
  43. data/features/time_tracking.feature +29 -0
  44. data/features/user_assignments.feature +33 -0
  45. data/features/users.feature +55 -0
  46. data/harvested.gemspec +159 -0
  47. data/lib/harvest/api/account.rb +15 -0
  48. data/lib/harvest/api/base.rb +42 -0
  49. data/lib/harvest/api/clients.rb +10 -0
  50. data/lib/harvest/api/contacts.rb +19 -0
  51. data/lib/harvest/api/expense_categories.rb +9 -0
  52. data/lib/harvest/api/expenses.rb +28 -0
  53. data/lib/harvest/api/projects.rb +54 -0
  54. data/lib/harvest/api/reports.rb +39 -0
  55. data/lib/harvest/api/task_assignments.rb +32 -0
  56. data/lib/harvest/api/tasks.rb +9 -0
  57. data/lib/harvest/api/time.rb +32 -0
  58. data/lib/harvest/api/user_assignments.rb +32 -0
  59. data/lib/harvest/api/users.rb +21 -0
  60. data/lib/harvest/base.rb +258 -0
  61. data/lib/harvest/base_model.rb +73 -0
  62. data/lib/harvest/behavior/activatable.rb +31 -0
  63. data/lib/harvest/behavior/crud.rb +57 -0
  64. data/lib/harvest/client.rb +30 -0
  65. data/lib/harvest/contact.rb +29 -0
  66. data/lib/harvest/credentials.rb +21 -0
  67. data/lib/harvest/errors.rb +23 -0
  68. data/lib/harvest/expense.rb +19 -0
  69. data/lib/harvest/expense_category.rb +18 -0
  70. data/lib/harvest/hardy_client.rb +80 -0
  71. data/lib/harvest/project.rb +56 -0
  72. data/lib/harvest/rate_limit_status.rb +28 -0
  73. data/lib/harvest/task.rb +30 -0
  74. data/lib/harvest/task_assignment.rb +34 -0
  75. data/lib/harvest/time_entry.rb +42 -0
  76. data/lib/harvest/timezones.rb +149 -0
  77. data/lib/harvest/user.rb +66 -0
  78. data/lib/harvest/user_assignment.rb +34 -0
  79. data/lib/harvested.rb +62 -0
  80. data/spec/harvest/base_spec.rb +9 -0
  81. data/spec/harvest/credentials_spec.rb +22 -0
  82. data/spec/harvest/expense_spec.rb +15 -0
  83. data/spec/harvest/task_assignment_spec.rb +10 -0
  84. data/spec/harvest/time_entry_spec.rb +22 -0
  85. data/spec/harvest/user_assignment_spec.rb +10 -0
  86. data/spec/harvest/user_spec.rb +32 -0
  87. data/spec/spec.default.opts +1 -0
  88. data/spec/spec_helper.rb +10 -0
  89. metadata +264 -0
@@ -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
@@ -0,0 +1,90 @@
1
+ When /^I create an? (client|task|user|expense category) with the following:$/ do |type, table|
2
+ case type
3
+ when "client"
4
+ client = Harvest::Client.new(table.rows_hash)
5
+ harvest_api.clients.create(client)
6
+ when "task"
7
+ task = Harvest::Task.new(table.rows_hash)
8
+ harvest_api.tasks.create(task)
9
+ when "user"
10
+ user = Harvest::User.new(table.rows_hash)
11
+ harvest_api.users.create(user)
12
+ when "expense category"
13
+ expense_cat = Harvest::ExpenseCategory.new(table.rows_hash)
14
+ harvest_api.expense_categories.create(expense_cat)
15
+ else pending end
16
+ end
17
+
18
+ When /^I create a (contact|project) for the client "([^\"]*)" with the following:$/ do |type, client_name, table|
19
+ client = Then %Q{there should be a client "#{client_name}"}
20
+ attributes = table.rows_hash.merge("client_id" => client.id)
21
+
22
+ case type
23
+ when "contact"
24
+ contact = Harvest::Contact.new(attributes)
25
+ harvest_api.contacts.create(contact)
26
+ when "project"
27
+ project = Harvest::Project.new(attributes)
28
+ harvest_api.projects.create(project)
29
+ else pending end
30
+ end
31
+
32
+ Then /^there should be an? (contact|project|client|task|user|expense category) "([^\"]*)"$/ do |type, identifier|
33
+ collection = harvest_api.send(pluralize(type)).all
34
+ attribute = case type
35
+ when 'contact', 'user' then 'email'
36
+ when 'project', 'client', 'task', 'expense category' then 'name'
37
+ else pending end
38
+ item = collection.detect {|c| c.send(attribute) == identifier}
39
+ item.should_not be_nil
40
+ item
41
+ end
42
+
43
+ Then /^there should not be an? (contact|project|client|task|user|expense category) "([^\"]*)"$/ do |type, identifier|
44
+ collection = harvest_api.send(pluralize(type)).all
45
+ attribute = case type
46
+ when 'contact', 'user' then 'email'
47
+ when 'project', 'client', 'task', 'expense category' then 'name'
48
+ else pending end
49
+ item = collection.detect {|c| c.send(attribute) == identifier}
50
+ item.should be_nil
51
+ end
52
+
53
+ When /^I update the (contact|project|client|task|user|expense category) "([^\"]*)" with the following:$/ do |type, name, table|
54
+ item = Then %Q{there should be a #{type} "#{name}"}
55
+ item.attributes = table.rows_hash
56
+ harvest_api.send(pluralize(type)).update(item)
57
+ end
58
+
59
+ Then /^the (contact|project|client|task|user|expense category) "([^\"]*)" should have the following attributes:$/ do |type, name, table|
60
+ item = Then %Q{there should be a #{type} "#{name}"}
61
+ table.rows_hash.each do |key, value|
62
+ item.send(key).to_s.should == value
63
+ end
64
+ end
65
+
66
+ When /^I delete the (contact|project|client|task|user|expense category) "([^\"]*)"$/ do |type, name|
67
+ item = Then %Q{there should be a #{type} "#{name}"}
68
+ id = harvest_api.send(pluralize(type)).delete(item)
69
+ id.should == item.id
70
+ end
71
+
72
+ Then /^the (client|project|user) "([^\"]*)" should be activated$/ do |type, identifier|
73
+ item = Then %Q{there should be a #{type} "#{identifier}"}
74
+ item.should be_active
75
+ end
76
+
77
+ Then /^the (client|project|user) "([^\"]*)" should be deactivated$/ do |type, identifier|
78
+ item = Then %Q{there should be a #{type} "#{identifier}"}
79
+ item.should_not be_active
80
+ end
81
+
82
+ When /^I deactivate the (client|project|user) "([^\"]*)"$/ do |type, identifier|
83
+ item = Then %Q{there should be a #{type} "#{identifier}"}
84
+ harvest_api.send(pluralize(type)).deactivate(item)
85
+ end
86
+
87
+ When /^I activate the (client|project|user) "([^\"]*)"$/ do |type, identifier|
88
+ item = Then %Q{there should be a #{type} "#{identifier}"}
89
+ harvest_api.send(pluralize(type)).activate(item)
90
+ end
@@ -0,0 +1,4 @@
1
+ Then 'I reset the password of "$1"' do |email|
2
+ user = Then %Q{there should be a user "#{email}"}
3
+ harvest_api.users.reset_password(user)
4
+ end
@@ -0,0 +1,91 @@
1
+ When /^I run a project report for "([^\"]*)" for "([^\"]*)" and "([^\"]*)" the following entries should be returned:$/ do |project_name, start_date, end_date, table|
2
+ start_date = Time.parse(start_date)
3
+ end_date = Time.parse(end_date)
4
+ project = Then %Q{there should be a project "#{project_name}"}
5
+ entries = harvest_api.reports.time_by_project(project, start_date, end_date)
6
+ table.raw.each do |row|
7
+ entry = entries.detect {|e| e.notes == row.first }
8
+ entry.should_not be_nil
9
+ end
10
+ end
11
+
12
+ When /^I run a project report for "([^\"]*)" for "([^\"]*)" and "([^\"]*)" the following entries should not be returned:$/ do |project_name, start_date, end_date, table|
13
+ start_date = Time.parse(start_date)
14
+ end_date = Time.parse(end_date)
15
+ project = Then %Q{there should be a project "#{project_name}"}
16
+ entries = harvest_api.reports.time_by_project(project, start_date, end_date)
17
+ table.raw.each do |row|
18
+ entry = entries.detect {|e| e.notes == row.first }
19
+ entry.should be_nil
20
+ end
21
+ end
22
+
23
+ When /^I run a a project report for "([^\"]*)" for "([^\"]*)" and "([^\"]*)" filtered by my user the following entries should be returned:$/ do |project_name, start_date, end_date, table|
24
+ start_date = Time.parse(start_date)
25
+ end_date = Time.parse(end_date)
26
+ project = Then %Q{there should be a project "#{project_name}"}
27
+ user = Then %Q{there should be a user "#{@username}"}
28
+ entries = harvest_api.reports.time_by_project(project, start_date, end_date, user)
29
+ table.raw.each do |row|
30
+ entry = entries.detect {|e| e.notes == row.first }
31
+ entry.should_not be_nil
32
+ end
33
+ end
34
+
35
+ When /^I run a a project report for "([^\"]*)" for "([^\"]*)" and "([^\"]*)" filtered by my user the following entries should not be returned:$/ do |project_name, start_date, end_date, table|
36
+ start_date = Time.parse(start_date)
37
+ end_date = Time.parse(end_date)
38
+ project = Then %Q{there should be a project "#{project_name}"}
39
+ user = Then %Q{there should be a user "#{@username}"}
40
+ entries = harvest_api.reports.time_by_project(project, start_date, end_date, user)
41
+ table.raw.each do |row|
42
+ entry = entries.detect {|e| e.notes == row.first }
43
+ entry.should be_nil
44
+ end
45
+ end
46
+
47
+ When /^I run a people report for my user for "([^\"]*)" and "([^\"]*)" the following entries should be returned:$/ do |start_date, end_date, table|
48
+ start_date = Time.parse(start_date)
49
+ end_date = Time.parse(end_date)
50
+ user = Then %Q{there should be a user "#{@username}"}
51
+ entries = harvest_api.reports.time_by_user(user, start_date, end_date)
52
+ table.raw.each do |row|
53
+ entry = entries.detect {|e| e.notes == row.first }
54
+ entry.should_not be_nil
55
+ end
56
+ end
57
+
58
+ When /^I run a people report for my user for "([^\"]*)" and "([^\"]*)" filtered by the project "([^\"]*)" the following entries should be returned:$/ do |start_date, end_date, project_name, table|
59
+ start_date = Time.parse(start_date)
60
+ end_date = Time.parse(end_date)
61
+ user = Then %Q{there should be a user "#{@username}"}
62
+ project = Then %Q{there should be a project "#{project_name}"}
63
+ entries = harvest_api.reports.time_by_user(user, start_date, end_date, project)
64
+ table.raw.each do |row|
65
+ entry = entries.detect {|e| e.notes == row.first }
66
+ entry.should_not be_nil
67
+ end
68
+ end
69
+
70
+ When /^I run a people report for my user for "([^\"]*)" and "([^\"]*)" filtered by the project "([^\"]*)" the following entries should not be returned:$/ do |start_date, end_date, project_name, table|
71
+ start_date = Time.parse(start_date)
72
+ end_date = Time.parse(end_date)
73
+ user = Then %Q{there should be a user "#{@username}"}
74
+ project = Then %Q{there should be a project "#{project_name}"}
75
+ entries = harvest_api.reports.time_by_user(user, start_date, end_date, project)
76
+ table.raw.each do |row|
77
+ entry = entries.detect {|e| e.notes == row.first }
78
+ entry.should be_nil
79
+ end
80
+ end
81
+
82
+ When /^I run an expense report for my user for "([^\"]*)" and "([^\"]*)" the following entries should be returned:$/ do |start_date, end_date, table|
83
+ start_date = Time.parse(start_date)
84
+ end_date = Time.parse(end_date)
85
+ user = Then %Q{there should be a user "#{@username}"}
86
+ expenses = harvest_api.reports.expenses_by_user(user, start_date, end_date)
87
+ table.raw.each do |row|
88
+ expense = expenses.detect {|e| e.notes == row.first }
89
+ expense.should_not be_nil
90
+ end
91
+ end
@@ -0,0 +1,40 @@
1
+ When 'I create a time entry for the project "$1" and the task "$2" with the following:' do |project_name, task_name, table|
2
+ task = Then %Q{there should be a task "#{task_name}"}
3
+ project = Then %Q{there should be a project "#{project_name}"}
4
+ entry = Harvest::TimeEntry.new(table.rows_hash.merge('project_id' => project.id, 'task_id' => task.id))
5
+ created_entry = harvest_api.time.create(entry)
6
+ created_entry.project.should == project.name
7
+ created_entry.task.should == task.name
8
+ end
9
+
10
+ Then 'there should be a time entry "$1" on "$2"' do |notes, date|
11
+ entries = harvest_api.time.all(date)
12
+ entry = entries.detect {|e| e.notes == notes}
13
+ entry.should_not be_nil
14
+ entry
15
+ end
16
+
17
+ Then 'there should not be a time entry "$1" on "$2"' do |notes, date|
18
+ entries = harvest_api.time.all(date)
19
+ entry = entries.detect {|e| e.notes == notes}
20
+ entry.should be_nil
21
+ end
22
+
23
+ When 'I update the time entry "$1" on "$2" with the following:' do |note, date, table|
24
+ entry = Then %Q{there should be a time entry "#{note}" on "#{date}"}
25
+ entry.attributes = table.rows_hash
26
+ harvest_api.time.update(entry)
27
+ end
28
+
29
+ Then 'the time entry "$1" on "$2" should have the following attributes:' do |note, date, table|
30
+ entry = Then %Q{there should be a time entry "#{note}" on "#{date}"}
31
+ table.rows_hash.each do |key, value|
32
+ entry.send(key).to_s.should == value
33
+ end
34
+ end
35
+
36
+ When 'I delete the time entry "$1" on "$2"' do |note, date|
37
+ entry = Then %Q{there should be a time entry "#{note}" on "#{date}"}
38
+ id = harvest_api.time.delete(entry)
39
+ entry.id.should == id
40
+ end
@@ -0,0 +1,37 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../../lib')
2
+ require 'harvested'
3
+ require 'ruby-debug'
4
+ require 'fakeweb'
5
+
6
+ require 'spec/expectations'
7
+
8
+ Before do
9
+ FakeWeb.clean_registry
10
+ FakeWeb.allow_net_connect = true
11
+ end
12
+
13
+ Before('@disconnected') do
14
+ FakeWeb.allow_net_connect = false
15
+ end
16
+
17
+ Before('@clean') do
18
+ credentials = YAML.load_file("#{File.dirname(__FILE__)}/harvest_credentials.yml")
19
+ api = Harvest.hardy_client(credentials["subdomain"], credentials["username"], credentials["password"], :ssl => credentials["ssl"])
20
+
21
+ api.users.all.each do |u|
22
+ api.users.delete(u) if u.email != credentials["username"]
23
+ end
24
+ my_user = api.users.all.first
25
+
26
+ api.reports.time_by_user(my_user, Time.parse('01/01/2000'), Time.now).each do |time|
27
+ api.time.delete(time)
28
+ end
29
+
30
+ api.reports.expenses_by_user(my_user, Time.parse('01/01/2000'), Time.now).each do |time|
31
+ api.expenses.delete(time)
32
+ end
33
+
34
+ %w(expenses expense_categories projects contacts clients tasks).each do |collection|
35
+ api.send(collection).all.each {|m| api.send(collection).delete(m) }
36
+ end
37
+ end
@@ -0,0 +1,18 @@
1
+ module ErrorHelpers
2
+ def set_time_and_return_and_error
3
+ @time = Time.now
4
+ @error = nil
5
+ begin
6
+ @clients = yield
7
+ rescue => e
8
+ case e
9
+ when Harvest::HTTPError, Net::HTTPError, Net::HTTPFatalError
10
+ @error = e
11
+ else
12
+ raise e
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ World(ErrorHelpers)
@@ -0,0 +1,2 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <nil-classes type="array"/>
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <hash>
3
+ <last-access-at type="datetime">2010-04-02T01:34:59+00:00</last-access-at>
4
+ <count type="integer">41</count>
5
+ <timeframe-limit type="integer">15</timeframe-limit>
6
+ <max-calls type="integer">40</max-calls>
7
+ <lockout-seconds type="integer">5</lockout-seconds>
8
+ </hash>
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <hash>
3
+ <last-access-at type="datetime">2010-04-02T01:34:59+00:00</last-access-at>
4
+ <count type="integer">2</count>
5
+ <timeframe-limit type="integer">15</timeframe-limit>
6
+ <max-calls type="integer">40</max-calls>
7
+ <lockout-seconds type="integer">5</lockout-seconds>
8
+ </hash>
@@ -0,0 +1,4 @@
1
+ username: "my@email.com"
2
+ password: "secure"
3
+ subdomain: "my-domain"
4
+ ssl: false
@@ -0,0 +1,11 @@
1
+ module HarvestHelpers
2
+ def standard_api
3
+ Harvest.client(@subdomain, @username, @password, :ssl => @ssl)
4
+ end
5
+
6
+ def harvest_api
7
+ Harvest.hardy_client(@subdomain, @username, @password, :ssl => @ssl)
8
+ end
9
+ end
10
+
11
+ World(HarvestHelpers)
@@ -0,0 +1,9 @@
1
+ module Inflections
2
+ def pluralize(string)
3
+ case string
4
+ when 'expense category' then 'expense_categories'
5
+ else "#{string}s" end
6
+ end
7
+ end
8
+
9
+ World(Inflections)