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