Commit | Line | Data |
---|---|---|
5ba09b9b TC |
1 | package BSE::Test; |
2 | use strict; | |
3 | use vars qw(@ISA @EXPORT @EXPORT_OK); | |
5bbf7309 TC |
4 | use Exporter 'import'; |
5 | @EXPORT = qw(base_url fetch_ok make_url skip make_ua); | |
6 | @EXPORT_OK = qw(base_url make_ua fetch_url fetch_ok make_url skip | |
de30d08a | 7 | make_post check_form post_ok check_content follow_ok |
9b89923c | 8 | follow_refresh_ok click_ok config test_actions); |
5bbf7309 TC |
9 | use lib 'site/cgi-bin/modules'; |
10 | use BSE::Cfg; | |
5ba09b9b | 11 | |
5bbf7309 | 12 | my $conffile = $ENV{BSETEST} || 'install.cfg'; |
5ba09b9b | 13 | |
5bbf7309 TC |
14 | my $cfg = BSE::Cfg->new |
15 | ( | |
16 | path => "site/cgi-bin", | |
17 | extra_file => $conffile, | |
18 | ); | |
d2730773 | 19 | |
5bbf7309 TC |
20 | sub config { |
21 | $cfg; | |
5ba09b9b | 22 | } |
5ba09b9b | 23 | |
5bbf7309 TC |
24 | sub base_url { |
25 | $cfg->entryVar("site", "url"); | |
6e3d2da5 TC |
26 | } |
27 | ||
5bbf7309 TC |
28 | sub base_securl { |
29 | $cfg->entryVar("site", "secureurl"); | |
30 | } | |
3bc94f98 | 31 | |
5bbf7309 TC |
32 | sub base_dir { |
33 | $cfg->entryVar("paths", "siteroot"); | |
34 | } | |
3bc94f98 | 35 | |
5bbf7309 TC |
36 | sub mysql_name { |
37 | $cfg->entry("binaries", "mysql", "mysql"); | |
9168c88c TC |
38 | } |
39 | ||
5bbf7309 TC |
40 | sub test_dsn { |
41 | $cfg->entry("db", "dsn"); | |
42 | } | |
2a295ea9 | 43 | |
5bbf7309 TC |
44 | sub test_dbuser { |
45 | $cfg->entry("db", "user"); | |
2a295ea9 TC |
46 | } |
47 | ||
5bbf7309 TC |
48 | sub test_dbpass { |
49 | $cfg->entry("db", "password"); | |
50 | } | |
5ba09b9b | 51 | |
5bbf7309 TC |
52 | sub test_dbclass { |
53 | $cfg->entry("db", "class", "BSE::DB::Mysql"); | |
54 | } | |
5ba09b9b | 55 | |
5bbf7309 TC |
56 | sub test_sessionclass { |
57 | $cfg->entry("basic", "session_class", "Apache::Session::Mysql"); | |
5ba09b9b TC |
58 | } |
59 | ||
5bbf7309 TC |
60 | sub test_perl { |
61 | $cfg->entry("paths", "perl", $^X); | |
62 | } | |
5ba09b9b | 63 | |
5bbf7309 TC |
64 | sub test_conffile { |
65 | $conffile; | |
5ba09b9b TC |
66 | } |
67 | ||
68 | sub make_ua { | |
aefcabcb | 69 | require WWW::Mechanize; |
5ba09b9b | 70 | require "HTTP/Cookies.pm"; |
0da5d199 | 71 | my $ua = WWW::Mechanize->new(onerror => undef); |
5ba09b9b TC |
72 | $ua->cookie_jar(HTTP::Cookies->new); |
73 | ||
74 | $ua; | |
75 | } | |
76 | ||
77 | # in scalar context returns content | |
78 | # in list context returns ($content, $code) | |
79 | # $post is any data to be part of a post request | |
80 | sub fetch_url { | |
81 | my ($ua, $url, $method, $post) = @_; | |
82 | ||
83 | $method ||= 'GET'; | |
3bc94f98 TC |
84 | my $hdrs = HTTP::Headers->new; |
85 | $hdrs->header(Content_Length => length $post) if $post; | |
86 | my $req = HTTP::Request->new($method, $url, $hdrs); | |
87 | $req->content($post) if $post; | |
5ba09b9b TC |
88 | my $resp = $ua->request($req); |
89 | if (wantarray) { | |
90 | return ($resp->content(), $resp->code(), $resp->is_success, | |
91 | $resp->headers_as_string()); | |
92 | } | |
93 | else { | |
94 | return $resp->is_success() ? $resp->content() : undef; | |
95 | } | |
96 | } | |
97 | ||
98 | sub make_url { | |
99 | my ($base, @data) = @_; | |
100 | ||
101 | require "URI/Escape.pm"; | |
102 | my @pairs; | |
103 | while (my ($key, $value) = splice(@data, 0, 2)) { | |
104 | push(@pairs, "$key=".URI::uri_escape($value)); | |
105 | } | |
106 | return $base."?".join("&",@pairs); | |
107 | } | |
108 | ||
7063727a TC |
109 | sub check_content { |
110 | my ($content, $note, $match) = @_; | |
5bbf7309 TC |
111 | my $tb = Test::Builder->new; |
112 | local $Test::Builder::Level = $Test::Builder::Level + 1; | |
113 | ||
114 | return $tb->like($content, qr/$match/s, "$note: match"); | |
7063727a TC |
115 | } |
116 | ||
3bc94f98 TC |
117 | sub _check_fetch { |
118 | my ($content, $code, $ok, $headers, | |
119 | $note, $match, $headmatch) = @_; | |
5ba09b9b | 120 | |
5bbf7309 TC |
121 | my $tb = Test::Builder->new; |
122 | local $Test::Builder::Level = $Test::Builder::Level + 1; | |
123 | ||
3bc94f98 | 124 | my $good = $ok; |
5bbf7309 TC |
125 | $tb->ok($ok, "$note: fetch ($code)"); |
126 | SKIP: | |
127 | { | |
128 | my $count = 0; | |
129 | $count++ if $match; | |
130 | $count++ if $headmatch; | |
131 | $ok or skip("$note: fetch failed", $count) if $count; | |
5ba09b9b | 132 | if ($match) { |
5bbf7309 TC |
133 | unless ($tb->like($content, qr/$match/s, "$note: match")) { |
134 | #print "# wanted /$match/ got:\n"; | |
135 | #my $copy = $content; | |
136 | #$copy =~ s/^/# /gm; | |
137 | #$copy .= "\n" unless $copy =~ /\n\z/; | |
138 | #print $copy; | |
3bc94f98 | 139 | $good = 0; |
5ba09b9b TC |
140 | } |
141 | } | |
142 | if ($headmatch) { | |
5bbf7309 TC |
143 | unless ($tb->like($headers, qr/$headmatch/s, "$note: headmatch")) { |
144 | #print "# wanted /$headmatch/ got:\n"; | |
145 | #my $copy = $headers; | |
146 | #$copy =~ s/^/# /gm; | |
147 | #$copy .= "\n" unless $copy =~ /\n\z/; | |
148 | #print $copy; | |
3bc94f98 | 149 | $good = 0; |
5ba09b9b TC |
150 | } |
151 | } | |
152 | } | |
3bc94f98 TC |
153 | |
154 | if (wantarray) { | |
155 | return ($content, $code, $good, $headers); | |
156 | } | |
157 | else { | |
158 | return $good ? $content : undef; | |
159 | } | |
160 | } | |
161 | ||
162 | sub make_post { | |
163 | my (@data) = @_; | |
164 | ||
165 | require "URI/Escape.pm"; | |
166 | my @pairs; | |
167 | while (my ($key, $value) = splice(@data, 0, 2)) { | |
168 | push(@pairs, "$key=".URI::Escape::uri_escape($value)); | |
169 | } | |
170 | return join("&",@pairs); | |
171 | } | |
172 | ||
173 | sub post_ok { | |
174 | my ($ua, $note, $url, $data, $match, $headmatch) = @_; | |
175 | ||
176 | $data = make_post(@$data) if ref $data; | |
177 | ||
178 | my ($content, $code, $ok, $headers) = fetch_url($ua, $url, POST=>$data); | |
179 | ||
180 | return _check_fetch($content, $code, $ok, $headers, | |
181 | $note, $match, $headmatch) | |
182 | } | |
183 | ||
184 | sub fetch_ok { | |
185 | my ($ua, $note, $url, $match, $headmatch) = @_; | |
186 | ||
ebe779a5 TC |
187 | my $resp = $ua->get($url); |
188 | my $ok = $resp->is_success; | |
7063727a TC |
189 | return _check_fetch($ua->{content}, $ua->{status}, $ok, |
190 | $ua->{res}->headers_as_string, $note, | |
191 | $match, $headmatch) | |
3bc94f98 TC |
192 | } |
193 | ||
de30d08a TC |
194 | sub follow_ok { |
195 | my ($ua, $note, $link, $match, $headmatch) = @_; | |
196 | ||
ebe779a5 TC |
197 | my $ok; |
198 | if (ref $link) { | |
199 | my $resp = $ua->follow_link(%$link); | |
200 | $ok = $resp->is_success; | |
201 | } | |
202 | else { | |
0da5d199 | 203 | $ok = $ua->follow_link(text_regex => qr/\Q$link/); |
ebe779a5 | 204 | } |
de30d08a TC |
205 | |
206 | return _check_fetch($ua->{content}, $ua->{status}, $ok, | |
207 | $ua->{res}->headers_as_string, $note, | |
208 | $match, $headmatch) | |
209 | } | |
210 | ||
211 | sub follow_refresh_ok { | |
212 | my ($ua, $note, $match, $headmatch) = @_; | |
213 | ||
214 | my $skip = 1; | |
215 | ++$skip if $match; | |
216 | ++$headmatch if $headmatch; | |
217 | my $refresh = $ua->response->header('Refresh'); | |
218 | if (ok($refresh, "$note - refresh header")) { | |
219 | my $url; | |
220 | if ($refresh =~ /^\s*\d+\s*;\s*url=\"([^\"]+)\"/ | |
221 | or $refresh =~ /^\s*\d+\s*;\s*url\s*=\s*(\S+)/) { | |
222 | $url = $1; | |
223 | $url = URI->new_abs($url, $ua->uri); | |
224 | } | |
225 | else { | |
226 | $url = $ua->uri; | |
227 | } | |
228 | print "# refresh to $url\n"; | |
229 | fetch_ok($ua, "$note - fetch", $url); | |
230 | } | |
231 | else { | |
232 | skip("$note - skipped, not a refresh", $skip); | |
233 | } | |
234 | } | |
235 | ||
236 | sub click_ok { | |
237 | my ($ua, $note, $name, $match, $headmatch) = @_; | |
238 | ||
5bbf7309 TC |
239 | local $Test::Builder::Level = $Test::Builder::Level + 1; |
240 | my $tb = Test::Builder->new; | |
241 | ||
242 | my $ok = $tb->ok($ua->click($name), "$note - click"); | |
de30d08a TC |
243 | return _check_fetch($ua->{content}, $ua->{status}, $ok, |
244 | $ua->{res}->headers_as_string, $note, | |
245 | $match, $headmatch) | |
246 | } | |
247 | ||
3bc94f98 TC |
248 | sub check_form { |
249 | my ($content, $note, %checks) = @_; | |
250 | ||
251 | require 'HTML/Parser.pm'; | |
252 | require 'HTML/Entities.pm'; | |
253 | my $in; | |
254 | my $keep; | |
255 | my $saved = ''; | |
256 | my %todo = %checks; | |
257 | my $inselect; | |
258 | my $selname; | |
259 | my $checked_sel_value; | |
260 | my $textname; | |
261 | my %values; | |
262 | ||
5bbf7309 TC |
263 | my $tb = Test::Builder->new; |
264 | local $Test::Builder::Level = $Test::Builder::Level + 1; | |
265 | ||
3bc94f98 TC |
266 | my $text = |
267 | sub { | |
268 | my ($t) = @_; | |
269 | $saved .= $t if $keep; | |
270 | }; | |
271 | my $start = | |
272 | sub { | |
273 | my ($tagname, $attr) = @_; | |
274 | ||
275 | if ($tagname eq 'input') { | |
276 | my $name = $attr->{name}; | |
277 | if ($name && $todo{$name}) { | |
5bbf7309 | 278 | $tb->ok(1, "$note - $name - field is present"); |
3bc94f98 TC |
279 | $values{$name} = $attr->{$name}; |
280 | if (defined $todo{$name}[0]) { | |
281 | my $cvalue = $checks{$name}[0]; | |
282 | my $fvalue = $attr->{value}; | |
283 | if (defined $fvalue) { | |
5bbf7309 | 284 | $tb->ok($cvalue eq $fvalue, "$note - $name - comparing values"); |
3bc94f98 TC |
285 | } |
286 | else { | |
5bbf7309 | 287 | $tb->ok(0, "$note - $name - value is not present"); |
3bc94f98 TC |
288 | } |
289 | } | |
290 | if (defined $todo{$name}[1]) { | |
291 | my $ttype = $todo{$name}[1]; | |
292 | my $ftype = $attr->{type}; | |
293 | if (defined $ftype) { | |
5bbf7309 | 294 | $tb->ok($ttype eq $ftype, "$note - $name - comparing types"); |
3bc94f98 TC |
295 | } |
296 | else { | |
5bbf7309 | 297 | $tb->ok(0, "$note - $name - type not present"); |
3bc94f98 TC |
298 | } |
299 | } | |
300 | delete $todo{$name}; | |
301 | } | |
302 | } | |
303 | elsif ($tagname eq 'select') { | |
304 | $selname = $attr->{name}; | |
305 | ||
306 | if ($todo{$selname}) { | |
5bbf7309 | 307 | $tb->ok(1, "$note - $selname - field is present"); |
3bc94f98 TC |
308 | $inselect = 1; |
309 | if (defined $todo{$selname}[1]) { | |
310 | $checked_sel_value = 0; | |
311 | my $ttype = $todo{$selname}[1]; | |
5bbf7309 | 312 | $tb->ok ($ttype eq 'select', "$note - $selname - checking type (select)"); |
3bc94f98 TC |
313 | } |
314 | } | |
315 | } | |
316 | elsif ($tagname eq 'option' && $inselect) { | |
317 | unless (exists $attr->{value}) { | |
318 | print "# warning - option in select $selname missing value\n"; | |
319 | } | |
320 | if (exists $attr->{selected}) { | |
321 | $checked_sel_value = 1; | |
322 | $values{$selname} = $attr->{value}; | |
323 | if (defined $todo{$selname}[0]) { | |
324 | my $fvalue = $attr->{value}; | |
325 | my $tvalue = $todo{$selname}[0]; | |
326 | if (defined $fvalue) { | |
5bbf7309 | 327 | $tb->ok($fvalue eq $tvalue, "$note - $selname - checking value ($fvalue vs $tvalue)"); |
3bc94f98 TC |
328 | } |
329 | else { | |
5bbf7309 | 330 | $tb->ok(0, "$note - $selname - no value supplied"); |
3bc94f98 TC |
331 | } |
332 | } | |
333 | } | |
334 | } | |
335 | elsif ($tagname eq 'textarea') { | |
336 | $textname = $attr->{name}; | |
337 | $saved = ''; | |
338 | ++$keep; | |
339 | } | |
340 | }; | |
341 | my $end = | |
342 | sub { | |
343 | my ($tagname) = @_; | |
344 | ||
345 | if ($tagname eq 'select' && $inselect) { | |
346 | if (!$checked_sel_value) { | |
5bbf7309 | 347 | $tb->ok(0, "$note - $selname - no value selected"); |
3bc94f98 TC |
348 | } |
349 | delete $todo{$selname}; | |
350 | } | |
351 | elsif ($tagname eq 'textarea') { | |
352 | $keep = 0; | |
353 | if ($todo{$textname}) { | |
354 | my $fvalue = HTML::Entities::decode_entities($saved); | |
355 | $values{$textname} = $fvalue; | |
5bbf7309 | 356 | $tb->ok(1, "$note - $textname - field exists"); |
3bc94f98 TC |
357 | if (defined $todo{$textname}[0]) { |
358 | my $tvalue = $todo{$textname}[0]; | |
5bbf7309 | 359 | $tb->ok($tvalue eq $fvalue, "$note - $textname - checking value($tvalue vs $fvalue)"); |
3bc94f98 TC |
360 | } |
361 | if (defined $todo{$textname}[1]) { | |
5bbf7309 | 362 | $tb->ok ($todo{$textname}[1] eq 'textarea', |
3bc94f98 TC |
363 | "$note - $textname - check field type"); |
364 | } | |
365 | delete $todo{$textname}; | |
366 | } | |
367 | } | |
368 | }; | |
369 | my $p = HTML::Parser->new( text_h => [ $text, "dtext" ], | |
370 | start_h => [ $start, "tagname, attr" ], | |
371 | end_h => [ $end, "tagname" ]); | |
372 | $p->parse($content); | |
373 | $p->eof; | |
374 | for my $name (keys %todo) { | |
5bbf7309 | 375 | $tb->ok(0, "$note - $name - field doesn't exist"); |
3bc94f98 TC |
376 | my $count = 0; |
377 | ++$count if defined $todo{$name}[0]; | |
378 | ++$count if defined $todo{$name}[1]; | |
5bbf7309 TC |
379 | SKIP: { |
380 | skip("$note - $name - no field", $count) if $count; | |
381 | } | |
3bc94f98 TC |
382 | } |
383 | ||
384 | return %values; | |
5ba09b9b TC |
385 | } |
386 | ||
9b89923c TC |
387 | # test that all actions have methods for a given dispatcher class |
388 | sub test_actions { | |
389 | my ($class) = @_; | |
390 | ||
391 | my $tb = Test::Builder->new; | |
5bbf7309 | 392 | local $Test::Builder::Level = $Test::Builder::Level + 1; |
9b89923c TC |
393 | |
394 | my $obj = $class->new; | |
395 | my $actions = $obj->actions; | |
396 | my @bad; | |
397 | for my $action (sort keys %$actions) { | |
398 | my $method = "req_$action"; | |
399 | unless ($obj->can($method)) { | |
400 | push @bad, $action; | |
401 | } | |
402 | } | |
403 | $tb->ok(!@bad, "check all actions have a method for $class"); | |
404 | print STDERR "No method found for $class action $_\n" for @bad; | |
405 | ||
406 | return !@bad; | |
407 | } | |
408 | ||
5ba09b9b TC |
409 | 1; |
410 |