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.
- data/lib/harvest/resources/timer.rb +96 -0
- data/test/unit/resources/timer_test.rb +152 -0
- metadata +3 -1
@@ -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.
|
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
|