hcl 0.4.9 → 0.4.10

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1ea82cda49c0d4f4aa43038029ae70b5e22b9e18
4
- data.tar.gz: 3bf6719883563294afd3545ce02213abe468e7a5
3
+ metadata.gz: 0e94f259c4106d6c9fd541aea356ad7bcc14c8b9
4
+ data.tar.gz: 6c62d7d40bd19500585b8bfd8f2c1ef70a6861c7
5
5
  SHA512:
6
- metadata.gz: f0994f4ee68ce6b5adabb2de5cec31a8db29091fc02eba53929461015e639b152805ba5b42707c693a1bcf1efb200e0918f07c3b6e4522e33beb46187cda9aa8
7
- data.tar.gz: 1749a65a4eaaaa8c580573e5dab944ae026fb057bdaa33a3ae225569f1033d9cccada2c5efd77a7c0d1e5785d38fb154ef23d8943e70db48728c3ef0ad54b41f
6
+ metadata.gz: 1fbad2e2f47d19d6c4bed45cfc0ee74be7abbdfbaca0289891ed5bcc04a5ac55b51f2ea612575fc43b9006cb494303d21ac29252b427073b5b9a7470cd4c40b7
7
+ data.tar.gz: 0f98b72e9ec279307e13ee2edf6093725ea1ebd8d788201dab4f17bc00df3ff9da97a2a1ab9fff8909b9ecae287ee9ea1125cdf087b935028efa4df7247d2782
data/CHANGELOG CHANGED
@@ -1,5 +1,10 @@
1
1
  = Recent Changes in HCl
2
2
 
3
+ == next version
4
+
5
+ * added `config` command to display current credentials
6
+ * added `console` command for exploring the Harvest API
7
+
3
8
  == v0.4.9 2013-12-21
4
9
 
5
10
  * MacOS X: store password in default keychain
data/Gemfile CHANGED
@@ -1,5 +1,6 @@
1
1
  source "https://rubygems.org"
2
2
  gemspec
3
+ gem 'pry', group:['test','development']
3
4
  # XXX this is dumb but it's crazy hard to get platform specfic deps into a gemspec
4
5
  gem 'rubysl-abbrev', platform:'rbx'
5
6
  gem 'rubysl-singleton', platform:'rbx'
data/README.markdown CHANGED
@@ -3,11 +3,14 @@
3
3
  HCl is a command-line tool for interacting with Harvest time sheets using the
4
4
  [Harvest time tracking API][htt].
5
5
 
6
- [htt]: http://www.getharvest.com/api/time_tracking
6
+ [View this documentation online][rdoc].
7
7
 
