File Coverage

blib/lib/HTTP/WebTest/Plugin/DateTest.pm
Criterion Covered Total %
statement 66 93 70.9
branch 24 34 70.5
condition 4 7 57.1
subroutine 9 11 81.8
pod 0 3 0.0
total 103 148 69.5


line stmt bran cond sub pod time code
1             # $Id: DateTest.pm,v 1.8 2003/05/16 08:11:47 joezespak Exp $
2             package HTTP::WebTest::Plugin::DateTest;
3 3     3   95128 use strict;
  3         8  
  3         111  
4 3     3   2756 use Date::Parse;
  3         26563  
  3         386  
5 3     3   2559 use Date::Language::English;
  3         19806  
  3         95  
6 3     3   24 use base qw(HTTP::WebTest::Plugin);
  3         6  
  3         1579  
7              
8 3     3   15 use vars qw($VERSION);
  3         5  
  3         3263  
9             $VERSION = '1.01';
10              
11             =head1 NAME
12              
13             HTTP::WebTest::Plugin::DateTest - Evaluate the "age" of embedded date strings in response body
14              
15             =head1 VERSION
16              
17             Version 1.01 - $Revision: 1.8 $
18              
19             Compatible with L 2.x API
20              
21             =head1 SYNOPSIS
22              
23             Not Applicable - see HTTP::WEBTEST
24              
25              
26             =head1 DESCRIPTION
27              
28             This plugin provides a test for the age of a date string
29             inside the response body. It supports anything C
30             can parse.
31             There is limited support for other locales for which a
32             C module exist.
33             The C and L modules are
34             part of the C distribution, available from a
35             CPAN near you.
36              
37             =head1 TEST PARAMETERS
38              
39              
40             NOTE: The following parameters C, C
41             and C are lists, so they should be specified
42             in order for multiple date tests.
43              
44             =head2 date_start
45              
46             Text string which marks the start of a date string
47             in the returned page.
48             The date string should look like anything that L is
49             able to understand.
50             Leading/trailing whitespace is no problem
51              
52             =head2 date_end
53              
54             Text string which marks the end of a date string
55             in the returned page.
56             The date string should look like anything that L is
57             able to understand.
58             Leading/trailing whitespace is no problem
59              
60             =head2 date_maxage
61              
62             Maximum age of the parsed date string in seconds.
63             This is evaluated against the current time at
64             runtime.
65              
66             Format:
67             N [units]
68              
69             where C is a (floating point-) number, followed by
70             one of these unit specifiers (case insensitive):
71              
72             s(econds) - default
73             m(inutes)
74             h(ours)
75             d(ays)
76             w(eeks)
77              
78             The default is seconds.
79             Only the first character is relevant, any leading text is
80             ignored. An unknown unit specifier defaults to seconds.
81              
82             =head2 date_locale
83              
84             Global parameter for all date tests.
85              
86             Specify the language in which the date string is written.
87             Locales are taken from C modules (part
88             of L). The value of C is normalized to
89             Capitalized notation, so this parameter is not case sensitive.
90              
91             WARNING: this works by literally translating the date string
92             components to their English names. This fails if the notational
93             conventions are very different (order of day, month, year etc.)
94              
95             There are languages where abbreviated day- and month names are
96             the same, notably Idi and Is in French, and
97             Indag and Irt in Dutch.
98              
99             To work around the resulting ambiguity, all non-numerical components
100             are stripped from the left side of the date string. This works for
101             the common case where a weekday starts the string, but doesn't in
102             some other cases.
103              
104             Example (this will work):
105              
106             date_locale = 'French'
107              
108             "Mar 19 Mars 2002, 17:25"
109             => "19 Mars 2002, 17:25"
110             => "19 Mar 2002, 17:25"
111              
112             Example (this will fail for languages other than English):
113              
114             "Mar 19, 2002 17:25"
115             => "19, 2002 17:25"
116             => (not parsable)
117              
118             Note: the last notation is very uncommon in Dutch,
119             so this assumption is generally no problem for this locale. YMMV!
120              
121             =cut
122              
123             sub param_types {
124 0     0 0 0 return q(
125             date_start list
126             date_end list
127             date_maxage list
128             date_locale scalar
129             );
130             }
131              
132             sub check_response {
133 0     0 0 0 my $self = shift;
134              
135             # response content
136 0         0 my $content = $self->webtest->current_response->content;
137              
138 0         0 $self->validate_params(qw(date_start date_end date_maxage date_locale));
139              
140             # test results
141 0         0 my @results = ();
142 0         0 my @ret = ();
143              
144             # store current time
145 0         0 my $now = time();
146              
147             # check for date strings
148 0         0 my @tests = @{$self->test_param('date_start', [])};
  0         0  
149 0         0 my $locale = $self->test_param('date_locale', '');
150 0         0 for (my $i=0; $i < @tests; $i++) {
151 0         0 my $maxage = ${$self->test_param('date_maxage', [])}[$i];
  0         0  
152 0         0 my $start = $tests[$i];
153 0         0 my $end = ${$self->test_param('date_end', [])}[$i];
  0         0  
154 0         0 my $pgdate;
155 0         0 my $datestr = 'unknown';
156 0 0       0 if ($content =~ /\Q$start\E\s*(.+?)\s*\Q$end\E/) {
157 0         0 $datestr = $1;
158 0         0 $pgdate = &_str2time_locale($datestr, $locale);
159             }
160 0 0       0 my $age = ($pgdate) ? $now - $pgdate : 'unknown';
161 0         0 my ($maxsecs, $units) = &_str2seconds($maxage);
162 0   0     0 my $ok = ($age ne 'unknown') && ($age < $maxsecs);
163              
164 0         0 push @results,
165             $self->test_result($ok,
166             sprintf("Wanted max %s and got %s (%s)",
167             $maxage, &_seconds2str($age, $units), $datestr)
168             );
169             }
170              
171 0 0       0 push @ret, ['Max. age of date string', @results] if @results;
172 0         0 return @ret;
173             }
174              
175             sub TIMETAB {
176 31   100 31 0 217 my $units = shift || '';
177 31         336 my $tt = {
178             s => 1,
179             m => 60,
180             h => 3600,
181             d => 86400,
182             w => 604800,
183             };
184 31         162 return $tt->{$units};
185             }
186              
187             # look for trailing characters and interprete them as time unit
188             sub _str2seconds {
189 15     15   5277 my $date = shift;
190 15         16 my $units = '';
191 15 100       56 if ($date =~ /^\s*([\-+0-9.]+)\s*([smhdwSMHDW]).*/) {
192 12         25 $units = lc($2);
193 12         19 $date = $1 * &TIMETAB($units);
194             } else {
195 3         17 $date =~ s/^\s*([\-+0-9.]+)\s*.*$/$1/g;
196             }
197 15 100       72 $date = 0 unless ($date =~ /^[\-+0-9.]/);
198 15 50       44 return wantarray ? ($date, $units) : $date;
199             }
200              
201             # convert seconds into time string
202             sub _seconds2str {
203 9     9   3425 my ($date, $units) = @_;
204 9 100       45 return 'unknown' unless ($date =~ /^[+-]?[\d\.]+$/);
205 7 100       16 if (&TIMETAB($units)) {
206 6 100       20 return ($units eq 's') ? "$date s"
207             : sprintf("%4.2f %s", $date/&TIMETAB($units), $units);
208             }
209 1         2 my $str = '';
210 1         3 my $frag = 0;
211 1 50       3 if ($frag = int($date / &TIMETAB('w'))) {
212 1         3 $str .= "${frag}w ";
213 1         3 $date -= $frag * &TIMETAB('w');
214             }
215 1 50       4 if ($frag = int($date / &TIMETAB('d'))) {
216 1         3 $str .= "${frag}d ";
217 1         4 $date -= $frag * &TIMETAB('d');
218             }
219 1         3 $frag = int($date / &TIMETAB('h'));
220 1         4 $str .= sprintf "%02d:", $frag;
221 1         11 $date -= $frag * &TIMETAB('h');
222 1         3 $frag = int($date / &TIMETAB('m'));
223 1         5 $str .= sprintf "%02d:", $frag;
224 1         3 $date -= $frag * &TIMETAB('m');
225 1         4 $str .= sprintf "%02d", $date;
226 1         3 return $str;
227             }
228              
229             sub _str2time_locale {
230 16     16   8703 my $date = shift;
231 16   100     75 my $locale = ucfirst(lc(shift)) || 'English';
232             # return if date is, well, empty...
233 16 100       75 return if ($date =~ /^\s*$/sg);
234 14 100       45 return str2time $date if ($locale eq 'English');
235              
236             # normalize spaces, incl. multiple lines
237 8         52 $date =~ s/\s+/ /sg;
238              
239             # NOTE: "mar" (Mardi) and "mars" (French) would break.
240             # Assume we have a weekday name prefix if
241             # date string does not start with digits.
242             # Strip up to 1st digit:
243 8         29 $date =~ s/^[^0-9]+//g;
244              
245             # strip leading/trailing spaces
246 8         38 $date =~ s/^\s*(.+)\s*$/$1/g;
247              
248 8         460 eval "require Date::Language::$locale";
249 8 50       2053 warn $@ if $@;
250 8         358 my @MoY = eval "\@Date::Language::${locale}::MoY";
251 8         29 my $MoY_EN = \@Date::Language::English::MoY;
252              
253 8         14 my $nwdate = '';
254 8         80 foreach my $token (split(/(\s|-|:|\/)/, $date)) {
255 70 100       200 if ($token =~ /^[0-9,-:\/\s]+$/) {
256 63         158 $nwdate .= $token;
257             } else {
258             # match token with full month name.
259 7         21 for (my $i = 0; $i < @MoY; $i++) {
260 84 100       1321 if ($MoY[$i] =~ /^\Q$token\E.*/i) {
261 7         24 $nwdate .= $MoY_EN->[$i];
262             }
263             }
264             }
265             }
266             # explicitly return for empty or '0' date string
267 8 100       29 return unless $nwdate;
268 7         26 return str2time $nwdate;
269             }
270              
271             =head1 COPYRIGHT
272              
273             Copyright (c) 2002,2003 Johannes la Poutre. All rights reserved.
274              
275             This module is free software. It may be used, redistributed and/or
276             modified under the terms of the Perl Artistic License.
277              
278             =head1 SEE ALSO
279              
280             L
281              
282             L
283              
284             L
285              
286             L
287              
288             L
289              
290             =cut
291              
292             1;