roust 1.3.0 → 1.4.0
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 +4 -4
- data/.rspec +2 -0
- data/.rubocop.yml +3 -0
- data/.rubocop_roust.yml +11 -0
- data/.rubocop_todo.yml +15 -0
- data/.travis.yml +6 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +29 -11
- data/README.md +1 -0
- data/Rakefile +5 -6
- data/lib/roust/exceptions.rb +5 -0
- data/lib/roust/queue.rb +21 -0
- data/lib/roust/ticket.rb +245 -0
- data/lib/roust/user.rb +48 -0
- data/lib/roust/version.rb +1 -1
- data/lib/roust.rb +23 -329
- data/roust.gemspec +13 -13
- data/spec/mocks/ticket-1-show-unauthenticated.txt +1 -0
- data/spec/roust/authentication_spec.rb +33 -0
- data/spec/roust/queue_spec.rb +40 -0
- data/spec/roust/ticket_spec.rb +80 -0
- data/spec/roust/user_spec.rb +58 -0
- data/spec/spec_helper.rb +28 -9
- metadata +21 -5
- data/examples/example.rb +0 -15
- data/spec/roust_spec.rb +0 -202
data/lib/roust.rb
CHANGED
@@ -1,12 +1,16 @@
|
|
1
1
|
require 'httparty'
|
2
2
|
require 'mail'
|
3
3
|
require 'active_support/core_ext/hash'
|
4
|
-
|
5
|
-
|
4
|
+
require 'roust/ticket'
|
5
|
+
require 'roust/queue'
|
6
|
+
require 'roust/user'
|
7
|
+
require 'roust/exceptions'
|
6
8
|
|
7
9
|
class Roust
|
8
10
|
include HTTParty
|
9
|
-
|
11
|
+
include Roust::Ticket
|
12
|
+
include Roust::Queue
|
13
|
+
include Roust::User
|
10
14
|
|
11
15
|
def initialize(credentials)
|
12
16
|
server = credentials[:server]
|
@@ -43,358 +47,48 @@ class Roust
|
|
43
47
|
authenticated?
|
44
48
|
end
|
45
49
|
|
46
|
-
def show(id)
|
47
|
-
response = self.class.get("/ticket/#{id}/show")
|
48
|
-
|
49
|
-
body, status = explode_response(response)
|
50
|
-
|
51
|
-
if match = body.match(/^# (Ticket (\d+) does not exist\.)/)
|
52
|
-
return { 'error' => match[1] }
|
53
|
-
end
|
54
|
-
|
55
|
-
# Replace CF spaces with underscores
|
56
|
-
while body.match(/CF\.\{[\w_ ]*[ ]+[\w ]*\}/)
|
57
|
-
body.gsub!(/CF\.\{([\w_ ]*)([ ]+)([\w ]*)\}/, 'CF.{\1_\3}')
|
58
|
-
end
|
59
|
-
|
60
|
-
# Sometimes the API returns requestors formatted like this:
|
61
|
-
#
|
62
|
-
# Requestors: foo@example.org,
|
63
|
-
# bar@example.org, baz@example.org
|
64
|
-
# qux@example.org, quux@example.org,
|
65
|
-
# corge@example.org
|
66
|
-
#
|
67
|
-
# Turn it into this:
|
68
|
-
#
|
69
|
-
# Requestors: foo@example.org, bar@example.org, baz@example.org, ...
|
70
|
-
#
|
71
|
-
body.gsub!(/\n\n/, "\n")
|
72
|
-
|
73
|
-
%w(Requestors Cc AdminCc).each do |field|
|
74
|
-
body.gsub!(/^#{field}:(.+)^\n/m) do |match|
|
75
|
-
match.strip.split(/,\s+/).join(', ').strip
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
message = Mail.new(body)
|
80
|
-
|
81
|
-
hash = Hash[message.header.fields.map {|header|
|
82
|
-
key = header.name.to_s
|
83
|
-
value = header.value.to_s
|
84
|
-
[ key, value ]
|
85
|
-
}]
|
86
|
-
|
87
|
-
%w(Requestors Cc AdminCc).each do |field|
|
88
|
-
hash[field] = hash[field].split(', ') if hash[field]
|
89
|
-
end
|
90
|
-
|
91
|
-
hash["id"] = hash["id"].split('/').last
|
92
|
-
|
93
|
-
hash
|
94
|
-
end
|
95
|
-
|
96
|
-
def create(attrs)
|
97
|
-
default_attrs = {
|
98
|
-
'id' => 'ticket/new'
|
99
|
-
}
|
100
|
-
attrs = default_attrs.merge(attrs).stringify_keys!
|
101
|
-
|
102
|
-
if error = create_invalid?(attrs)
|
103
|
-
return {'error' => error }
|
104
|
-
end
|
105
|
-
|
106
|
-
attrs['Text'].gsub!(/\n/,"\n ") if attrs['Text'] # insert a space on continuation lines.
|
107
|
-
|
108
|
-
# We can't set more than one AdminCc when creating a ticket. WTF RT.
|
109
|
-
#
|
110
|
-
# Delete it from the ticket we are creating, and we'll update the ticket
|
111
|
-
# after we've created.
|
112
|
-
admincc = attrs.delete("AdminCc")
|
113
|
-
|
114
|
-
content = attrs.map { |k,v|
|
115
|
-
# Don't lowercase strings if they're already camel cased.
|
116
|
-
k = case
|
117
|
-
when k.is_a?(Symbol)
|
118
|
-
k.to_s
|
119
|
-
when k == 'id'
|
120
|
-
k
|
121
|
-
when k =~ /^[a-z]/
|
122
|
-
k.capitalize
|
123
|
-
else
|
124
|
-
k
|
125
|
-
end
|
126
|
-
|
127
|
-
v = v.join(', ') if v.respond_to?(:join)
|
128
|
-
|
129
|
-
"#{k}: #{v}"
|
130
|
-
}.join("\n")
|
131
|
-
|
132
|
-
response = self.class.post(
|
133
|
-
"/ticket/new",
|
134
|
-
:body => {
|
135
|
-
:content => content
|
136
|
-
},
|
137
|
-
)
|
138
|
-
|
139
|
-
body, status = explode_response(response)
|
140
|
-
|
141
|
-
case body
|
142
|
-
when /^# Could not create ticket/
|
143
|
-
false
|
144
|
-
when /^# Syntax error/
|
145
|
-
false
|
146
|
-
when /^# Ticket (\d+) created/
|
147
|
-
id = body[/^# Ticket (\d+) created/, 1]
|
148
|
-
update(id, 'AdminCc' => admincc) if admincc
|
149
|
-
show(id)
|
150
|
-
else
|
151
|
-
# We should never hit this, but if we do, just pass it through and
|
152
|
-
# surprise the user (!!!).
|
153
|
-
body
|
154
|
-
end
|
155
|
-
end
|
156
|
-
|
157
|
-
def update(id, attrs)
|
158
|
-
content = compose_content('ticket', id, attrs)
|
159
|
-
|
160
|
-
response = self.class.post(
|
161
|
-
"/ticket/#{id}/edit",
|
162
|
-
:body => {
|
163
|
-
:content => content
|
164
|
-
},
|
165
|
-
)
|
166
|
-
|
167
|
-
body, status = explode_response(response)
|
168
|
-
|
169
|
-
case body
|
170
|
-
when /^# You are not allowed to modify ticket \d+/
|
171
|
-
{ 'error' => body.strip }
|
172
|
-
when /^# Syntax error/
|
173
|
-
{ 'error' => body.strip }
|
174
|
-
when /^# Ticket (\d+) updated/
|
175
|
-
id = body[/^# Ticket (\d+) updated/, 1]
|
176
|
-
show(id)
|
177
|
-
else
|
178
|
-
# We should never hit this, but if we do, just pass it through and
|
179
|
-
# surprise the user (!!!).
|
180
|
-
body
|
181
|
-
end
|
182
|
-
end
|
183
|
-
|
184
50
|
def authenticated?
|
185
51
|
return true if show('1')
|
186
52
|
end
|
187
53
|
|
188
|
-
def search(query)
|
189
|
-
params = {
|
190
|
-
:query => query,
|
191
|
-
:format => 's',
|
192
|
-
:orderby => '+id'
|
193
|
-
}
|
194
|
-
response = self.class.get("/search/ticket", :query => params)
|
195
|
-
body = response.body
|
196
|
-
body.gsub!(/RT\/\d+\.\d+\.\d+\s\d{3}\s.*\n\n/,"")
|
197
|
-
|
198
|
-
body.split("\n").map do |t|
|
199
|
-
id, subject = t.split(': ', 2)
|
200
|
-
{'id' => id, 'Subject' => subject}
|
201
|
-
end
|
202
|
-
end
|
203
|
-
|
204
|
-
def history(id, opts={})
|
205
|
-
options = {
|
206
|
-
:format => 'short',
|
207
|
-
:comments => false
|
208
|
-
}.merge(opts)
|
209
|
-
|
210
|
-
format = options[:format]
|
211
|
-
comments = options[:comments]
|
212
|
-
params = {
|
213
|
-
:format => format[0]
|
214
|
-
}
|
215
|
-
|
216
|
-
response = self.class.get("/ticket/#{id}/history", :query => params)
|
217
|
-
|
218
|
-
body, status = explode_response(response)
|
219
|
-
|
220
|
-
case format
|
221
|
-
when 'short'
|
222
|
-
parse_short_history(body, :comments => comments)
|
223
|
-
when 'long'
|
224
|
-
parse_long_history(body, :comments => comments)
|
225
|
-
end
|
226
|
-
end
|
227
|
-
|
228
|
-
# id can be numeric (e.g. 28) or textual (e.g. sales)
|
229
|
-
def queue(id)
|
230
|
-
response = self.class.get("/queue/#{id}")
|
231
|
-
|
232
|
-
body, status = explode_response(response)
|
233
|
-
case body
|
234
|
-
when /No queue named/
|
235
|
-
nil
|
236
|
-
else
|
237
|
-
body.gsub!(/\n\s*\n/,"\n") # remove blank lines for Mail
|
238
|
-
message = Mail.new(body)
|
239
|
-
Hash[message.header.fields.map {|header|
|
240
|
-
key = header.name.to_s.downcase
|
241
|
-
value = header.value.to_s
|
242
|
-
[ key, value ]
|
243
|
-
}]
|
244
|
-
end
|
245
|
-
end
|
246
|
-
|
247
|
-
# id can be numeric (e.g. 28) or textual (e.g. john)
|
248
|
-
def user_show(id)
|
249
|
-
response = self.class.get("/user/#{id}")
|
250
|
-
|
251
|
-
body, status = explode_response(response)
|
252
|
-
case body
|
253
|
-
when /No user named/
|
254
|
-
nil
|
255
|
-
else
|
256
|
-
body.gsub!(/\n\s*\n/,"\n") # remove blank lines for Mail
|
257
|
-
message = Mail.new(body)
|
258
|
-
Hash[message.header.fields.map {|header|
|
259
|
-
key = header.name.to_s.downcase
|
260
|
-
value = header.value.to_s
|
261
|
-
[ key, value ]
|
262
|
-
}]
|
263
|
-
end
|
264
|
-
end
|
265
|
-
|
266
|
-
alias :user :user_show
|
267
|
-
|
268
|
-
def user_update(id, attrs)
|
269
|
-
content = compose_content('user', id, attrs)
|
270
|
-
|
271
|
-
response = self.class.post(
|
272
|
-
"/user/#{id}/edit",
|
273
|
-
:body => {
|
274
|
-
:content => content
|
275
|
-
},
|
276
|
-
)
|
277
|
-
|
278
|
-
body, status = explode_response(response)
|
279
|
-
|
280
|
-
case body
|
281
|
-
when /^# You are not allowed to modify user \d+/
|
282
|
-
{ 'error' => body.strip }
|
283
|
-
when /^# Syntax error/
|
284
|
-
{ 'error' => body.strip }
|
285
|
-
when /^# User (.+) updated/
|
286
|
-
id = body[/^# User (.+) updated/, 1]
|
287
|
-
user_show(id)
|
288
|
-
else
|
289
|
-
# We should never hit this, but if we do, just pass it through and
|
290
|
-
# surprise the user (!!!).
|
291
|
-
body
|
292
|
-
end
|
293
|
-
end
|
294
|
-
|
295
54
|
private
|
55
|
+
|
296
56
|
def compose_content(type, id, attrs)
|
297
57
|
default_attrs = {
|
298
58
|
'id' => [ type, id ].join('/')
|
299
59
|
}
|
300
60
|
attrs = default_attrs.merge(attrs).stringify_keys!
|
301
61
|
|
302
|
-
content = attrs.map
|
62
|
+
content = attrs.map do |k, v|
|
303
63
|
# Don't lowercase strings if they're already camel cased.
|
304
64
|
k = case
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
65
|
+
when k.is_a?(Symbol)
|
66
|
+
k.to_s
|
67
|
+
when k == 'id'
|
68
|
+
k
|
69
|
+
when k =~ /^[a-z]/
|
70
|
+
k.capitalize
|
71
|
+
else
|
72
|
+
k
|
73
|
+
end
|
314
74
|
|
315
75
|
v = v.join(', ') if v.respond_to?(:join)
|
316
76
|
|
317
77
|
"#{k}: #{v}"
|
318
|
-
|
78
|
+
end
|
79
|
+
|
80
|
+
content.join("\n")
|
319
81
|
end
|
320
82
|
|
321
83
|
def explode_response(response)
|
322
84
|
body = response.body
|
323
85
|
status = body[/RT\/\d+\.\d+\.\d+\s(\d{3}\s.*)\n/, 1]
|
324
86
|
|
325
|
-
body.gsub!(/RT\/\d+\.\d+\.\d+\s\d{3}\s.*\n/,
|
87
|
+
body.gsub!(/RT\/\d+\.\d+\.\d+\s\d{3}\s.*\n/, '')
|
326
88
|
body = body.empty? ? nil : body.lstrip
|
327
89
|
|
328
|
-
raise Unauthenticated,
|
90
|
+
raise Unauthenticated, 'Invalid username or password' if status =~ /401 Credentials required/
|
329
91
|
|
330
92
|
return body, status
|
331
93
|
end
|
332
|
-
|
333
|
-
def create_invalid?(attrs)
|
334
|
-
missing = %w(id Subject Queue).find_all {|k| !attrs.include?(k) }
|
335
|
-
|
336
|
-
if missing.empty?
|
337
|
-
return false
|
338
|
-
else
|
339
|
-
"Needs attributes: #{missing.join(', ')}"
|
340
|
-
end
|
341
|
-
end
|
342
|
-
|
343
|
-
def parse_short_history(body, opts={})
|
344
|
-
comments = opts[:comments]
|
345
|
-
regex = comments ? '^\d+:' : '^\d+: [^Comments]'
|
346
|
-
history = body.split("\n").select { |l| l =~ /#{regex}/ }
|
347
|
-
history.map { |l| l.split(": ", 2) }
|
348
|
-
end
|
349
|
-
|
350
|
-
def parse_long_history(body, opts={})
|
351
|
-
comments = opts[:comments]
|
352
|
-
items = body.split("\n--\n")
|
353
|
-
list = []
|
354
|
-
items.each do |item|
|
355
|
-
# Yes, this messes with the "content:" field but that's the one that's upsetting Mail.new
|
356
|
-
item.gsub!(/\n\s*\n/,"\n") # remove blank lines for Mail
|
357
|
-
history = Mail.new(item)
|
358
|
-
next if not comments and history['type'].to_s =~ /Comment/ # skip comments
|
359
|
-
reply = {}
|
360
|
-
|
361
|
-
history.header.fields.each_with_index do |header, index|
|
362
|
-
next if index == 0
|
363
|
-
|
364
|
-
key = header.name.to_s.downcase
|
365
|
-
value = header.value.to_s
|
366
|
-
|
367
|
-
attachments = []
|
368
|
-
case key
|
369
|
-
when "attachments"
|
370
|
-
temp = item.match(/Attachments:\s*(.*)/m)
|
371
|
-
if temp.class != NilClass
|
372
|
-
atarr = temp[1].split("\n")
|
373
|
-
atarr.map { |a| a.gsub!(/^\s*/,"") }
|
374
|
-
atarr.each do |a|
|
375
|
-
i = a.match(/(\d+):\s*(.*)/)
|
376
|
-
s = {
|
377
|
-
:id => i[1].to_s,
|
378
|
-
:name => i[2].to_s
|
379
|
-
}
|
380
|
-
sz = i[2].match(/(.*?)\s*\((.*?)\)/)
|
381
|
-
if sz.class == MatchData
|
382
|
-
s[:name] = sz[1].to_s
|
383
|
-
s[:size] = sz[2].to_s
|
384
|
-
end
|
385
|
-
attachments << s
|
386
|
-
end
|
387
|
-
reply["attachments"] = attachments
|
388
|
-
end
|
389
|
-
when "content"
|
390
|
-
reply["content"] = value
|
391
|
-
else
|
392
|
-
reply["#{key}"] = value
|
393
|
-
end
|
394
|
-
end
|
395
|
-
list << reply
|
396
|
-
end
|
397
|
-
|
398
|
-
return list
|
399
|
-
end
|
400
94
|
end
|
data/roust.gemspec
CHANGED
@@ -1,25 +1,25 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
-
|
3
|
-
require
|
2
|
+
$LOAD_PATH.push(File.expand_path('../lib', __FILE__))
|
3
|
+
require 'roust/version'
|
4
4
|
|
5
5
|
Gem::Specification.new do |s|
|
6
|
-
s.name =
|
6
|
+
s.name = 'roust'
|
7
7
|
s.version = Roust::VERSION
|
8
|
-
s.date = %
|
8
|
+
s.date = Time.now.strftime('%Y-%m-%d')
|
9
9
|
|
10
|
-
s.authors = [
|
11
|
-
s.email = [
|
12
|
-
s.summary =
|
13
|
-
s.description =
|
14
|
-
s.homepage =
|
15
|
-
s.license =
|
10
|
+
s.authors = ['Lindsay Holmwood']
|
11
|
+
s.email = ['lindsay@holmwood.id.au']
|
12
|
+
s.summary = "Ruby client for RT's REST API"
|
13
|
+
s.description = 'Roust is a Ruby API client that accesses the REST interface version 1.0 of a Request Tracker instance. See http://www.bestpractical.com/ for Request Tracker.'
|
14
|
+
s.homepage = 'http://github.com/bulletproofnetworks/roust'
|
15
|
+
s.license = 'Apache 2.0'
|
16
16
|
|
17
|
-
s.required_ruby_version =
|
17
|
+
s.required_ruby_version = '>= 1.9.2'
|
18
18
|
|
19
19
|
s.files = `git ls-files`.split("\n")
|
20
20
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
21
|
-
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
22
|
-
s.require_paths =
|
21
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
22
|
+
s.require_paths = %w(lib)
|
23
23
|
|
24
24
|
s.add_runtime_dependency 'mail', '>= 2.5.4'
|
25
25
|
s.add_runtime_dependency 'httparty', '>= 0.13.1'
|
@@ -0,0 +1 @@
|
|
1
|
+
RT/3.4.6 401 Credentials required
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'roust'
|
3
|
+
|
4
|
+
describe Roust do
|
5
|
+
include_context 'credentials'
|
6
|
+
|
7
|
+
describe 'authentication' do
|
8
|
+
it 'authenticates on instantiation' do
|
9
|
+
@rt = Roust.new(credentials)
|
10
|
+
expect(@rt.authenticated?).to eq(true)
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'errors when credentials are incorrect' do
|
14
|
+
mocks_path = Pathname.new(__FILE__).parent.parent.join('mocks')
|
15
|
+
|
16
|
+
stub_request(:post, 'http://rt.example.org/index.html').
|
17
|
+
with(:body => {
|
18
|
+
'user'=>'admin',
|
19
|
+
'pass'=>'incorrect',
|
20
|
+
}).
|
21
|
+
to_return(:status => 200, :body => '', :headers => {})
|
22
|
+
|
23
|
+
stub_request(:get, 'http://rt.example.org/REST/1.0/ticket/1/show').
|
24
|
+
to_return(:status => 200,
|
25
|
+
:body => mocks_path.join('ticket-1-show-unauthenticated.txt').read,
|
26
|
+
:headers => {})
|
27
|
+
|
28
|
+
credentials.merge!({:username => 'admin', :password => 'incorrect'})
|
29
|
+
|
30
|
+
expect { Roust.new(credentials) }.to raise_error(Unauthenticated)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'roust'
|
3
|
+
|
4
|
+
describe Roust do
|
5
|
+
include_context 'credentials'
|
6
|
+
|
7
|
+
before do
|
8
|
+
mocks_path = Pathname.new(__FILE__).parent.parent.join('mocks')
|
9
|
+
|
10
|
+
stub_request(:get, 'http://rt.example.org/REST/1.0/queue/13')
|
11
|
+
.to_return(:status => 200,
|
12
|
+
:body => mocks_path.join('queue-13.txt').read,
|
13
|
+
:headers => {})
|
14
|
+
|
15
|
+
stub_request(:get, 'http://rt.example.org/REST/1.0/queue/nil')
|
16
|
+
.to_return(:status => 200,
|
17
|
+
:body => mocks_path.join('queue-nil.txt').read,
|
18
|
+
:headers => {})
|
19
|
+
|
20
|
+
@rt = Roust.new(credentials)
|
21
|
+
expect(@rt.authenticated?).to eq(true)
|
22
|
+
end
|
23
|
+
|
24
|
+
describe 'queue' do
|
25
|
+
it 'can lookup queue details' do
|
26
|
+
attrs = %w(id name description correspondaddress commentaddress) +
|
27
|
+
%w(initialpriority finalpriority defaultduein)
|
28
|
+
|
29
|
+
queue = @rt.queue('13')
|
30
|
+
attrs.each do |attr|
|
31
|
+
expect(queue[attr]).to_not eq(nil), "#{attr} key doesn't exist"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'returns nil for unknown queues' do
|
36
|
+
queue = @rt.queue('nil')
|
37
|
+
expect(queue).to eq(nil)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'roust'
|
3
|
+
|
4
|
+
describe Roust do
|
5
|
+
include_context 'credentials'
|
6
|
+
|
7
|
+
before do
|
8
|
+
mocks_path = Pathname.new(__FILE__).parent.parent.join('mocks')
|
9
|
+
|
10
|
+
stub_request(:get, 'http://rt.example.org/REST/1.0/search/ticket?format=s&orderby=%2Bid&query%5Bquery%5D=id%20=%201%20or%20id%20=%202')
|
11
|
+
.to_return(:status => 200,
|
12
|
+
:body => mocks_path.join('ticket-search-1-or-2.txt').read,
|
13
|
+
:headers => {})
|
14
|
+
|
15
|
+
stub_request(:get, 'http://rt.example.org/REST/1.0/ticket/1/history?format=s')
|
16
|
+
.to_return(:status => 200,
|
17
|
+
:body => mocks_path.join('ticket-1-history-short.txt').read,
|
18
|
+
:headers => {})
|
19
|
+
|
20
|
+
stub_request(:get, 'http://rt.example.org/REST/1.0/ticket/1/history?format=l')
|
21
|
+
.to_return(:status => 200,
|
22
|
+
:body => mocks_path.join('ticket-1-history-long.txt').read,
|
23
|
+
:headers => {})
|
24
|
+
|
25
|
+
@rt = Roust.new(credentials)
|
26
|
+
expect(@rt.authenticated?).to eq(true)
|
27
|
+
end
|
28
|
+
|
29
|
+
describe 'tickets' do
|
30
|
+
it 'can list tickets matching a query' do
|
31
|
+
results = @rt.search(:query => 'id = 1 or id = 2')
|
32
|
+
expect(results.size).to eq(2)
|
33
|
+
results.each do |result|
|
34
|
+
expect(result.size).to eq(2)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'can fetch metadata on individual tickets' do
|
39
|
+
ticket = @rt.show('1')
|
40
|
+
expect(ticket).to_not eq(nil)
|
41
|
+
|
42
|
+
attrs = %w(id Subject Queue) +
|
43
|
+
%w(Requestors Cc AdminCc Owner Creator) +
|
44
|
+
%w(Resolved Status) +
|
45
|
+
%w(Starts Started TimeLeft Due TimeWorked TimeEstimated) +
|
46
|
+
%w(LastUpdated Created Told) +
|
47
|
+
%w(Priority FinalPriority InitialPriority)
|
48
|
+
|
49
|
+
attrs.each do |attr|
|
50
|
+
expect(ticket[attr]).to_not eq(nil), "#{attr} key doesn't exist"
|
51
|
+
end
|
52
|
+
|
53
|
+
%w(Requestors Cc AdminCc).each do |field|
|
54
|
+
expect(ticket[field].size).to be > 1
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'can fetch transactions on individual tickets' do
|
59
|
+
short = @rt.history('1', :format => 'short')
|
60
|
+
|
61
|
+
expect(short.size).to be > 1
|
62
|
+
short.each do |txn|
|
63
|
+
expect(txn.size).to eq(2)
|
64
|
+
expect(txn.first).to match(/^\d+$/)
|
65
|
+
expect(txn.last).to match(/^\w.*\w$/)
|
66
|
+
end
|
67
|
+
|
68
|
+
attrs = %w(ticket data oldvalue timetaken) +
|
69
|
+
%w(id type field newvalue content description)
|
70
|
+
|
71
|
+
long = @rt.history('1', :format => 'long')
|
72
|
+
expect(long.size).to be > 0
|
73
|
+
long.each do |txn|
|
74
|
+
attrs.each do |attr|
|
75
|
+
expect(txn[attr]).to_not eq(nil), "#{attr} key doesn't exist"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'roust'
|
3
|
+
|
4
|
+
describe Roust do
|
5
|
+
include_context 'credentials'
|
6
|
+
|
7
|
+
before do
|
8
|
+
mocks_path = Pathname.new(__FILE__).parent.parent.join('mocks')
|
9
|
+
|
10
|
+
stub_request(:get, 'http://rt.example.org/REST/1.0/user/dan@us.example')
|
11
|
+
.to_return(:status => 200,
|
12
|
+
:body => mocks_path.join('user-dan@us.example.txt').read,
|
13
|
+
:headers => {})
|
14
|
+
|
15
|
+
stub_request(:get, 'http://rt.example.org/REST/1.0/user/nil')
|
16
|
+
.to_return(:status => 200,
|
17
|
+
:body => mocks_path.join('user-nil.txt').read,
|
18
|
+
:headers => {})
|
19
|
+
|
20
|
+
stub_request(:post, 'http://rt.example.org/REST/1.0/user/dan@us.example/edit')
|
21
|
+
.with(:body => 'content=id%3A%20user%2Fdan%40us.example%0ARealName%3A%20Daniel%20Smith')
|
22
|
+
.to_return(:status => 200,
|
23
|
+
:body => mocks_path.join('user-dan@us.example-edit.txt').read,
|
24
|
+
:headers => {})
|
25
|
+
|
26
|
+
@rt = Roust.new(credentials)
|
27
|
+
expect(@rt.authenticated?).to eq(true)
|
28
|
+
end
|
29
|
+
|
30
|
+
describe 'user' do
|
31
|
+
it 'can lookup user details' do
|
32
|
+
attrs = %w(name realname gecos nickname emailaddress id lang password)
|
33
|
+
|
34
|
+
user = @rt.user_show('dan@us.example')
|
35
|
+
attrs.each do |attr|
|
36
|
+
expect(user[attr]).to_not eq(nil), "#{attr} key doesn't exist"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'returns nil for unknown users' do
|
41
|
+
queue = @rt.user_show('nil')
|
42
|
+
expect(queue).to eq(nil)
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'can modify an existing user' do
|
46
|
+
mocks_path = Pathname.new(__FILE__).parent.parent.join('mocks')
|
47
|
+
stub_request(:get, 'http://rt.example.org/REST/1.0/user/dan@us.example')
|
48
|
+
.to_return(:status => 200,
|
49
|
+
:body => mocks_path.join('user-dan@us.example-after-edit.txt').read,
|
50
|
+
:headers => {})
|
51
|
+
|
52
|
+
attrs = {'RealName' => 'Daniel Smith'}
|
53
|
+
user = @rt.user_update('dan@us.example', attrs)
|
54
|
+
|
55
|
+
expect(user['realname']).to eq('Daniel Smith')
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -7,20 +7,39 @@
|
|
7
7
|
|
8
8
|
require 'pathname'
|
9
9
|
lib = Pathname.new(__FILE__).parent.parent.join('lib').to_s
|
10
|
-
|
10
|
+
$LOAD_PATH << lib
|
11
11
|
require 'webmock/rspec'
|
12
12
|
|
13
13
|
RSpec.configure do |config|
|
14
|
-
# Use color in STDOUT
|
15
|
-
config.color_enabled = true
|
16
|
-
|
17
14
|
# Use color not only in STDOUT but also in pagers and files
|
18
15
|
config.tty = true
|
16
|
+
end
|
17
|
+
|
18
|
+
# Boilerplate for all tests.
|
19
|
+
#
|
20
|
+
# All tests need to authenticate before they can do anything, so mock it out.
|
21
|
+
RSpec.shared_context 'credentials' do
|
22
|
+
let :credentials do
|
23
|
+
{
|
24
|
+
:server => 'http://rt.example.org',
|
25
|
+
:username => 'admin',
|
26
|
+
:password => 'password'
|
27
|
+
}
|
28
|
+
end
|
19
29
|
|
20
|
-
|
21
|
-
|
30
|
+
before(:each) do
|
31
|
+
mocks_path = Pathname.new(__FILE__).parent.join('mocks')
|
22
32
|
|
23
|
-
|
24
|
-
|
25
|
-
|
33
|
+
stub_request(:post, 'http://rt.example.org/index.html')
|
34
|
+
.with(:body => {
|
35
|
+
'user' => 'admin',
|
36
|
+
'pass' => 'password'
|
37
|
+
})
|
38
|
+
.to_return(:status => 200, :body => '', :headers => {})
|
26
39
|
|
40
|
+
stub_request(:get, 'http://rt.example.org/REST/1.0/ticket/1/show')
|
41
|
+
.to_return(:status => 200,
|
42
|
+
:body => mocks_path.join('ticket-1-show.txt').read,
|
43
|
+
:headers => {})
|
44
|
+
end
|
45
|
+
end
|