summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorZack Hobson2013-12-22 23:28:00 -0800
committerZack Hobson2013-12-22 23:28:00 -0800
commit11310cefb8206487337b8040f9ceb70e570a06f7 (patch)
treef9b3624e4210616e48108ff650fa229c125d894d
parentd315a241dbd6c959085b26a413fc5e40683d63f0 (diff)
parentb8d0ef7022541999c2267fc7937f2a60e0da602c (diff)
downloadhcl-11310cefb8206487337b8040f9ceb70e570a06f7.tar.bz2
Merge pull request #46 from zenhob/faraday
Switch to a Faraday-based JSON API client for Harvest.
-rw-r--r--Gemfile1
-rw-r--r--Gemfile.lock16
-rw-r--r--hcl.gemspec3
-rw-r--r--lib/hcl.rb2
-rw-r--r--lib/hcl/app.rb10
-rw-r--r--lib/hcl/commands.rb3
-rw-r--r--lib/hcl/day_entry.rb22
-rw-r--r--lib/hcl/harvest_middleware.rb47
-rw-r--r--lib/hcl/net.rb48
-rw-r--r--lib/hcl/task.rb32
-rw-r--r--lib/hcl/timesheet_resource.rb88
-rw-r--r--test/app_test.rb6
-rw-r--r--test/day_entry_test.rb38
-rw-r--r--test/net_test.rb (renamed from test/timesheet_resource_test.rb)27
-rw-r--r--test/task_test.rb63
15 files changed, 173 insertions, 233 deletions
diff --git a/Gemfile b/Gemfile
index 432a0f7..b133fde 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,5 +1,6 @@
source "https://rubygems.org"
gemspec
+gem 'pry', group:['test','development']
# XXX this is dumb but it's crazy hard to get platform specfic deps into a gemspec
gem 'rubysl-abbrev', platform:'rbx'
gem 'rubysl-singleton', platform:'rbx'
diff --git a/Gemfile.lock b/Gemfile.lock
index 3e5e81e..5572f3d 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -3,20 +3,34 @@ PATH
specs:
hcl (0.4.9)
chronic
+ faraday
+ faraday_middleware
highline
+ multi_json
trollop
GEM
remote: https://rubygems.org/
specs:
chronic (0.10.2)
+ coderay (1.0.9)
fakeweb (1.3.0)
+ faraday (0.8.8)
+ multipart-post (~> 1.2.0)
+ faraday_middleware (0.9.0)
+ faraday (>= 0.7.4, < 0.9)
highline (1.6.20)
metaclass (0.0.1)
+ method_source (0.8.2)
minitest (4.7.5)
mocha (0.14.0)
metaclass (~> 0.0.1)
multi_json (1.8.2)
+ multipart-post (1.2.0)
+ pry (0.9.12.2)
+ coderay (~> 1.0.5)
+ method_source (~> 0.8)
+ slop (~> 3.4)
rake (10.1.0)
rubinius-coverage (2.0.3)
rubygems-tasks (0.2.4)
@@ -28,6 +42,7 @@ GEM
multi_json (~> 1.0)
simplecov-html (~> 0.7.1)
simplecov-html (0.7.1)
+ slop (3.4.7)
trollop (2.0)
yajl-ruby (1.1.0)
yard (0.8.7.3)
@@ -41,6 +56,7 @@ DEPENDENCIES
hcl!
minitest
mocha
+ pry
rake
rubinius-coverage
rubygems-tasks
diff --git a/hcl.gemspec b/hcl.gemspec
index ec2222b..4b769fc 100644
--- a/hcl.gemspec
+++ b/hcl.gemspec
@@ -18,6 +18,9 @@ Gem::Specification.new do |s|
s.add_runtime_dependency 'trollop'
s.add_runtime_dependency 'chronic'
s.add_runtime_dependency 'highline'
+ s.add_runtime_dependency 'faraday'
+ s.add_runtime_dependency 'faraday_middleware'
+ s.add_runtime_dependency 'multi_json'
s.add_development_dependency 'rake'
s.add_development_dependency 'rubygems-tasks'
s.add_development_dependency 'mocha'
diff --git a/lib/hcl.rb b/lib/hcl.rb
index 8f7be26..5d6f108 100644
--- a/lib/hcl.rb
+++ b/lib/hcl.rb
@@ -1,10 +1,12 @@
module HCl
autoload :VERSION, 'hcl/version'
autoload :App, 'hcl/app'
+ autoload :Net, 'hcl/net'
autoload :Commands, 'hcl/commands'
autoload :TimesheetResource, 'hcl/timesheet_resource'
autoload :Utility, 'hcl/utility'
autoload :Project, 'hcl/project'
autoload :Task, 'hcl/task'
autoload :DayEntry, 'hcl/day_entry'
+ autoload :HarvestMiddleware, 'hcl/harvest_middleware'
end
diff --git a/lib/hcl/app.rb b/lib/hcl/app.rb
index 1bceab8..1f641d5 100644
--- a/lib/hcl/app.rb
+++ b/lib/hcl/app.rb
@@ -62,15 +62,15 @@ module HCl
rescue SocketError => e
$stderr.puts "Connection failed. (#{e.message})"
exit 1
- rescue TimesheetResource::ThrottleFailure => e
+ rescue HarvestMiddleware::ThrottleFailure => e
$stderr.puts "Too many requests, retrying in #{e.retry_after+5} seconds..."
sleep e.retry_after+5
run
- rescue TimesheetResource::AuthFailure => e
+ rescue HarvestMiddleware::AuthFailure => e
$stderr.puts "Unable to authenticate: #{e}"
request_config
run
- rescue TimesheetResource::Failure => e
+ rescue HarvestMiddleware::Failure => e
$stderr.puts "API failure: #{e}"
exit 1
end
@@ -140,7 +140,7 @@ EOM
if has_security_command?
load_password config
end
- TimesheetResource.configure config
+ Net.configure config
else
request_config
end
@@ -153,7 +153,7 @@ EOM
config['password'] = ask("Password: ") { |q| q.echo = false }.to_s
config['subdomain'] = ask("Subdomain: ").to_s
config['ssl'] = /^y/.match(ask("Use SSL? (y/n): ").downcase)
- TimesheetResource.configure config
+ Net.configure config
write_config config
end
diff --git a/lib/hcl/commands.rb b/lib/hcl/commands.rb
index e9fd847..98daff5 100644
--- a/lib/hcl/commands.rb
+++ b/lib/hcl/commands.rb
@@ -7,8 +7,7 @@ module HCl
# Display a sanitized view of your auth credentials.
def config
- TimesheetResource.config_hash.merge(password:'***').
- map {|k,v| "#{k}: #{v}" }.join("\n")
+ Net.config_hash.merge(password:'***').map {|k,v| "#{k}: #{v}" }.join("\n")
end
def tasks project_code=nil
diff --git a/lib/hcl/day_entry.rb b/lib/hcl/day_entry.rb
index 6ef1708..645bb3d 100644
--- a/lib/hcl/day_entry.rb
+++ b/lib/hcl/day_entry.rb
@@ -1,5 +1,3 @@
-require 'rexml/document'
-
module HCl
class DayEntry < TimesheetResource
include Utility
@@ -8,7 +6,9 @@ module HCl
# defaults to today.
def self.all date = nil
url = date.nil? ? 'daily' : "daily/#{date.strftime '%j/%Y'}"
- from_xml get(url)
+ doc = Net.get url
+ Task.cache_tasks_hash doc
+ doc[:day_entries].map {|e| new e}
end
def to_s
@@ -19,19 +19,10 @@ module HCl
@data[:task]
end
- def self.from_xml xml
- doc = REXML::Document.new xml
- raise Failure, "No root node in XML document: #{xml}" if doc.root.nil?
- Task.cache_tasks doc
- doc.root.elements.collect('//day_entry') do |day|
- new xml_to_hash(day)
- end
- end
-
def cancel
begin
DayEntry.delete("daily/delete/#{id}")
- rescue TimesheetResource::Failure
+ rescue HarvestMiddleware::Failure
return false
end
true
@@ -46,8 +37,7 @@ module HCl
# If I don't include hours it gets reset.
# This doens't appear to be the case for task and project.
(self.notes << "\n#{new_notes}").lstrip!
- DayEntry.post "daily/update/#{id}",
- %{<request><notes>#{notes}</notes><hours>#{hours}</hours></request>}
+ Net.post "daily/update/#{id}", notes:notes, hours:hours
end
def self.with_timer date=nil
@@ -73,7 +63,7 @@ module HCl
end
def toggle
- DayEntry.get("daily/timer/#{id}")
+ Net.get("daily/timer/#{id}")
self
end
diff --git a/lib/hcl/harvest_middleware.rb b/lib/hcl/harvest_middleware.rb
new file mode 100644
index 0000000..4d742ed
--- /dev/null
+++ b/lib/hcl/harvest_middleware.rb
@@ -0,0 +1,47 @@
+require 'faraday_middleware/response_middleware'
+require 'multi_json'
+require 'cgi'
+
+class HCl::HarvestMiddleware < FaradayMiddleware::ResponseMiddleware
+ class Failure < StandardError; end
+ class AuthFailure < StandardError; end
+ class ThrottleFailure < StandardError
+ attr_reader :retry_after
+ def initialize env
+ @retry_after = env[:response_headers]['retry-after'].to_i
+ super "Too many requests! Try again in #{@retry_after} seconds."
+ end
+ end
+
+ def call(env)
+ @app.call(env).on_complete do |env|
+ case env[:status]
+ when 200..299
+ begin
+ env[:body] = unescape(MultiJson.load(env[:body], symbolize_keys:true))
+ rescue MultiJson::LoadError
+ env[:body]
+ end
+ when 300..399
+ raise Failure, "Redirected! Perhaps your ssl configuration variable is set incorrectly?"
+ when 400..499
+ raise AuthFailure, "Login failed."
+ when 503
+ raise ThrottleFailure, env
+ else
+ raise Failure, "Unexpected response from the upstream API."
+ end
+ end
+ end
+
+ def unescape obj
+ if obj.kind_of? Hash
+ obj.inject({}){|o,(k,v)| o[k] = unescape(v);o}
+ elsif obj.kind_of? Array
+ obj.inject([]){|o,v| o << unescape(v);o}
+ else
+ CGI.unescape_html(obj.to_s)
+ end
+ end
+
+end
diff --git a/lib/hcl/net.rb b/lib/hcl/net.rb
new file mode 100644
index 0000000..874240c
--- /dev/null
+++ b/lib/hcl/net.rb
@@ -0,0 +1,48 @@
+require 'faraday_middleware'
+
+module HCl
+ module Net
+ class << self
+ # configuration accessors
+ CONFIG_VARS = [ :login, :password, :subdomain, :ssl ].freeze
+ CONFIG_VARS.each { |config_var| attr_accessor config_var }
+
+ def config_hash
+ CONFIG_VARS.inject({}) {|c,k| c.update(k => send(k)) }
+ end
+
+ def configure opts = nil
+ if opts
+ self.login = opts['login']
+ self.password = opts['password']
+ self.subdomain = opts['subdomain']
+ self.ssl = opts['ssl']
+ end
+ end
+
+ def faraday
+ @faraday ||= Faraday.new(
+ "http#{ssl && 's'}://#{subdomain}.harvestapp.com"
+ ) do |f|
+ f.headers['Accept'] = 'application/json'
+ f.request :json
+ f.request :basic_auth, login, password
+ f.use HCl::HarvestMiddleware, content_type: /\bjson\b/
+ f.adapter Faraday.default_adapter
+ end
+ end
+
+ def get action
+ faraday.get(action).body
+ end
+
+ def post action, data
+ faraday.post(action, data).body
+ end
+
+ def delete action
+ faraday.delete(action).body
+ end
+ end
+ end
+end
diff --git a/lib/hcl/task.rb b/lib/hcl/task.rb
index 79b9917..e1d5b58 100644
--- a/lib/hcl/task.rb
+++ b/lib/hcl/task.rb
@@ -3,18 +3,13 @@ require 'fileutils'
module HCl
class Task < TimesheetResource
- def self.cache_tasks doc
- tasks = []
- doc.root.elements.collect('projects/project') do |project_elem|
- project = Project.new xml_to_hash(project_elem)
- tasks.concat(project_elem.elements.collect('tasks/task') do |task|
- new xml_to_hash(task).merge(:project => project)
- end)
- end
+ def self.cache_tasks_hash day_entry_hash
+ tasks = day_entry_hash[:projects].
+ map { |p| p[:tasks].map {|t| new t.merge(project:Project.new(p)) } }.flatten.uniq
unless tasks.empty?
FileUtils.mkdir_p(cache_dir)
File.open(cache_file, 'w') do |f|
- f.write tasks.uniq.to_yaml
+ f.write tasks.to_yaml
end
end
end
@@ -55,16 +50,13 @@ module HCl
def add opts
notes = opts[:note]
starting_time = opts[:starting_time] || 0
- days = DayEntry.from_xml Task.post("daily/add", <<-EOT)
- <request>
- <notes>#{notes}</notes>
- <hours>#{starting_time}</hours>
- <project_id type="integer">#{project.id}</project_id>
- <task_id type="integer">#{id}</task_id>
- <spent_at type="date">#{Date.today}</spent_at>
- </request>
- EOT
- days.first
+ DayEntry.new Net.post("daily/add", {
+ notes: notes,
+ hours: starting_time,
+ project_id: project.id,
+ task_id: id,
+ spent_at: Date.today
+ })
end
def start opts
@@ -72,7 +64,7 @@ module HCl
if day.running?
day
else
- DayEntry.from_xml(Task.get("daily/timer/#{day.id}")).first
+ DayEntry.new Net.get("daily/timer/#{day.id}")
end
end
end
diff --git a/lib/hcl/timesheet_resource.rb b/lib/hcl/timesheet_resource.rb
index 8fa7f5c..28e5378 100644
--- a/lib/hcl/timesheet_resource.rb
+++ b/lib/hcl/timesheet_resource.rb
@@ -1,90 +1,9 @@
-require 'net/http'
-require 'net/https'
-require 'cgi'
-
module HCl
class TimesheetResource
- class Failure < StandardError; end
- class AuthFailure < StandardError; end
- class ThrottleFailure < StandardError
- attr_reader :retry_after
- def initialize response
- @retry_after = response.headers['Retry-After'].to_i
- super "Too many requests! Try again in #{@retry_after} seconds."
- end
- end
-
- def self.configure opts = nil
- if opts
- self.login = opts['login']
- self.password = opts['password']
- self.subdomain = opts['subdomain']
- self.ssl = opts['ssl']
- end
- end
-
- # configuration accessors
- CONFIG_VARS = [ :login, :password, :subdomain, :ssl ].freeze
- CONFIG_VARS.each do |config_var|
- class_eval <<-EOC
- def self.#{config_var}= arg
- @@#{config_var} = arg
- end
- def self.#{config_var}
- @@#{config_var}
- end
- EOC
- end
-
- # @return [Hash]
- def self.config_hash
- CONFIG_VARS.inject({}) {|c,k| c.update(k => TimesheetResource.send(k)) }
- end
-
def initialize params
@data = params
end
- def self.get action
- http_do Net::HTTP::Get, action
- end
-
- def self.post action, data
- http_do Net::HTTP::Post, action, data
- end
-
- def self.delete action
- http_do Net::HTTP::Delete, action
- end
-
- def self.connect
- Net::HTTP.new("#{subdomain}.harvestapp.com", (ssl ? 443 : 80)).tap do |https|
- https.use_ssl = ssl
- https.verify_mode = OpenSSL::SSL::VERIFY_NONE if ssl
- end
- end
-
- def self.http_do method_class, action, data = nil
- https = connect
- request = method_class.new "/#{action}"
- request.basic_auth login, password
- request.content_type = 'application/xml'
- request['Accept'] = 'application/xml'
- response = https.request request, data
- case response
- when Net::HTTPSuccess
- response.body
- when Net::HTTPFound
- raise Failure, "Redirected! Perhaps your ssl configuration variable is set incorrectly?"
- when Net::HTTPServiceUnavailable
- raise ThrottleFailure, response
- when Net::HTTPUnauthorized
- raise AuthFailure, "Login failed."
- else
- raise Failure, "Unexpected response from the upstream API."
- end
- end
-
def id
@data[:id]
end
@@ -96,12 +15,5 @@ module HCl
def respond_to? method
(@data && @data.key?(method.to_sym)) || super
end
-
- def self.xml_to_hash elem
- elem.elements.map { |e| e.name }.inject({}) do |a, f|
- a[f.to_sym] = CGI.unescape_html(elem.elements[f].text || '') if elem.elements[f]
- a
- end
- end
end
end
diff --git a/test/app_test.rb b/test/app_test.rb
index 68e3782..1fc1073 100644
--- a/test/app_test.rb
+++ b/test/app_test.rb
@@ -23,7 +23,7 @@ class AppTest < HCl::TestCase
app = HCl::App.new
throttled = states('throttled').starts_as(false)
app.expects(:show).
- raises(HCl::TimesheetResource::ThrottleFailure, stub(headers:{'Retry-After' => 42})).
+ raises(HCl::HarvestMiddleware::ThrottleFailure, {response_headers:{'retry-after' => 42}}).
then(throttled.is(true))
app.expects(:sleep).with(47).when(throttled.is(true))
app.expects(:show).when(throttled.is(true))
@@ -48,7 +48,7 @@ class AppTest < HCl::TestCase
def test_configure_on_auth_failure
app = HCl::App.new
configured = states('configured').starts_as(false)
- app.expects(:show).raises(HCl::TimesheetResource::AuthFailure).when(configured.is(false))
+ app.expects(:show).raises(HCl::HarvestMiddleware::AuthFailure).when(configured.is(false))
app.expects(:ask).returns('xxx').times(4).when(configured.is(false))
app.expects(:write_config).then(configured.is(true))
app.expects(:show).when(configured.is(true))
@@ -58,7 +58,7 @@ class AppTest < HCl::TestCase
def test_api_failure
app = HCl::App.new
- app.expects(:show).raises(HCl::TimesheetResource::Failure)
+ app.expects(:show).raises(HCl::HarvestMiddleware::Failure)
app.expects(:exit).with(1)
app.process_args('show').run
assert_match /API failure/i, error_output
diff --git a/test/day_entry_test.rb b/test/day_entry_test.rb
index 581b22b..3e8be24 100644
--- a/test/day_entry_test.rb
+++ b/test/day_entry_test.rb
@@ -9,7 +9,7 @@ class DayEntryTest < HCl::TestCase
def test_cancel_failure
entry = HCl::DayEntry.new(id:123)
- HCl::DayEntry.expects(:delete).raises(HCl::TimesheetResource::Failure)
+ HCl::DayEntry.expects(:delete).raises(HCl::HarvestMiddleware::Failure)
assert !entry.cancel
end
@@ -19,48 +19,16 @@ class DayEntryTest < HCl::TestCase
assert_equal "Taco Town - Pizza Taco - Preparation (1:12)", entry.to_s
end
- def test_from_xml
- entries = HCl::DayEntry.from_xml(<<-EOD)
-<daily>
- <for_day type="date">Wed, 18 Oct 2006</for_day>
- <day_entries>
- <day_entry>
- <id type="integer">195168</id>
- <client>Iridesco</client>
- <project>Harvest</project>
- <task>Backend Programming</task>
- <hours type="float">2.06</hours>
- <notes>Test api support</notes>
- <timer_started_at type="datetime">
- Wed, 18 Oct 2006 09:53:06 -0000
- </timer_started_at>
- <created_at type="datetime">Wed, 18 Oct 2006 09:53:06 -0000</created_at>
- </day_entry>
- </day_entries>
-</daily>
- EOD
- assert_equal 1, entries.size
- {
- :project => 'Harvest',
- :client => 'Iridesco',
- :task => 'Backend Programming',
- :notes => 'Test api support',
- :hours => '2.06',
- }.each do |method, value|
- assert_equal value, entries.first.send(method)
- end
- end
-
def test_append_note
entry = HCl::DayEntry.new(:id => '1', :notes => 'yourmom.', :hours => '1.0')
- HCl::DayEntry.stubs(:post)
+ HCl::Net.stubs(:post)
entry.append_note('hi world')
assert_equal "yourmom.\nhi world", entry.notes
end
def test_append_note_to_empty
entry = HCl::DayEntry.new(:id => '1', :notes => nil, :hours => '1.0')
- HCl::DayEntry.stubs(:post)
+ HCl::Net.stubs(:post)
entry.append_note('hi world')
assert_equal 'hi world', entry.notes
end
diff --git a/test/timesheet_resource_test.rb b/test/net_test.rb
index 4153429..c4e39e1 100644
--- a/test/timesheet_resource_test.rb
+++ b/test/net_test.rb
@@ -1,10 +1,10 @@
require 'test_helper'
-class TimesheetResourceTest < HCl::TestCase
+class NetTest < HCl::TestCase
def setup
FakeWeb.allow_net_connect = false
- HCl::TimesheetResource.configure \
+ HCl::Net.configure \
'login' => 'bob',
'password' => 'secret',
'subdomain' => 'bobclock',
@@ -12,27 +12,30 @@ class TimesheetResourceTest < HCl::TestCase
end
def test_configure
- assert_equal 'bob', HCl::TimesheetResource.login
- assert_equal 'secret', HCl::TimesheetResource.password
- assert_equal 'bobclock', HCl::TimesheetResource.subdomain
- assert_equal true, HCl::TimesheetResource.ssl
+ assert_equal 'bob', HCl::Net.login
+ assert_equal 'secret', HCl::Net.password
+ assert_equal 'bobclock', HCl::Net.subdomain
+ assert_equal true, HCl::Net.ssl
end
def test_http_get
- FakeWeb.register_uri(:get, "https://bob:secret@bobclock.harvestapp.com/foo", :body => 'gotten!')
- body = HCl::TimesheetResource.get 'foo'
+ FakeWeb.register_uri(:get, "https://bob:secret@bobclock.harvestapp.com/foo",
+ :body => 'gotten!'.inspect)
+ body = HCl::Net.get 'foo'
assert_equal 'gotten!', body
end
def test_http_post
- FakeWeb.register_uri(:post, "https://bob:secret@bobclock.harvestapp.com/foo", :body => 'posted!')
- body = HCl::TimesheetResource.post 'foo', {pizza:'taco'}
+ FakeWeb.register_uri(:post, "https://bob:secret@bobclock.harvestapp.com/foo",
+ :body => 'posted!'.inspect)
+ body = HCl::Net.post 'foo', {pizza:'taco'}
assert_equal 'posted!', body
end
def test_http_delete
- FakeWeb.register_uri(:delete, "https://bob:secret@bobclock.harvestapp.com/foo", :body => 'wiped!')
- body = HCl::TimesheetResource.delete 'foo'
+ FakeWeb.register_uri(:delete, "https://bob:secret@bobclock.harvestapp.com/foo",
+ :body => 'wiped!'.inspect)
+ body = HCl::Net.delete 'foo'
assert_equal 'wiped!', body
end
end
diff --git a/test/task_test.rb b/test/task_test.rb
index f5c30b8..bbdce89 100644
--- a/test/task_test.rb
+++ b/test/task_test.rb
@@ -1,61 +1,20 @@
class TaskTest < HCl::TestCase
- DAILY_ENTRY = %{<daily>
- <for_day type="date">Wed, 18 Oct 2006</for_day>
- <day_entries>
- <day_entry>
- <id type="integer">195168</id>
- <client>Iridesco</client>
- <project>Harvest</project>
- <task>Backend Programming</task>
- <hours type="float">2.06</hours>
- <notes>Test api support</notes>
- <timer_started_at type="datetime">
- Wed, 18 Oct 2006 09:53:06 -0000
- </timer_started_at>
- <created_at type="datetime">Wed, 18 Oct 2006 09:53:06 -0000</created_at>
- </day_entry>
- </day_entries>
- </daily>}
-
- def test_add_task
- task = HCl::Task.new(id:456, project:HCl::Project.new(id:123))
- Date.expects(:today).returns('now')
- HCl::Task.expects(:post).with('daily/add', <<-EOT).returns(DAILY_ENTRY)
- <request>
- <notes>hi world</notes>
- <hours>0.5</hours>
- <project_id type="integer">123</project_id>
- <task_id type="integer">456</task_id>
- <spent_at type="date">now</spent_at>
- </request>
- EOT
- task.add note:'hi world', starting_time:0.5
- end
def test_cache_file
assert_equal "#{HCl::App::HCL_DIR}/cache/tasks.yml", HCl::Task.cache_file
end
- def test_cache_tasks
- HCl::Task.cache_tasks(REXML::Document.new(<<-EOD))
-<daily>
- <projects>
- <project>
- <name>Click and Type</name>
- <code></code>
- <id type="integer">3</id>
- <client>AFS</client>
- <tasks>
- <task>
- <name>Security support</name>
- <id type="integer">14</id>
- <billable type="boolean">true</billable>
- </task>
- </tasks>
- </project>
- </projects>
-</daily>
- EOD
+ def test_cache_tasks_hash
+ HCl::Task.cache_tasks_hash({ projects: [{
+ name: "Click and Type",
+ id: 3,
+ client: "AFS",
+ tasks: [{
+ name: "Security support",
+ id: 14,
+ billable: true
+ }]
+ }]})
assert_equal 1, HCl::Task.all.size
assert_equal 'Security support', HCl::Task.all.first.name
end