[Catalyst-commits] r14382 - in CatalystX-CRUD/CatalystX-CRUD-Controller-REST/trunk: lib/CatalystX/CRUD/Controller t t/lib/MyApp/Controller t/lib/MyApp/Controller/REST

karpet at dev.catalyst.perl.org karpet at dev.catalyst.perl.org
Mon Nov 5 03:05:32 GMT 2012


Author: karpet
Date: 2012-11-05 03:05:32 +0000 (Mon, 05 Nov 2012)
New Revision: 14382

Removed:
   CatalystX-CRUD/CatalystX-CRUD-Controller-REST/trunk/t/boilerplate.t
Modified:
   CatalystX-CRUD/CatalystX-CRUD-Controller-REST/trunk/lib/CatalystX/CRUD/Controller/REST.pm
   CatalystX-CRUD/CatalystX-CRUD-Controller-REST/trunk/t/001-file.t
   CatalystX-CRUD/CatalystX-CRUD-Controller-REST/trunk/t/lib/MyApp/Controller/REST/File.pm
   CatalystX-CRUD/CatalystX-CRUD-Controller-REST/trunk/t/lib/MyApp/Controller/Root.pm
Log:
add POD; all tests passing

Modified: CatalystX-CRUD/CatalystX-CRUD-Controller-REST/trunk/lib/CatalystX/CRUD/Controller/REST.pm
===================================================================
--- CatalystX-CRUD/CatalystX-CRUD-Controller-REST/trunk/lib/CatalystX/CRUD/Controller/REST.pm	2012-11-04 03:43:38 UTC (rev 14381)
+++ CatalystX-CRUD/CatalystX-CRUD-Controller-REST/trunk/lib/CatalystX/CRUD/Controller/REST.pm	2012-11-05 03:05:32 UTC (rev 14382)
@@ -23,6 +23,8 @@
 
 with 'CatalystX::CRUD::ControllerRole';
 
+use CatalystX::CRUD::Results;
+
 =head1 NAME
 
 CatalystX::CRUD::Controller::REST - Catalyst::Controller::REST with CRUD
@@ -54,7 +56,7 @@
  # POST      /foo/<pk>/bar       -> create 'bar' object related to 'foo' (idempotent)
  # PUT       /foo/<pk>/bar/<pk2> -> create relationship between 'foo' and 'bar'
  # DELETE    /foo/<pk>/bar/<pk2> -> sever 'bar' object relationship to 'foo'
- # POST      /foo/<pk>/bar/<pk2> -> update 'bar' object related to 'foo'
+ # PUT       /foo/<pk>/bar/<pk2> -> create/update 'bar' object related to 'foo'
 
 =head1 DESCRIPTION
 
@@ -80,12 +82,23 @@
 ##############################################################
 # Local actions
 
+=head2 search_objects
+
+Registers URL space for B<search>.
+
+=cut
+
 sub search_objects : Path('search') : Args(0) : ActionClass('REST') { }
 
+=head2 search_objects_GET
+
+Query the model and return results. See do_search().
+
+=cut
+
 sub search_objects_GET {
     my ( $self, $c ) = @_;
-    $c->log->debug('search_GET');
-    $self->search($c);
+    $self->do_search($c);
     if ( !blessed( $c->stash->{results} ) ) {
         $self->status_bad_request( $c,
             message => 'Must provide search parameters' );
@@ -95,12 +108,26 @@
     }
 }
 
+=head2 count_objects
+
+Registers URl space for B<count>.
+
+=cut
+
 sub count_objects : Path('count') : Args(0) : ActionClass('REST') { }
 
