supaspoida-harvest 0.8.2 → 0.8.3

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.
@@ -0,0 +1,96 @@
1
+ module Harvest
2
+ module Resources
3
+ # This class is a hack to maintain compatibility with the ActiveResource
4
+ # API and Harvest's fucked time tracking API.
5
+ class Timer < Harvest::HarvestResource
6
+ self.element_name = 'daily'
7
+ self.prefix = '/daily/:action_hack'
8
+ self.collection_name = ''
9
+
10
+ # Override to not use collection_path
11
+ def create
12
+ headers = self.class.headers.merge 'Accept' => 'application/xml'
13
+ connection.post('/daily/add', encode, headers).tap do |response|
14
+ self.id = id_from_response(response)
15
+ load_attributes_from_response(response)
16
+ end
17
+ end
18
+
19
+ # Override to use POST instead of PUT, and update path
20
+ def update
21
+ headers = self.class.headers.merge 'Accept' => 'application/xml'
22
+ connection.post(element_path(prefix_options.merge(:action_hack => :update)), encode, headers).tap do |response|
23
+ load_attributes_from_response(response)
24
+ end
25
+ end
26
+
27
+ # Override to use delete path
28
+ def destroy
29
+ connection.delete(element_path(:action_hack => :delete), self.class.headers)
30
+ end
31
+
32
+ # Override to pull day_entry element data into attributes
33
+ def initialize attributes={}
34
+ @attributes = {}
35
+ @prefix_options = {}
36
+ load(attributes.has_key?('day_entry') ? attributes['day_entry'] : attributes)
37
+ end
38
+
39
+ # Override to support Harvest's custom XML format
40
+ def encode options={}
41
+ massaged_attributes = {
42
+ :notes => attributes['notes'],
43
+ :hours => attributes['hours'].to_s,
44
+ :project_id => (project ? project.id : nil),
45
+ :task_id => (task ? task.id : nil),
46
+ :spent_at => attributes['spent_at'] || Date.today
47
+ }
48
+ self.class.format.encode(massaged_attributes, {:root => 'request'}.merge(options))
49
+ end
50
+
51
+ def project
52
+ @project ||= find_project
53
+ end
54
+
55
+ def find_project
56
+ case p = attributes['project']
57
+ when Integer then Harvest::Resources::Project.find(p)
58
+ else; Harvest::Resources::Project.find(:all).find{|pp|pp.name.strip == p.to_s.strip}
59
+ end
60
+ end
61
+
62
+ def task
63
+ @task ||= find_task
64
+ end
65
+
66
+ def find_task
67
+ case t = attributes['task']
68
+ when Integer then Harvest::Resources::Task.find(t)
69
+ else; Harvest::Resources::Task.find(:all).find{|tt|tt.name.strip == t.to_s.strip}
70
+ end
71
+ end
72
+
73
+ class << self
74
+ # Override to remove file extension
75
+ def element_path_with_extension_removal id, prefix_options = {}, query_options = nil
76
+ element_path_without_extension_removal(id, prefix_options, query_options).sub(/\.#{format.extension}/, '')
77
+ end
78
+ alias_method_chain :element_path, :extension_removal
79
+
80
+ # Override to use show path
81
+ def find_single_with_action_hack scope, options
82
+ options ||= {} and options[:params] ||= {}
83
+ options[:params].merge!(:action_hack => :show)
84
+ find_single_without_action_hack scope, options
85
+ end
86
+ alias_method_chain :find_single, :action_hack
87
+
88
+ # Override to use delete path
89
+ def delete_with_action_hack id, options={}
90
+ delete_without_action_hack id, options.merge(:action_hack => :delete)
91
+ end
92
+ alias_method_chain :delete, :action_hack
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,152 @@
1
+ require File.join(File.dirname(__FILE__), "..", "..", "test_helper")
2
+
3
+ class TimerTest < Test::Unit::TestCase
4
+
5
+ def setup_resources
6
+ attrs = {
7
+ :id => 1,
8
+ :hours => 5,
9
+ :client => 'Iridesco',
10
+ :project => 'Harvest',
11
+ :task => 'Backend Programming',
12
+ :notes => 'Test api support'
13
+ }
14
+ @timer_xml = {:day_entry => attrs}.to_xml(:root => "timer")
15
+ @task = {:id => 1, :name => "Backend Programming"}.to_xml(:root => "task")
16
+ @tasks = [{:id => 1, :name => "Backend Programming"}].to_xml(:root => "tasks")
17
+ @project_xml = {:id => 1, :name => "Harvest"}.to_xml(:root => "project")
18
+ @projects = [{:id => 1, :name => "Harvest"}].to_xml(:root => "projects")
19
+ end
20
+
21
+ def mock_responses
22
+ @headers = {
23
+ 'Accept' => 'application/xml'
24
+ }
25
+ @post_headers = {
26
+ 'Content-Type' => 'application/xml; charset=utf-8',
27
+ }.merge @headers
28
+ ActiveResource::HttpMock.respond_to do |mock|
29
+ mock.get "/daily/show/1", @headers, @timer_xml, 200
30
+ mock.post "/daily/add", @post_headers, @timer_xml, 201, "Location" => "/daily/show/2"
31
+ mock.post "/daily/update/1", @post_headers, nil, 200
32
+ mock.delete "/daily/delete/1", @headers, nil, 200
33
+
34
+ mock.get "/projects.xml", {}, @projects
35
+ mock.get "/projects/1.xml", {}, @project_xml
36
+
37
+ mock.get "/tasks.xml", {}, @tasks
38
+ mock.get "/tasks/1.xml", {}, @task
39
+ end
40
+ end
41
+
42
+ context "Timer CRUD actions -- " do
43
+ setup do
44
+ setup_resources
45
+ mock_responses
46
+ end
47
+
48
+ # should "get index" do
49
+ # Harvest::Resources::Daily.find(:all)
50
+ # expected_request = ActiveResource::Request.new(:get, "/daily/#{@day}/#{@year}")
51
+ # assert ActiveResource::HttpMock.requests.include?(expected_request)
52
+ # end
53
+
54
+ should "get a single timer" do
55
+ Harvest::Resources::Timer.find(1)
56
+ expected_request = ActiveResource::Request.new(:get, "/daily/show/1")
57
+ assert ActiveResource::HttpMock.requests.include?(expected_request)
58
+ end
59
+
60
+ should "create a new timer" do
61
+ timer = Harvest::Resources::Timer.new(:hours => 5)
62
+ timer.save
63
+ expected_request = ActiveResource::Request.new(:post, "/daily/add", timer.encode, @post_headers)
64
+ assert ActiveResource::HttpMock.requests.include?(expected_request)
65
+ end
66
+
67
+ should "update an existing timer" do
68
+ timer = Harvest::Resources::Timer.find(1)
69
+ timer.hours = 10
70
+ timer.save
71
+ expected_request = ActiveResource::Request.new(:post, "/daily/update/1", timer.encode, @post_headers)
72
+ assert ActiveResource::HttpMock.requests.include?(expected_request)
73
+ end
74
+
75
+ should "delete an existing timer" do
76
+ Harvest::Resources::Timer.delete(1)
77
+ expected_request = ActiveResource::Request.new(:delete, "/daily/delete/1")
78
+ assert ActiveResource::HttpMock.requests.include?(expected_request)
79
+ end
80
+
81
+ should "delete an existing timer using an instance" do
82
+ timer = Harvest::Resources::Timer.find(1)
83
+ timer.destroy
84
+ expected_request = ActiveResource::Request.new(:delete, "/daily/delete/1")
85
+ assert ActiveResource::HttpMock.requests.include?(expected_request)
86
+ end
87
+
88
+ should "load attributes from the day_entry element" do
89
+ timer = Harvest::Resources::Timer.find(1)
90
+ assert !timer.id.nil?
91
+ end
92
+
93
+ should "output XML per Harvest's spec" do
94
+ timer = Harvest::Resources::Timer.find(1)
95
+ xml = <<-XML
96
+ <?xml version="1.0" encoding="UTF-8"?>
97
+ <request>
98
+ <notes>Test api support</notes>
99
+ <hours>5</hours>
100
+ <project_id type="integer">1</project_id>
101
+ <task_id type="integer">1</task_id>
102
+ <spent_at type="date">#{Date.today.strftime("%a, %e %b %Y")}</spent_at>
103
+ </request>
104
+ XML
105
+ expected = Hash.from_xml(xml)
106
+ real = Hash.from_xml(timer.encode)
107
+ assert_equal expected, real
108
+ end
109
+ end
110
+
111
+ context "Projects" do
112
+ setup do
113
+ setup_resources
114
+ mock_responses
115
+ end
116
+
117
+ should "provide access to the project when set with an id" do
118
+ timer = Harvest::Resources::Timer.new(:project => 1)
119
+ assert timer.project.is_a?(Harvest::Resources::Project)
120
+ assert_equal 1, timer.project.id
121
+ assert_equal 'Harvest', timer.project.name
122
+ end
123
+
124
+ should "provide access to the project when set with a string" do
125
+ timer = Harvest::Resources::Timer.new(:project => "Harvest")
126
+ assert timer.project.is_a?(Harvest::Resources::Project)
127
+ assert_equal 1, timer.project.id
128
+ assert_equal 'Harvest', timer.project.name
129
+ end
130
+ end
131
+
132
+ context "Tasks" do
133
+ setup do
134
+ setup_resources
135
+ mock_responses
136
+ end
137
+
138
+ should "provide access to the task when set with an id" do
139
+ timer = Harvest::Resources::Timer.new(:task => 1)
140
+ assert timer.task.is_a?(Harvest::Resources::Task)
141
+ assert_equal 1, timer.task.id
142
+ assert_equal 'Backend Programming', timer.task.name
143
+ end
144
+
145
+ should "provide access to the task when set with a string" do
146
+ timer = Harvest::Resources::Timer.new(:task => "Backend Programming")
147
+ assert timer.task.is_a?(Harvest::Resources::Task)
148
+ assert_equal 1, timer.task.id
149
+ assert_equal 'Backend Programming', timer.task.name
150
+ end
151
+ end
152
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: supaspoida-harvest
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.2
4
+ version: 0.8.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kyle Banker
@@ -56,6 +56,7 @@ files:
56
56
  - lib/harvest/resources/project.rb
57
57
  - lib/harvest/resources/task.rb
58
58
  - lib/harvest/resources/task_assignment.rb
59
+ - lib/harvest/resources/timer.rb
59
60
  - lib/harvest/resources/user_assignment.rb
60
61
  - lib/harvest/base.rb
61
62
  - lib/harvest/harvest_resource.rb
@@ -97,6 +98,7 @@ test_files:
97
98
  - test/unit/resources/project_test.rb
98
99
  - test/unit/resources/task_test.rb
99
100
  - test/unit/resources/task_assignment_test.rb
101
+ - test/unit/resources/timer_test.rb
100
102
  - test/unit/resources/user_assignment_test.rb
101
103
  - test/integration/client_integration.rb
102
104
  - test/integration/task_integration.rb