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,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)
@@ -0,0 +1,69 @@
1
+ @clean
2
+ Feature: Task Assignments
3
+
4
+ Scenario: Adding and Updating a Task on 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 create a task with the following:
15
+ | name | Test Task |
16
+ | billable | true |
17
+ | deactivated | false |
18
+ | hourly_rate | 120 |
19
+ Then there should be a task "Test Task"
20
+ When I assign the task "Test Task" to the project "Test Project"
21
+ Then the task "Test Task" should be assigned to the project "Test Project"
22
+ When I update the task "Test Task" for the project "Test Project" with the following:
23
+ | hourly_rate | 75 |
24
+ | billable | false |
25
+ Then the task "Test Task" for the project "Test Project" should have the following attributes:
26
+ | hourly_rate | 75.0 |
27
+ | billable? | false |
28
+ | active? | true |
29
+
30
+ Scenario: Removing Tasks on a project
31
+ Given I am using the credentials from "./support/harvest_credentials.yml"
32
+ When I create a client with the following:
33
+ | name | Client Projects |
34
+ | details | Building API Widgets across the country |
35
+ When I create a project for the client "Client Projects" with the following:
36
+ | name | Test Project |
37
+ | active | true |
38
+ | notes | project to test the api |
39
+ Then there should be a project "Test Project"
40
+ When I create a task with the following:
41
+ | name | Test Task1 |
42
+ When I create a task with the following:
43
+ | name | Test Task2 |
44
+ Then there should be a task "Test Task1"
45
+ Then there should be a task "Test Task2"
46
+ When I assign the task "Test Task1" to the project "Test Project"
47
+ When I assign the task "Test Task2" to the project "Test Project"
48
+ Then the task "Test Task1" should be assigned to the project "Test Project"
49
+ Then the task "Test Task2" should be assigned to the project "Test Project"
50
+ When I remove the task "Test Task1" from the project "Test Project"
51
+ Then the task "Test Task1" should not be assigned to the project "Test Project"
52
+ When I try to remove the task "Test Task2" from the project "Test Project"
53
+ Then a 400 error should be raised
54
+
55
+ Scenario: Removing a task that has recorded hours
56
+
57
+ Scenario: Creating a task and immediately assigning it
58
+ Given I am using the credentials from "./support/harvest_credentials.yml"
59
+ When I create a client with the following:
60
+ | name | Client Projects |
61
+ | details | Building API Widgets across the country |
62
+ When I create a project for the client "Client Projects" with the following:
63
+ | name | Test Project |
64
+ | active | true |
65
+ | notes | project to test the api |
66
+ Then there should be a project "Test Project"
67
+ When I create and assign a task "Created Task" to the project "Test Project"
68
+ Then there should be a task "Created Task"
69
+ Then the task "Created Task" should be assigned to the project "Test Project"
@@ -0,0 +1,25 @@
1
+ @clean
2
+ Feature: Managing Tasks
3
+
4
+ Scenario: Adding, Updating, and Removing a Task
5
+ Given I am using the credentials from "./support/harvest_credentials.yml"
6
+ When I create a task with the following:
7
+ | name | Test Task |
8
+ | billable | true |
9
+ | deactivated | false |
10
+ | hourly_rate | 120 |
11
+ Then there should be a task "Test Task"
12
+ When I update the task "Test Task" with the following:
13
+ | name | Updated Task |
14
+ | hourly_rate | 100.0 |
15
+ | deactivated | true |
16
+ | billable | false |
17
+ | default | true |
18
+ Then the task "Updated Task" should have the following attributes:
19
+ | hourly_rate | 100.0 |
20
+ | deactivated | true |
21
+ | active? | false |
22
+ | billable | false |
23
+ | default? | true |
24
+ When I delete the task "Updated Task"
25
+ Then there should not be a task "Updated Task"
@@ -0,0 +1,29 @@
1
+ @clean
2
+ Feature: Time Tracking
3
+
4
+ Scenario: Adding, Updating, and Deleting Time Entries
5
+ Given I am using the credentials from "./support/harvest_credentials.yml"
6
+ And I create a client with the following:
7
+ | name | Time Client |
8
+ | details | Client to track time against |
9
+ And I create a project for the client "Time Client" with the following:
10
+ | name | Time Project |
11
+ | active | true |
12
+ And I create a task with the following:
13
+ | name | Time Task |
14
+ | billable | true |
15
+ | hourly_rate | 120 |
16
+ And I assign the task "Time Task" to the project "Time Project"
17
+ When I create a time entry for the project "Time Project" and the task "Time Task" with the following:
18
+ | notes | Test api support |
19
+ | hours | 3.0 |
20
+ | spent_at | 12/28/2009 |
21
+ Then there should be a time entry "Test api support" on "12/28/2009"
22
+ When I update the time entry "Test api support" on "12/28/2009" with the following:
23
+ | hours | 4.0 |
24
+ Then the time entry "Test api support" on "12/28/2009" should have the following attributes:
25
+ | hours | 4.0 |
26
+ When I delete the time entry "Test api support" on "12/28/2009"
27
+ Then there should not be a time entry "Test api support" on "12/28/2009"
28
+
29
+ Scenario: Toggling Timers