# t/comment.t # # Test LJ::Comment. # # This code was forked from the LiveJournal project owned and operated # by Live Journal, Inc. The code has been modified and expanded by # Dreamwidth Studios, LLC. These files were originally licensed under # the terms of the license supplied by Live Journal, Inc, which can # currently be found at: # # http://code.livejournal.org/trac/livejournal/browser/trunk/LICENSE-LiveJournal.txt # # In accordance with the original license, this code and all its # modifications are provided under the GNU General Public License. # A copy of that license can be found in the LICENSE file included as # part of this distribution. use strict; use warnings; use Test::More tests => 405; BEGIN { $LJ::_T_CONFIG = 1; require "$ENV{LJHOME}/cgi-bin/ljlib.pl"; } use LJ::Utils qw(rand_chars); use LJ::Protocol; use LJ::Comment; use LJ::Talk; use LJ::Test qw(memcache_stress temp_user); use POSIX (); my $u = temp_user(); sub run_tests { # constructor tests { my $c; $c = eval { LJ::Comment->new( {}, jtalkid => 1 ) }; like( $@, qr/invalid journalid/, "invalid journalid parameter" ); $c = eval { LJ::Comment->new( 0, jtalkid => 1 ) }; like( $@, qr/invalid journalid/, "invalid user from userid" ); $c = eval { LJ::Comment->new( $u, jtalkid => 1, 'foo' ) }; like( $@, qr/wrong number/, "wrong number of arguments" ); $c = eval { LJ::Comment->new( $u, jtalkid => undef ) }; like( $@, qr/need to supply jtalkid/, "need to supply jtalkid" ); $c = eval { LJ::Comment->new( $u, jtalkid => 1, foo => 1, bar => 2 ) }; like( $@, qr/wrong number/, "wrong number of arguments (unknown parameters)" ); } # post a comment { my $e1 = $u->t_post_fake_entry; ok( $e1, "Posted entry" ); my $c1 = $e1->t_enter_comment; ok( $c1, "Posted comment" ); # check that the comment happened in the last 60 seconds my $c1time = $c1->unixtime; ok( $c1time, "Got comment time" ); ok( POSIX::abs( $c1time - time() ) < 60, "Comment happened in last minute" ); } # test prop setting/modifying/deleting { my $e2 = $u->t_post_fake_entry; my $c2 = $e2->t_enter_comment; # set a prop once, then re-set its value again my $jtalkid = $c2->jtalkid; # FIXME the whole idea of using undef as one of the loop values seem to make the code # more complex, check if this can be changed foreach my $propval ( 0, 1, undef, 1 ) { # re-instantiate if we've blown $c2 away $c2 ||= LJ::Comment->new( $u, jtalkid => $jtalkid ); my $inserted = 0; $LJ::_T_COMMENT_SET_PROPS_INSERT = sub { $inserted++ }; my $deleted = 0; $LJ::_T_COMMENT_SET_PROPS_DELETE = sub { $deleted++ }; $c2->set_prop( 'opt_preformatted', $propval ); if ( defined $propval ) { ok( $inserted == 1 && $deleted == 0, "$propval: Inserted talkprop row prop-erly" ); } else { ok( $deleted == 1 && $inserted == 0, "undef: Deleted talkprop row prop-erly" ); } is( $c2->prop('opt_preformatted'), $propval, ( defined $propval ? $propval : 'undef' ) . ": Set prop and read back via ->prop" ); is( $c2->props->{opt_preformatted}, $propval, ( defined $propval ? $propval : 'undef' ) . ": Set prop and read back via ->props" ); # clear the singleton and load again LJ::Comment->reset_singletons; my $loaded = 0; $LJ::_T_GET_TALK_PROPS2_MEMCACHE = sub { $loaded++ }; my $c2_new = LJ::Comment->new( $u, jtalkid => $jtalkid ); my $propval = $c2_new->prop('opt_preformatted'); ok( $loaded == 1, ( defined $propval ? $propval : 'undef' ) . ", Re-instantiated comment and re-loaded prop" ); ok( $c2_new != $c2, ( defined $propval ? $propval : 'undef' ) . ", Re-instantiated comment and re-loaded prop" ); } # test raw prop setting/modifying { # re-instantiate if we've blown $c2 away $c2 ||= LJ::Comment->new( $u, jtalkid => $jtalkid ); my $inserted = 0; $LJ::_T_COMMENT_SET_PROPS_INSERT = sub { $inserted++ }; my $deleted = 0; $LJ::_T_COMMENT_SET_PROPS_DELETE = sub { $deleted++ }; $c2->set_prop_raw( 'edit_time', "UNIX_TIMESTAMP()" ); ok( $inserted == 1 && $deleted == 0, "Inserted raw talkprop row prop-erly" ); ok( $c2->prop('edit_time') =~ /^\d+$/, "Set raw prop and read back via ->prop" ); ok( $c2->props->{edit_time} =~ /^\d+$/, "Set raw prop and read back via ->props" ); # clear the singleton and load again LJ::Comment->reset_singletons; my $loaded = 0; $LJ::_T_GET_TALK_PROPS2_MEMCACHE = sub { $loaded++ }; my $c2_new = LJ::Comment->new( $u, jtalkid => $jtalkid ); my $propval = $c2_new->prop('edit_time'); ok( $loaded == 1 && $c2_new != $c2 && $propval == $propval, "Re-instantiated comment and re-loaded raw prop" ); } # test prop multi-setting/modifying/deleting # test prop setting/modifying/deleting { my $inserted = 0; $LJ::_T_COMMENT_SET_PROPS_INSERT = sub { $inserted++ }; my $deleted = 0; $LJ::_T_COMMENT_SET_PROPS_DELETE = sub { $deleted++ }; my $queried = 0; $LJ::_T_GET_TALK_PROPS2_MEMCACHE = sub { $queried++ }; { # both inserts my $e3 = $u->t_post_fake_entry; my $c3 = $e3->t_enter_comment; $c3->set_props( 'opt_preformatted' => 1, 'picture_keyword' => 2 ); ok( $c3->prop('opt_preformatted') == 1 && $c3->prop('picture_keyword') == 2 && $inserted == 1 && $deleted == 0 && $queried == 1, "Set 2 props and read back" ); } ( $inserted, $deleted, $queried ) = ( 0, 0, 0 ); { # mixed my $e4 = $u->t_post_fake_entry; my $c4 = $e4->t_enter_comment; $c4->set_props( 'opt_preformatted' => undef, 'picture_keyword' => 2 ); ok( !defined( $c4->prop('opt_preformatted') ) && $c4->prop('picture_keyword') == 2 && $inserted == 1 && $deleted == 1 && $queried == 1, "Set 1 prop, deleted 1, and read back" ); } ( $inserted, $deleted, $queried ) = ( 0, 0, 0 ); { # deletes my $e5 = $u->t_post_fake_entry; my $c5 = $e5->t_enter_comment; $c5->set_props( 'opt_preformatted' => undef, 'picture_keyword' => undef ); ok( !defined( $c5->prop('opt_preformatted') ) && !defined( $c5->prop('picture_keyword') ) && $inserted == 0 && $deleted == 1 && $queried == 1, "Set 1 prop, deleted 1, and read back" ); } ( $inserted, $deleted, $queried ) = ( 0, 0, 0 ); { # raw my $e6 = $u->t_post_fake_entry; my $c6 = $e6->t_enter_comment; $c6->set_props_raw( 'edit_time' => "UNIX_TIMESTAMP()", 'opt_preformatted' => 1 ); ok( $c6->prop('opt_preformatted') == 1 && $c6->prop('edit_time') =~ /^\d+$/ && $inserted == 1 && $deleted == 0 && $queried == 1, "Set 2 raw props and read back" ); } } } # post a tree of comments { # step counter so we can test multiple legacy API interactions foreach my $step ( 0 .. 2 ) { my $entry = $u->t_post_fake_entry; # entry # - child 0 # - child 1 # + child 1.1 # - child 2 # + child 2.1 # + child 2.2 # - child 3 # + child 3.1 # + child 3.2 # + child 3.3 # - child 4 # + child 4.1 # + child 4.2 # + child 4.3 # + child 4.4 # - child 5 # + child 5.1 # + child 5.2 # + child 5.3 # + child 5.4 # + child 5.5 my @tree = (); # [ child => [ sub_children ] # create 5 comments on this entry foreach my $top_reply_ct ( 0 .. 5 ) { my $c = $entry->t_enter_comment; my $curr = [ $c => [] ]; push @tree, $curr; # now make 5 replies to each comment, except for the first foreach my $reply_ct ( 1 .. $top_reply_ct ) { last if $top_reply_ct == 0; my $child = $c->t_reply; $child->set_prop( 'opt_preformatted', 1 ); push @{ $curr->[1] }, $child; } } # are the first-level children created properly? ok( @tree == 6, "$step: Created 6 child comments" ); # how about subchildren? my %want = map { $_ => 1 } 0 .. 5; delete $want{ scalar @{ $_->[1] } } foreach @tree; ok( !%want, "$step: Created 0..5 sub-child comments" ); # test accesses to ->children methods in cases where legacy APIs are also called my %access_ct = (); $LJ::_T_GET_TALK_DATA_MEMCACHE = sub { $access_ct{data}++ }; $LJ::_T_GET_TALK_TEXT2_MEMCACHE = sub { $access_ct{text}++ }; $LJ::_T_GET_TALK_PROPS2_MEMCACHE = sub { $access_ct{props}++ }; # 0: straight call to ->children # 1: get_talk_data call # 2: $entry->comment_list call # 3: load_comments call LJ::Talk::get_talk_data( $u, 'L', $entry->jitemid ) if $step == 1; $entry->comment_list if $step == 2; LJ::Talk::load_comments( $u, undef, 'L', $entry->jitemid ) if $step == 3; %want = map { $_ => 1 } 0 .. 5; foreach my $parent ( map { $_->[0] } @tree ) { my @children = $parent->children; delete $want{ scalar @children }; # now access text and props # FIXME: should test case of legacy prop/text access, then object method $parent->props; $parent->body_raw; foreach my $child (@children) { $child->props; $child->subject_raw; } } ok( !%want, "$step: Retrieved 0..5 sub-children LJ::Comment objects" ); ok( $access_ct{data} == 1, "$step: Only one talk data access with legacy interaction" ); ok( $access_ct{text} == 1, "$step: Only one text data access with legacy interaction" ); ok( $access_ct{props} == 1, "$step: Only one prop data access with legacy interaction" ); # Add to the tree: # - child 6 # + child 6.1 # + child 6.1.1 # + child 6.1.1.1 my $comment = $entry->t_enter_comment; my $curr = [ $comment => [] ]; push @tree, $curr; foreach ( 1 .. 3 ) { $comment = $comment->t_reply; push @{ $curr->[1] }, $comment; } # look up root foreach my $parent ( map { $_->[0] } @tree ) { ok( $parent->threadrootid eq $parent->jtalkid, "Comment depth 1: this is the thread root" ); my @children = $parent->children; foreach my $child (@children) { ok( $child->parenttalkid == $child->threadrootid, "Comment depth 2: thread root and parent are equivalent." ); my $descendant = $child; my $depth = 2; foreach ( $descendant->children ) { ok( $child->parenttalkid == $descendant->threadrootid, "Comment depth $depth: thread root is no longer directly linked to this comment." ); $depth++; $descendant = $_; } } } } } # test editing of comment text { my $e = $u->t_post_fake_entry; my $c = $e->t_enter_comment; my $jtalkid = $c->jtalkid; my $old_subject = $c->subject_raw; my $old_body = $c->body_raw; { my $new_subject = LJ::rand_chars(25); my $new_body = LJ::rand_chars(500); $c->set_subject($new_subject); ok( $c->subject_raw eq $new_subject && $c->body_raw eq $old_body, "Set subject okay" ); $c->set_body($new_body); ok( $c->subject_raw eq $new_subject && $c->body_raw eq $new_body, "Set body okay" ); # clear out and check memcache LJ::Comment->reset_singletons; $c = LJ::Comment->new( $u, jtalkid => $jtalkid ); ok( $c->subject_raw eq $new_subject && $c->body_raw eq $new_body, "Read subject and body back from memcache" ); } { my $new_subject = LJ::rand_chars(25); my $new_body = LJ::rand_chars(500); $c->set_subject_and_body( $new_subject, $new_body ); ok( $c->subject_raw eq $new_subject && $c->body_raw eq $new_body, "Set subject and body at once" ); # clear out and check memcache LJ::Comment->reset_singletons; $c = LJ::Comment->new( $u, jtalkid => $jtalkid ); ok( $c->subject_raw eq $new_subject && $c->body_raw eq $new_body, "Read subject and body back from memcache" ); } # test setting of subejct / body with unknown8bit set { $c->set_prop( 'unknown8bit', 1 ); eval { $c->set_subject($old_subject) }; ok( $@ =~ 'unknown8bit', "Can't set unknown8bit without subject / body" ); eval { $c->set_subject_and_body( $old_subject, $old_body ) }; ok( !$@ && $c->subject_raw eq $old_subject && $c->body_raw eq $old_body, "Able to set unknown8bit with subject and body" ); ok( $c->prop('unknown8bit') == 0, "unknown8bit prop unset" ); } } } memcache_stress { run_tests(); }; 1;