+=head2 count_objects_GET
+
+Like search_objects_GET() but does not set result values, only a total count.
+Useful for AJAX-y types of situations where you want to query for a total
+number of matches and create a pager but not actually retrieve any data.
+
+=cut
+
 sub count_objects_GET {
     my ( $self, $c ) = @_;
-    $c->log->debug('count_GET');
-    $self->count($c);
+    $c->stash( fetch_no_results => 1 );    # optimize a little
+    $self->do_search($c);
     if ( !blessed( $c->stash->{results} ) ) {
         $self->status_bad_request( $c,
             message => 'Must provide search parameters' );
@@ -113,17 +140,34 @@
 ##############################################################
 # REST actions
 
-# base method for /
+=head2 zero_args
+
+Registers URL space for 0 path arguments.
+
+=cut
+
 sub zero_args : Path('') : Args(0) : ActionClass('REST') { }
 
-# list objects
+=head2 zero_args_GET( I<ctx> )
+
+GET /foo -> list objects of type foo.
+
+Calls do_search().
+
+=cut
+
 sub zero_args_GET {
     my ( $self, $c ) = @_;
-    $self->list($c);
+    $self->do_search($c);
     $self->status_ok( $c, entity => $c->stash->{results}->serialize );
 }
 
-# create object
+=head2 zero_args_POST( I<ctx> )
+
+POST /foo -> create object of type foo.
+
+=cut
+
 sub zero_args_POST {
     my ( $self, $c ) = @_;
 
@@ -147,27 +191,41 @@
     }
 }
 
-# base method for /<pk>
-sub one_arg : Path('') : Args(1) : ActionClass('REST') {
+=head2 one_arg
+
+Registers URL space for 1 path argument.
+
+=cut
+
+sub one_arg : Path('') : Args(1) : ActionClass('REST::ForBrowsers') {
     my ( $self, $c, $id ) = @_;
     $self->fetch( $c, $id );
 }
 
+=head2 one_arg_GET( I<ctx>, I<pk> )
+
+GET /foo/<pk> -> retrieve object for I<pk>.
+
+=cut
+
 sub one_arg_GET {
     my ( $self, $c, $id ) = @_;
-
-    # rely on one_arg() to handle errors to this point.
-    if ( $self->has_errors($c) ) {
-        $c->clear_errors;
-        return if $c->response->status;
-    }
-
+    return if $c->stash->{fetch_failed};
+    return if $c->stash->{object}->is_new;    # 404
     $self->status_ok( $c, entity => $c->stash->{object}->serialize );
 }
 
-# create or update object (idempotent)
+=head2 one_arg_PUT( I<ctx>, I<pk> )
+
+PUT /foo/<pk> -> create or update the object for I<pk>.
+
+This method must be idempotent. POST is not.
+
+=cut
+
 sub one_arg_PUT {
     my ( $self, $c, $id ) = @_;
+    return if $c->stash->{fetch_failed};
 
     # remember if we're creating or updating
     my $obj_is_new = $c->stash->{object}->is_new;
@@ -197,16 +255,16 @@
     }
 }
 
-# delete object
+=head2 one_arg_DELETE( I<ctx>, I<pk> )
+
+DELETE /foo/<pk> -> delete the object for I<pk>.
+
+=cut
+
 sub one_arg_DELETE {
     my ( $self, $c, $id ) = @_;
+    return if $c->stash->{fetch_failed};
 
-    # rely on one_arg() to handle errors to this point.
-    if ( $self->has_errors($c) ) {
-        $c->clear_errors;
-        return if $c->response->status;
-    }
-
     unless ( $self->can_write($c) ) {
         $self->status_forbidden( $c, message => 'Permission denied' );
         return;
@@ -221,44 +279,64 @@
     }
 }
 
-# related to /<pk>
-sub two_args : Path('') : Args(2) : ActionClass('REST') {
+=head2 two_args
+
+Registers URL space for 2 path arguments.
+
+=cut
+
+sub two_args : Path('') : Args(2) : ActionClass('REST::ForBrowsers') {
     my ( $self, $c, $id, $rel ) = @_;
+    $c->stash( rel_name => $rel );
     $self->fetch( $c, $id );
-    $c->stash( rel_name => $rel );
 }
 
-# list /<pk>/<rel>
+=head2 two_args_GET( I<ctx>, I<pk>, I<rel> )
+
+GET /foo/<pk>/bar -> a list of objects of type bar related to foo.
+
+=cut
+
 sub two_args_GET {
     my ( $self, $c, $id, $rel ) = @_;
-
-    # rely on two_args() to handle errors to this point.
+    return if $c->stash->{fetch_failed};
+    my $results
+        = $self->do_model( $c, 'iterator_related', $c->stash->{object},
+        $rel, );
     if ( $self->has_errors($c) ) {
+        my $err = $c->error->[0];
+        if ( $err =~ m/^(unsupported relationship name: (\S+))/i ) {
+            $self->status_not_found( $c, message => $1 );
+        }
+        else {
+            $self->status_bad_request( $c, message => $err );
+        }
         $c->clear_errors;
-        return if $c->response->status;
     }
-
-    my $results
-        = $self->do_model( $c, 'iterator_related', $c->stash->{object},
-        $rel, );
-    $self->status_ok( $c, entity => $results->serialize );
+    else {
+        $self->status_ok( $c, entity => $results->serialize );
+    }
 }
 
-# create /<pk>/<rel>
+=head2 two_args_POST( I<ctx>, I<pk>, I<rel> )
+
+POST /foo/<pk>/bar  -> create relationship between foo and bar.
+
+B<TODO> This method calls a not-yet-implemented create_related()
+action in the CXC::Model.
+
+=cut
+
 sub two_args_POST {
     my ( $self, $c, $id, $rel ) = @_;
-
-    # rely on two_args() to handle errors to this point.
-    if ( $self->has_errors($c) ) {
-        $c->clear_errors;
-        return if $c->response->status;
-    }
-
+    return if $c->stash->{fetch_failed};
     unless ( $self->can_write($c) ) {
         $self->status_forbidden( $c, message => 'Permission denied' );
         return;
     }
 
+    $self->throw_error('TODO');
+
     my $rel_obj
         = $self->do_model( $c, 'create_related', $c->stash->{object}, $rel, );
     if ($rel_obj) {
@@ -277,24 +355,28 @@
 
 }
 
-# actions on <rel> related to /<pk>
-sub three_args : Path('') : Args(3) : ActionClass('REST') {
+=head2 three_args
+
+Registers the URL space for 3 path arguments.
+
+=cut
+
+sub three_args : Path('') : Args(3) : ActionClass('REST::ForBrowsers') {
     my ( $self, $c, $id, $rel, $rel_id ) = @_;
-    $self->fetch( $c, $id );
     $c->stash( rel_name         => $rel );
     $c->stash( foreign_pk_value => $rel_id );
+    $self->fetch( $c, $id );
 }
 
-# /<pk>/<re>/<pk2>
+=head2 three_args_GET( I<ctx>, I<pk>, I<rel>, I<rel_id> )
+
+GET /foo/<pk>/<re>/<pk2>
+
+=cut
+
 sub three_args_GET {
     my ( $self, $c, $id, $rel, $rel_id ) = @_;
-
-    # rely on three_args() to handle errors to this point.
-    if ( $self->has_errors($c) ) {
-        $c->clear_errors;
-        return if $c->response->status;
-    }
-
+    return if $c->stash->{fetch_failed};
     my $result = $self->do_model( $c, 'find_related', $c->stash->{object},
         $rel, $rel_id, );
     if ( !$result ) {
@@ -306,27 +388,22 @@
     }
 }
 
-# DELETE    /foo/<pk>/bar/<pk2> -> sever 'bar' object relationship to 'foo'
+=head2 three_args_DELETE( I<ctx>, I<pk>, I<rel>, I<rel_pk> )
 
+DELETE /foo/<pk>/bar/<pk2> -> sever 'bar' object relationship to 'foo'
+
+=cut
+
 sub three_args_DELETE {
     my ( $self, $c, $id, $rel, $rel_id ) = @_;
-
-    # rely on three_args() to handle errors to this point.
-    if ( $self->has_errors($c) ) {
-        $c->clear_errors;
-        return if $c->response->status;
-    }
-
+    return if $c->stash->{fetch_failed};
     unless ( $self->can_write($c) ) {
         $self->status_forbidden( $c, message => 'Permission denied' );
         return;
     }
 
-    my $rt = $self->do_model(
-        $c, 'rm_related', $c->stash->{object},
-        $c->stash->{rel_name},
-        $c->stash->{foreign_pk_value}
-    );
+    my $rt = $self->do_model( $c, 'rm_related', $c->stash->{object},
+        $rel, $rel_id, );
     if ($rt) {
         $self->status_no_content($c);
     }
@@ -337,16 +414,65 @@
     }
 }
 
-# TODO
-# POST      /foo/<pk>/bar/<pk2> -> create relationship between 'foo' and 'bar'
-# PUT       /foo/<pk>/bar/<pk2> -> update 'bar' object related to 'foo'
+=head2 three_args_POST( I<ctx>, I<pk>, I<rel>, I<rel_pk> )
 
+POST /foo/<pk>/bar/<pk2> -> create relationship between 'foo' and 'bar'
+
+=cut
+
+sub three_args_POST {
+    my ( $self, $c, $id, $rel, $rel_id ) = @_;
+    return if $c->stash->{fetch_failed};
+    my $rt = $self->do_model( $c, 'add_related', $c->stash->{object},
+        $rel, $rel_id, );
+    if ($rt) {
+        $self->status_no_content($c);
+    }
+    else {
+        # TODO msg
+        $self->status_bad_request( $c,
+            message => 'Failed to remove relationship' );
+    }
+}
+
+=head2 three_args_PUT( I<ctx>, I<pk>, I<rel>, I<rel_pk> )
+
+PUT /foo/<pk>/bar/<pk2> -> create/update 'bar' object related to 'foo'
+
+=cut
+
+sub three_args_PUT {
+    my ( $self, $c, $id, $rel, $rel_id ) = @_;
+    return if $c->stash->{fetch_failed};
+    my $rt = $self->do_model( $c, 'put_related', $c->stash->{object},
+        $rel, $rel_id, );
+
+    if ($rt) {
+        $self->status_no_content($c);
+    }
+    else {
+        # TODO msg
+        $self->status_bad_request( $c,
+            message => 'Failed to PUT relationship' );
+    }
+}
+
 ##########################################################
 # CRUD methods
 
-# override base method
+=head2 save_object( I<ctx> )
+
+Calls can_write(), inflate_object(), precommit(), create_or_update_object()
+and postcommit().
+
+=cut
+
 sub save_object {
     my ( $self, $c ) = @_;
+    unless ( $self->can_write($c) ) {
+        $self->status_forbidden( $c, message => 'Permission denied' );
+        return;
+    }
 
     # get a valid object
     my $obj = $self->inflate_object($c);
@@ -357,7 +483,6 @@
 
     # write our changes
     unless ( $self->precommit( $c, $obj ) ) {
-        $c->stash->{template} ||= $self->default_template;
         return 0;
     }
     $self->create_or_update_object( $c, $obj );
@@ -385,6 +510,13 @@
     }
 }
 
+=head2 delete_object( I<ctx> )
+
+Checks can_write(), precommit(), and if both true,
+calls the delete() method on the B<object> in the stash().
+
+=cut
+
 sub delete_object {
     my ( $self, $c ) = @_;
     unless ( $self->can_write($c) ) {
@@ -435,21 +567,58 @@
     return $object;
 }
 
-sub can_read  {1}
+=head2 can_read( I<context> )
+
+Returns true if the current request is authorized to read() the C<object> in
+stash().
+
+Default is true.
+
+=cut
+
+sub can_read {1}
+
+=head2 can_write( I<context> )
+
+Returns true if the current request is authorized to create() or update()
+the C<object> in stash().
+
+=cut
+
 sub can_write {1}
 
+=head2 precommit( I<context>, I<object> )
+
+Called by save_object(). If precommit() returns a false value, save_object() is aborted.
+If precommit() returns a true value, create_or_update_object() gets called.
+
+The default return is true.
+
+=cut
+
 sub precommit {1}
 
 =head2 postcommit( I<cxt>, I<obj> )
 
-Called internally inside save_object(). Overrides parent class
-which issues redirect on successful save_object(). Our default just returns true.
+Called internally inside save_object(). Our default just returns true.
 Override this method to post-process a successful save_object() action.
 
 =cut
 
 sub postcommit {1}
 
+=head2 fetch( I<ctx>, I<pk> )
+
+Determines the correct value and field name for I<pk>
+and calls the do_model() method for C<fetch>.
+
+On success, the B<object> key will be set in stash().
+
+On failure, calls status_not_found() and sets the
+B<fetch_failed> stash() key.
+
+=cut
+
 sub fetch {
     my ( $self, $c, $id ) = @_;
 
@@ -486,7 +655,10 @@
             = sprintf( "No such %s with id '%s'", $self->model_name, $id );
         $self->status_not_found( $c, message => $err_msg );
         $c->log->error($err_msg);
+        $c->stash( fetch_failed => 1 );
+        return 0;
     }
+    return $c->stash->{object};
 }
 
 =head2 do_search( I<context>, I<arg> )
@@ -507,21 +679,11 @@
 sub do_search {
     my ( $self, $c, @arg ) = @_;
 
-    $self->throw_error("TODO");
-
-    # stash the form so it can be re-displayed
-    # subclasses must stick-ify it in their own way.
-    $c->stash->{form} ||= $self->form($c);
-
     # if we have no input, just return for initial search
     if ( !@arg && !$c->req->param && $c->action->name eq 'search' ) {
         return;
     }
 
-    # turn flag on unless explicitly turned off
-    $c->stash->{view_on_single_result} = 1
-        unless exists $c->stash->{view_on_single_result};
-
     my $query;
     if ( $self->can('make_query') ) {
         $query = $self->make_query( $c, @arg );
@@ -539,36 +701,43 @@
         $results = $self->do_model( $c, 'search', $query );
     }
 
-    if (   $results
-        && $count == 1
-        && $c->stash->{view_on_single_result}
-        && ( my $uri = $self->uri_for_view_on_single_result( $c, $results ) )
-        )
-    {
-        $c->log->debug("redirect for single_result") if $c->debug;
-        $c->response->redirect($uri);
+    my $pager;
+    if ( $count && $self->model_can( $c, 'make_pager' ) ) {
+        $pager = $self->do_model( $c, 'make_pager', $count, $results );
     }
-    else {
 
-        my $pager;
-        if ( $count && $self->model_can( $c, 'make_pager' ) ) {
-            $pager = $self->do_model( $c, 'make_pager', $count, $results );
+    $c->stash->{results}
+        = $self->naked_results
+        ? $results
+        : CatalystX::CRUD::Results->new(
+        {   count   => $count,
+            pager   => $pager,
+            results => $results,
+            query   => $query,
         }
+        );
 
-        $c->stash->{results}
-            = $self->naked_results
-            ? $results
-            : CatalystX::CRUD::Results->new(
-            {   count   => $count,
-                pager   => $pager,
-                results => $results,
-                query   => $query,
-            }
-            );
+}
+
+=head2 do_model( I<ctx>, I<args> )
+
+Wrapper around the ControllerRole method of the same name.
+The wrapper does an eval and sets the I<ctx> error param
+with $@.
+
+=cut
+
+around 'do_model' => sub {
+    my ( $orig, $self, $c, @args ) = @_;
+    my $results;
+    eval { $results = $self->$orig( $c, @args ); };
+    if ($@) {
+        $c->error($@);    # re-throw
+        return $results;
     }
+    return $results;
+};
 
-}
-
 1;
 
 __END__

Modified: CatalystX-CRUD/CatalystX-CRUD-Controller-REST/trunk/t/001-file.t
===================================================================
--- CatalystX-CRUD/CatalystX-CRUD-Controller-REST/trunk/t/001-file.t	2012-11-04 03:43:38 UTC (rev 14381)
+++ CatalystX-CRUD/CatalystX-CRUD-Controller-REST/trunk/t/001-file.t	2012-11-05 03:05:32 UTC (rev 14382)
@@ -1,6 +1,6 @@
 #!/usr/bin/env perl
 
-use Test::More tests => 54;
+use Test::More tests => 43;
 use strict;
 use lib qw( lib t/lib );
 use_ok('CatalystX::CRUD::Model::File');
@@ -12,11 +12,15 @@
 use JSON;
 
 ####################################################
-# do CRUD stuff
+# basic CRUD
 
 my $res;
 
-# create
+# confirm testfile is not there
+ok( $res = request( GET('/rest/file/testfile') ), "GET testfile" );
+is( $res->code, 404, "no testfile at start" );
+
+# create testfile
 ok( $res = request(
         PUT('/rest/file/testfile',
             Content => encode_json( { content => 'hello world' } )
@@ -31,42 +35,50 @@
     "PUT new file response"
 );
 
-####################################################
 # read the file we just created
 ok( $res = request( HTTP::Request->new( GET => '/rest/file/testfile' ) ),
     "GET new file" );
 
 #diag( $res->content );
 
-like( $res->content, qr/content => "hello world"/, "read file" );
+is_deeply(
+    decode_json( $res->content ),
+    { content => "hello world", file => "testfile" },
+    "GET file response"
+);
 
-####################################################
 # update the file
 ok( $res = request(
-        POST( '/rest/file/testfile', [ content => 'foo bar baz' ] )
+        PUT('/rest/file/testfile',
+            Content => encode_json( { content => 'foo bar baz' } )
+        )
     ),
     "update file"
 );
 
-like( $res->content, qr/content => "foo bar baz"/, "update file" );
+is_deeply(
+    decode_json( $res->content ),
+    { content => "foo bar baz", file => "testfile" },
+    "PUT file update response"
+);
 
 ####################################################
-# create related file
+# create another new file
 ok( $res = request(
-        POST(
-            '/rest/file/otherdir%2ftestfile2',
-            [ content => 'hello world 2' ]
+        PUT('/rest/file/otherdir%2ftestfile2',
+            Content => encode_json( { content => 'hello world 2' } )
         )
     ),
-    "POST new file2"
+    "PUT new file2"
 );
 
-is( $res->content,
-    '{ content => "hello world 2", file => "otherdir/testfile2" }',
-    "POST new file2 response"
+is_deeply(
+    decode_json( $res->content ),
+    { content => "hello world 2", file => "otherdir/testfile2" },
+    "PUT new file2 response"
 );
 
-is( $res->code, 302, "new file 302 redirect status" );
+is( $res->code, 201, "new file 201 status" );
 
 ###################################################
 # test with no args
@@ -75,34 +87,80 @@
 
 ok( $res = request('/rest/file'), "/ request with multiple items" );
 is( $res->code, 200, "/ request with multiple items lists" );
-ok( $res->content =~ qr/foo bar baz/ && $res->content =~ qr/hello world/,
-    "content has 2 files" );
 
+#diag( dump( decode_json( $res->content ) ) );
+is_deeply(
+    decode_json( $res->content ),
+    {   count   => 2,
+        query   => 1,
+        results => [
+            { content => "foo bar baz",   file => "./testfile" },
+            { content => "hello world 2", file => "otherdir/testfile2" },
+        ],
+    },
+    "content has 2 files"
+);
+
 ###################################################
-# test the Arg matching with no rpc
+# test dispatching
 
-ok( $res = request('/rest/file/create'), "/rest/file/create" );
-is( $res->code, 302, "/rest/file/create" );
 ok( $res = request('/rest/file'), "zero" );
 is( $res->code, 200, "zero => list()" );
 ok( $res = request('/rest/file/testfile'), "one" );
 is( $res->code, 200, "oid == one" );
 ok( $res = request('/rest/file/testfile/view'), "view" );
 is( $res->code, 404, "rpc == two" );
-ok( $res
-        = request(
-        POST( '/rest/file/testfile/dir/otherdir%2ftestfile2', [] ) ),
-    "three"
-);
+
+######################################################
+# relate 2 files together
+
+# create relationship between testfile and testfile2
+ok( $res = request( PUT('/rest/file/testfile/dir/otherdir%2ftestfile2') ),
+    "three" );
 is( $res->code, 204, "related == three" );
-ok( $res = request(
-        POST( '/rest/file/testfile/dir/otherdir%2ftestfile2/rpc', [] )
-    ),
-    "four"
+
+# more test routing
+ok( $res = request( PUT('/rest/file/testfile/dir/otherdir%2ftestfile2/rpc') ),
+    "four" );
+is( $res->code, 404, "404 4 is too many args" );
+ok( $res = request('/rest/file/testfile/two/three/four/five'), "five" );
+is( $res->code, 404, "404 5 is too many args" );
+
+########################################################
+# non-CRUD actions: search and count
+ok( $res = request( GET('/rest/file/search?file=testfile') ),
+    "/search?file=testfile" );
+
+#diag( dump decode_json( $res->content ) );
+is_deeply(
+    decode_json( $res->content ),
+    {   count   => 3,
+        query   => 1,
+        results => [
+            { content => "foo bar baz",   file => "./testfile" },
+            { content => "foo bar baz",   file => "./testfile2" },
+            { content => "hello world 2", file => "otherdir/testfile2" },
+        ],
+    },
+    "search gets 3 results"
 );
-is( $res->code, 404, "404 == related rpc with no enable_rpc_compat" );
-ok( $res = request('/rest/file/testfile/two/three/four/five'), "five" );
-is( $res->code, 404, "404 == five" );
+
+ok( $res = request( GET('/rest/file/count') ), "GET /count" );
+
+#diag( dump decode_json( $res->content ) );
+is_deeply(
+    decode_json( $res->content ),
+    {   count   => 3,
+        query   => 1,
+        results => [],
+    },
+    "count gets 3 results"
+);
+
+########################################################
+# test "browser-like" behavior tunneling through POST
+
+# delete relationship between testfile and testfile2
 ok( $res = request(
         POST(
             '/rest/file/testfile/dir/otherdir%2ftestfile2',
@@ -111,43 +169,40 @@
     ),
     "three"
 );
-is( $res->code, 204, "related == three" );
+is( $res->code, 204, "tunneled DELETE related == three" );
 
-# delete the file
-
+# delete testfile
 ok( $res = request(
-        POST( '/rest/file/testfile', [ _http_method => 'DELETE' ] )
+        POST( '/rest/file/testfile', [ 'x-tunneled-method' => 'DELETE' ] )
     ),
     "rm file"
 );
 
 ok( $res = request(
-        POST( '/rest/file/testfile2/delete', [ _http_method => 'DELETE' ] )
-    ),
-    "rm file2"
-);
-
-ok( $res = request(
         POST(
-            '/rest/file/otherdir%2ftestfile2/delete',
-            [ _http_method => 'DELETE' ]
+            '/rest/file/otherdir%2ftestfile2',
+            [ 'x-tunneled-method' => 'DELETE' ]
         )
     ),
     "rm otherdir/testfile2"
 );
+is( $res->code, 204, "tunneled DELETE otherdir/testfile2" );
 
 #diag( $res->content );
 
-# confirm it is gone
-ok( $res = request( HTTP::Request->new( GET => '/rest/file/testfile' ) ),
+# confirm testfile is gone
+ok( $res = request( GET('/rest/file/testfile') ),
     "confirm we nuked the file" );
 
-#diag( $res->content );
+#diag( dump $res->content );
+is( $res->code, 404, "testfile is gone" );
 
-like( $res->content, qr/content => undef/, "file nuked" );
-
 ok( $res = request('/rest/file'), "/ request with no items" );
+is( $res->code, 200, "/ request with no items == 200" );
 
-#dump $res;
-is( $res->code,    200, "/ request with no items == 200" );
-is( $res->content, "",  "no content for no results" );
+#diag( dump decode_json( $res->content ) );
+is_deeply(
+    decode_json( $res->content ),
+    { count => 0, query => 1, results => [], },
+    "no content for no results"
+);

Deleted: CatalystX-CRUD/CatalystX-CRUD-Controller-REST/trunk/t/boilerplate.t
===================================================================
--- CatalystX-CRUD/CatalystX-CRUD-Controller-REST/trunk/t/boilerplate.t	2012-11-04 03:43:38 UTC (rev 14381)
+++ CatalystX-CRUD/CatalystX-CRUD-Controller-REST/trunk/t/boilerplate.t	2012-11-05 03:05:32 UTC (rev 14382)
@@ -1,55 +0,0 @@
-#!perl -T
-
-use strict;
-use warnings;
-use Test::More tests => 3;
-
-sub not_in_file_ok {
-    my ($filename, %regex) = @_;
-    open( my $fh, '<', $filename )
-        or die "couldn't open $filename for reading: $!";
-
-    my %violated;
-
-    while (my $line = <$fh>) {
-        while (my ($desc, $regex) = each %regex) {
-            if ($line =~ $regex) {
-                push @{$violated{$desc}||=[]}, $.;
-            }
-        }
-    }
-
-    if (%violated) {
-        fail("$filename contains boilerplate text");
-        diag "$_ appears on lines @{$violated{$_}}" for keys %violated;
-    } else {
-        pass("$filename contains no boilerplate text");
-    }
-}
-
-sub module_boilerplate_ok {
-    my ($module) = @_;
-    not_in_file_ok($module =>
-        'the great new $MODULENAME'   => qr/ - The great new /,
-        'boilerplate description'     => qr/Quick summary of what the module/,
-        'stub function definition'    => qr/function[12]/,
-    );
-}
-
-TODO: {
-  local $TODO = "Need to replace the boilerplate text";
-
-  not_in_file_ok(README =>
-    "The README is used..."       => qr/The README is used/,
-    "'version information here'"  => qr/to provide version information/,
-  );
-
-  not_in_file_ok(Changes =>
-    "placeholder date/time"       => qr(Date/time)
-  );
-
-  module_boilerplate_ok('lib/CatalystX/CRUD/Controller/REST.pm');
-
-
-}
-

Modified: CatalystX-CRUD/CatalystX-CRUD-Controller-REST/trunk/t/lib/MyApp/Controller/REST/File.pm
===================================================================
--- CatalystX-CRUD/CatalystX-CRUD-Controller-REST/trunk/t/lib/MyApp/Controller/REST/File.pm	2012-11-04 03:43:38 UTC (rev 14381)
+++ CatalystX-CRUD/CatalystX-CRUD-Controller-REST/trunk/t/lib/MyApp/Controller/REST/File.pm	2012-11-05 03:05:32 UTC (rev 14382)
@@ -20,24 +20,21 @@
 
 sub fetch {
     my ( $self, $c, $id ) = @_;
-    eval { $self->next::method( $c, $id ); };
-    if ( $self->has_errors($c) or $c->res->status == 404 ) {
+    my $rt = $self->next::method( $c, $id );
 
-        my $err = $c->error->[0] || 'No such File';
-        if ( $err =~ m/^No such File/ ) {
-            my $file = $self->do_model( $c, 'new_object', file => $id );
-            $file = $self->do_model( $c, 'prep_new_object', $file );
-            $c->log->debug("empty file object:$file") if $c->debug;
-            $c->stash( object => $file );
-        }
-        else {
-            # re-throw
-            $self->throw_error($err);
-        }
+    # File model requires an object to work on
+    # regardless of whether we fetched one.
+    if ( !$rt and $rt == 0 ) {
+        my $file = $self->do_model( $c, 'new_object', file => $id );
+        $file = $self->do_model( $c, 'prep_new_object', $file );
+        $c->log->debug("empty file object:$file") if $c->debug;
+        $c->stash( object => $file );
+        delete $c->stash->{fetch_failed};
     }
 
     # clean up at end
     MyApp::Controller::Root->push_temp_files( $c->stash->{object} );
+    return $rt;
 }
 
 sub do_search {

Modified: CatalystX-CRUD/CatalystX-CRUD-Controller-REST/trunk/t/lib/MyApp/Controller/Root.pm
===================================================================
--- CatalystX-CRUD/CatalystX-CRUD-Controller-REST/trunk/t/lib/MyApp/Controller/Root.pm	2012-11-04 03:43:38 UTC (rev 14381)
+++ CatalystX-CRUD/CatalystX-CRUD-Controller-REST/trunk/t/lib/MyApp/Controller/Root.pm	2012-11-05 03:05:32 UTC (rev 14382)
@@ -28,6 +28,9 @@
 
 END {
     for my $f (@temp_files) {
+        if ( defined -s $f ) {
+            warn "exists > $f\n";
+        }
         warn "unlinking $f\n" if $ENV{CATALYST_DEBUG};
         $f->remove;
     }




More information about the Catalyst-commits mailing list