8
8
  [![Build Status](https://travis-ci.org/zenhob/hcl.png?branch=master)](https://travis-ci.org/zenhob/hcl)
9
9
  [![Gem Version](https://badge.fury.io/rb/hcl.png)](http://badge.fury.io/rb/hcl)
10
10
 
11
+ [htt]: http://www.getharvest.com/api/time_tracking
12
+ [rdoc]: http://rdoc.info/github/zenhob/hcl/file/README.markdown
13
+
11
14
  ## Quick Start
12
15
 
13
16
  You can install hcl directly from rubygems.org:
@@ -30,6 +33,7 @@ or you can install from source:
30
33
  hcl alias <task_alias> <project_id> <task_id>
31
34
  hcl aliases
32
35
  hcl (cancel | nvm | oops)
36
+ hcl config
33
37
 
34
38
  ### Available Projects and Tasks
35
39
 
@@ -117,16 +121,42 @@ configuration:
117
121
 
118
122
  ### Configuration Profiles
119
123
 
120
- You can specify an alternate configuration directory in the environment as
121
- `HCL_DIR`. This can be used to easily interact with multiple harvest accounts.
124
+ You can modify your credentials with the `--reauth` option, and review them
125
+ with `hcl config`. If you'd rather store multiple configurations at
126
+ once, specify an alternate configuration directory in the environment as
127
+ `HCL_DIR`. This can be used to interact with multiple harvest accounts at
128
+ once.
129
+
122
130
  Here is a shell alias `myhcl` with a separate configuration from the
123
- main `hcl` command, including alias completion:
131
+ main `hcl` command, and another command to configure alias completion:
124
132
 
125
133
  alias myhcl="env HCL_DIR=~/.myhcl hcl"
126
134
  eval `myhcl completion myhcl`
127
135
 
128
- When using `myhcl` you can use different credentials and aliases, while
129
- `hcl` will continue to function with your original configuration.
136
+ Adding something like the above to your bashrc will enable a new command,
137
+ `myhcl`. When using `myhcl` you can use different credentials and aliases,
138
+ while `hcl` will continue to function with your original configuration.
139
+
140
+ ### Interactive Console
141
+
142
+ An interactive Ruby console is provided to allow you to use the fairly
143
+ powerful Harvest API client built into HCl, since not all of its
144
+ features are exposed via the command line. The current {HCl::App}
145
+ instance is available as `hcl`.
146
+
147
+ It's also possible to issue HCl commands directly (except `alias`, see
148
+ below), or to query specific JSON end points and have the results
149
+ pretty-printed:
150
+
151
+ hcl console
152
+ hcl> show "yesterday"
153
+ # => prints yesterday's timesheet, note the quotes!
154
+ hcl> hcl.http.get('daily')
155
+ # => displays a pretty-printed version of the JSON output
156
+
157
+ Note that the HCl internals may change without notice.
158
+ Also, commands (like `alias`) that are also reserved words in Ruby
159
+ can't be issued directly (use `send :alias` instead).
130
160
 
131
161
  ### Date Formats
132
162
 
data/lib/hcl.rb CHANGED
@@ -1,10 +1,13 @@
1
1
  module HCl
2
2
  autoload :VERSION, 'hcl/version'
3
3
  autoload :App, 'hcl/app'
4
+ autoload :Net, 'hcl/net'
4
5
  autoload :Commands, 'hcl/commands'
6
+ autoload :Console, 'hcl/console'
5
7
  autoload :TimesheetResource, 'hcl/timesheet_resource'
6
8
  autoload :Utility, 'hcl/utility'
7
9
  autoload :Project, 'hcl/project'
8
10
  autoload :Task, 'hcl/task'
9
11
  autoload :DayEntry, 'hcl/day_entry'
12
+ autoload :HarvestMiddleware, 'hcl/harvest_middleware'
10
13
  end
data/lib/hcl/app.rb CHANGED
@@ -9,11 +9,11 @@ module HCl
9
9
  include HCl::Utility
10
10
  include HCl::Commands
11
11
 
12
- HCL_DIR = ENV['HCL_DIR'] || "#{ENV['HOME']}/.hcl"
13
- SETTINGS_FILE = "#{HCL_DIR}/settings.yml"
14
- CONFIG_FILE = "#{HCL_DIR}/config.yml"
15
- OLD_SETTINGS_FILE = "#{ENV['HOME']}/.hcl_settings"
16
- OLD_CONFIG_FILE = "#{ENV['HOME']}/.hcl_config"
12
+ HCL_DIR = (ENV['HCL_DIR'] || "#{ENV['HOME']}/.hcl").freeze
13
+ SETTINGS_FILE = "#{HCL_DIR}/settings.yml".freeze
14
+ CONFIG_FILE = "#{HCL_DIR}/config.yml".freeze
15
+
16
+ attr_reader :http
17
17
 
18
18
  def initialize
19
19
  FileUtils.mkdir_p(HCL_DIR)
@@ -64,15 +64,15 @@ module HCl
64
64
  rescue SocketError => e
65
65
  $stderr.puts "Connection failed. (#{e.message})"
66
66
  exit 1
67
- rescue TimesheetResource::ThrottleFailure => e
67
+ rescue HarvestMiddleware::ThrottleFailure => e
68
68
  $stderr.puts "Too many requests, retrying in #{e.retry_after+5} seconds..."
69
69
  sleep e.retry_after+5
70
70
  run
71
- rescue TimesheetResource::AuthFailure => e
71
+ rescue HarvestMiddleware::AuthFailure => e
72
72
  $stderr.puts "Unable to authenticate: #{e}"
73
73
  request_config
74
74
  run
75
- rescue TimesheetResource::Failure => e
75
+ rescue HarvestMiddleware::Failure => e
76
76
  $stderr.puts "API failure: #{e}"
77
77
  exit 1
78
78
  end
@@ -142,11 +142,7 @@ EOM
142
142
  if has_security_command?
143
143
  load_password config
144
144
  end
145
- TimesheetResource.configure config
146
- elsif File.exists? OLD_CONFIG_FILE
147
- config = YAML::load File.read(OLD_CONFIG_FILE)
148
- TimesheetResource.configure config
149
- write_config config
145
+ @http = HCl::Net.new config
150
146
  else
151
147
  request_config
152
148
  end
@@ -158,8 +154,8 @@ EOM
158
154
  config['login'] = ask("Email Address: ").to_s
159
155
  config['password'] = ask("Password: ") { |q| q.echo = false }.to_s
160
156
  config['subdomain'] = ask("Subdomain: ").to_s
161
- config['ssl'] = %w(y yes).include?(ask("Use SSL? (y/n): ").downcase)
162
- TimesheetResource.configure config
157
+ config['ssl'] = /^y/.match(ask("Use SSL? (y/n): ").downcase)
158
+ @http = HCl::Net.new config
163
159
  write_config config
164
160
  end
165
161
 
@@ -177,9 +173,6 @@ EOM
177
173
  def read_settings
178
174
  if File.exists? SETTINGS_FILE
179
175
  @settings = YAML.load(File.read(SETTINGS_FILE))
180
- elsif File.exists? OLD_SETTINGS_FILE
181
- @settings = YAML.load(File.read(OLD_SETTINGS_FILE))
182
- write_settings
183
176
  else
184
177
  @settings = {}
185
178
  end
data/lib/hcl/commands.rb CHANGED
@@ -5,10 +5,20 @@ module HCl
5
5
  module Commands
6
6
  class Error < StandardError; end
7
7
 
8
+ # Display a sanitized view of your auth credentials.
9
+ def config
10
+ http.config_hash.merge(password:'***').map {|k,v| "#{k}: #{v}" }.join("\n")
11
+ end
12
+
13
+ def console
14
+ Console.new(self)
15
+ nil
16
+ end
17
+
8
18
  def tasks project_code=nil
9
19
  tasks = Task.all
10
20
  if tasks.empty? # cache tasks
11
- DayEntry.all
21
+ DayEntry.today
12
22
  tasks = Task.all
13
23
  end
14
24
  tasks.select! {|t| t.project.code == project_code } if project_code
@@ -33,9 +43,9 @@ module HCl
33
43
  end
34
44
 
35
45
  def cancel
36
- entry = DayEntry.with_timer || DayEntry.last
46
+ entry = DayEntry.with_timer(http) || DayEntry.last(http)
37
47
  if entry
38
- if entry.cancel
48
+ if entry.cancel http
39
49
  "Deleted entry #{entry}."
40
50
  else
41
51
  fail "Failed to delete #{entry}!"
@@ -67,7 +77,8 @@ module HCl
67
77
  end
68
78
  end
69
79
 
70
- def completion command=$PROGRAM_NAME
80
+ def completion command=nil
81
+ command ||= $PROGRAM_NAME.split('/').last
71
82
  %[complete -W "#{aliases.join ' '}" #{command}]
72
83
  end
73
84
 
@@ -81,23 +92,23 @@ module HCl
81
92
  if task.nil?
82
93
  fail "Unknown task alias, try one of the following: ", aliases.join(', ')
83
94
  end
84
- timer = task.start \
95
+ timer = task.start http,
85
96
  :starting_time => starting_time,
86
97
  :note => args.join(' ')
87
98
  "Started timer for #{timer} (at #{current_time})"
88
99
  end
89
100
 
90
101
  def log *args
91
- fail "There is already a timer running." if DayEntry.with_timer
102
+ fail "There is already a timer running." if DayEntry.with_timer(http)
92
103
  start *args
93
104
  stop
94
105
  end
95
106
 
96
107
  def stop *args
97
- entry = DayEntry.with_timer || DayEntry.with_timer(DateTime.yesterday)
108
+ entry = DayEntry.with_timer(http) || DayEntry.with_timer(http, DateTime.yesterday)
98
109
  if entry
99
- entry.append_note(args.join(' ')) if args.any?
100
- entry.toggle
110
+ entry.append_note(http, args.join(' ')) if args.any?
111
+ entry.toggle http
101
112
  "Stopped #{entry} (at #{current_time})"
102
113
  else
103
114
  fail "No running timers found."
@@ -105,12 +116,12 @@ module HCl
105
116
  end
106
117
 
107
118
  def note *args
108
- entry = DayEntry.with_timer
119
+ entry = DayEntry.with_timer http
109
120
  if entry
110
121
  if args.empty?
111
122
  return entry.notes
112
123
  else
113
- entry.append_note args.join(' ')
124
+ entry.append_note http, args.join(' ')
114
125
  "Added note to #{entry}."
115
126
  end
116
127
  else
@@ -122,7 +133,7 @@ module HCl
122
133
  date = args.empty? ? nil : Chronic.parse(args.join(' '))
123
134
  total_hours = 0.0
124
135
  result = ''
125
- DayEntry.all(date).each do |day|
136
+ DayEntry.daily(http, date).each do |day|
126
137
  running = day.running? ? '(running) ' : ''
127
138
  columns = HighLine::SystemExtensions.terminal_size[0] rescue 80
128
139
  result << "\t#{day.formatted_hours}\t#{running}#{day.project}: #{day.notes.lines.to_a.last}\n"[0..columns-1]
@@ -136,12 +147,12 @@ module HCl
136
147
  ident = get_ident args
137
148
  entry = if ident
138
149
  task_ids = get_task_ids ident, args
139
- DayEntry.last_by_task *task_ids
150
+ DayEntry.last_by_task http, *task_ids
140
151
  else
141
- DayEntry.last
152
+ DayEntry.last(http)
142
153
  end
143
154
  if entry
144
- entry.toggle
155
+ entry.toggle http
145
156
  else
146
157
  fail "No matching timer found."
147
158
  end
@@ -0,0 +1,22 @@
1
+ require 'pp'
2
+ require 'pry'
3
+
4
+ module HCl
5
+ class Console
6
+ attr_reader :hcl
7
+ def initialize app
8
+ @hcl = app
9
+ prompt = $PROGRAM_NAME.split('/').last + "> "
10
+ columns = HighLine::SystemExtensions.terminal_size[0] rescue 80
11
+ binding.pry quiet: true,
12
+ prompt:[->(a,b,c){ prompt }],
13
+ print:->(io, *p){ PP.pp p, io, columns }
14
+ end
15
+
16
+ Commands.instance_methods.each do |command|
17
+ define_method command do |*args|
18
+ puts @hcl.send(command, *args)
19
+ end
20
+ end
21
+ end
22
+ end
data/lib/hcl/day_entry.rb CHANGED
@@ -1,15 +1,11 @@
1
- require 'rexml/document'
2
-
3
1
  module HCl
4
2
  class DayEntry < TimesheetResource
5
3
  include Utility
6
4
 
7
- # Get the time sheet entries for a given day. If no date is provided
8
- # defaults to today.
9
- def self.all date = nil
10
- url = date.nil? ? 'daily' : "daily/#{date.strftime '%j/%Y'}"
11
- from_xml get(url)
12
- end
5
+ collection_name :day_entries
6
+ resources :today, 'daily', load_cb:->(data) { Task.cache_tasks_hash data }
7
+ resources(:daily) {|date| date ? "daily/#{date.strftime '%j/%Y'}" : 'daily' }
8
+ resource(:project_info, class_name:'Project') { "projects/#{project_id}" }
13
9
 
14
10
  def to_s
15
11
  "#{client} - #{project} - #{task} (#{formatted_hours})"
@@ -19,19 +15,10 @@ module HCl
19
15
  @data[:task]
20
16
  end
21
17
 
22
- def self.from_xml xml
23
- doc = REXML::Document.new xml
24
- raise Failure, "No root node in XML document: #{xml}" if doc.root.nil?
25
- Task.cache_tasks doc
26
- doc.root.elements.collect('//day_entry') do |day|
27
- new xml_to_hash(day)
28
- end
29
- end
30
-
31
- def cancel
18
+ def cancel http
32
19
  begin
33
- DayEntry.delete("daily/delete/#{id}")
34
- rescue TimesheetResource::Failure
20
+ http.delete("daily/delete/#{id}")
21
+ rescue HarvestMiddleware::Failure
35
22
  return false
36
23
  end
37
24
  true
@@ -42,38 +29,32 @@ module HCl
42
29
  end
43
30
 
44
31
  # Append a string to the notes for this task.
45
- def append_note new_notes
32
+ def append_note http, new_notes
46
33
  # If I don't include hours it gets reset.
47
34
  # This doens't appear to be the case for task and project.
48
35
  (self.notes << "\n#{new_notes}").lstrip!
49
- DayEntry.post "daily/update/#{id}",
50
- %{<request><notes>#{notes}</notes><hours>#{hours}</hours></request>}
36
+ http.post "daily/update/#{id}", notes:notes, hours:hours
51
37
  end
52
38
 
53
- def self.with_timer date=nil
54
- all(date).detect {|t| t.running? }
39
+ def self.with_timer http, date=nil
40
+ daily(http, date).detect {|t| t.running? }
55
41
  end
56
42
 
57
- def self.last_by_task project_id, task_id
58
- all.sort {|a,b| b.updated_at<=>a.updated_at}.
43
+ def self.last_by_task http, project_id, task_id
44
+ today(http).sort {|a,b| b.updated_at<=>a.updated_at}.
59
45
  detect {|t| t.project_id == project_id && t.task_id == task_id }
60
46
  end
61
47
 
62
- def self.last
63
- all.sort {|a,b| a.updated_at<=>b.updated_at}[-1]
48
+ def self.last http
49
+ today(http).sort {|a,b| a.updated_at<=>b.updated_at}[-1]
64
50
  end
65
51
 
66
52
  def running?
67
53
  !@data[:timer_started_at].nil? && !@data[:timer_started_at].empty?
68
54
  end
69
55
 
70
- def initialize *args
71
- super
72
- # TODO cache client/project names and ids
73
- end
74
-
75
- def toggle
76
- DayEntry.get("daily/timer/#{id}")
56
+ def toggle http
57
+ http.get("daily/timer/#{id}")
77
58
  self
78
59
  end
79
60
 
@@ -0,0 +1,58 @@
1
+ require 'faraday'
2
+
3
+ class HCl::HarvestMiddleware < Faraday::Request::BasicAuthentication
4
+ Faraday.register_middleware harvest: ->{ self }
5
+ MIME_TYPE = 'application/json'.freeze
6
+
7
+ dependency do
8
+ require 'yajl'
9
+ require 'escape_utils'
10
+ end
11
+
12
+ def call(env)
13
+ # encode with and accept json
14
+ env[:request_headers]['Accept'] = MIME_TYPE
15
+ env[:request_headers]['Content-Type'] = MIME_TYPE
16
+ env[:body] = Yajl::Encoder.encode(env[:body])
17
+
18
+ # response processing
19
+ super(env).on_complete do |env|
20
+ case env[:status]
21
+ when 200..299
22
+ begin
23
+ env[:body] = deep_html_unescape(Yajl::Parser.parse(env[:body], symbolize_keys:true))
24
+ rescue Yajl::ParseError
25
+ env[:body]
26
+ end
27
+ when 300..399
28
+ raise Failure, "Redirected! Perhaps your ssl configuration variable is set incorrectly?"
29
+ when 400..499
30
+ raise AuthFailure, "Login failed."
31
+ when 503
32
+ raise ThrottleFailure, env
33
+ else
34
+ raise Failure, "Unexpected response from the upstream API."
35
+ end
36
+ end
37
+ end
38
+
39
+ def deep_html_unescape obj
40
+ if obj.kind_of? Hash
41
+ obj.inject({}){|o,(k,v)| o.update(k => deep_html_unescape(v)) }
42
+ elsif obj.kind_of? Array
43
+ obj.inject([]){|o,v| o << deep_html_unescape(v) }
44
+ else
45
+ EscapeUtils.unescape_html(obj.to_s)
46
+ end
47
+ end
48
+
49
+ class Failure < StandardError; end
50
+ class AuthFailure < StandardError; end
51
+ class ThrottleFailure < StandardError
52
+ attr_reader :retry_after
53
+ def initialize env
54
+ @retry_after = env[:response_headers]['retry-after'].to_i
55
+ super "Too many requests! Try again in #{@retry_after} seconds."
56
+ end
57
+ end
58
+ end
data/lib/hcl/net.rb ADDED
@@ -0,0 +1,39 @@
1
+ require 'hcl/harvest_middleware'
2
+ require 'faraday'
3
+
4
+ module HCl
5
+ class Net
6
+ # configuration accessors
7
+ CONFIG_VARS = [ :login, :password, :subdomain, :ssl ].freeze.
8
+ each { |config_var| attr_reader config_var }
9
+
10
+ def config_hash
11
+ CONFIG_VARS.inject({}) {|c,k| c.update(k => send(k)) }
12
+ end
13
+
14
+ def initialize opts
15
+ @login = opts['login'].freeze
16
+ @password = opts['password'].freeze
17
+ @subdomain = opts['subdomain'].freeze
18
+ @ssl = !!opts['ssl']
19
+ @http = Faraday.new(
20
+ "http#{ssl ? 's' : '' }://#{subdomain}.harvestapp.com"
21
+ ) do |f|
22
+ f.use :harvest, login, password
23
+ f.adapter Faraday.default_adapter
24
+ end
25
+ end
26
+
27
+ def get action
28
+ @http.get(action).body
29
+ end
30
+
31
+ def post action, data
32
+ @http.post(action, data).body
33
+ end
34
+
35
+ def delete action
36
+ @http.delete(action).body
37
+ end
38
+ end
39
+ end
data/lib/hcl/project.rb CHANGED
@@ -1,3 +1,4 @@
1
1
  class HCl::Project < HCl::TimesheetResource
2
+ collection_name :projects
2
3
  end
3
4
 
data/lib/hcl/task.rb CHANGED
@@ -3,18 +3,13 @@ require 'fileutils'
3
3
 
4
4
  module HCl
5
5
  class Task < TimesheetResource
6
- def self.cache_tasks doc
7
- tasks = []
8
- doc.root.elements.collect('projects/project') do |project_elem|
9
- project = Project.new xml_to_hash(project_elem)
10
- tasks.concat(project_elem.elements.collect('tasks/task') do |task|
11
- new xml_to_hash(task).merge(:project => project)
12
- end)
13
- end
6
+ def self.cache_tasks_hash day_entry_hash
7
+ tasks = day_entry_hash[:projects].
8
+ map { |p| p[:tasks].map {|t| new t.merge(project:Project.new(p)) } }.flatten.uniq
14
9
  unless tasks.empty?
15
10
  FileUtils.mkdir_p(cache_dir)
16
11
  File.open(cache_file, 'w') do |f|
17
- f.write tasks.uniq.to_yaml
12
+ f.write tasks.to_yaml
18
13
  end
19
14
  end
20
15
  end
@@ -52,27 +47,24 @@ module HCl
52
47
  end
53
48
  end
54
49
 
55
- def add opts
50
+ def add http, opts
56
51
  notes = opts[:note]
57
52
  starting_time = opts[:starting_time] || 0
58
- days = DayEntry.from_xml Task.post("daily/add", <<-EOT)
59
- <request>
60
- <notes>#{notes}</notes>
61
- <hours>#{starting_time}</hours>
62
- <project_id type="integer">#{project.id}</project_id>
63
- <task_id type="integer">#{id}</task_id>
64
- <spent_at type="date">#{Date.today}</spent_at>
65
- </request>
66
- EOT
67
- days.first
53
+ DayEntry.new http.post("daily/add", {
54
+ notes: notes,
55
+ hours: starting_time,
56
+ project_id: project.id,
57
+ task_id: id,
58
+ spent_at: Date.today
59
+ })
68
60
  end
69
61
 
70
- def start opts
71
- day = add opts
62
+ def start http, opts
63
+ day = add http, opts
72
64
  if day.running?
73
65
  day
74
66
  else
75
- DayEntry.from_xml(Task.get("daily/timer/#{day.id}")).first
67
+ DayEntry.new http.get("daily/timer/#{day.id}")
76
68
  end
77
69
  end
78
70
  end
@@ -1,84 +1,9 @@
1
- require 'net/http'
2
- require 'net/https'
3
- require 'cgi'
4
-
5
1
  module HCl
6
2
  class TimesheetResource
7
- class Failure < StandardError; end
8
- class AuthFailure < StandardError; end
9
- class ThrottleFailure < StandardError
10
- attr_reader :retry_after
11
- def initialize response
12
- @retry_after = response.headers['Retry-After'].to_i
13
- super "Too many requests! Try again in #{@retry_after} seconds."
14
- end
15
- end
16
-
17
- def self.configure opts = nil
18
- if opts
19
- self.login = opts['login']
20
- self.password = opts['password']
21
- self.subdomain = opts['subdomain']
22
- self.ssl = opts['ssl']
23
- end
24
- end
25
-
26
- # configuration accessors
27
- %w[ login password subdomain ssl ].each do |config_var|
28
- class_eval <<-EOC
29
- def self.#{config_var}= arg
30
- @@#{config_var} = arg
31
- end
32
- def self.#{config_var}
33
- @@#{config_var}
34
- end
35
- EOC
36
- end
37
-
38
3
  def initialize params
39
4
  @data = params
40
5
  end
41
6
 
42
- def self.get action
43
- http_do Net::HTTP::Get, action
44
- end
45
-
46
- def self.post action, data
47
- http_do Net::HTTP::Post, action, data
48
- end
49
-
50
- def self.delete action
51
- http_do Net::HTTP::Delete, action
52
- end
53
-
54
- def self.connect
55
- Net::HTTP.new("#{subdomain}.harvestapp.com", (ssl ? 443 : 80)).tap do |https|
56
- https.use_ssl = ssl
57
- https.verify_mode = OpenSSL::SSL::VERIFY_NONE if ssl
58
- end
59
- end
60
-
61
- def self.http_do method_class, action, data = nil
62
- https = connect
63
- request = method_class.new "/#{action}"
64
- request.basic_auth login, password
65
- request.content_type = 'application/xml'
66
- request['Accept'] = 'application/xml'
67
- response = https.request request, data
68
- case response
69
- when Net::HTTPSuccess
70
- response.body
71
- when Net::HTTPFound
72
- raise Failure, "Redirected! Perhaps your ssl configuration variable is set incorrectly?"
73
- when Net::HTTPServiceUnavailable
74
- raise ThrottleFailure, response
75
- when Net::HTTPUnauthorized
76
- raise AuthFailure, "Login failed."
77
- else
78
- raise Failure, "Unexpected response from the upstream API."
79
- end
80
- end
81
-
82
7
  def id
83
8
  @data[:id]
84
9
  end
@@ -91,10 +16,52 @@ module HCl
91
16
  (@data && @data.key?(method.to_sym)) || super
92
17
  end
93
18
 
94
- def self.xml_to_hash elem
95
- elem.elements.map { |e| e.name }.inject({}) do |a, f|
96
- a[f.to_sym] = CGI.unescape_html(elem.elements[f].text || '') if elem.elements[f]
97
- a
19
+ class << self
20
+ def _prepare_resource name, *args, &url_cb
21
+ ((@resources ||= {})[name] = {}).tap do |res|
22
+ opt_or_cb = args.pop
23
+ res[:url_cb] = url_cb
24
+ res[:opts] = {}
25
+ case opt_or_cb
26
+ when String
27
+ res[:url_cb] = ->() { opt_or_cb }
28
+ res[:opts] = args.pop || {}
29
+ when Hash
30
+ res[:opts] = opt_or_cb
31
+ url = args.pop
32
+ res[:url_cb] = ->() { url } if url
33
+ end
34
+ end
35
+ end
36
+
37
+ def resources name, *args, &url_cb
38
+ res = _prepare_resource name, *args, &url_cb
39
+ cls = res[:opts][:class_name] ? HCl.const_get(res[:opts][:class_name]) : self
40
+ method = cls == self ? :define_singleton_method : :define_method
41
+ send(method, name) do |http, *args|
42
+ url = instance_exec *args, &res[:url_cb]
43
+ cb = res[:opts][:load_cb]
44
+ http.get(url).tap{|e| cb.call(e) if cb }[cls.collection_name].map{|e|new(e)}
45
+ end
46
+ end
47
+
48
+ def resource name, *args, &url_cb
49
+ res = _prepare_resource name, *args, &url_cb
50
+ cls = res[:opts][:class_name] ? HCl.const_get(res[:opts][:class_name]) : self
51
+ method = cls == self ? :define_singleton_method : :define_method
52
+ send(method, name) do |http, *args|
53
+ url = instance_exec *args, &res[:url_cb]
54
+ cb = res[:opts][:load_cb]
55
+ cls.new http.get(url).tap{|e| cb.call(e) if cb }[cls.underscore_name]
56
+ end
57
+ end
58
+
59
+ def underscore_name
60
+ @underscore_name ||= name.split('::').last.split(/(?=[A-Z])/).map(&:downcase).join('_').to_sym
61
+ end
62
+
63
+ def collection_name name=nil
64
+ name ? (@collection_name = name) : @collection_name
98
65
  end
99
66
  end
100
67
  end
data/lib/hcl/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module HCl
2
- VERSION = '0.4.9'
2
+ VERSION = '0.4.10'
3
3
  end
data/test/app_test.rb CHANGED
@@ -2,6 +2,7 @@ require 'test_helper'
2
2
  class AppTest < HCl::TestCase
3
3
 
4
4
  def setup
5
+ super
5
6
  # touch config to avoid triggering manual config
6
7
  FileUtils.mkdir_p HCl::App::HCL_DIR
7
8
  FileUtils.touch File.join(HCl::App::HCL_DIR, "config.yml")
@@ -13,7 +14,7 @@ class AppTest < HCl::TestCase
13
14
  end
14
15
 
15
16
  def test_command_show
16
- HCl::DayEntry.expects(:all).returns [HCl::DayEntry.new(
17
+ HCl::DayEntry.expects(:daily).returns [HCl::DayEntry.new(
17
18
  hours:'2.06', notes:'hi world', project:'App'
18
19
  )]
19
20
  HCl::App.command 'show'
@@ -23,7 +24,7 @@ class AppTest < HCl::TestCase
23
24
  app = HCl::App.new
24
25
  throttled = states('throttled').starts_as(false)
25
26
  app.expects(:show).
26
- raises(HCl::TimesheetResource::ThrottleFailure, stub(headers:{'Retry-After' => 42})).
27
+ raises(HCl::HarvestMiddleware::ThrottleFailure, {response_headers:{'retry-after' => 42}}).
27
28
  then(throttled.is(true))
28
29
  app.expects(:sleep).with(47).when(throttled.is(true))
29
30
  app.expects(:show).when(throttled.is(true))
@@ -48,7 +49,7 @@ class AppTest < HCl::TestCase
48
49
  def test_configure_on_auth_failure
49
50
  app = HCl::App.new
50
51
  configured = states('configured').starts_as(false)
51
- app.expects(:show).raises(HCl::TimesheetResource::AuthFailure).when(configured.is(false))
52
+ app.expects(:show).raises(HCl::HarvestMiddleware::AuthFailure).when(configured.is(false))
52
53
  app.expects(:ask).returns('xxx').times(4).when(configured.is(false))
53
54
  app.expects(:write_config).then(configured.is(true))
54
55
  app.expects(:show).when(configured.is(true))
@@ -58,7 +59,7 @@ class AppTest < HCl::TestCase
58
59
 
59
60
  def test_api_failure
60
61
  app = HCl::App.new
61
- app.expects(:show).raises(HCl::TimesheetResource::Failure)
62
+ app.expects(:show).raises(HCl::HarvestMiddleware::Failure)
62
63
  app.expects(:exit).with(1)
63
64
  app.process_args('show').run
64
65
  assert_match /API failure/i, error_output
data/test/command_test.rb CHANGED
@@ -4,6 +4,7 @@ class CommandTest < HCl::TestCase
4
4
  include HCl::Utility
5
5
 
6
6
  def setup
7
+ super
7
8
  @settings = {}
8
9
  end
9
10
 
@@ -34,7 +35,7 @@ class CommandTest < HCl::TestCase
34
35
  end
35
36
 
36
37
  def test_show
37
- HCl::DayEntry.expects(:all).returns([HCl::DayEntry.new({
38
+ HCl::DayEntry.expects(:daily).returns([HCl::DayEntry.new({
38
39
  hours:'2.06',
39
40
  notes: 'hi world',
40
41
  project: 'App'
@@ -68,15 +69,15 @@ class CommandTest < HCl::TestCase
68
69
  project: HCl::Project.new(id:456, name:'App', client:'Bob', code:'b')
69
70
  )
70
71
  HCl::Task.expects(:find).with('456','123').returns(task)
71
- task.expects(:start).with(starting_time:nil, note:'do stuff')
72
+ task.expects(:start).with(http, starting_time:nil, note:'do stuff')
72
73
  start *%w[ 456 123 do stuff ]
73
74
  end
74
75
 
75
76
  def test_stop
76
77
  entry = stub
77
- HCl::DayEntry.expects(:with_timer).returns(entry)
78
- entry.expects(:append_note).with('all done')
79
- entry.expects(:toggle)
78
+ register_uri(:get, '/daily', {day_entries:[{id:123,notes:'',hours:1,client:nil,project:nil,timer_started_at:DateTime.now}]})
79
+ register_uri(:post, '/daily/update/123', {day_entry:{notes:'all done'}})
80
+ register_uri(:get, '/daily/timer/123')
80
81
  stop 'all done'
81
82
  end
82
83
 
@@ -90,22 +91,22 @@ class CommandTest < HCl::TestCase
90
91
  def test_resume_with_task_alias
91
92
  entry = stub
92
93
  expects(:get_task_ids).with('mytask',[]).returns(%w[ 456 789 ])
93
- HCl::DayEntry.expects(:last_by_task).with('456', '789').returns(entry)
94
- entry.expects(:toggle)
94
+ HCl::DayEntry.expects(:last_by_task).with(http, '456', '789').returns(entry)
95
+ entry.expects(:toggle).with(http)
95
96
  resume 'mytask'
96
97
  end
97
98
 
98
99
  def test_cancel
99
100
  entry = stub
100
- HCl::DayEntry.expects(:with_timer).returns(entry)
101
- entry.expects(:cancel).returns(true)
101
+ HCl::DayEntry.expects(:with_timer).with(http).returns(entry)
102
+ entry.expects(:cancel).with(http).returns(true)
102
103
  cancel
103
104
  end
104
105
 
105
106
  def test_note
106
107
  entry = stub
107
108
  HCl::DayEntry.expects(:with_timer).returns(entry)
108
- entry.expects(:append_note).with('hi world')
109
+ entry.expects(:append_note).with(http, 'hi world')
109
110
  note 'hi world'
110
111
  end
111
112
 
@@ -1,49 +1,62 @@
1
1
  require 'test_helper'
2
2
 
3
3
  class DayEntryTest < HCl::TestCase
4
- def test_from_xml
5
- entries = HCl::DayEntry.from_xml(<<-EOD)
6
- <daily>
7
- <for_day type="date">Wed, 18 Oct 2006</for_day>
8
- <day_entries>
9
- <day_entry>
10
- <id type="integer">195168</id>
11
- <client>Iridesco</client>
12
- <project>Harvest</project>
13
- <task>Backend Programming</task>
14
- <hours type="float">2.06</hours>
15
- <notes>Test api support</notes>
16
- <timer_started_at type="datetime">
17
- Wed, 18 Oct 2006 09:53:06 -0000
18
- </timer_started_at>
19
- <created_at type="datetime">Wed, 18 Oct 2006 09:53:06 -0000</created_at>
20
- </day_entry>
21
- </day_entries>
22
- </daily>
23
- EOD
24
- assert_equal 1, entries.size
25
- {
26
- :project => 'Harvest',
27
- :client => 'Iridesco',
28
- :task => 'Backend Programming',
29
- :notes => 'Test api support',
30
- :hours => '2.06',
31
- }.each do |method, value|
32
- assert_equal value, entries.first.send(method)
33
- end
4
+ def test_project_info
5
+ register_uri(:get, '/daily', {projects:[], day_entries:[{project_id:123}]})
6
+ register_uri(:get, '/projects/123', {project:{name:'fun times'}})
7
+ assert_equal 'fun times', HCl::DayEntry.today(http).first.project_info(http).name
8
+ end
9
+
10
+ def test_all_today_empty
11
+ register_uri(:get, '/daily', {projects:[],day_entries:[]})
12
+ assert HCl::DayEntry.today(http).empty?
13
+ end
14
+
15
+ def test_all_today
16
+ register_uri(:get, '/daily', {projects:[], day_entries:[{id:1,note:'hi'}]})
17
+ assert_equal 'hi', HCl::DayEntry.today(http).first.note
18
+ end
19
+
20
+ def test_all_with_date
21
+ register_uri(:get, '/daily/013/2013', {projects:[], day_entries:[{id:1,note:'hi'}]})
22
+ assert_equal 'hi', HCl::DayEntry.daily(http,Date.civil(2013,1,13)).first.note
23
+ end
24
+
25
+ def test_toggle
26
+ entry = HCl::DayEntry.new(id:123)
27
+ register_uri(:get, '/daily/timer/123', {note:'hi'})
28
+ entry.toggle http
29
+ end
30
+
31
+ def test_cancel_success
32
+ entry = HCl::DayEntry.new(id:123)
33
+ register_uri(:delete, '/daily/delete/123')
34
+ assert entry.cancel http
35
+ end
36
+
37
+ def test_cancel_failure
38
+ entry = HCl::DayEntry.new(id:123)
39
+ http.expects(:delete).raises(HCl::HarvestMiddleware::Failure)
40
+ assert !entry.cancel(http)
41
+ end
42
+
43
+ def test_to_s
44
+ entry = HCl::DayEntry.new \
45
+ hours: '1.2', client: 'Taco Town', project:'Pizza Taco', task:'Preparation'
46
+ assert_equal "Taco Town - Pizza Taco - Preparation (1:12)", entry.to_s
34
47
  end
35
48
 
36
49
  def test_append_note
37
50
  entry = HCl::DayEntry.new(:id => '1', :notes => 'yourmom.', :hours => '1.0')
38
- HCl::DayEntry.stubs(:post)
39
- entry.append_note('hi world')
51
+ http.stubs(:post)
52
+ entry.append_note(http, 'hi world')
40
53
  assert_equal "yourmom.\nhi world", entry.notes
41
54
  end
42
55
 
43
56
  def test_append_note_to_empty
44
57
  entry = HCl::DayEntry.new(:id => '1', :notes => nil, :hours => '1.0')
45
- HCl::DayEntry.stubs(:post)
46
- entry.append_note('hi world')
58
+ http.stubs(:post)
59
+ entry.append_note(http, 'hi world')
47
60
  assert_equal 'hi world', entry.notes
48
61
  end
49
62
  end
@@ -1,8 +1,10 @@
1
1
  module CaptureOutput
2
2
  def before_setup
3
3
  super
4
- $stderr = @stderr = StringIO.new
5
- $stdout = @stdout = StringIO.new
4
+ @stderr = StringIO.new
5
+ @stdout = StringIO.new
6
+ $stderr = @stderr
7
+ $stdout = @stdout
6
8
  end
7
9
  def after_teardown
8
10
  super
data/test/net_test.rb ADDED
@@ -0,0 +1,40 @@
1
+ require 'test_helper'
2
+
3
+ class NetTest < HCl::TestCase
4
+
5
+ def test_configure
6
+ assert_equal 'bob', http.login
7
+ assert_equal 'secret', http.password
8
+ assert_equal 'bobclock', http.subdomain
9
+ assert_equal true, http.ssl
10
+ end
11
+
12
+ def test_http_deep_unescape
13
+ register_uri(:get, "/foo", {
14
+ status:'gotten &amp; got!',
15
+ comparisons:['burrito &gt; taco', 'rain &lt; sun']
16
+ })
17
+ body = http.get 'foo'
18
+ assert_equal 'gotten & got!', body[:status]
19
+ assert_equal 'burrito > taco', body[:comparisons][0]
20
+ assert_equal 'rain < sun', body[:comparisons][1]
21
+ end
22
+
23
+ def test_http_get
24
+ register_uri(:get, "/foo", {message:'gotten!'})
25
+ body = http.get 'foo'
26
+ assert_equal 'gotten!', body[:message]
27
+ end
28
+
29
+ def test_http_post
30
+ register_uri(:post, "/foo", {message:'posted!'})
31
+ body = http.post 'foo', {pizza:'taco'}
32
+ assert_equal 'posted!', body[:message]
33
+ end
34
+
35
+ def test_http_delete
36
+ register_uri(:delete, "/foo", {message:'wiped!'})
37
+ body = http.delete 'foo'
38
+ assert_equal 'wiped!', body[:message]
39
+ end
40
+ end
data/test/task_test.rb CHANGED
@@ -1,30 +1,47 @@
1
1
 
2
- class Task < HCl::TestCase
2
+ class TaskTest < HCl::TestCase
3
3
  def test_cache_file
4
4
  assert_equal "#{HCl::App::HCL_DIR}/cache/tasks.yml", HCl::Task.cache_file
5
5
  end
6
6
 
7
- def test_cache_tasks
8
- HCl::Task.cache_tasks(REXML::Document.new(<<-EOD))
9
- <daily>
10
- <projects>
11
- <project>
12
- <name>Click and Type</name>
13
- <code></code>
14
- <id type="integer">3</id>
15
- <client>AFS</client>
16
- <tasks>
17
- <task>
18
- <name>Security support</name>
19
- <id type="integer">14</id>
20
- <billable type="boolean">true</billable>
21
- </task>
22
- </tasks>
23
- </project>
24
- </projects>
25
- </daily>
26
- EOD
7
+ def test_cache_tasks_hash
8
+ HCl::Task.cache_tasks_hash({ projects: [{
9
+ name: "Click and Type",
10
+ id: 3,
11
+ client: "AFS",
12
+ tasks: [{
13
+ name: "Security support",
14
+ id: 14,
15
+ billable: true
16
+ }]
17
+ }]})
27
18
  assert_equal 1, HCl::Task.all.size
28
19
  assert_equal 'Security support', HCl::Task.all.first.name
29
20
  end
21
+
22
+ def test_add
23
+ task = HCl::Task.new(id:1, project:HCl::Project.new({id:2}))
24
+ register_uri(:post, '/daily/add', {
25
+ note:'good stuff', hours:0.2, project_id:2, task_id:1, spent_at: Date.today})
26
+ entry = task.add(http, note:'good stuff', starting_time:0.2)
27
+ assert_equal 'good stuff', entry.note
28
+ end
29
+
30
+ def test_start_running
31
+ task = HCl::Task.new(id:1, project:HCl::Project.new({id:2}))
32
+ register_uri(:post, '/daily/add', {
33
+ note:'good stuff', timer_started_at:DateTime.now,
34
+ hours:0.2, project_id:2, task_id:1, spent_at: Date.today})
35
+ entry = task.start(http, note:'good stuff', starting_time:0.2)
36
+ assert_equal 'good stuff', entry.note
37
+ end
38
+
39
+ def test_start_then_toggle
40
+ task = HCl::Task.new(id:1, project:HCl::Project.new({id:2}))
41
+ register_uri(:post, '/daily/add', {id:123, note:'woot'})
42
+ register_uri(:get, '/daily/timer/123', {note:'good stuff', hours:0.2,
43
+ project_id:2, task_id:1, spent_at: Date.today})
44
+ entry = task.start(http, note:'good stuff', starting_time:0.2)
45
+ assert_equal 'good stuff', entry.note
46
+ end
30
47
  end
data/test/test_helper.rb CHANGED
@@ -9,11 +9,7 @@ begin
9
9
  source_file.lines.count < 15
10
10
  end
11
11
  # source: https://travis-ci.org/zenhob/hcl
12
- minimum_coverage case RUBY_ENGINE
13
- when "rbx" then 84
14
- when "jruby" then 73
15
- else 78
16
- end
12
+ minimum_coverage 84
17
13
  end
18
14
  rescue LoadError => e
19
15
  $stderr.puts 'No test coverage tools found, skipping coverage check.'
@@ -31,5 +27,25 @@ require 'fakeweb'
31
27
  # require test extensions/helpers
32
28
  Dir[File.dirname(__FILE__) + '/ext/*.rb'].each { |ext| require ext }
33
29
 
34
- class HCl::TestCase < MiniTest::Unit::TestCase; end
30
+ class HCl::TestCase < MiniTest::Unit::TestCase
31
+ attr_reader :http
32
+ def setup
33
+ FakeWeb.allow_net_connect = false
34
+ @http = HCl::Net.new \
35
+ 'login' => 'bob',
36
+ 'password' => 'secret',
37
+ 'subdomain' => 'bobclock',
38
+ 'ssl' => true
39
+ end
40
+
41
+ def register_uri method, path, data={}
42
+ FakeWeb.register_uri(method, "https://bob:secret@bobclock.harvestapp.com#{path}",
43
+ body: Yajl::Encoder.encode(data))
44
+ end
45
+
46
+ def teardown
47
+ FakeWeb.clean_registry
48
+ end
49
+ end
50
+
35
51
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hcl
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.9
4
+ version: 0.4.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Zack Hobson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-12-22 00:00:00.000000000 Z
11
+ date: 2014-01-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: trollop
@@ -52,6 +52,62 @@ dependencies:
52
52
  - - '>='
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: faraday
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: yajl-ruby
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: escape_utils
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: pry
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
55
111
  - !ruby/object:Gem::Dependency
56
112
  name: rake
57
113
  requirement: !ruby/object:Gem::Requirement
@@ -166,7 +222,10 @@ files:
166
222
  - bin/hcl
167
223
  - lib/hcl/app.rb
168
224
  - lib/hcl/commands.rb
225
+ - lib/hcl/console.rb
169
226
  - lib/hcl/day_entry.rb
227
+ - lib/hcl/harvest_middleware.rb
228
+ - lib/hcl/net.rb
170
229
  - lib/hcl/project.rb
171
230
  - lib/hcl/task.rb
172
231
  - lib/hcl/timesheet_resource.rb
@@ -177,9 +236,9 @@ files:
177
236
  - test/command_test.rb
178
237
  - test/day_entry_test.rb
179
238
  - test/ext/capture_output.rb
239
+ - test/net_test.rb
180
240
  - test/task_test.rb
181
241
  - test/test_helper.rb
182
- - test/timesheet_resource_test.rb
183
242
  - test/utility_test.rb
184
243
  homepage: http://zackhobson.com/hcl/
185
244
  licenses:
@@ -1,38 +0,0 @@
1
- require 'test_helper'
2
-
3
- class TimesheetResourceTest < HCl::TestCase
4
-
5
- def setup
6
- FakeWeb.allow_net_connect = false
7
- HCl::TimesheetResource.configure \
8
- 'login' => 'bob',
9
- 'password' => 'secret',
10
- 'subdomain' => 'bobclock',
11
- 'ssl' => true
12
- end
13
-
14
- def test_configure
15
- assert_equal 'bob', HCl::TimesheetResource.login
16
- assert_equal 'secret', HCl::TimesheetResource.password
17
- assert_equal 'bobclock', HCl::TimesheetResource.subdomain
18
- assert_equal true, HCl::TimesheetResource.ssl
19
- end
20
-
21
- def test_http_get
22
- FakeWeb.register_uri(:get, "https://bob:secret@bobclock.harvestapp.com/foo", :body => 'gotten!')
23
- body = HCl::TimesheetResource.get 'foo'
24
- assert_equal 'gotten!', body
25
- end
26
-
27
- def test_http_post
28
- FakeWeb.register_uri(:post, "https://bob:secret@bobclock.harvestapp.com/foo", :body => 'posted!')
29
- body = HCl::TimesheetResource.post 'foo', {pizza:'taco'}
30
- assert_equal 'posted!', body
31
- end
32
-
33
- def test_http_delete
34
- FakeWeb.register_uri(:delete, "https://bob:secret@bobclock.harvestapp.com/foo", :body => 'wiped!')
35
- body = HCl::TimesheetResource.delete 'foo'
36
- assert_equal 'wiped!', body
37
- end
38
- end