pasaporte 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +2 -0
- data/Manifest.txt +41 -0
- data/README.txt +72 -0
- data/Rakefile +111 -0
- data/TODO.txt +2 -0
- data/bin/pasaporte-fcgi.rb +17 -0
- data/lib/pasaporte/.DS_Store +0 -0
- data/lib/pasaporte/assets/.DS_Store +0 -0
- data/lib/pasaporte/assets/bgbar.png +0 -0
- data/lib/pasaporte/assets/lock.png +0 -0
- data/lib/pasaporte/assets/mainbg_green.gif +0 -0
- data/lib/pasaporte/assets/mainbg_red.gif +0 -0
- data/lib/pasaporte/assets/openid.png +0 -0
- data/lib/pasaporte/assets/pasaporte.css +192 -0
- data/lib/pasaporte/assets/pasaporte.js +10 -0
- data/lib/pasaporte/assets/user.png +0 -0
- data/lib/pasaporte/auth/cascade.rb +16 -0
- data/lib/pasaporte/auth/remote_web_workplace.rb +61 -0
- data/lib/pasaporte/auth/yaml_digest_table.rb +23 -0
- data/lib/pasaporte/auth/yaml_table.rb +43 -0
- data/lib/pasaporte/faster_openid.rb +39 -0
- data/lib/pasaporte/iso_countries.yml +247 -0
- data/lib/pasaporte/julik_state.rb +42 -0
- data/lib/pasaporte/markaby_ext.rb +8 -0
- data/lib/pasaporte/pasaporte_store.rb +60 -0
- data/lib/pasaporte/timezones.yml +797 -0
- data/lib/pasaporte.rb +1214 -0
- data/test/fixtures/pasaporte_approvals.yml +12 -0
- data/test/fixtures/pasaporte_profiles.yml +45 -0
- data/test/fixtures/pasaporte_throttles.yml +4 -0
- data/test/helper.rb +66 -0
- data/test/mosquito.rb +596 -0
- data/test/test_approval.rb +33 -0
- data/test/test_auth_backends.rb +59 -0
- data/test/test_openid.rb +363 -0
- data/test/test_pasaporte.rb +326 -0
- data/test/test_profile.rb +165 -0
- data/test/test_settings.rb +27 -0
- data/test/test_throttle.rb +70 -0
- data/test/testable_openid_fetcher.rb +82 -0
- metadata +151 -0
data/test/mosquito.rb
ADDED
@@ -0,0 +1,596 @@
|
|
1
|
+
%w(
|
2
|
+
rubygems
|
3
|
+
test/unit
|
4
|
+
active_record
|
5
|
+
active_record/fixtures
|
6
|
+
camping
|
7
|
+
camping/session
|
8
|
+
fileutils
|
9
|
+
tempfile
|
10
|
+
stringio
|
11
|
+
).each { |lib| require lib }
|
12
|
+
|
13
|
+
module Mosquito
|
14
|
+
VERSION = '0.1.3'
|
15
|
+
|
16
|
+
# For various methods that need to generate random text
|
17
|
+
def self.garbage(amount) #:nodoc:
|
18
|
+
fills = ("a".."z").to_a
|
19
|
+
str = (0...amount).map do
|
20
|
+
v = fills[rand(fills.length)]
|
21
|
+
(rand(2).zero? ? v.upcase : v)
|
22
|
+
end
|
23
|
+
str.join
|
24
|
+
end
|
25
|
+
|
26
|
+
# Will be raised if you try to test for something Camping does not support.
|
27
|
+
# Kind of a safeguard in the deep ocean of metaified Ruby goodness.
|
28
|
+
class SageAdvice < RuntimeError; end
|
29
|
+
|
30
|
+
# Will be raised if you try to call an absolute, canonical URL (with scheme and server).
|
31
|
+
# and the server does not match the specified request.
|
32
|
+
class NonLocalRequest < RuntimeError; end
|
33
|
+
|
34
|
+
def self.stash(something) #:nodoc:
|
35
|
+
@stashed = something
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.unstash #:nodoc:
|
39
|
+
x, @stashed = @stashed, nil; x
|
40
|
+
end
|
41
|
+
|
42
|
+
module Dusty
|
43
|
+
##
|
44
|
+
# From Jay Fields.
|
45
|
+
#
|
46
|
+
# Allows tests to be specified as a block.
|
47
|
+
#
|
48
|
+
# test "should do this and that" do
|
49
|
+
# ...
|
50
|
+
# end
|
51
|
+
|
52
|
+
def test(name, &block)
|
53
|
+
test_name = :"test_#{name.gsub(' ','_')}"
|
54
|
+
raise ArgumentError, "#{test_name} is already defined" if self.instance_methods.include? test_name.to_s
|
55
|
+
define_method test_name, &block
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
in_mem_sqlite = {:adapter => 'sqlite3', :database => ":memory:"}
|
61
|
+
ActiveRecord::Base.establish_connection(in_mem_sqlite)
|
62
|
+
ActiveRecord::Base.configurations['test'] = in_mem_sqlite.stringify_keys
|
63
|
+
|
64
|
+
ActiveRecord::Base.logger = Logger.new("test/test.log") rescue Logger.new("test.log")
|
65
|
+
|
66
|
+
# This needs to be set relative to the file where the test comes from, NOT relative to the
|
67
|
+
# mosquito itself
|
68
|
+
Test::Unit::TestCase.fixture_path = "test/fixtures/"
|
69
|
+
|
70
|
+
# Mock request is used for composing the request body and headers
|
71
|
+
class Mosquito::MockRequest
|
72
|
+
# Should be a StringIO. However, you got some assignment methods that will
|
73
|
+
# stuff it with encoded parameters for you
|
74
|
+
attr_accessor :body
|
75
|
+
attr_reader :headers
|
76
|
+
|
77
|
+
DEFAULT_HEADERS = {
|
78
|
+
'SERVER_NAME' => 'test.host',
|
79
|
+
'PATH_INFO' => '',
|
80
|
+
'HTTP_ACCEPT_ENCODING' => 'gzip,deflate',
|
81
|
+
'HTTP_USER_AGENT' => 'Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O; en-US; rv:1.8.0.1) Gecko/20060214 Camino/1.0',
|
82
|
+
'SCRIPT_NAME' => '/',
|
83
|
+
'SERVER_PROTOCOL' => 'HTTP/1.1',
|
84
|
+
'HTTP_CACHE_CONTROL' => 'max-age=0',
|
85
|
+
'HTTP_ACCEPT_LANGUAGE' => 'en,ja;q=0.9,fr;q=0.9,de;q=0.8,es;q=0.7,it;q=0.7,nl;q=0.6,sv;q=0.5,nb;q=0.5,da;q=0.4,fi;q=0.3,pt;q=0.3,zh-Hans;q=0.2,zh-Hant;q=0.1,ko;q=0.1',
|
86
|
+
'HTTP_HOST' => 'test.host',
|
87
|
+
'REMOTE_ADDR' => '127.0.0.1',
|
88
|
+
'SERVER_SOFTWARE' => 'Mongrel 0.3.12.4',
|
89
|
+
'HTTP_KEEP_ALIVE' => '300',
|
90
|
+
'HTTP_REFERER' => 'http://localhost/',
|
91
|
+
'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
|
92
|
+
'HTTP_VERSION' => 'HTTP/1.1',
|
93
|
+
'REQUEST_URI' => '/',
|
94
|
+
'SERVER_PORT' => '80',
|
95
|
+
'GATEWAY_INTERFACE' => 'CGI/1.2',
|
96
|
+
'HTTP_ACCEPT' => 'text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5',
|
97
|
+
'HTTP_CONNECTION' => 'keep-alive',
|
98
|
+
'REQUEST_METHOD' => 'GET',
|
99
|
+
}
|
100
|
+
|
101
|
+
|
102
|
+
def initialize
|
103
|
+
@headers = DEFAULT_HEADERS.with_indifferent_access # :-)
|
104
|
+
@body = StringIO.new('hello Camping')
|
105
|
+
end
|
106
|
+
|
107
|
+
# Returns the hash of headers
|
108
|
+
def to_hash
|
109
|
+
@headers
|
110
|
+
end
|
111
|
+
|
112
|
+
# Gets a header
|
113
|
+
def [](key)
|
114
|
+
@headers[key]
|
115
|
+
end
|
116
|
+
|
117
|
+
# Sets a header
|
118
|
+
def []=(key, value)
|
119
|
+
@headers[key] = value
|
120
|
+
end
|
121
|
+
alias_method :set, :[]=
|
122
|
+
|
123
|
+
# Retrieve a composed query string (including the eventual "?") with URL-escaped segments
|
124
|
+
def query_string
|
125
|
+
(@query_string_with_qmark.blank? ? '' : @query_string_with_qmark)
|
126
|
+
end
|
127
|
+
|
128
|
+
# Set a composed query string, should have URL-escaped segments and include the elements after the "?"
|
129
|
+
def query_string=(nqs)
|
130
|
+
@query_string_with_qmark = nqs.gsub(/^([^\?])/, '?\1')
|
131
|
+
@headers["REQUEST_URI"] = @headers["REQUEST_URI"].split(/\?/).shift + @query_string_with_qmark
|
132
|
+
if nqs.blank?
|
133
|
+
@headers.delete "QUERY_STRING"
|
134
|
+
else
|
135
|
+
@headers["QUERY_STRING"] = nqs.gsub(/^\?/, '')
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# Retrieve the domain (analogous to HTTP_HOST)
|
140
|
+
def domain
|
141
|
+
server_name || http_host
|
142
|
+
end
|
143
|
+
|
144
|
+
# Set the domain (changes both HTTP_HOST and SERVER_NAME)
|
145
|
+
def domain=(nd)
|
146
|
+
self['SERVER_NAME'] = self['HTTP_HOST'] = nd
|
147
|
+
end
|
148
|
+
|
149
|
+
# Allow getters like this:
|
150
|
+
# o.REQUEST_METHOD or o.request_method
|
151
|
+
def method_missing(method_name, *args)
|
152
|
+
triables = [method_name.to_s, method_name.to_s.upcase, "HTTP_" + method_name.to_s.upcase]
|
153
|
+
triables.map do | possible_key |
|
154
|
+
return @headers[possible_key] if @headers.has_key?(possible_key)
|
155
|
+
end
|
156
|
+
super(method_name, args)
|
157
|
+
end
|
158
|
+
|
159
|
+
# Assign a hash of parameters that should be used for the query string
|
160
|
+
def query_string_params=(new_param_hash)
|
161
|
+
self.query_string = qs_build(new_param_hash)
|
162
|
+
end
|
163
|
+
|
164
|
+
# Append a freeform segment to the query string in the request. Useful when you
|
165
|
+
# want to quickly combine the query strings.
|
166
|
+
def append_to_query_string(piece)
|
167
|
+
new_qs = '?' + [self.query_string.gsub(/^\?/, ''), piece].reject{|e| e.blank? }.join('&')
|
168
|
+
self.query_string = new_qs
|
169
|
+
end
|
170
|
+
|
171
|
+
# Assign a hash of parameters that should be used for POST. These might include
|
172
|
+
# objects that act like a file upload (with #original_filename and all)
|
173
|
+
def post_params=(new_param_hash_or_str)
|
174
|
+
# First see if this is a body payload
|
175
|
+
if !new_param_hash_or_str.kind_of?(Hash)
|
176
|
+
compose_verbatim_payload(new_param_hash_or_str)
|
177
|
+
# then check if anything in the new param hash resembles an uplaod
|
178
|
+
elsif extract_values(new_param_hash_or_str).any?{|value| value.respond_to?(:original_filename) }
|
179
|
+
compose_multipart_params(new_param_hash_or_str)
|
180
|
+
else
|
181
|
+
compose_urlencoded_params(new_param_hash_or_str)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
# Generates a random 22-character MIME boundary (useful for composing multipart POSTs)
|
186
|
+
def generate_boundary
|
187
|
+
"msqto-" + Mosquito::garbage(16)
|
188
|
+
end
|
189
|
+
|
190
|
+
private
|
191
|
+
# Quickly URL-escape something
|
192
|
+
def esc(t); Camping.escape(t.to_s);end
|
193
|
+
|
194
|
+
# Extracts an array of values from a deeply-nested hash
|
195
|
+
def extract_values(hash_or_a)
|
196
|
+
returning([]) do | vals |
|
197
|
+
flatten_hash(hash_or_a) do | keys, value |
|
198
|
+
vals << value
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
# Configures the test request for a POST
|
204
|
+
def compose_urlencoded_params(new_param_hash)
|
205
|
+
self['REQUEST_METHOD'] = 'POST'
|
206
|
+
self['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'
|
207
|
+
@body = StringIO.new(qs_build(new_param_hash))
|
208
|
+
end
|
209
|
+
|
210
|
+
def compose_verbatim_payload(payload)
|
211
|
+
self['REQUEST_METHOD'] = 'POST'
|
212
|
+
self['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'
|
213
|
+
@body = StringIO.new(payload)
|
214
|
+
end
|
215
|
+
|
216
|
+
# Configures the test request for a multipart POST
|
217
|
+
def compose_multipart_params(new_param_hash)
|
218
|
+
# here we check if the encoded segments contain the boundary and should generate a new one
|
219
|
+
# if something is matched
|
220
|
+
boundary = "----------#{generate_boundary}"
|
221
|
+
self['REQUEST_METHOD'] = 'POST'
|
222
|
+
self['CONTENT_TYPE'] = "multipart/form-data; boundary=#{boundary}"
|
223
|
+
@body = StringIO.new(multipart_build(new_param_hash, boundary))
|
224
|
+
end
|
225
|
+
|
226
|
+
# Return a multipart value segment from a file upload handle.
|
227
|
+
def uploaded_file_segment(key, upload_io, boundary)
|
228
|
+
<<-EOF
|
229
|
+
--#{boundary}\r
|
230
|
+
Content-Disposition: form-data; name="#{key}"; filename="#{Camping.escape(upload_io.original_filename)}"\r
|
231
|
+
Content-Type: #{upload_io.content_type}\r
|
232
|
+
Content-Length: #{upload_io.size}\r
|
233
|
+
\r
|
234
|
+
#{upload_io.read}\r
|
235
|
+
EOF
|
236
|
+
end
|
237
|
+
|
238
|
+
# Return a conventional value segment from a parameter value
|
239
|
+
def conventional_segment(key, value, boundary)
|
240
|
+
<<-EOF
|
241
|
+
--#{boundary}\r
|
242
|
+
Content-Disposition: form-data; name="#{key}"\r
|
243
|
+
\r
|
244
|
+
#{value}\r
|
245
|
+
EOF
|
246
|
+
end
|
247
|
+
|
248
|
+
# Build a multipart request body that includes both uploaded files and conventional parameters.
|
249
|
+
# To have predictable results we sort the output segments (a hash passed in will not be
|
250
|
+
# iterated over in the original definition order anyway, as a good developer should know)
|
251
|
+
def multipart_build(params, boundary)
|
252
|
+
flat = []
|
253
|
+
flatten_hash(params) do | keys, value |
|
254
|
+
if keys[-1].nil? # warn the user that Camping will never see that
|
255
|
+
raise Mosquito::SageAdvice,
|
256
|
+
"Camping will only show you the last element of the array when using multipart forms"
|
257
|
+
end
|
258
|
+
|
259
|
+
flat_key = [esc(keys.shift), keys.map{|k| "[%s]" % esc(k) }].flatten.join
|
260
|
+
if value.respond_to?(:original_filename)
|
261
|
+
flat << uploaded_file_segment(flat_key, value, boundary)
|
262
|
+
else
|
263
|
+
flat << conventional_segment(flat_key, value, boundary)
|
264
|
+
end
|
265
|
+
end
|
266
|
+
flat.sort.join("")+"--#{boundary}--\r"
|
267
|
+
end
|
268
|
+
|
269
|
+
# Build a query string. The brackets are NOT encoded. Camping is peculiar in that
|
270
|
+
# in contrast to Rails it wants item=1&item=2 to make { item=>[1,2] } to make arrays. We have
|
271
|
+
# to account for that.
|
272
|
+
def qs_build (hash)
|
273
|
+
returning([]) do | qs |
|
274
|
+
flatten_hash(hash) do | keys, value |
|
275
|
+
keys.pop if keys[-1].nil? # cater for camping array handling
|
276
|
+
if value.respond_to?(:original_filename)
|
277
|
+
raise Mosquito::SageAdvice, "Sending a file using GET won't do you any good"
|
278
|
+
end
|
279
|
+
|
280
|
+
qs << [esc(keys.shift), keys.map{|k| "[%s]" % esc(k)}, '=', esc(value)].flatten.join
|
281
|
+
end
|
282
|
+
end.sort.join('&')
|
283
|
+
end
|
284
|
+
|
285
|
+
# Will accept a hash or array of any depth, collapse it into
|
286
|
+
# pairs in the form of ([first_level_k, second_level_k, ...], value)
|
287
|
+
# and yield these pairs as it goes to the supplied block. Some
|
288
|
+
# pairs might be yieled twice because arrays create repeating keys.
|
289
|
+
def flatten_hash(hash_or_a, parent_keys = [], &blk)
|
290
|
+
if hash_or_a.is_a?(Hash)
|
291
|
+
hash_or_a.each_pair do | k, v |
|
292
|
+
flatten_hash(v, parent_keys + [k], &blk)
|
293
|
+
end
|
294
|
+
elsif hash_or_a.is_a?(Array)
|
295
|
+
hash_or_a.map do | v |
|
296
|
+
blk.call(parent_keys + [nil], v)
|
297
|
+
end
|
298
|
+
else
|
299
|
+
blk.call(parent_keys, hash_or_a)
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
# Works like a wrapper for a simulated file upload. To use:
|
305
|
+
#
|
306
|
+
# uploaded = Mosquito::MockUpload.new("beach.jpg")
|
307
|
+
#
|
308
|
+
# This will create a file with the JPEG content-type and 122 bytes of purely random data, which
|
309
|
+
# can then be submitted as a part of the test request
|
310
|
+
class Mosquito::MockUpload < StringIO
|
311
|
+
attr_reader :local_path, :original_filename, :content_type, :extension
|
312
|
+
IMAGE_TYPES = {:jpg => 'image/jpeg', :png => 'image/png', :gif => 'image/gif',
|
313
|
+
:pdf => 'application/pdf' }.stringify_keys
|
314
|
+
|
315
|
+
def initialize(filename)
|
316
|
+
tempname = "tempfile_#{Time.now.to_i}"
|
317
|
+
|
318
|
+
@temp = ::Tempfile.new(tempname)
|
319
|
+
@local_path = @temp.path
|
320
|
+
@original_filename = File.basename(filename)
|
321
|
+
@extension = File.extname(@original_filename).gsub(/^\./, '').downcase
|
322
|
+
@content_type = IMAGE_TYPES[@extension] || "application/#{@extension}"
|
323
|
+
|
324
|
+
size = 100.bytes
|
325
|
+
super("Stub file %s \n%s\n" % [@original_filename, Mosquito::garbage(size)])
|
326
|
+
end
|
327
|
+
|
328
|
+
def inspect
|
329
|
+
info = " @size='#{length}' @filename='#{original_filename}' @content_type='#{content_type}'>"
|
330
|
+
super[0..-2] + info
|
331
|
+
end
|
332
|
+
|
333
|
+
end
|
334
|
+
|
335
|
+
# Stealing our assigns the evil way. This should pose no problem
|
336
|
+
# for things that happen in the controller actions, but might be tricky
|
337
|
+
# if some other service upstream munges the variables.
|
338
|
+
# This service will always get included last (innermost), so it runs regardless of
|
339
|
+
# the services upstream (such as HTTP auth) that might not call super
|
340
|
+
module Mosquito::Proboscis #:nodoc:
|
341
|
+
def service(*a)
|
342
|
+
returning(super(*a)) do
|
343
|
+
a = instance_variables.inject({}) do | assigns, ivar |
|
344
|
+
assigns[ivar.gsub(/^@/, '')] = instance_variable_get(ivar); assigns
|
345
|
+
end
|
346
|
+
Mosquito.stash(::Camping::H[a])
|
347
|
+
end
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
module Camping
|
352
|
+
|
353
|
+
# The basic Mosquito-wielding test case with some infrastructure
|
354
|
+
class Test < Test::Unit::TestCase
|
355
|
+
class << self; include Mosquito::Dusty; end
|
356
|
+
|
357
|
+
def test_default; end #:nodoc
|
358
|
+
|
359
|
+
# This is needed because Rails fixtures actually try to setup twice
|
360
|
+
self.use_transactional_fixtures = false
|
361
|
+
#
|
362
|
+
# # This is a removal for Rails fixture metamagic
|
363
|
+
# def self.method_added(*a); end #:nodoc:
|
364
|
+
#
|
365
|
+
# def setup #:nodoc:
|
366
|
+
# setup_with_fixtures
|
367
|
+
# end
|
368
|
+
#
|
369
|
+
# def teardown #:nodoc:
|
370
|
+
# teardown_with_fixtures
|
371
|
+
# super # This is good for people who use flexmock/mocha
|
372
|
+
# end
|
373
|
+
|
374
|
+
# The reverse of the reverse of the reverse of assert(condition)
|
375
|
+
def deny(condition, message='')
|
376
|
+
assert !condition, message
|
377
|
+
end
|
378
|
+
|
379
|
+
# http://project.ioni.st/post/217#post-217
|
380
|
+
#
|
381
|
+
# def test_new_publication
|
382
|
+
# assert_difference(Publication, :count) do
|
383
|
+
# post :create, :publication_title => ...
|
384
|
+
# # ...
|
385
|
+
# end
|
386
|
+
# end
|
387
|
+
#
|
388
|
+
# Is the number of items different?
|
389
|
+
#
|
390
|
+
# Can be used for increment and decrement.
|
391
|
+
#
|
392
|
+
def assert_difference(object, method = :count, difference = 1)
|
393
|
+
initial_value = object.send(method)
|
394
|
+
yield
|
395
|
+
assert_equal initial_value + difference, object.send(method), "#{object}##{method}"
|
396
|
+
end
|
397
|
+
|
398
|
+
# See +assert_difference+
|
399
|
+
def assert_no_difference(object, method, &block)
|
400
|
+
assert_difference object, method, 0, &block
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
# Used to test the controllers and rendering. The test should be called <App>Test
|
405
|
+
# (BlogTest for the aplication called Blog). A number of helper instance variables
|
406
|
+
# will be created for you - @request, which will contain a Mosquito::MockRequest
|
407
|
+
# object, @response (contains the response with headers and body), @cookies (a hash)
|
408
|
+
# and @state (a hash). Request and response will be reset in each test.
|
409
|
+
class WebTest < Test
|
410
|
+
|
411
|
+
# Gives you access to the instance variables assigned by the controller
|
412
|
+
attr_reader :assigns
|
413
|
+
|
414
|
+
def test_default; end #:nodoc
|
415
|
+
|
416
|
+
def setup
|
417
|
+
super
|
418
|
+
@class_name_abbr = self.class.name.gsub(/^Test/, '')
|
419
|
+
@request = Mosquito::MockRequest.new
|
420
|
+
@cookies, @response, @assigns = {}, {}, {}
|
421
|
+
end
|
422
|
+
|
423
|
+
# Send a GET request to a URL
|
424
|
+
def get(url='/', vars={})
|
425
|
+
send_request url, vars, 'GET'
|
426
|
+
end
|
427
|
+
|
428
|
+
# Send a POST request to a URL. All requests except GET will allow
|
429
|
+
# setting verbatim request body as the third argument instead
|
430
|
+
# of a hash.
|
431
|
+
def post(url, post_vars={})
|
432
|
+
send_request url, post_vars, 'POST'
|
433
|
+
end
|
434
|
+
|
435
|
+
# Send a DELETE request to a URL. All requests except GET will allow
|
436
|
+
# setting verbatim request body as the third argument instead
|
437
|
+
# of a hash.
|
438
|
+
def delete(url, vars={})
|
439
|
+
send_request url, vars, 'DELETE'
|
440
|
+
end
|
441
|
+
|
442
|
+
# Send a PUT request to a URL. All requests except GET will allow
|
443
|
+
# setting verbatim request body as the third argument instead
|
444
|
+
# of a hash.
|
445
|
+
def put(url, vars={})
|
446
|
+
send_request url, vars, 'PUT'
|
447
|
+
end
|
448
|
+
|
449
|
+
# Send any request. We will try to guess what you meant - if there are uploads to be
|
450
|
+
# processed it's not going to be a GET, that's for sure.
|
451
|
+
def send_request(composite_url, post_vars, method)
|
452
|
+
|
453
|
+
if method.to_s.downcase == "get"
|
454
|
+
@request.query_string_params = post_vars
|
455
|
+
else
|
456
|
+
@request.post_params = post_vars
|
457
|
+
end
|
458
|
+
|
459
|
+
# If there is some stuff in the URL to be used as a query string, why ignore it?
|
460
|
+
relative_url, qs_from_url = relativize_url(composite_url)
|
461
|
+
|
462
|
+
@request.append_to_query_string(qs_from_url) if qs_from_url
|
463
|
+
|
464
|
+
# We do allow the user to override that one
|
465
|
+
@request['REQUEST_METHOD'] = method
|
466
|
+
|
467
|
+
@request['SCRIPT_NAME'] = '/' + @class_name_abbr.downcase
|
468
|
+
@request['PATH_INFO'] = '/' + relative_url
|
469
|
+
|
470
|
+
# We need to munge this because the PATH_INFO has changed
|
471
|
+
@request['REQUEST_URI'] = [@request.SCRIPT_NAME, @request.PATH_INFO].join('').squeeze('/')
|
472
|
+
unless @request['QUERY_STRING'].blank?
|
473
|
+
@request['REQUEST_URI'] += ('?' + @request['QUERY_STRING'])
|
474
|
+
end
|
475
|
+
|
476
|
+
if @cookies
|
477
|
+
@request['HTTP_COOKIE'] = @cookies.map {|k,v| "#{k}=#{Camping.escape(v)}" }.join('; ')
|
478
|
+
end
|
479
|
+
|
480
|
+
# Get the Camping app
|
481
|
+
app_module = Kernel.const_get(@class_name_abbr)
|
482
|
+
|
483
|
+
# Inject the proboscis if we haven't already done so
|
484
|
+
app_module.send(:include, Mosquito::Proboscis) unless app_module.ancestors.include?(Mosquito::Proboscis)
|
485
|
+
|
486
|
+
# Run the request. Do not use eval because
|
487
|
+
@response = app_module.run @request.body, @request
|
488
|
+
|
489
|
+
# Add content_type to response
|
490
|
+
eval("class << @response; def content_type; @headers['Content-Type']; end; end")
|
491
|
+
|
492
|
+
# Downgrade the disguised Mab into a string
|
493
|
+
@response.body = @response.body.to_s
|
494
|
+
@assigns = Mosquito::unstash
|
495
|
+
|
496
|
+
# We need to restore the cookies separately so that the app
|
497
|
+
# restores our session on the next request. We retrieve cookies and
|
498
|
+
# the session in their assigned form instead of parsing the headers and
|
499
|
+
# doing a deserialization cycle
|
500
|
+
@cookies = @assigns.cookies || H[{}]
|
501
|
+
@state = @assigns.state || H[{}]
|
502
|
+
|
503
|
+
if @response.headers['X-Sendfile']
|
504
|
+
@response.body = File.read(@response.headers['X-Sendfile'])
|
505
|
+
end
|
506
|
+
end
|
507
|
+
|
508
|
+
# Assert a specific response (:success, :error or a freeform error code as integer)
|
509
|
+
def assert_response(status_code)
|
510
|
+
case status_code
|
511
|
+
when :success
|
512
|
+
assert_equal 200, @response.status
|
513
|
+
when :redirect
|
514
|
+
assert_equal 302, @response.status
|
515
|
+
when :error
|
516
|
+
assert @response.status >= 500,
|
517
|
+
"Response status should have been >= 500 but was #{@response.status}"
|
518
|
+
else
|
519
|
+
assert_equal status_code, @response.status
|
520
|
+
end
|
521
|
+
end
|
522
|
+
|
523
|
+
# Check that the text in the body matches a regexp
|
524
|
+
def assert_match_body(regex, message=nil)
|
525
|
+
assert_match regex, @response.body, message
|
526
|
+
end
|
527
|
+
|
528
|
+
# Opposite of +assert_match_body+
|
529
|
+
def assert_no_match_body(regex, message=nil)
|
530
|
+
assert_no_match regex, @response.body, message
|
531
|
+
end
|
532
|
+
|
533
|
+
# Make sure that we are redirected to a certain URL. It's not needed
|
534
|
+
# to prepend the URL with a mount (instead of "/blog/latest-news" you can use "/latest-news")
|
535
|
+
#
|
536
|
+
# Checks both the response status and the url.
|
537
|
+
def assert_redirected_to(url, message=nil)
|
538
|
+
assert_response :redirect
|
539
|
+
assert_equal url, extract_redirection_url, message
|
540
|
+
end
|
541
|
+
|
542
|
+
# Assert that a cookie of name matches a certain pattern
|
543
|
+
def assert_cookie(name, pat, message=nil)
|
544
|
+
assert_match pat, @cookies[name], message
|
545
|
+
end
|
546
|
+
|
547
|
+
# Nothing is new under the sun
|
548
|
+
def follow_redirect
|
549
|
+
get extract_redirection_url
|
550
|
+
end
|
551
|
+
|
552
|
+
# Quickly gives you a handle to a file with random content
|
553
|
+
def upload(filename)
|
554
|
+
Mosquito::MockUpload.new(filename)
|
555
|
+
end
|
556
|
+
|
557
|
+
# Checks that Camping sent us a cookie to attach a session
|
558
|
+
def assert_session_started
|
559
|
+
assert_not_nil @cookies["camping_sid"],
|
560
|
+
"The session ID cookie was empty although session should have started"
|
561
|
+
end
|
562
|
+
|
563
|
+
# The reverse of +assert_session_started+
|
564
|
+
def assert_no_session
|
565
|
+
assert_nil @cookies["camping_sid"],
|
566
|
+
"A session cookie was sent although this should not happen"
|
567
|
+
end
|
568
|
+
|
569
|
+
private
|
570
|
+
def extract_redirection_url
|
571
|
+
loc = @response.headers['Location']
|
572
|
+
path_seg = @response.headers['Location'].path.gsub(%r!/#{@class_name_abbr.downcase}!, '')
|
573
|
+
loc.query ? (path_seg + "?" + loc.query).to_s : path_seg.to_s
|
574
|
+
end
|
575
|
+
|
576
|
+
def relativize_url(url)
|
577
|
+
parsed = URI.parse(url)
|
578
|
+
if !parsed.scheme.blank? && parsed.host != @request.domain
|
579
|
+
raise ::Mosquito::NonLocalRequest,
|
580
|
+
"You tried to callout to '#{parsed}' which is outside of the test domain (#{@request.domain})"
|
581
|
+
end
|
582
|
+
# Now remove the path
|
583
|
+
parsed.path.gsub!(/^\/#{@class_name_abbr.downcase}\//, '/')
|
584
|
+
[parsed.path, parsed.query]
|
585
|
+
end
|
586
|
+
end
|
587
|
+
|
588
|
+
# Used to test the models - no infrastructure will be created for running the request
|
589
|
+
class ModelTest < Test
|
590
|
+
def test_default; end #:nodoc
|
591
|
+
end
|
592
|
+
|
593
|
+
# Deprecated but humane
|
594
|
+
UnitTest = ModelTest
|
595
|
+
FunctionalTest = WebTest
|
596
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestApproval < Camping::ModelTest
|
4
|
+
TATIVILLE_WIKI = 'http://tativille.fr/wiki/'
|
5
|
+
fixtures :pasaporte_profiles
|
6
|
+
|
7
|
+
def setup
|
8
|
+
super; @hulot = Profile.find(1)
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_associations_defined
|
12
|
+
assert_kind_of Enumerable, @hulot.approvals, "An association for approvals should exist"
|
13
|
+
assert_kind_of NilClass, Approval.new.profile
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_presence_validations
|
17
|
+
approval = Approval.new
|
18
|
+
deny approval.valid?
|
19
|
+
approval.profile = @hulot
|
20
|
+
|
21
|
+
deny approval.valid?
|
22
|
+
approval.trust_root = TATIVILLE_WIKI
|
23
|
+
assert approval.valid?, "Now the approval is valid"
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_uniqueness_validations
|
27
|
+
approval = Approval.create :profile => @hulot, :trust_root => TATIVILLE_WIKI
|
28
|
+
deny approval.new_record?
|
29
|
+
assert_raise(ActiveRecord::RecordInvalid) do
|
30
|
+
Approval.create! :profile => @hulot, :trust_root => TATIVILLE_WIKI
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/helper'
|
2
|
+
|
3
|
+
silence_warnings do
|
4
|
+
require 'pasaporte/auth/yaml_table'
|
5
|
+
require 'pasaporte/auth/yaml_digest_table'
|
6
|
+
end
|
7
|
+
|
8
|
+
class TestAuthBackends < Camping::ModelTest
|
9
|
+
STORE = File.dirname(Pasaporte::PATH) + '/pasaporte'
|
10
|
+
YAML_T = STORE + '/users_per_domain.yml'
|
11
|
+
|
12
|
+
def test_yaml_table
|
13
|
+
h = {}
|
14
|
+
h["test.foo"] = [{"login" => 'schmutz', "pass" => "xx"}]
|
15
|
+
h["test.bar"] = [{"login" => 'schtolz', "pass" => "xx"}]
|
16
|
+
File.open(YAML_T, 'w') { | f | f << h.to_yaml }
|
17
|
+
|
18
|
+
auth = Pasaporte::Auth::YamlTable.new
|
19
|
+
deny auth.call("foo", "bar", "xxx.com")
|
20
|
+
deny auth.call("schmutz", "xx", "test.bar")
|
21
|
+
|
22
|
+
assert auth.call("schmutz", "xx", "test.foo")
|
23
|
+
assert auth.call("schtolz", "xx", "test.bar")
|
24
|
+
end
|
25
|
+
|
26
|
+
# def test_yaml_table_with_implicit_reload
|
27
|
+
# h = {}
|
28
|
+
# h["test.foo"] = [{"login" => 'schmutz', "pass" => "xx"}]
|
29
|
+
# File.open(YAML_T, 'w') { | f | f << h.to_yaml }
|
30
|
+
#
|
31
|
+
# auth = Pasaporte::Auth::YamlTable.new
|
32
|
+
#
|
33
|
+
# assert auth.call("schmutz", "xx", "test.foo"), "Schmutz is in the table"
|
34
|
+
# deny auth.call("schtolz", "xx", "test.foo"), "This guy is not listed in the table yet"
|
35
|
+
#
|
36
|
+
# h["test.foo"] << {"login" => 'schtolz', "pass" => "xx"}
|
37
|
+
# File.open(YAML_T, 'w') { | f | f << h.to_yaml }
|
38
|
+
#
|
39
|
+
# assert auth.call("schtolz", "xx", "test.foo"), "Schtolz is now in"
|
40
|
+
# end
|
41
|
+
|
42
|
+
def test_yaml_digest_table
|
43
|
+
h = {}
|
44
|
+
h["test.foo"] = [{"login" => 'schmutz', "pass_md5" => _md5("xyz")}]
|
45
|
+
h["test.bar"] = [{"login" => 'schtolz', "pass_md5" => _md5("watoi")}]
|
46
|
+
File.open(YAML_T, 'w') { | f | f << h.to_yaml }
|
47
|
+
|
48
|
+
auth = Pasaporte::Auth::YamlDigestTable.new
|
49
|
+
end
|
50
|
+
|
51
|
+
def teardown
|
52
|
+
FileUtils.rm_rf(YAML_T) rescue Errno::ENOENT
|
53
|
+
super
|
54
|
+
end
|
55
|
+
|
56
|
+
def _md5(x)
|
57
|
+
Digest::MD5.hexdigest(x).to_s
|
58
|
+
end
|
59
|
+
end
|