root/branches/release-39/t/41-atom.t @ 2441

Revision 2441, 19.8 kB (checked in by fumiakiy, 18 months ago)

Finish iterator cleanly to suppress warnings. The iterator returned from _do_group_by does not support early finish yet.

  • Property svn:mime-type set to text/plain
  • Property svn:keywords set to Author Date Id Revision
Line 
1use strict;
2use lib 't/lib', 'extlib', 'lib', '../lib', '../extlib';
3use POSIX;
4
5use MT;
6use MT::Atom;
7use XML::LibXML; # this test would not work without it
8use XML::XPath;
9use XML::Atom;
10use XML::Atom::Feed;
11use XML::Atom::Entry;
12use POSIX qw( ceil );
13
14use Test::More qw( no_plan );#tests => 97;
15
16# To keep away from being under FastCGI
17$ENV{HTTP_HOST} = 'localhost';
18
19use vars qw( $DB_DIR $T_CFG );
20my $mt = MT->new( Config => $T_CFG ) or die MT->errstr;
21isa_ok($mt, 'MT');
22
23use MT::Test qw(:db :data);
24
25my %test_data;
26$test_data{'/mt-atom.cgi/weblog'} = <<XML1;
27<?xml version="1.0" encoding="utf-8"?>
28    <entry xmlns="http://purl.org/atom/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
29    <title>Fight the Power</title>
30    <content>Elvis was a hero to most but he never meant shit to me</content>
31    <issued>2004-08-06T00:43:34+01:00</issued>
32    </entry>
33XML1
34$test_data{'/mt-atom.cgi/1.0'} = <<XML2;
35<?xml version="1.0" encoding="utf-8"?>
36<entry xmlns="http://www.w3.org/2005/Atom">
37<title>Fight the Power</title>
38<content type="html">Elvis was a hero to most but he never meant shit to me</content>
39<published>2004-08-06T00:43:34+01:00</published>
40</entry>
41XML2
42
43my %feed_link = (
44    '/mt-atom.cgi/weblog' => sub {
45        my ($resp) = @_;
46        my $feed = XML::Atom::Feed->new(\$resp->content());
47        ok($feed, 'got feed');
48        my ($sfeed) = grep {
49            $_->rel eq 'service.feed'
50        } $feed->links;
51        $sfeed->href;
52    },
53    '/mt-atom.cgi/1.0' => sub {
54        my ($resp) = @_;
55        my $feed = XML::XPath->new(xml => $resp->content());
56        ok($feed, 'got feed');
57        my $col = $feed->getNodeText('/service/workspace[1]/collection/@href');
58        $col;
59    }
60);
61
62my $username = 'Chuck D';
63my $chuck = MT::Author->load({name => $username})
64    or die "Couldn't load $username";
65my $chuck_token = $chuck->api_password;
66
67# not a good nonce-maker
68my @hexch = ('0' .. '9', 'a' .. 'f');
69sub make_nonce {
70    join '', map { $hexch[rand() * @hexch] } (0..7);
71}
72sub make_wsse {
73    my ($password) = @_;
74    my $timestamp = strftime("%Y-%m-%dT%H:%M:%SZ", gmtime(time));
75    my $nonce = make_nonce();
76    my $PasswordDigest = sha1_base64($nonce . $timestamp . $chuck_token);
77    # print "# PasswordDigest is sha1('$nonce$timestamp$chuck_token')\n";
78    $nonce = MIME::Base64::encode_base64($nonce, '');
79    return "UserNameToken Username=\"$username\", "
80                 . "PasswordDigest=\"$PasswordDigest\", Nonce=\"$nonce\", "
81                 . "Created=\"$timestamp\"";
82}
83
84require LWP::UserAgent::Local;
85my $ua = new LWP::UserAgent::Local({ ScriptAlias => '/' });
86
87foreach my $base_uri ( qw{/mt-atom.cgi/weblog /mt-atom.cgi/1.0 } ) {
88{
89# # # # First try a req with baloney auth, make sure it fails
90    # TBD: Try more bogus auth varieties
91    my $nonce = make_nonce();
92    $nonce = MIME::Base64::encode_base64($nonce, '');
93
94    my $uri = new URI();
95    $uri->path($base_uri . '/blog_id=1');
96    my $req = new HTTP::Request(GET => $uri);
97    $req->header(Authentication => 'Atom');
98    $req->header('X-WSSE' => "UserNameToken Username=\"Melody\", PasswordDigest=\"Oj12fART+XZvBZBe39vVvkirg4w\", Nonce=\"" . $nonce . "\", Created=\"2004-05-19T20:08:57Z\"");
99    print "# requesting: " . $req->uri . "\n";
100    my $resp = $ua->request($req);
101
102    print "# response code was: " . $resp->code . "\n";
103    print "# content was: " . $resp->content . "\n";# if !$resp->code();
104    ok($resp->is_error());
105}
106
107# test blog lists
108{
109    my $wsse_header = make_wsse($chuck_token);
110    my $uri = new URI;
111    $uri->path($base_uri);
112    my $req = new HTTP::Request(GET => $uri);
113    $req->header('Authorization' => 'Atom');
114    $req->header('X-WSSE' => $wsse_header);
115
116    print "# X-WSSE: $wsse_header\n";
117
118    my $resp = $ua->request($req);
119    if (ok($resp->is_success)) {
120        my $blog_feed_url = $feed_link{$base_uri}->($resp);
121        my $blog_feed_uri = new URI($blog_feed_url);
122        is($blog_feed_uri->path, $base_uri . '/blog_id=1', 'blog feed url is correct');
123    }
124    else {
125        die 'failed to retrieve blog feed';
126    }
127}
128
129# test blog feed
130{
131    my $wsse_header = make_wsse($chuck_token);
132    my $uri = new URI;
133    $uri->path($base_uri . "/blog_id=1");
134    my $req = new HTTP::Request(GET => $uri);
135    $req->header('Authorization' => 'Atom');
136    $req->header('X-WSSE' => $wsse_header);
137
138    print "# X-WSSE: $wsse_header\n";
139
140    my $resp = $ua->request($req);
141    if (ok($resp->is_success)) {
142        my $feed = XML::Atom::Feed->new(\$resp->content());
143        ok($feed, 'got feed');
144        is($feed->title, 'none');
145        my ($alternate) = grep {
146            $_->rel eq 'alternate' && $_->type eq 'text/html'
147        } $feed->links;
148        is($alternate->href, 'http://narnia.na/nana/', 'blog url is correct');
149        my $entry_count = MT::Entry->count(
150            { blog_id => 1 },
151            { limit => 21 },
152        );
153        my @entries = $feed->entries;
154        is($entry_count, scalar(@entries), 'number of entries is correct');
155
156        # check if entries have replies link relation
157        my $failed = 0;
158        foreach my $entry (@entries) {
159            next if !$entry->id && $entry->title =~ /^I just finished installing Movable Type/;
160            my $mt_entry = MT::Entry->load({
161                atom_id => $entry->id,
162                blog_id => 1,
163            });
164            $failed = 1, last unless $mt_entry;
165            my ($replies) = grep {
166                $_->rel eq 'replies'
167            } $entry->links;
168            $failed = 1, last unless $replies;
169            my $replies_uri = new URI($replies->href);
170            $failed = 1, last unless $replies_uri->path eq '/mt-atom.cgi/comments/blog_id=1/entry_id='.$mt_entry->id;
171        }
172        is($failed, 0, 'all the entries have replies link rel');
173    }
174    else {
175        die 'failed to retrieve blog feed';
176    }
177}
178
179my $entry_id;
180
181use constant USE_DIGEST => 0;
182
183if (USE_DIGEST)
184{
185# # # # # # # use this to do a Digest auth with MD5 as the algorithm.
186    my $realm = 'movabletype';
187#     $username = shift;
188#     $hashed_pwd = shift;
189    require Digest::MD5;
190    my $hashed_pwd = Digest::MD5::md5(join ':', $username, $realm, $chuck_token);
191    my $method = 'POST';
192
193    my $uri = new URI;
194    $uri->path($base_uri . '/blog_id=1');
195    my $req = new HTTP::Request(POST => $uri);
196
197    my $resp = $ua->simple_request($req);
198    ok($resp->is_error());
199    my $auth_header = $resp->header('WWW-Authenticate');
200    my ($nonce) = $auth_header =~ /nonce=(\S*)/;
201
202    my $A1 = pack('H*', $hashed_pwd);
203    my $A2 = $method . ':' . $uri;
204   
205    require Digest::MD5;
206    my $hash = \&Digest::MD5::md5;
207    my $kd = sub { $hash->($_[0].':'.$_[1]) };
208
209    print STDERR "Signing ", join(':',
210                                  $nonce,
211                                  '',
212                                  '',
213                                  'auth',
214                                  $hash->($A2)), "\n";
215    print STDERR "with key ", $hash->($A1), "\n";
216
217    my $response = $kd->($hash->($A1), join(':',
218                                            $nonce,
219                                            '',
220                                            '',
221                                            'auth',
222                                            $hash->($A2)));
223    print "# ", ($response = unpack('H*', $response)), "\n";
224
225    $uri = new URI;
226    $uri->path($base_uri . '/blog_id=1');
227    $req = new HTTP::Request(POST => $uri);
228
229    $req->header('X-Atom-Authorization', "Digest nonce=$nonce, username=$username, realm=movabletype, uri=$uri, qop=auth, response=$response");
230    print "# sending: ", $req->header('X-Atom-Authorization');
231
232    $req->content($test_data{$base_uri});
233
234    $resp = $ua->simple_request($req);
235
236    print "# " . $resp->code . " " . $resp->message . "\n";
237    print "# " . $resp->content . "\n";
238    ok($resp->is_success());
239
240    print "##\n". $resp->header('Location')  . "\n##\n";
241    my $location = $resp->header('Location')
242        || die "No Location: header. Did get\n"
243                . $resp->code() . $resp->message() . "\n"
244                . $resp->headers_as_string() . "\n"
245                . $resp->content();
246    ($entry_id) = ($location =~ /entry_id=(\d+)/);
247
248    $entry_id || die "Couldn't get entry ID from header Location: "
249        . $location;
250
251    print "# entry ID is $entry_id\n";
252
253    ok($entry_id);
254}
255
256unless (USE_DIGEST)
257{
258    print "# Doing Digest auth\n";
259# # # # Now try posting an entry with authentication
260    my $nonce = make_nonce();
261    my $timestamp = strftime("%Y-%m-%dT%H:%M:%SZ", gmtime(time));
262    use Digest::SHA1 qw(sha1_base64);
263    my $PasswordDigest = sha1_base64($nonce . $timestamp . $chuck_token);
264
265#     print STDERR ("Client hashing (",
266#                 unpack('H*', $nonce . $timestamp . $chuck_token), ")\n");
267#     print STDERR "      produces: $PasswordDigest\n";
268
269    $nonce = MIME::Base64::encode_base64($nonce, '');
270
271    print "# nonce: $nonce\n";
272
273    my $dir = `pwd`; chomp $dir;
274    while (! -x ($dir . "/mt-atom.cgi")) {
275        $dir =~ s!(/[^/]*)$!!;
276        last if $dir =~ m!^/?$!;
277    }   
278
279    my $uri = new URI;
280    $uri->path($base_uri . '/blog_id=1');
281    my $req = new HTTP::Request(POST => $uri);
282    $req->header('Authorization' => 'Atom');
283    $req->header('X-WSSE' => "UserNameToken Username=\"$username\", "
284                 . "PasswordDigest=\"$PasswordDigest\", Nonce=\"$nonce\", "
285                 . "Created=\"$timestamp\"");
286
287    $req->content($test_data{$base_uri});
288
289    my $resp = $ua->simple_request($req);
290
291    print "######### RESPONSE #########\n";
292    print "# " . $resp->code . " " . $resp->message . "\n";
293    my $content =  $resp->content;
294    $content =~ s/^/# /gm;
295    print $content;
296    print "#########~RESPONSE #########\n";
297    ok($resp->is_success());
298
299    #print "##\n". $resp->header('Location')  . "\n##\n";
300    my $location = $resp->header('Location')
301        || die "No Location: header. Did get\n"
302                . $resp->headers_as_string() . "\n"
303                . $resp->content();
304    ($entry_id) = ($location =~ /entry_id=(\d+)/);
305
306    $entry_id || die "Couldn't get entry ID from header Location: "
307        . $location;
308
309    print "# entry ID is $entry_id\n";
310
311    ok($entry_id);
312    my $atom_obj = XML::Atom::Entry->new(\$resp->content());
313    require Date::Parse;
314    print "# ", $atom_obj->issued(), "\n";
315    is(Date::Parse::str2time($atom_obj->issued()),
316       Date::Parse::str2time('2004-08-06T00:43:34+01:00'), $atom_obj->issued());
317}
318
319{
320    my $wsse_header = make_wsse($chuck_token);
321    my $uri = new URI;
322    $uri->path($base_uri . "/blog_id=1/entry_id=$entry_id");
323    my $req = new HTTP::Request(GET => $uri);
324    $req->header('Authorization' => 'Atom');
325    $req->header('X-WSSE' => $wsse_header);
326
327    print "# X-WSSE: $wsse_header\n";
328
329    my $resp = $ua->request($req);
330
331    my $atom_entry = XML::Atom::Entry->new(Stream => \$resp->content());
332
333    is($atom_entry->title(), "Fight the Power");
334    is($atom_entry->author()->name(), $chuck->nickname);
335    is($atom_entry->content()->body(),
336       "Elvis was a hero to most but he never meant shit to me");
337
338    $wsse_header = make_wsse($chuck_token);
339    $uri = new URI;
340    $uri->path($base_uri . "/blog_id=1/entry_id=$entry_id");
341    $req = new HTTP::Request(PUT => $uri);
342    $req->header('Authorization' => 'Atom');
343    $req->header('X-WSSE' => $wsse_header);
344    my $body = $atom_entry->as_xml();
345    $req->content($body);
346   
347    $resp = $ua->request($req);
348
349    if (ok($resp->is_success)) {
350        $atom_entry = XML::Atom::Entry->new(Stream => \$resp->content());
351        is($atom_entry->title, "Fight the Power");
352        is(Date::Parse::str2time($atom_entry->issued),
353           Date::Parse::str2time("2004-08-05T21:13:34-0230"));
354    } else {
355        print STDERR "# PUT request returned ", $resp->status_line(), "\n";
356        skip(1);
357        skip(1);
358    }
359}
360
361} #end foreach
362
363COMMENT:
364# comments retrieval
365{
366    my $wsse_header = make_wsse($chuck_token);
367    my $uri = new URI;
368    $uri->path('/mt-atom.cgi/comments/blog_id=1');
369    my $req = new HTTP::Request(GET => $uri);
370    $req->header('Authorization' => 'Atom');
371    $req->header('X-WSSE' => $wsse_header);
372
373    my $resp = $ua->request($req);
374    if (ok($resp->is_success)) {
375        my $thr_ns = XML::Atom::Namespace->new(prefix => undef, uri => 'http://purl.org/syndication/thread/1.0');
376        my $comments = XML::Atom::Feed->new(\$resp->content());
377        my $count = MT::Comment->count({
378            blog_id => 1, visible => 1
379        });
380        is( $count, scalar($comments->entries), 'comment count' );
381        foreach my $c ( $comments->entries ) {
382            my $id = $c->id;
383            my ( $cmt_id ) = $id =~ m{/([0-9]+)$};
384            die unless $cmt_id;
385            my $mt_comment = MT::Comment->load($cmt_id);
386            die unless $mt_comment;
387            my $mt_entry = $mt_comment->entry;
388            is($c->title, $mt_entry->title, 'comment title == entry title');
389            is( $c->author->name, $mt_comment->author, 'comment author' );
390            is( $c->author->email || '', $mt_comment->email || '', 'commenter email' );
391            is( $c->author->uri || '', $mt_comment->url || '', 'commenter url'  );
392            if ( $XML::Atom::LIBXML ) {
393                my $nodelist = $c->elem->getElementsByTagNameNS('http://purl.org/syndication/thread/1.0', 'in-reply-to');   
394                my $irt = $nodelist->shift;
395                ok($irt, 'in-reply-to');
396                is( $irt->ref, $mt_entry->atom_id, 'in-reply-to/ref' );
397                is( $irt->href, $mt_entry->permalink, 'in-reply-to/href' );
398            }
399        }
400    }
401    else {
402        die $resp->content();
403    }
404}
405
406{
407    my $iter = MT::Comment->count_group_by(
408        { blog_id => 1, visible => 1 },
409        { group => ['entry_id'], sort => [ { desc => 'DESC', column => '1' } ]
410        }
411    );
412    #$Data::ObjectDriver::PROFILE = 1;
413    #my $p = Data::ObjectDriver->profiler;
414    #$p->reset;
415    #print "$_\n" foreach @{$p->query_log};
416    my ( $count, $eid ) = $iter->();
417    # finish iterator cleanly
418    while ( my @dump = $iter->() ) {}
419
420    my $entry = MT::Entry->load($eid);
421
422    my $wsse_header = make_wsse($chuck_token);
423    my $uri = new URI;
424    $uri->path('/mt-atom.cgi/1.0/blog_id=1/entry_id=' . $entry->id);
425    my $req = new HTTP::Request(GET => $uri);
426    $req->header('Authorization' => 'Atom');
427    $req->header('X-WSSE' => $wsse_header);
428
429    my $resp = $ua->request($req);
430    if (ok($resp->is_success)) {
431        my $feed = XML::Atom::Entry->new(\$resp->content());
432        my ($replies) = grep {
433            $_->rel eq 'replies'
434        } $feed->links;
435
436        # retrieve comments from replies url
437        my $replies_uri = new URI($replies->href);
438        my $wsse_header = make_wsse($chuck_token);
439        my $uri = new URI;
440        $uri->path($replies_uri->path);
441        my $req = new HTTP::Request(GET => $uri);
442        $req->header('Authorization' => 'Atom');
443        $req->header('X-WSSE' => $wsse_header);
444
445        my $resp = $ua->request($req);
446        my $thr_ns = XML::Atom::Namespace->new(prefix => undef, uri => 'http://purl.org/syndication/thread/1.0');
447        if (ok($resp->is_success)) {
448            my $comments = XML::Atom::Feed->new(\$resp->content());
449            is( $count, scalar($comments->entries), 'comment count' );
450            foreach my $e ( $comments->entries ) {
451                is($e->title, $entry->title, 'comment title == entry title');
452                my $id = $e->id;
453                my ( $cmt_id ) = $id =~ m{/([0-9]+)$};
454                die unless $cmt_id;
455                my $mt_comment = MT::Comment->load($cmt_id);
456                die unless $mt_comment;
457                is( $e->author->name, $mt_comment->author, 'comment author' );
458                is( $e->author->email || '', $mt_comment->email || '', 'commenter email' );
459                is( $e->author->uri || '', $mt_comment->url || '', 'commenter url'  );
460                if ( $XML::Atom::LIBXML ) {
461                    my $nodelist = $e->elem->getElementsByTagNameNS('http://purl.org/syndication/thread/1.0', 'in-reply-to');   
462                    my $irt = $nodelist->shift;
463                    ok($irt, 'in-reply-to');
464                    is( $irt->ref, $entry->atom_id, 'in-reply-to/ref' );
465                    is( $irt->href, $entry->permalink, 'in-reply-to/href' );
466                }
467            }
468        }
469        else {
470            die $resp->content();
471        }
472    }
473}
474
475{
476    my $thr_ns = XML::Atom::Namespace->new(prefix => undef, uri => 'http://purl.org/syndication/thread/1.0');
477    my $wsse_header = make_wsse($chuck_token);
478    my $uri = new URI;
479    $uri->path('/mt-atom.cgi/comments/blog_id=1/comment_id=1');
480    my $req = new HTTP::Request(GET => $uri);
481    $req->header('Authorization' => 'Atom');
482    $req->header('X-WSSE' => $wsse_header);
483
484    my $resp = $ua->request($req);
485    if (ok($resp->is_success)) {
486        my $c = XML::Atom::Entry->new(\$resp->content());
487        my $mt_comment = MT::Comment->load(1);
488        die unless $mt_comment;
489        my $entry = $mt_comment->entry;
490        is( $c->title, $entry->title, 'comment title == entry title' );
491        is( $c->author->name, $mt_comment->author, 'comment author' );
492        is( $c->author->email || '', $mt_comment->email || '', 'commenter email' );
493        is( $c->author->uri || '', $mt_comment->url || '', 'commenter url'  );
494        if ( $XML::Atom::LIBXML ) {
495            my $nodelist = $c->elem->getElementsByTagNameNS('http://purl.org/syndication/thread/1.0', 'in-reply-to');   
496            my $irt = $nodelist->shift;
497            ok($irt, 'in-reply-to');
498            is( $irt->ref, $entry->atom_id, 'in-reply-to/ref' );
499            is( $irt->href, $entry->permalink, 'in-reply-to/href' );
500        }
501    }
502    else {
503        die $resp->content();
504    }
505}
506
507sub _test_limit_offset {
508    my ( $uri, $url, $limit, $total, $desc ) = @_;
509    my %items;
510    for ( my $i = 0; $i < ceil($total/$limit); $i++ ) {
511        my $wsse_header = make_wsse($chuck_token);
512        my $req = new HTTP::Request(GET => $uri);
513        $req->header('Authorization' => 'Atom');
514        $req->header('X-WSSE' => $wsse_header);
515
516        my $resp = $ua->request($req);
517        if (ok($resp->is_success)) {
518            my $thr_ns = XML::Atom::Namespace->new(prefix => undef, uri => 'http://purl.org/syndication/thread/1.0');
519            my $items = XML::Atom::Feed->new(\$resp->content());
520            ok ($items, 'items retrieved');
521            if ( $total >= $limit * ($i+1) ) {
522                is( scalar($items->entries), $limit, 'limit applied ' . $desc );
523            }
524            else {
525                ok( scalar($items->entries) < $limit, 'limit applied ' . $desc );
526            }
527            foreach my $c ( $items->entries ) {
528                my $id = $c->id;
529                die unless $id;
530                ok( !exists($items{$id}), 'no dupe items - offset applied ' . $desc );
531                $items{$id} = 1;
532            }
533        }
534        else {
535            die $resp->content();
536        }
537        $uri->path($url . '/offset=' . ($limit*($i+1)));
538    }
539}
540
541#offset and limit
542{
543    my $uri = new URI;
544    my $limit = 3;
545    my $url = "/mt-atom.cgi/1.0/blog_id=1/limit=$limit";
546    $uri->path($url);
547
548    my $count = MT::Entry->count({
549        blog_id => 1
550    });
551
552    &_test_limit_offset( $uri, $url, $limit, $count, 'blog entries');
553
554    $url = "/mt-atom.cgi/comments/blog_id=1/limit=$limit";
555    $uri->path($url);
556
557    my $count = MT::Comment->count({
558        blog_id => 1, visible => 1
559    });
560
561    &_test_limit_offset( $uri, $url, $limit, $count, 'blog comments');
562
563    my $iter = MT::Comment->count_group_by(
564        { blog_id => 1, visible => 1 },
565        { group => ['entry_id'], sort => [ { desc => 'DESC', column => '1' } ]
566        }
567    );
568
569    $limit = 2;
570    my $eid;
571    while ( ( $count, $eid ) = $iter->() ) {
572        if ( $count > 2 ) {
573            last;
574        }
575    }
576    # finish iterator cleanly
577    while ( my @dump = $iter->() ) {}
578    die unless $eid;
579
580    $url = "/mt-atom.cgi/comments/blog_id=1/entry_id=$eid/limit=$limit";
581    $uri->path($url);
582
583    &_test_limit_offset($uri, $url, $limit, $count, 'entry comments');
584}
585
586END {
587    #my $melody = MT::Author->load({ name => $username });
588    #$melody->delete() if $melody;
589}
Note: See TracBrowser for help on using the browser.