harvest 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/HISTORY +3 -0
  2. data/LICENSE +23 -0
  3. data/README.rdoc +173 -0
  4. data/Rakefile +44 -0
  5. data/lib/harvest.rb +35 -0
  6. data/lib/harvest/base.rb +77 -0
  7. data/lib/harvest/harvest_resource.rb +15 -0
  8. data/lib/harvest/plugins/active_resource_inheritable_headers.rb +36 -0
  9. data/lib/harvest/plugins/toggleable.rb +12 -0
  10. data/lib/harvest/resources/client.rb +8 -0
  11. data/lib/harvest/resources/entry.rb +34 -0
  12. data/lib/harvest/resources/expense.rb +22 -0
  13. data/lib/harvest/resources/expense_category.rb +7 -0
  14. data/lib/harvest/resources/person.rb +44 -0
  15. data/lib/harvest/resources/project.rb +51 -0
  16. data/lib/harvest/resources/task.rb +8 -0
  17. data/lib/harvest/resources/task_assignment.rb +27 -0
  18. data/lib/harvest/resources/user_assignment.rb +27 -0
  19. data/test/integration/client_integration.rb +17 -0
  20. data/test/integration/client_teardown.rb +11 -0
  21. data/test/integration/expense_category_integration.rb +16 -0
  22. data/test/integration/expense_category_teardown.rb +12 -0
  23. data/test/integration/harvest_integration_test.rb +47 -0
  24. data/test/integration/project_integration.rb +19 -0
  25. data/test/integration/project_teardown.rb +12 -0
  26. data/test/integration/task_integration.rb +19 -0
  27. data/test/integration/task_teardown.rb +12 -0
  28. data/test/test_helper.rb +51 -0
  29. data/test/unit/base_test.rb +88 -0
  30. data/test/unit/resources/client_test.rb +82 -0
  31. data/test/unit/resources/expense_category_test.rb +49 -0
  32. data/test/unit/resources/expense_test.rb +14 -0
  33. data/test/unit/resources/person_test.rb +150 -0
  34. data/test/unit/resources/project_test.rb +154 -0
  35. data/test/unit/resources/task_assignment_test.rb +72 -0
  36. data/test/unit/resources/task_test.rb +82 -0
  37. data/test/unit/resources/user_assignment_test.rb +71 -0
  38. metadata +111 -0
