hcl 0.4.9 → 0.4.10

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.
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