arrow 1.0.7
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/ChangeLog +1590 -0
- data/LICENSE +28 -0
- data/README +75 -0
- data/Rakefile +366 -0
- data/Rakefile.local +63 -0
- data/data/arrow/applets/TEMPLATE.rb.tpl +53 -0
- data/data/arrow/applets/args.rb +50 -0
- data/data/arrow/applets/config.rb +55 -0
- data/data/arrow/applets/error.rb +63 -0
- data/data/arrow/applets/files.rb +46 -0
- data/data/arrow/applets/inspect.rb +46 -0
- data/data/arrow/applets/nosuchapplet.rb +31 -0
- data/data/arrow/applets/status.rb +92 -0
- data/data/arrow/applets/test.rb +133 -0
- data/data/arrow/applets/tutorial/counter.rb +96 -0
- data/data/arrow/applets/tutorial/dingus.rb +67 -0
- data/data/arrow/applets/tutorial/hello.rb +34 -0
- data/data/arrow/applets/tutorial/hello2.rb +73 -0
- data/data/arrow/applets/tutorial/imgtext.rb +90 -0
- data/data/arrow/applets/tutorial/imgtext2.rb +286 -0
- data/data/arrow/applets/tutorial/index.rb +36 -0
- data/data/arrow/applets/tutorial/logo.rb +98 -0
- data/data/arrow/applets/tutorial/memcache.rb +61 -0
- data/data/arrow/applets/tutorial/missing.rb +37 -0
- data/data/arrow/applets/tutorial/protected.rb +100 -0
- data/data/arrow/applets/tutorial/redirector.rb +52 -0
- data/data/arrow/applets/tutorial/rndimages.rb +159 -0
- data/data/arrow/applets/tutorial/sharenotes.rb +83 -0
- data/data/arrow/applets/tutorial/subclassed-hello.rb +32 -0
- data/data/arrow/applets/tutorial/superhello.rb +72 -0
- data/data/arrow/applets/tutorial/timeclock.rb +78 -0
- data/data/arrow/applets/view-applet.rb +123 -0
- data/data/arrow/applets/view-template.rb +85 -0
- data/data/arrow/applets/wiki.rb +274 -0
- data/data/arrow/templates/TEMPLATE.tmpl.tpl +36 -0
- data/data/arrow/templates/applet-status.tmpl +153 -0
- data/data/arrow/templates/args-display.tmpl +120 -0
- data/data/arrow/templates/config/display-table.tmpl +36 -0
- data/data/arrow/templates/config/display.tmpl +36 -0
- data/data/arrow/templates/counter-deleted.tmpl +33 -0
- data/data/arrow/templates/counter.tmpl +59 -0
- data/data/arrow/templates/dingus.tmpl +55 -0
- data/data/arrow/templates/enumtable.tmpl +8 -0
- data/data/arrow/templates/error-display.tmpl +92 -0
- data/data/arrow/templates/filemap.tmpl +89 -0
- data/data/arrow/templates/hello-world-src.tmpl +34 -0
- data/data/arrow/templates/hello-world.tmpl +60 -0
- data/data/arrow/templates/imgtext/fontlist.tmpl +46 -0
- data/data/arrow/templates/imgtext/form.tmpl +70 -0
- data/data/arrow/templates/imgtext/reload-error.tmpl +40 -0
- data/data/arrow/templates/imgtext/reload.tmpl +55 -0
- data/data/arrow/templates/inspect/display.tmpl +80 -0
- data/data/arrow/templates/loginform.tmpl +64 -0
- data/data/arrow/templates/logout.tmpl +32 -0
- data/data/arrow/templates/memcache/display.tmpl +41 -0
- data/data/arrow/templates/navbar.incl +27 -0
- data/data/arrow/templates/nosuchapplet.tmpl +32 -0
- data/data/arrow/templates/printsource.tmpl +35 -0
- data/data/arrow/templates/protected.tmpl +36 -0
- data/data/arrow/templates/rndimages.tmpl +38 -0
- data/data/arrow/templates/service-response.tmpl +13 -0
- data/data/arrow/templates/sharenotes/display.tmpl +38 -0
- data/data/arrow/templates/status.tmpl +120 -0
- data/data/arrow/templates/templateviewer.tmpl +43 -0
- data/data/arrow/templates/test/harness.tmpl +57 -0
- data/data/arrow/templates/test/list.tmpl +48 -0
- data/data/arrow/templates/test/problem.tmpl +42 -0
- data/data/arrow/templates/tutorial/index.tmpl +37 -0
- data/data/arrow/templates/tutorial/missingapplet.tmpl +29 -0
- data/data/arrow/templates/view-applet-nosuch.tmpl +32 -0
- data/data/arrow/templates/view-applet.tmpl +40 -0
- data/data/arrow/templates/view-template.tmpl +83 -0
- data/data/arrow/templates/wiki/formerror.tmpl +47 -0
- data/data/arrow/templates/wiki/markup_help.incl +6 -0
- data/data/arrow/templates/wiki/new.tmpl +56 -0
- data/data/arrow/templates/wiki/new_system.tmpl +122 -0
- data/data/arrow/templates/wiki/sectionlist.tmpl +43 -0
- data/data/arrow/templates/wiki/show.tmpl +34 -0
- data/docs/manual/layouts/default.page +43 -0
- data/docs/manual/lib/api-filter.rb +81 -0
- data/docs/manual/lib/editorial-filter.rb +64 -0
- data/docs/manual/lib/examples-filter.rb +244 -0
- data/docs/manual/lib/links-filter.rb +117 -0
- data/lib/apache/fakerequest.rb +448 -0
- data/lib/apache/logger.rb +33 -0
- data/lib/arrow.rb +51 -0
- data/lib/arrow/acceptparam.rb +207 -0
- data/lib/arrow/applet.rb +725 -0
- data/lib/arrow/appletmixins.rb +218 -0
- data/lib/arrow/appletregistry.rb +590 -0
- data/lib/arrow/applettestcase.rb +503 -0
- data/lib/arrow/broker.rb +255 -0
- data/lib/arrow/cache.rb +176 -0
- data/lib/arrow/config-loaders/yaml.rb +75 -0
- data/lib/arrow/config.rb +615 -0
- data/lib/arrow/constants.rb +24 -0
- data/lib/arrow/cookie.rb +359 -0
- data/lib/arrow/cookieset.rb +108 -0
- data/lib/arrow/dispatcher.rb +368 -0
- data/lib/arrow/dispatcherloader.rb +50 -0
- data/lib/arrow/exceptions.rb +61 -0
- data/lib/arrow/fallbackhandler.rb +48 -0
- data/lib/arrow/formvalidator.rb +631 -0
- data/lib/arrow/htmltokenizer.rb +343 -0
- data/lib/arrow/logger.rb +488 -0
- data/lib/arrow/logger/apacheoutputter.rb +69 -0
- data/lib/arrow/logger/arrayoutputter.rb +63 -0
- data/lib/arrow/logger/coloroutputter.rb +111 -0
- data/lib/arrow/logger/fileoutputter.rb +96 -0
- data/lib/arrow/logger/htmloutputter.rb +54 -0
- data/lib/arrow/logger/outputter.rb +123 -0
- data/lib/arrow/mixins.rb +425 -0
- data/lib/arrow/monkeypatches.rb +94 -0
- data/lib/arrow/object.rb +117 -0
- data/lib/arrow/path.rb +196 -0
- data/lib/arrow/service.rb +447 -0
- data/lib/arrow/session.rb +289 -0
- data/lib/arrow/session/dbstore.rb +100 -0
- data/lib/arrow/session/filelock.rb +160 -0
- data/lib/arrow/session/filestore.rb +132 -0
- data/lib/arrow/session/id.rb +98 -0
- data/lib/arrow/session/lock.rb +253 -0
- data/lib/arrow/session/md5id.rb +42 -0
- data/lib/arrow/session/nulllock.rb +42 -0
- data/lib/arrow/session/posixlock.rb +166 -0
- data/lib/arrow/session/sha1id.rb +54 -0
- data/lib/arrow/session/store.rb +366 -0
- data/lib/arrow/session/usertrackid.rb +52 -0
- data/lib/arrow/spechelpers.rb +73 -0
- data/lib/arrow/template.rb +713 -0
- data/lib/arrow/template/attr.rb +31 -0
- data/lib/arrow/template/call.rb +31 -0
- data/lib/arrow/template/comment.rb +33 -0
- data/lib/arrow/template/container.rb +118 -0
- data/lib/arrow/template/else.rb +41 -0
- data/lib/arrow/template/elsif.rb +44 -0
- data/lib/arrow/template/escape.rb +53 -0
- data/lib/arrow/template/export.rb +87 -0
- data/lib/arrow/template/for.rb +145 -0
- data/lib/arrow/template/if.rb +78 -0
- data/lib/arrow/template/import.rb +119 -0
- data/lib/arrow/template/include.rb +206 -0
- data/lib/arrow/template/iterator.rb +208 -0
- data/lib/arrow/template/nodes.rb +734 -0
- data/lib/arrow/template/parser.rb +571 -0
- data/lib/arrow/template/prettyprint.rb +53 -0
- data/lib/arrow/template/render.rb +191 -0
- data/lib/arrow/template/selectlist.rb +94 -0
- data/lib/arrow/template/set.rb +87 -0
- data/lib/arrow/template/timedelta.rb +81 -0
- data/lib/arrow/template/unless.rb +78 -0
- data/lib/arrow/template/urlencode.rb +51 -0
- data/lib/arrow/template/yield.rb +139 -0
- data/lib/arrow/templatefactory.rb +125 -0
- data/lib/arrow/testcase.rb +567 -0
- data/lib/arrow/transaction.rb +608 -0
- data/rake/191_compat.rb +26 -0
- data/rake/dependencies.rb +76 -0
- data/rake/documentation.rb +114 -0
- data/rake/helpers.rb +502 -0
- data/rake/hg.rb +282 -0
- data/rake/manual.rb +787 -0
- data/rake/packaging.rb +129 -0
- data/rake/publishing.rb +278 -0
- data/rake/style.rb +62 -0
- data/rake/svn.rb +668 -0
- data/rake/testing.rb +187 -0
- data/rake/verifytask.rb +64 -0
- data/spec/arrow/acceptparam_spec.rb +157 -0
- data/spec/arrow/applet_spec.rb +575 -0
- data/spec/arrow/appletmixins_spec.rb +409 -0
- data/spec/arrow/appletregistry_spec.rb +294 -0
- data/spec/arrow/broker_spec.rb +153 -0
- data/spec/arrow/config_spec.rb +224 -0
- data/spec/arrow/cookieset_spec.rb +164 -0
- data/spec/arrow/dispatcher_spec.rb +137 -0
- data/spec/arrow/dispatcherloader_spec.rb +65 -0
- data/spec/arrow/formvalidator_spec.rb +781 -0
- data/spec/arrow/logger_spec.rb +346 -0
- data/spec/arrow/mixins_spec.rb +120 -0
- data/spec/arrow/service_spec.rb +645 -0
- data/spec/arrow/session_spec.rb +121 -0
- data/spec/arrow/template/iterator_spec.rb +222 -0
- data/spec/arrow/templatefactory_spec.rb +185 -0
- data/spec/arrow/transaction_spec.rb +319 -0
- data/spec/arrow_spec.rb +37 -0
- data/spec/lib/appletmatchers.rb +281 -0
- data/spec/lib/constants.rb +77 -0
- data/spec/lib/helpers.rb +41 -0
- data/spec/lib/matchers.rb +44 -0
- data/tests/cookie.tests.rb +310 -0
- data/tests/path.tests.rb +157 -0
- data/tests/session.tests.rb +111 -0
- data/tests/session_id.tests.rb +82 -0
- data/tests/session_lock.tests.rb +191 -0
- data/tests/session_store.tests.rb +53 -0
- data/tests/template.tests.rb +1360 -0
- metadata +339 -0
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
BEGIN {
|
|
4
|
+
require 'pathname'
|
|
5
|
+
basedir = Pathname.new( __FILE__ ).dirname.parent.parent
|
|
6
|
+
|
|
7
|
+
libdir = basedir + "lib"
|
|
8
|
+
|
|
9
|
+
$LOAD_PATH.unshift( libdir ) unless $LOAD_PATH.include?( libdir )
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
require 'spec'
|
|
13
|
+
require 'spec/lib/helpers'
|
|
14
|
+
|
|
15
|
+
require 'apache/fakerequest'
|
|
16
|
+
|
|
17
|
+
require 'arrow'
|
|
18
|
+
require 'arrow/transaction'
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
#####################################################################
|
|
22
|
+
### C O N T E X T S
|
|
23
|
+
#####################################################################
|
|
24
|
+
|
|
25
|
+
describe Arrow::Transaction do
|
|
26
|
+
include Arrow::SpecHelpers
|
|
27
|
+
|
|
28
|
+
TEST_ACCEPT_HEADER = 'application/x-yaml, application/json; q=0.2, text/xml; q=0.75'
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
before( :all ) do
|
|
32
|
+
setup_logging( :crit )
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
before( :each ) do
|
|
36
|
+
@options = {}
|
|
37
|
+
|
|
38
|
+
@request = mock( "request object" )
|
|
39
|
+
@request.stub!( :options ).and_return( @options )
|
|
40
|
+
@request.stub!( :hostname ).and_return( 'testhost' )
|
|
41
|
+
|
|
42
|
+
@headers_in = Apache::Table.new
|
|
43
|
+
@headers_out = Apache::Table.new
|
|
44
|
+
@request.stub!( :headers_in ).and_return( @headers_in )
|
|
45
|
+
@request.stub!( :headers_out ).and_return( @headers_out )
|
|
46
|
+
@request.stub!( :sync_header= )
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
after( :all ) do
|
|
51
|
+
reset_logging()
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
it "knows it's dispatched from a handler mounted at / when its request " +
|
|
56
|
+
"has the root_dispatcher option set to 'yes'" do
|
|
57
|
+
@options['root_dispatcher'] = 'yes'
|
|
58
|
+
Arrow::Transaction.new( @request, nil, nil ).root_dispatcher?.should be_true()
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it "knows it's dispatched from a handler mounted at / when its request " +
|
|
62
|
+
"has the root_dispatcher option set to 'true'" do
|
|
63
|
+
@options['root_dispatcher'] = 'true'
|
|
64
|
+
Arrow::Transaction.new( @request, nil, nil ).root_dispatcher?.should be_true()
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it "knows it's dispatched from a handler mounted at / when its request " +
|
|
68
|
+
"has the root_dispatcher option set to '1'" do
|
|
69
|
+
@options['root_dispatcher'] = '1'
|
|
70
|
+
Arrow::Transaction.new( @request, nil, nil ).root_dispatcher?.should be_true()
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it "knows it's not dispatched from a handler mounted at / when its request " +
|
|
74
|
+
"has the root_dispatcher option set to 'false'" do
|
|
75
|
+
@options['root_dispatcher'] = 'false'
|
|
76
|
+
Arrow::Transaction.new( @request, nil, nil ).root_dispatcher?.should be_false()
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it "knows it's not dispatched from a handler mounted at / when its request " +
|
|
80
|
+
"has the root_dispatcher option set to 'no'" do
|
|
81
|
+
@options['root_dispatcher'] = 'no'
|
|
82
|
+
Arrow::Transaction.new( @request, nil, nil ).root_dispatcher?.should be_false()
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it "knows it's not dispatched from a handler mounted at / when its request " +
|
|
86
|
+
"has the root_dispatcher option set to '0'" do
|
|
87
|
+
@options['root_dispatcher'] = '0'
|
|
88
|
+
Arrow::Transaction.new( @request, nil, nil ).root_dispatcher?.should be_false()
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
it "dispatched from a root dispatcher returns an empty string for the approot" do
|
|
92
|
+
@options['root_dispatcher'] = 'yes'
|
|
93
|
+
Arrow::Transaction.new( @request, nil, nil ).app_root.should == ''
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
describe " (an instance)" do
|
|
98
|
+
|
|
99
|
+
before( :each ) do
|
|
100
|
+
@txn = Arrow::Transaction.new( @request, nil, nil )
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
it "knows that a form was submitted if there's a urlencoded form content-type header with a POST" do
|
|
105
|
+
@headers_in[ 'content-type' ] = 'application/x-www-form-urlencoded'
|
|
106
|
+
@request.should_receive( :request_method ).at_least(1).and_return( 'POST' )
|
|
107
|
+
@txn.should be_a_form_request
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
it "knows that a form was submitted if there's a urlencoded form content-type header with a PUT" do
|
|
111
|
+
@headers_in[ 'content-type' ] = 'application/x-www-form-urlencoded'
|
|
112
|
+
@request.should_receive( :unparsed_uri ).and_return( '' ) # query is nil
|
|
113
|
+
@request.should_receive( :request_method ).and_return( 'PUT' )
|
|
114
|
+
@txn.should be_a_form_request
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
it "knows that a form was submitted if there's a urlencoded form content-type header with a GET" do
|
|
118
|
+
@request.should_receive( :unparsed_uri ).and_return( 'foo?bar=bas&biz=boz' )
|
|
119
|
+
@request.should_receive( :request_method ).and_return( 'GET' )
|
|
120
|
+
@txn.should be_a_form_request
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
it "knows that a form was submitted if there's a urlencoded form content-type header with a DELETE" do
|
|
124
|
+
@request.should_receive( :unparsed_uri ).and_return( 'foo?bar=bas&biz=boz' )
|
|
125
|
+
@request.should_receive( :request_method ).and_return( 'DELETE' )
|
|
126
|
+
@txn.should be_a_form_request
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
#it "knows that it wasn't served over secure transport if its request's schema isn't 'https'" do
|
|
131
|
+
# @request.should_receive( :unparsed_uri ).and_return( )
|
|
132
|
+
#end
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
it "should indicate a successful response when the status is 200" do
|
|
136
|
+
@request.should_receive( :status ).at_least( :once ).and_return( Apache::HTTP_OK )
|
|
137
|
+
@txn.is_success?.should be_true
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
it "should indicate a successful response when the status is 201" do
|
|
141
|
+
@request.should_receive( :status ).at_least( :once ).and_return( Apache::HTTP_CREATED )
|
|
142
|
+
@txn.is_success?.should be_true
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
it "should indicate a successful response when the status is 202" do
|
|
146
|
+
@request.should_receive( :status ).at_least( :once ).and_return( Apache::HTTP_ACCEPTED )
|
|
147
|
+
@txn.is_success?.should be_true
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
it "should indicate a non-successful response when the status is 302" do
|
|
151
|
+
@request.should_receive( :status ).at_least( :once ).and_return( Apache::HTTP_MOVED_TEMPORARILY )
|
|
152
|
+
@txn.is_success?.should_not be_true
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
it "should set its Apache status to REDIRECT when #redirect is called" do
|
|
156
|
+
@request.should_receive( :status= ).with( Apache::HTTP_MOVED_TEMPORARILY )
|
|
157
|
+
@txn.redirect( 'http://example.com/something' )
|
|
158
|
+
@txn.handler_status.should == Apache::REDIRECT
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
it "delegates to the request for request methods" do
|
|
162
|
+
@request.should_receive( :allowed ).and_return( :yep )
|
|
163
|
+
@txn.allowed.should == :yep
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
it "returns the X-Forwarded-Host header if present for the value returned by #proxied_host" do
|
|
168
|
+
@headers_in[ 'X-Forwarded-Host' ] = 'foo.bar.com'
|
|
169
|
+
@txn.proxied_host.should == 'foo.bar.com'
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
it "returns the X-Forwarded-Server header if X-Forwarded-Host is not " +
|
|
173
|
+
"present for the value returned by #proxied_host" do
|
|
174
|
+
@headers_in[ 'X-Forwarded-Server' ] = 'foo.bar.com'
|
|
175
|
+
@txn.proxied_host.should == 'foo.bar.com'
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
it "uses the proxy header for #construct_url" do
|
|
180
|
+
@headers_in[ 'X-Forwarded-Host' ] = 'foo.bar.com'
|
|
181
|
+
@headers_in[ 'X-Forwarded-Server' ] = 'foo.bar.com'
|
|
182
|
+
|
|
183
|
+
@request.should_receive( :construct_url ).and_return( 'http://hostname/bar' )
|
|
184
|
+
|
|
185
|
+
@txn.construct_url( "/bar" ).should == 'http://foo.bar.com/bar'
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
it "knows when the transaction is requested via XHR by the X-Requested-With header" do
|
|
189
|
+
@headers_in[ 'X-Requested-With' ] = 'XMLHttpRequest'
|
|
190
|
+
@txn.is_ajax_request?.should be_true()
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
it "knows when the transaction is not requested via XHR by the absence " +
|
|
195
|
+
"of an X-Requested-With header" do
|
|
196
|
+
@txn.is_ajax_request?.should be_false()
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
it "knows when the transaction is not requested via XHR by a non-AJAX " +
|
|
200
|
+
"X-Requested-With header" do
|
|
201
|
+
@headers_in[ 'X-Requested-With' ] = 'magic jellybeans of doom'
|
|
202
|
+
@txn.is_ajax_request?.should be_false()
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
it "returns cookies from its headers as an Arrow::CookieSet" do
|
|
207
|
+
@headers_in[ 'Cookie' ] = 'foo=12'
|
|
208
|
+
|
|
209
|
+
# Cookies are parsed on transaction creation, so we can't use the
|
|
210
|
+
# transaction that's created in the before(:each)
|
|
211
|
+
txn = Arrow::Transaction.new( @request, nil, nil )
|
|
212
|
+
|
|
213
|
+
txn.request_cookies.should be_an_instance_of( Arrow::CookieSet )
|
|
214
|
+
txn.request_cookies.should include( 'foo' )
|
|
215
|
+
txn.request_cookies['foo'].should be_an_instance_of( Arrow::Cookie )
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
it "adds Cookie headers for each cookie in a successful response" do
|
|
219
|
+
@request.should_receive( :status ).
|
|
220
|
+
at_least(:once).
|
|
221
|
+
and_return( Apache::HTTP_OK )
|
|
222
|
+
|
|
223
|
+
@headers_out.should_receive( :[]= ).with( 'Set-Cookie', /glah=locke/i )
|
|
224
|
+
@headers_out.should_receive( :[]= ).with( 'Set-Cookie', /foo=bar/i )
|
|
225
|
+
@headers_out.should_receive( :[]= ).with( 'Set-Cookie', /pants=velcro/i )
|
|
226
|
+
|
|
227
|
+
@txn.cookies['glah'] = 'locke'
|
|
228
|
+
@txn.cookies['foo'] = 'bar'
|
|
229
|
+
@txn.cookies['pants'] = 'velcro!'
|
|
230
|
+
@txn.cookies['pants'].expires = 'Sat Nov 12 22:04:00 1955'
|
|
231
|
+
|
|
232
|
+
@txn.add_cookie_headers
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
it "adds Cookie error headers for each cookie in an non-OK response" do
|
|
236
|
+
output_headers = mock( "output headers", :null_object => true )
|
|
237
|
+
err_output_headers = mock( "error output headers", :null_object => true )
|
|
238
|
+
@request.should_not_receive( :headers_out )
|
|
239
|
+
@request.should_receive( :err_headers_out ).
|
|
240
|
+
at_least(:once).
|
|
241
|
+
and_return( err_output_headers )
|
|
242
|
+
@request.should_receive( :status ).
|
|
243
|
+
at_least(:once).
|
|
244
|
+
and_return( Apache::REDIRECT )
|
|
245
|
+
|
|
246
|
+
err_output_headers.should_receive( :[]= ).with( 'Set-Cookie', /glah=locke/i )
|
|
247
|
+
err_output_headers.should_receive( :[]= ).with( 'Set-Cookie', /foo=bar/i )
|
|
248
|
+
err_output_headers.should_receive( :[]= ).with( 'Set-Cookie', /pants=velcro/i )
|
|
249
|
+
|
|
250
|
+
@txn.cookies['glah'] = 'locke'
|
|
251
|
+
@txn.cookies['foo'] = 'bar'
|
|
252
|
+
@txn.cookies['pants'] = 'velcro!'
|
|
253
|
+
@txn.cookies['pants'].expires = 'Sat Nov 12 22:04:00 1955'
|
|
254
|
+
|
|
255
|
+
@txn.add_cookie_headers
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
it "parses the 'Accept' header into one or more AcceptParam structs" do
|
|
259
|
+
@headers_in['Accept'] = TEST_ACCEPT_HEADER
|
|
260
|
+
|
|
261
|
+
@txn.accepted_types.should have( 3 ).members
|
|
262
|
+
@txn.accepted_types[0].mediatype.should == 'application/x-yaml'
|
|
263
|
+
@txn.accepted_types[1].mediatype.should == 'application/json'
|
|
264
|
+
@txn.accepted_types[2].mediatype.should == 'text/xml'
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
it "knows what mimetypes are acceptable responses" do
|
|
268
|
+
@headers_in[ 'Accept' ] = 'text/html, text/plain; q=0.5, image/*;q=0.1'
|
|
269
|
+
|
|
270
|
+
@txn.accepts?( 'text/html' ).should be_true()
|
|
271
|
+
@txn.accepts?( 'text/plain' ).should be_true()
|
|
272
|
+
@txn.accepts?( 'text/ascii' ).should be_false()
|
|
273
|
+
@txn.accepts?( 'image/png' ).should be_true()
|
|
274
|
+
@txn.accepts?( 'application/x-yaml' ).should be_false()
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
it "knows what mimetypes are explicitly acceptable responses" do
|
|
279
|
+
@headers_in[ 'Accept' ] = 'text/html, text/plain; q=0.5, image/*;q=0.1, */*'
|
|
280
|
+
|
|
281
|
+
@txn.explicitly_accepts?( 'text/html' ).should be_true()
|
|
282
|
+
@txn.explicitly_accepts?( 'text/plain' ).should be_true()
|
|
283
|
+
@txn.explicitly_accepts?( 'text/ascii' ).should be_false()
|
|
284
|
+
@txn.explicitly_accepts?( 'image/png' ).should be_false()
|
|
285
|
+
@txn.explicitly_accepts?( 'application/x-yaml' ).should be_false()
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
it "accepts anything if the client doesn't provide an Accept header" do
|
|
290
|
+
@txn.accepts?( 'text/html' ).should be_true()
|
|
291
|
+
@txn.accepts?( 'text/plain' ).should be_true()
|
|
292
|
+
@txn.accepts?( 'text/ascii' ).should be_true()
|
|
293
|
+
@txn.accepts?( 'image/png' ).should be_true()
|
|
294
|
+
@txn.accepts?( 'application/x-yaml' ).should be_true()
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
it "knows that the request accepts HTML if its Accept: header indicates it accepts " +
|
|
298
|
+
"'text/html'" do
|
|
299
|
+
@headers_in[ 'Accept' ] = 'text/html, text/plain; q=0.5, image/*;q=0.1'
|
|
300
|
+
@txn.accepts_html?.should be_true()
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
it "knows that the request accepts HTML if its Accept: header indicates it accepts " +
|
|
304
|
+
"'application/xhtml+xml'" do
|
|
305
|
+
@headers_in[ 'Accept' ] = 'application/xhtml+xml, text/plain; q=0.5, image/*;q=0.1'
|
|
306
|
+
@txn.accepts_html?.should be_true()
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
it "knows that the request doesn't accept HTML if its Accept: header indicates it doesn't" do
|
|
310
|
+
@headers_in[ 'Accept' ] = 'text/plain; q=0.5, image/*;q=0.1'
|
|
311
|
+
@txn.accepts_html?.should be_false()
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
# vim: set nosta noet ts=4 sw=4:
|
data/spec/arrow_spec.rb
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
BEGIN {
|
|
4
|
+
require 'pathname'
|
|
5
|
+
basedir = Pathname.new( __FILE__ ).dirname.parent
|
|
6
|
+
|
|
7
|
+
libdir = basedir + "lib"
|
|
8
|
+
|
|
9
|
+
$LOAD_PATH.unshift( libdir ) unless $LOAD_PATH.include?( libdir )
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
require 'spec'
|
|
14
|
+
require 'spec/lib/constants'
|
|
15
|
+
require 'spec/lib/helpers'
|
|
16
|
+
|
|
17
|
+
require 'arrow'
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
#####################################################################
|
|
21
|
+
### C O N T E X T S
|
|
22
|
+
#####################################################################
|
|
23
|
+
|
|
24
|
+
describe Arrow do
|
|
25
|
+
include Arrow::SpecHelpers
|
|
26
|
+
|
|
27
|
+
it "returns a version string if asked" do
|
|
28
|
+
Arrow.version_string.should =~ /\w+ [\d.]+/
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
it "returns a version string with a build number if asked" do
|
|
33
|
+
Arrow.version_string(true).should =~ /\w+ [\d.]+ \(build [[:xdigit:]]+\)/
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
end
|
|
37
|
+
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
# These are a collection of RSpec matchers to make unit testing of Arrow
|
|
4
|
+
# applets easier.
|
|
5
|
+
|
|
6
|
+
require 'apache/fakerequest'
|
|
7
|
+
require 'arrow'
|
|
8
|
+
|
|
9
|
+
require 'spec/matchers'
|
|
10
|
+
|
|
11
|
+
### Fixturing functions
|
|
12
|
+
module Arrow::AppletMatchers
|
|
13
|
+
|
|
14
|
+
STATUS_NAMES = {
|
|
15
|
+
100 => 'CONTINUE',
|
|
16
|
+
101 => 'SWITCHING_PROTOCOLS',
|
|
17
|
+
102 => 'PROCESSING',
|
|
18
|
+
|
|
19
|
+
200 => 'OK',
|
|
20
|
+
201 => 'CREATED',
|
|
21
|
+
202 => 'ACCEPTED',
|
|
22
|
+
203 => 'NON_AUTHORITATIVE',
|
|
23
|
+
204 => 'NO_CONTENT',
|
|
24
|
+
205 => 'RESET_CONTENT',
|
|
25
|
+
206 => 'PARTIAL_CONTENT',
|
|
26
|
+
207 => 'MULTI_STATUS',
|
|
27
|
+
|
|
28
|
+
300 => 'MULTIPLE_CHOICES',
|
|
29
|
+
301 => 'MOVED',
|
|
30
|
+
302 => 'REDIRECT',
|
|
31
|
+
303 => 'SEE_OTHER',
|
|
32
|
+
304 => 'NOT_MODIFIED',
|
|
33
|
+
305 => 'USE_PROXY',
|
|
34
|
+
307 => 'TEMPORARY_REDIRECT',
|
|
35
|
+
|
|
36
|
+
400 => 'BAD_REQUEST',
|
|
37
|
+
401 => 'UNAUTHORIZED',
|
|
38
|
+
402 => 'PAYMENT_REQUIRED',
|
|
39
|
+
403 => 'FORBIDDEN',
|
|
40
|
+
404 => 'NOT_FOUND',
|
|
41
|
+
405 => 'METHOD_NOT_ALLOWED',
|
|
42
|
+
406 => 'NOT_ACCEPTABLE',
|
|
43
|
+
407 => 'PROXY_AUTHENTICATION_REQUIRED',
|
|
44
|
+
408 => 'REQUEST_TIME_OUT',
|
|
45
|
+
409 => 'CONFLICT',
|
|
46
|
+
410 => 'GONE',
|
|
47
|
+
411 => 'LENGTH_REQUIRED',
|
|
48
|
+
412 => 'PRECONDITION_FAILED',
|
|
49
|
+
413 => 'REQUEST_ENTITY_TOO_LARGE',
|
|
50
|
+
414 => 'REQUEST_URI_TOO_LARGE',
|
|
51
|
+
415 => 'UNSUPPORTED_MEDIA_TYPE',
|
|
52
|
+
416 => 'RANGE_NOT_SATISFIABLE',
|
|
53
|
+
417 => 'EXPECTATION_FAILED',
|
|
54
|
+
422 => 'UNPROCESSABLE_ENTITY',
|
|
55
|
+
423 => 'LOCKED',
|
|
56
|
+
424 => 'FAILED_DEPENDENCY',
|
|
57
|
+
|
|
58
|
+
500 => 'SERVER_ERROR',
|
|
59
|
+
501 => 'NOT_IMPLEMENTED',
|
|
60
|
+
502 => 'BAD_GATEWAY',
|
|
61
|
+
503 => 'SERVICE_UNAVAILABLE',
|
|
62
|
+
504 => 'GATEWAY_TIME_OUT',
|
|
63
|
+
505 => 'VERSION_NOT_SUPPORTED',
|
|
64
|
+
506 => 'VARIANT_ALSO_VARIES',
|
|
65
|
+
507 => 'INSUFFICIENT_STORAGE',
|
|
66
|
+
510 => 'NOT_EXTENDED',
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
### A matcher that wraps an applet's #run method and can assert things about the
|
|
71
|
+
### generated response.
|
|
72
|
+
class RunWithMatcher
|
|
73
|
+
|
|
74
|
+
### Create a new FinishWithMatcher that should finish with the given
|
|
75
|
+
###
|
|
76
|
+
def initialize( status, message=:any_message )
|
|
77
|
+
@expected_status = status
|
|
78
|
+
@expected_message = message
|
|
79
|
+
@actual = nil
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
### Returns true if the code executed in the given +procobj+ calls
|
|
84
|
+
### 'finish_with'
|
|
85
|
+
def matches?( procobj )
|
|
86
|
+
begin
|
|
87
|
+
@actual = catch( :finish, &procobj )
|
|
88
|
+
rescue => err
|
|
89
|
+
@actual = err
|
|
90
|
+
ensure
|
|
91
|
+
return statuses_match? && messages_match?
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
### Returns true if the expected status is the same as the actual
|
|
96
|
+
### status
|
|
97
|
+
def statuses_match?
|
|
98
|
+
return @actual.is_a?( Hash ) &&
|
|
99
|
+
@actual.key?( :status ) &&
|
|
100
|
+
@actual[:status] == @expected_status
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
### Returns true if the expected message matches the actual message
|
|
104
|
+
def messages_match?
|
|
105
|
+
return false unless @actual.is_a?( Hash ) && @actual.key?( :message )
|
|
106
|
+
return true if @expected_message == :any_message
|
|
107
|
+
|
|
108
|
+
case @expected_message
|
|
109
|
+
when String
|
|
110
|
+
return @actual[:message] == @expected_message
|
|
111
|
+
when Regexp
|
|
112
|
+
return @actual[:message] =~ @expected_message
|
|
113
|
+
else
|
|
114
|
+
return false
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
### Generate an appropriate failure message for a #should match
|
|
120
|
+
### failure.
|
|
121
|
+
def failure_message
|
|
122
|
+
if @actual
|
|
123
|
+
return "expected %s, got %p" % [
|
|
124
|
+
self.expectation_description,
|
|
125
|
+
@actual
|
|
126
|
+
]
|
|
127
|
+
else
|
|
128
|
+
return
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
### Generate an appropriate failure message for a #should_not match
|
|
134
|
+
### failure.
|
|
135
|
+
def negative_failure_message
|
|
136
|
+
if @expected_status
|
|
137
|
+
return "expected %s" % self.description
|
|
138
|
+
else
|
|
139
|
+
return "expected a normal return, but applet finished with %d (%s): %s"
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
### Return a human-readable description of the matcher
|
|
145
|
+
def description
|
|
146
|
+
return "the action to %s" % self.expectation_description
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
### Return a description of the expected status and message.
|
|
151
|
+
def expectation_description
|
|
152
|
+
if @expected_message.is_a?( Regexp )
|
|
153
|
+
return "finish_with %d (%s) status and a message which matches %p" % [
|
|
154
|
+
@expected_status,
|
|
155
|
+
STATUS_NAMES[@expected_status],
|
|
156
|
+
@expected_message
|
|
157
|
+
]
|
|
158
|
+
else
|
|
159
|
+
return "finish_with %d (%s) status and a message which is %p" % [
|
|
160
|
+
@expected_status,
|
|
161
|
+
STATUS_NAMES[@expected_status],
|
|
162
|
+
@expected_message
|
|
163
|
+
]
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
### A matcher that wraps an action method and can trap abnormal responses
|
|
170
|
+
class FinishWithMatcher
|
|
171
|
+
|
|
172
|
+
### Create a new FinishWithMatcher that should finish with the given
|
|
173
|
+
###
|
|
174
|
+
def initialize( status, message=:any_message )
|
|
175
|
+
@expected_status = status
|
|
176
|
+
@expected_message = message
|
|
177
|
+
@actual = nil
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
### Returns true if the code executed in the given +procobj+ calls
|
|
182
|
+
### 'finish_with'
|
|
183
|
+
def matches?( procobj )
|
|
184
|
+
begin
|
|
185
|
+
@actual = catch( :finish, &procobj )
|
|
186
|
+
rescue => err
|
|
187
|
+
@actual = err
|
|
188
|
+
ensure
|
|
189
|
+
return statuses_match? && messages_match?
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
### Returns true if the expected status is the same as the actual
|
|
194
|
+
### status
|
|
195
|
+
def statuses_match?
|
|
196
|
+
return @actual.is_a?( Hash ) &&
|
|
197
|
+
@actual.key?( :status ) &&
|
|
198
|
+
@actual[:status] == @expected_status
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
### Returns true if the expected message matches the actual message
|
|
202
|
+
def messages_match?
|
|
203
|
+
return false unless @actual.is_a?( Hash ) && @actual.key?( :message )
|
|
204
|
+
return true if @expected_message == :any_message
|
|
205
|
+
|
|
206
|
+
case @expected_message
|
|
207
|
+
when String
|
|
208
|
+
return @actual[:message] == @expected_message
|
|
209
|
+
when Regexp
|
|
210
|
+
return @actual[:message] =~ @expected_message
|
|
211
|
+
else
|
|
212
|
+
return false
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
### Generate an appropriate failure message for a #should match
|
|
218
|
+
### failure.
|
|
219
|
+
def failure_message
|
|
220
|
+
if @actual
|
|
221
|
+
return "expected %s, got %p" % [
|
|
222
|
+
self.expectation_description,
|
|
223
|
+
@actual
|
|
224
|
+
]
|
|
225
|
+
else
|
|
226
|
+
return
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
### Generate an appropriate failure message for a #should_not match
|
|
232
|
+
### failure.
|
|
233
|
+
def negative_failure_message
|
|
234
|
+
if @expected_status
|
|
235
|
+
return "expected %s" % self.description
|
|
236
|
+
else
|
|
237
|
+
return "expected a normal return, but applet finished with %d (%s): %s"
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
### Return a human-readable description of the matcher
|
|
243
|
+
def description
|
|
244
|
+
return "the action to %s" % self.expectation_description
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
### Return a description of the expected status and message.
|
|
249
|
+
def expectation_description
|
|
250
|
+
if @expected_message.is_a?( Regexp )
|
|
251
|
+
return "finish_with %d (%s) status and a message which matches %p" % [
|
|
252
|
+
@expected_status,
|
|
253
|
+
STATUS_NAMES[@expected_status],
|
|
254
|
+
@expected_message
|
|
255
|
+
]
|
|
256
|
+
else
|
|
257
|
+
return "finish_with %d (%s) status and a message which is %p" % [
|
|
258
|
+
@expected_status,
|
|
259
|
+
STATUS_NAMES[@expected_status],
|
|
260
|
+
@expected_message
|
|
261
|
+
]
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
### The main matcher expression. Assert that the given block is exited via a thrown status
|
|
268
|
+
### code of +status+ and a message which matches +message+ (if given).
|
|
269
|
+
def run_with( status, message=:any_message )
|
|
270
|
+
return Arrow::AppletMatchers::RunWithMatcher.new( status, message )
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
### The action method matcher expression. Assert that the given block is exited via a
|
|
274
|
+
### thrown status code of +status+ and a message which matches +message+ (if given).
|
|
275
|
+
def finish_with( status, message=:any_message )
|
|
276
|
+
return Arrow::AppletMatchers::FinishWithMatcher.new( status, message )
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
|