@@ -0,0 +1,22 @@
1
+ module Harvest
2
+ module Resources
3
+ class Expense < Harvest::HarvestResource
4
+
5
+ self.element_name = "expense"
6
+
7
+ class << self
8
+
9
+ def person_id=(id)
10
+ @person_id = id
11
+ self.site = self.site + "/people/#{@person_id}"
12
+ end
13
+
14
+ def person_id
15
+ @person_id
16
+ end
17
+
18
+ end
19
+
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,7 @@
1
+ module Harvest
2
+ module Resources
3
+ class ExpenseCategory < Harvest::HarvestResource
4
+
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,44 @@
1
+ module Harvest
2
+ module Resources
3
+ class Person < Harvest::HarvestResource
4
+ include Harvest::Plugins::Toggleable
5
+
6
+ # Find all entries for the given person;
7
+ # options[:from] and options[:to] are required;
8
+ # include options[:user_id] to limit by a specific project.
9
+ def entries(options={})
10
+ validate_options(options)
11
+ entry_class = Harvest::Resources::Entry.clone
12
+ entry_class.person_id = self.id
13
+ entry_class.find :all, :params => format_params(options)
14
+ end
15
+
16
+ def expenses(options={})
17
+ validate_options(options)
18
+ expense_class = Harvest::Resources::Expense.clone
19
+ expense_class.person_id = self.id
20
+ expense_class.find :all, :params => format_params(options)
21
+ end
22
+
23
+ private
24
+
25
+ def validate_options(options)
26
+ if [:from, :to].any? {|key| !options[key].respond_to?(:strftime) }
27
+ raise ArgumentError, "Must specify :from and :to as dates."
28
+ end
29
+
30
+ if options[:from] > options[:to]
31
+ raise ArgumentError, ":start must precede :end."
32
+ end
33
+
34
+ end
35
+ def format_params(options)
36
+ ops = { :from => options[:from].strftime("%Y%m%d"),
37
+ :to => options[:to].strftime("%Y%m%d")}
38
+ ops[:project_id] = options[:project_id] if options[:project_id]
39
+ return ops
40
+ end
41
+
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,51 @@
1
+ module Harvest
2
+ module Resources
3
+ # Supports the following:
4
+ class Project < Harvest::HarvestResource
5
+ include Harvest::Plugins::Toggleable
6
+
7
+ def users
8
+ user_class = Harvest::Resources::UserAssignment.clone
9
+ user_class.project_id = self.id
10
+ user_class
11
+ end
12
+
13
+ def tasks
14
+ task_class = Harvest::Resources::TaskAssignment.clone
15
+ task_class.project_id = self.id
16
+ task_class
17
+ end
18
+
19
+ # Find all entries for the given project;
20
+ # options[:from] and options[:to] are required;
21
+ # include options[:user_id] to limit by a specific user.
22
+ #
23
+ def entries(options={})
24
+ validate_entries_options(options)
25
+ entry_class = Harvest::Resources::Entry.clone
26
+ entry_class.project_id = self.id
27
+ entry_class.find :all, :params => format_params(options)
28
+ end
29
+
30
+ private
31
+
32
+ def validate_entries_options(options)
33
+ if [:from, :to].any? {|key| !options[key].respond_to?(:strftime) }
34
+ raise ArgumentError, "Must specify :from and :to as dates."
35
+ end
36
+
37
+ if options[:from] > options[:to]
38
+ raise ArgumentError, ":start must precede :end."
39
+ end
40
+ end
41
+
42
+ def format_params(options)
43
+ ops = { :from => options[:from].strftime("%Y%m%d"),
44
+ :to => options[:to].strftime("%Y%m%d")}
45
+ ops[:user_id] = options[:user_id] if options[:user_id]
46
+ return ops
47
+ end
48
+
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,8 @@
1
+ module Harvest
2
+ module Resources
3
+ class Task < Harvest::HarvestResource
4
+ include Harvest::Plugins::Toggleable
5
+
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,27 @@
1
+ # This class is accessed by an instance of Project.
2
+ module Harvest
3
+ module Resources
4
+ class TaskAssignment < Harvest::HarvestResource
5
+
6
+ self.element_name = "task_assignment"
7
+
8
+ class << self
9
+
10
+ def project_id=(id)
11
+ @project_id = id
12
+ set_site
13
+ end
14
+
15
+ def project_id
16
+ @project_id
17
+ end
18
+
19
+ def set_site
20
+ self.site = self.site + "/projects/#{self.project_id}"
21
+ end
22
+
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,27 @@
1
+ # This class is accessed by an instance of Project.
2
+ module Harvest
3
+ module Resources
4
+ class UserAssignment < Harvest::HarvestResource
5
+
6
+ self.element_name = "user_assignment"
7
+
8
+ class << self
9
+
10
+ def project_id=(id)
11
+ @project_id = id
12
+ set_site
13
+ end
14
+
15
+ def project_id
16
+ @project_id
17
+ end
18
+
19
+ def set_site
20
+ self.site = self.site + "/projects/#{self.project_id}"
21
+ end
22
+
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,17 @@
1
+ class ClientIntegration < Test::Unit::TestCase
2
+
3
+ def test_should_create_and_update_a_new_client
4
+ # create
5
+ client = $harvest.clients.new
6
+ client.name = "HarvestGem, INC"
7
+ client.details = "New York, NY"
8
+ client.save
9
+ $test_client = $harvest.clients.find(:all).detect {|c| c.name == "HarvestGem, INC"}
10
+
11
+ #update
12
+ client.details = "San Francisco, CA"
13
+ client.save
14
+ assert_equal "San Francisco, CA", $harvest.clients.find($test_client.id).details
15
+ end
16
+
17
+ end
@@ -0,0 +1,11 @@
1
+ class ClientTeardown < Test::Unit::TestCase
2
+
3
+ def test_should_destroy_the_client
4
+ client = $harvest.clients.find($test_client.id)
5
+ client.destroy
6
+ assert_raise ActiveResource::ResourceNotFound do
7
+ $harvest.clients.find($test_client)
8
+ end
9
+ end
10
+
11
+ end
@@ -0,0 +1,16 @@
1
+ class ExpenseCategoryIntegration < Test::Unit::TestCase
2
+
3
+ def test_should_create_and_update_a_new_expense_category
4
+ # create
5
+ expense_category = $harvest.expense_categories.new
6
+ expense_category.name = "GemEntertainmentBeforeUpdate"
7
+ expense_category.save
8
+ $test_expense_category = $harvest.expense_categories.find(:all).detect {|c| c.name == "GemEntertainmentBeforeUpdate"}
9
+
10
+ #update
11
+ expense_category.name = "GemEntertainment"
12
+ expense_category.save
13
+ assert_equal "GemEntertainment", $harvest.expense_categories.find(:all).detect {|c| c.name == "GemEntertainment"}.name
14
+ end
15
+
16
+ end
@@ -0,0 +1,12 @@
1
+ class ExpenseCategoryTeardown < Test::Unit::TestCase
2
+
3
+ def test_should_destroy_the_expense_category
4
+ expense_category = $harvest.expense_categories.find(:all).detect {|c| c.name == "GemEntertainment"}
5
+ expense_category.destroy
6
+ assert_raise ActiveResource::ResourceNotFound do
7
+ $harvest.expense_categories.find($test_expense_category)
8
+ end
9
+ end
10
+
11
+ end
12
+
@@ -0,0 +1,47 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+ $test_mode = :integration
3
+
4
+ require File.join(File.dirname(__FILE__), "..", "test_helper")
5
+ require "test/unit/testsuite"
6
+ require "test/unit/ui/console/testrunner"
7
+
8
+ require "client_integration"
9
+ require "client_teardown"
10
+ require "expense_category_integration"
11
+ require "expense_category_teardown"
12
+ require "project_integration"
13
+ require "project_teardown"
14
+ require "task_integration"
15
+ require "task_teardown"
16
+
17
+
18
+ # Make sure that credentials have been specified:
19
+ if [:email, :password, :sub_domain].any? { |k| $integration_credentials[k].nil? }
20
+ raise ArgumentError, "Integration test cannot be run without login credentials; Please specify in test/test_helper.rb"
21
+ end
22
+
23
+ # Initialize a harvest object with credentials.
24
+ $harvest = Harvest($integration_credentials)
25
+
26
+ # Global variables to hold resources
27
+ # for later reference.
28
+ $test_client = nil
29
+ $test_project = nil
30
+ $test_person = nil
31
+
32
+ class HarvestIntegration
33
+ def self.suite
34
+ suite = Test::Unit::TestSuite.new
35
+ suite << ExpenseCategoryIntegration.suite
36
+ suite << TaskIntegration.suite
37
+ suite << ClientIntegration.suite
38
+ suite << ProjectIntegration.suite
39
+ suite << ProjectTeardown.suite
40
+ suite << ClientTeardown.suite
41
+ suite << TaskTeardown.suite
42
+ suite << ExpenseCategoryTeardown.suite
43
+ return suite
44
+ end
45
+ end
46
+
47
+ Test::Unit::UI::Console::TestRunner.run(HarvestIntegration)
@@ -0,0 +1,19 @@
1
+ class ProjectIntegration < Test::Unit::TestCase
2
+
3
+ def test_should_create_and_update_a_new_project
4
+ # create
5
+ project = $harvest.projects.new
6
+ project.name = "HarvestGem Project"
7
+ project.active = false
8
+ project.bill_by = "None"
9
+ project.client_id = $test_client.id
10
+ project.save
11
+
12
+ # update
13
+ $test_project = $harvest.projects.find(:all).detect {|c| c.name == "HarvestGem Project"}
14
+ project.active = true
15
+ project.save
16
+ assert $harvest.projects.find($test_project.id).active?
17
+ end
18
+
19
+ end
@@ -0,0 +1,12 @@
1
+ class ProjectTeardown < Test::Unit::TestCase
2
+
3
+ def test_should_destroy_the_project
4
+ project = $harvest.projects.find($test_project.id)
5
+ project.destroy
6
+ assert_raise ActiveResource::ResourceNotFound do
7
+ $harvest.projects.find($test_project)
8
+ end
9
+ end
10
+
11
+ end
12
+
@@ -0,0 +1,19 @@
1
+ class TaskIntegration < Test::Unit::TestCase
2
+
3
+ def test_should_create_and_update_a_new_task
4
+ # create
5
+ task = $harvest.tasks.new
6
+ task.billable_by_default = false
7
+ task.default_hourly_rate = 100
8
+ task.is_default = false
9
+ task.name = "GemIntegration"
10
+ task.save
11
+
12
+ # update
13
+ $test_task = $harvest.tasks.find(:all).detect {|t| t.name == "GemIntegration"}
14
+ task.name = "GemIntegrationUpdated"
15
+ task.save
16
+ assert_equal "GemIntegrationUpdated", $harvest.tasks.find($test_task.id).name
17
+ end
18
+
19
+ end
@@ -0,0 +1,12 @@
1
+ class TaskTeardown < Test::Unit::TestCase
2
+
3
+ def test_should_destroy_the_task
4
+ task = $harvest.tasks.find($test_task.id)
5
+ task.destroy
6
+ assert_raise ActiveResource::ResourceNotFound do
7
+ $harvest.tasks.find($test_task)
8
+ end
9
+ end
10
+
11
+ end
12
+
@@ -0,0 +1,51 @@
1
+ require "rubygems"
2
+ require "active_resource"
3
+ require "active_resource_throttle"
4
+ require "test/unit"
5
+ require "shoulda"
6
+ require "mocha"
7
+
8
+ # The integration test will not run by default.
9
+ # To run it, type:
10
+ # rake test:integration
11
+ #
12
+ # If running the integration test, login credentials
13
+ # must be specified here. The integration test verifies
14
+ # that various resources in a Harvest account can be created,
15
+ # deleted, updated, destroyed, etc.
16
+ #
17
+ # It's best to run this test on a trial account, where no
18
+ # sensitive data can be manipulated.
19
+ #
20
+ # Leave these values nil if not running the integration test.
21
+ $integration_credentials = {:email => nil,
22
+ :password => nil,
23
+ :sub_domain => nil}
24
+
25
+ require File.join(File.dirname(__FILE__), "..", "lib", "harvest.rb")
26
+
27
+ # Disengage throttling if in unit test mode (Integration test needs throttling).
28
+ if ($test_mode ||= :unit) == :unit
29
+ require "active_resource/http_mock"
30
+ Harvest::HarvestResource.throttle(:interval => 0, :requests => 0)
31
+ Harvest::HarvestResource.site = "http://example.com/"
32
+ end
33
+
34
+ # Load all available resources.
35
+ ResourcesPath = File.join(File.dirname(__FILE__), "..", "lib", "harvest", "resources")
36
+ Harvest.load_all_ruby_files_from_path(ResourcesPath)
37
+
38
+ # Custom assert_raise to test exception message in addition to the exception itself.
39
+ class Test::Unit::TestCase
40
+ def assert_raise_plus(exception_class, exception_message, message=nil, &block)
41
+ begin
42
+ yield
43
+ rescue => e
44
+ error_message = build_message(message, '<?>, <?> expected but was <?>, <?>', exception_class, exception_message, e.class, e.message)
45
+ assert_block(error_message) { e.class == exception_class && e.message == exception_message }
46
+ else
47
+ error_message = build_message(nil, '<?>, <?> expected but raised nothing.', exception_class, exception_message)
48
+ assert_block(error_message) { false }
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,88 @@
1
+ require File.join(File.dirname(__FILE__), "..", "test_helper")
2
+
3
+ class BaseTest < Test::Unit::TestCase
4
+
5
+ context "A Harvest object" do
6
+ setup do
7
+ @password = "secret"
8
+ @email = "james@example.com"
9
+ @sub_domain = "bond"
10
+ @headers = {"User-Agent" => "HarvestGemTest"}
11
+ @harvest = Harvest::Base.new(:email => @email,
12
+ :password => @password,
13
+ :sub_domain => @sub_domain,
14
+ :headers => @headers)
15
+ end
16
+
17
+ should "initialize the resource base class" do
18
+ assert_equal "http://bond.harvestapp.com", Harvest::HarvestResource.site.to_s
19
+ assert_equal @password, Harvest::HarvestResource.password
20
+ assert_equal @email, Harvest::HarvestResource.user
21
+ end
22
+
23
+ should "raise an error if sub_domain is missing" do
24
+ assert_raise_plus ArgumentError, "Missing required option(s): sub_domain" do
25
+ Harvest(:email => "joe@example.com", :password => "secret")
26
+ end
27
+ end
28
+
29
+ should "raise an error if password is missing" do
30
+ assert_raise_plus ArgumentError, "Missing required option(s): password" do
31
+ Harvest(:email => "joe@example.com", :sub_domain => "time")
32
+ end
33
+ end
34
+
35
+ should "raise an error if email is missing" do
36
+ assert_raise_plus ArgumentError, "Missing required option(s): email" do
37
+ Harvest(:password => "secret", :sub_domain => "time")
38
+ end
39
+ end
40
+
41
+ should "set the headers" do
42
+ assert_equal @headers, Harvest::HarvestResource.headers
43
+ end
44
+
45
+ should "return the Client class" do
46
+ assert_equal Harvest::Resources::Client, @harvest.clients
47
+ end
48
+
49
+ should "return the Expense class" do
50
+ assert_equal Harvest::Resources::Expense, @harvest.expenses
51
+ end
52
+
53
+ should "return the ExpenseCategory class" do
54
+ assert_equal Harvest::Resources::ExpenseCategory, @harvest.expense_categories
55
+ end
56
+
57
+ should "return the Person class" do
58
+ assert_equal Harvest::Resources::Project, @harvest.projects
59
+ end
60
+
61
+ should "return the Project class" do
62
+ assert_equal Harvest::Resources::Project, @harvest.projects
63
+ end
64
+
65
+ should "return the Task class" do
66
+ assert_equal Harvest::Resources::Task, @harvest.tasks
67
+ end
68
+
69
+ context "with SSL enabled" do
70
+ setup do
71
+ @harvest = Harvest::Base.new(:email => @email,
72
+ :password => @password,
73
+ :sub_domain => @sub_domain,
74
+ :headers => @headers,
75
+ :ssl => true)
76
+ end
77
+
78
+ should "have https in URL" do
79
+ assert_equal "https://bond.harvestapp.com", Harvest::HarvestResource.site.to_s
80
+ end
81
+ end
82
+
83
+ context "who_am_i" do
84
+ @person = @harvest.who_am_i
85
+ end
86
+ end
87
+
88
+ end