diff options
author | franck cuny <franck@lumberjaph.net> | 2009-12-08 10:35:46 +0100 |
---|---|---|
committer | franck cuny <franck@lumberjaph.net> | 2009-12-08 10:35:46 +0100 |
commit | a2b7ab98ccb7083ad6bdda0839a1e2e6e21ea847 (patch) | |
tree | 309219b95ca9846f53cc078a01baa2577d3399ab | |
parent | Checking in changes prior to tagging of version 0.01. Changelog diff is: (diff) | |
parent | small updates to tests (diff) | |
download | moosex-net-api-a2b7ab98ccb7083ad6bdda0839a1e2e6e21ea847.tar.gz |
Merge branch 'topic/create_tests'
* topic/create_tests: small updates to tests add a catalyst app to tests add basic tests remove meta class and method move meta class and method to new file, add meta to handle tests
-rw-r--r-- | lib/MooseX/Net/API.pm | 141 | ||||
-rw-r--r-- | lib/MooseX/Net/API/Meta/Class.pm | 34 | ||||
-rw-r--r-- | lib/MooseX/Net/API/Meta/Method.pm | 18 | ||||
-rw-r--r-- | lib/MooseX/Net/API/Test.pm | 120 | ||||
-rw-r--r-- | t/00_compile.t | 4 | ||||
-rw-r--r-- | t/01_basic.t | 3 | ||||
-rw-r--r-- | t/10_mx_net_api_test.t | 43 | ||||
-rw-r--r-- | t/lib/TestAPI.pm | 19 | ||||
-rw-r--r-- | t/lib/TestApp.pm | 11 | ||||
-rw-r--r-- | t/lib/TestApp/Controller/Root.pm | 16 |
10 files changed, 344 insertions, 65 deletions
diff --git a/lib/MooseX/Net/API.pm b/lib/MooseX/Net/API.pm index e1ab774..4e1ae5e 100644 --- a/lib/MooseX/Net/API.pm +++ b/lib/MooseX/Net/API.pm @@ -1,13 +1,17 @@ package MooseX::Net::API; -use Carp; use URI; -use HTTP::Request; +use Carp; use Try::Tiny; +use HTTP::Request; + +use Moose; use Moose::Exporter; use MooseX::Net::API::Error; -use MooseX::Net::API::Role::Deserialize; +use MooseX::Net::API::Meta::Class; +use MooseX::Net::API::Meta::Method; use MooseX::Net::API::Role::Serialize; +use MooseX::Net::API::Role::Deserialize; our $VERSION = '0.01'; @@ -30,6 +34,16 @@ my $reverse_content_type = { Moose::Exporter->setup_import_methods( with_caller => [qw/net_api_method net_api_declare/], ); +sub init_meta { + my ( $me, %options ) = @_; + + my $for = $options{for_class}; + Moose::Util::MetaRole::apply_metaclass_roles( + for_class => $for, + metaclass_roles => ['MooseX::Net::API::Meta::Class'], + ); +} + my ( $do_auth, $auth_method, $deserialize_method ); sub net_api_declare { @@ -39,18 +53,13 @@ sub net_api_declare { my $class = Moose::Meta::Class->initialize($caller); - if ( !$options{base_url} ) { - croak "base_url is missing in your api declaration"; - } - else { - $class->add_attribute( - 'api_base_url', - is => 'ro', - isa => 'Str', - lazy => 1, - default => delete $options{base_url} - ); - } + $class->add_attribute( + 'api_base_url', + is => 'rw', + isa => 'Str', + lazy => 1, + default => delete $options{base_url} || '', + ); if ( !$options{format} ) { croak "format is missing in your api declaration"; @@ -180,37 +189,8 @@ sub net_api_method { my $format = $self->api_format(); $url .= "." . $format if ( $self->api_format_mode() eq 'append' ); my $uri = URI->new($url); + my $res = _request( $self, $format, \%options, $uri, \%args ); - my $req; - my $method = $options{method}; - if ( $method =~ /^(?:GET|DELETE)$/ || $options{params_in_url} ) { - $uri->query_form(%args); - $req = HTTP::Request->new( $method => $uri ); - } - elsif ( $method =~ /^(?:POST|PUT)$/ ) { - $req = HTTP::Request->new( $method => $uri ); - # XXX GNI - use JSON::XS; - $req->content( encode_json \%args ); - } - else { - croak "$method is not defined"; - } - - # XXX check presence content type - $req->header( 'Content-Type' => $list_content_type->{$format} ) - if $self->api_format_mode eq 'content-type'; - - if ($do_auth) { - if ($auth_method) { - $req = $self->$auth_method($req); - } - else { - $req = _do_authentication( $self, $req ); - } - } - - my $res = $self->useragent->request($req); my $content_type = $res->headers->{"content-type"}; $content_type =~ s/(;.+)$//; @@ -249,6 +229,7 @@ sub net_api_method { %options, ), ); + $class->_add_api_method($name); } sub _add_useragent { @@ -277,6 +258,41 @@ sub _add_useragent { ); } +sub _request { + my ( $self, $format, $options, $uri, $args ) = @_; + + my $req; + my $method = $options->{method}; + if ( $method =~ /^(?:GET|DELETE)$/ || $options->{params_in_url} ) { + $uri->query_form(%$args); + $req = HTTP::Request->new( $method => $uri ); + } + elsif ( $method =~ /^(?:POST|PUT)$/ ) { + $req = HTTP::Request->new( $method => $uri ); + + # XXX proper serialisation + use JSON::XS; + $req->content( encode_json $args ); + } + else { + croak "$method is not defined"; + } + + $req->header( 'Content-Type' => $list_content_type->{$format} ) + if $self->api_format_mode eq 'content-type'; + + if ($do_auth) { + if ($auth_method) { + $req = $self->$auth_method($req); + } + else { + $req = _do_authentication( $self, $req ); + } + } + + return $self->useragent->request($req); +} + sub _do_authentication { my ( $caller, $req ) = @_; $req->headers->authorization_basic( $caller->api_username, @@ -305,23 +321,6 @@ sub _do_deserialization { } } -package MooseX::Net::API::Meta::Method; - -use Moose; -extends 'Moose::Meta::Method'; - -has description => ( is => 'ro', isa => 'Str' ); -has path => ( is => 'ro', isa => 'Str', required => 1 ); -has method => ( is => 'ro', isa => 'Str', required => 1 ); -has params => ( is => 'ro', isa => 'ArrayRef', required => 0 ); -has required => ( is => 'ro', isa => 'ArrayRef', required => 0 ); - -sub new { - my $class = shift; - my %args = @_; - $class->SUPER::wrap(@_); -} - 1; __END__ @@ -338,6 +337,7 @@ MooseX::Net::API - Easily create client for net API # we declare an API, the base_url is http://exemple.com/api # the format is json and it will be happened to the query + # You can set base_url later, calling $my_obj->api_base_url('http://..') net_api_declare my_api => ( base_url => 'http://exemple.com/api', format => 'json', @@ -426,6 +426,23 @@ HTTP method (GET, POST, PUT, DELETE) path of the query. +If you defined your path and params like this + + net_api_method user_comments => ( + ... + path => '/user/$user/list/$date/', + params => [qw/user date foo bar/], + ... + ); + +and you call + + $obj->user_comments(user => 'franck', date => 'today', foo => 1, bar => 2); + +the url generetad will look like + + /user/franck/list/today/?foo=1&bar=2 + =item B<params> [arrayref] list of params. diff --git a/lib/MooseX/Net/API/Meta/Class.pm b/lib/MooseX/Net/API/Meta/Class.pm new file mode 100644 index 0000000..80075f8 --- /dev/null +++ b/lib/MooseX/Net/API/Meta/Class.pm @@ -0,0 +1,34 @@ +package MooseX::Net::API::Meta::Class; + +use Moose::Role; +use Moose::Meta::Class; +use MooseX::Types::Moose qw(Str ArrayRef ClassName Object); + +has local_api_methods => ( + traits => ['Array'], + is => 'ro', + isa => ArrayRef [Str], + required => 1, + default => sub { [] }, + auto_deref => 1, + handles => { '_add_api_method' => 'push' }, +); +has local_api_test_methods => ( + traits => ['Array'], + is => 'ro', + isa => ArrayRef [Str], + required => 1, + default => sub { [] }, + auto_deref => 1, + handles => { '_add_api_test_method' => 'push' }, +); + +sub _build_meta_class { + my $self = shift; + return Moose::Meta::Class->create_anon_class( + superclasses => [ $self->method_metaclass ], + cache => 1, + ); +} + +1; diff --git a/lib/MooseX/Net/API/Meta/Method.pm b/lib/MooseX/Net/API/Meta/Method.pm new file mode 100644 index 0000000..74f9a07 --- /dev/null +++ b/lib/MooseX/Net/API/Meta/Method.pm @@ -0,0 +1,18 @@ +package MooseX::Net::API::Meta::Method; + +use Moose; +extends 'Moose::Meta::Method'; + +has description => ( is => 'ro', isa => 'Str' ); +has path => ( is => 'ro', isa => 'Str', required => 1 ); +has method => ( is => 'ro', isa => 'Str', required => 1 ); +has params => ( is => 'ro', isa => 'ArrayRef', required => 0 ); +has required => ( is => 'ro', isa => 'ArrayRef', required => 0 ); + +sub new { + my $class = shift; + my %args = @_; + $class->SUPER::wrap(@_); +} + +1; diff --git a/lib/MooseX/Net/API/Test.pm b/lib/MooseX/Net/API/Test.pm index 2f2e428..e991b7f 100644 --- a/lib/MooseX/Net/API/Test.pm +++ b/lib/MooseX/Net/API/Test.pm @@ -1,13 +1,131 @@ package MooseX::Net::API::Test; +use lib ('t/lib'); +use Try::Tiny; + +use Test::More; +use Moose; use Moose::Exporter; +use MooseX::Net::API::Meta::Class; +use MooseX::Net::API::Meta::Method; + +Moose::Exporter->setup_import_methods( + with_caller => [qw/test_api_method test_api_declare run/] ); + +my $api_to_test; + +sub init_meta { + my ( $me, %options ) = @_; + + my $for = $options{for_class}; + Moose::Util::MetaRole::apply_metaclass_roles( + for_class => $for, + metaclass_roles => ['MooseX::Net::API::Meta::Class'], + ); +} + +my $list_content_type = { + 'json' => 'application/json', + 'yaml' => 'text/x-yaml', + 'xml' => 'text/xml', +}; + +my $tests_count = 0; + +sub test_api_declare { + my $caller = shift; + my $name = shift; + my %options = @_; + + unless ( Class::MOP::is_class_loaded($name) ) { + Class::MOP::load_class($name); + } -Moose::Exporter->setup_import_methods( with_caller => [qw/test_api_method/] ); + $api_to_test = $name; + + if ( $options{catalyst} ) { + my $app = $options{catalyst_app_name}; + + Class::MOP::load_class("HTTP::Request"); + Class::MOP::load_class("Catalyst::Test"); + + Catalyst::Test->import($app); + + my $res = __PACKAGE__->meta->remove_method('_request'); + MooseX::Net::API->meta->add_method( + '_request' => sub { + my ( $class, $format, $options, $uri, $args ) = @_; + my $method = $options->{method}; + + my $res; + if ( $method =~ /^(?:GET|DELETE)$/ + || $options->{params_in_url} ) + { + $uri->query_form(%$args); + my $req = HTTP::Request->new( $method => $uri ); + $req->header( + 'Content-Type' => $list_content_type->{$format} ); + $res = request($req); + } + else { + my $req = HTTP::Request->new( $method => $uri ); + $req->header( + 'Content-Type' => $list_content_type->{$format} ); + $req->header( 'Content' => Dump $args); + $res = request($req); + } + return $res; + } + ); + } +} sub test_api_method { my $caller = shift; my $name = shift; my %options = @_; + + my $meta = $api_to_test->meta; + my $method = $meta->find_method_by_name($name); + + if ( !$method ) { + die "method $name does not exists\n"; + } + + my $class = Moose::Meta::Class->initialize($caller); + foreach my $test_name ( keys %{ $options{tests} } ) { + foreach my $test ( @{ $options{tests}{$test_name} } ) { + __PACKAGE__->meta->add_method( + $test_name => sub { + my $res = $method->execute( $api_to_test->new ); + if (ref $test eq 'HASH') { + my $action = $test->{test}; + my $result = $test->{expected}; + # XXX sucky sucky sucky + if ( $action eq 'is_deeply' ) { + is_deeply( $res, $result ); + } + }else{ + if ($test eq 'ok') { + ok $res; + } + } + } + ); + $class->_add_api_test_method($test_name); + } + } +} + +sub run { + my $caller = shift; + + my $class = Moose::Meta::Class->initialize($caller); + my @test_methods = $class->local_api_test_methods(); + foreach my $m (@test_methods) { + my $method = __PACKAGE__->meta->find_method_by_name($m); + $method->execute(); + } } 1; diff --git a/t/00_compile.t b/t/00_compile.t index 86438a0..10b03a4 100644 --- a/t/00_compile.t +++ b/t/00_compile.t @@ -1,4 +1,4 @@ use strict; -use Test::More tests => 1; +use Test::More tests => 2; -BEGIN { use_ok 'MooseX::Net::API' } +BEGIN { use_ok 'MooseX::Net::API'; use_ok 'MooseX::Net::API::Test' } diff --git a/t/01_basic.t b/t/01_basic.t index 9c108d2..471f3dd 100644 --- a/t/01_basic.t +++ b/t/01_basic.t @@ -29,4 +29,7 @@ throws_ok { } qr/foo is not declared as a param/, "... check declared params"; +ok my @methods = $obj->meta->local_api_methods(), '... get api methods'; +is scalar @methods, 3, '... got 3 methods in our API'; + done_testing; diff --git a/t/10_mx_net_api_test.t b/t/10_mx_net_api_test.t new file mode 100644 index 0000000..96bf2be --- /dev/null +++ b/t/10_mx_net_api_test.t @@ -0,0 +1,43 @@ +use strict; +use warnings; +use Test::More; + +BEGIN { + plan skip_all => 'requires Catalyst::Action::REST' + unless eval { require Catalyst::Action::REST }; +} + +{ + + package catalysttestapi; + use Moose; + use MooseX::Net::API::Test; + + test_api_declare 'TestAPI' => ( + catalyst => 1, + catalyst_app_name => 'TestApp' + ); + + test_api_method foo => ( + tests => { + simple => [ + { + # pouvoir surcharger + test => 'is_deeply', + expected => { status => 1 } + }, + 'ok', + ] + } + ); +} + +#content_like => [ { expected => qr/status: 1/ }, ], +#action_ok => [], +#action_redirect => [], +#action_notfound => [], +#contenttype_is => [], + +catalysttestapi->run(); + +done_testing; diff --git a/t/lib/TestAPI.pm b/t/lib/TestAPI.pm new file mode 100644 index 0000000..1f58351 --- /dev/null +++ b/t/lib/TestAPI.pm @@ -0,0 +1,19 @@ +package TestAPI; +use Moose; +use MooseX::Net::API; + +net_api_declare fake_api => ( + base_url => 'http://localhost/root', + format => 'json', + format_mode => 'content-type', + require_authentication => 0, +); + +net_api_method foo => ( + description => 'this does foo', + method => 'GET', + path => '/foo/', +); + +1; + diff --git a/t/lib/TestApp.pm b/t/lib/TestApp.pm new file mode 100644 index 0000000..69ec93d --- /dev/null +++ b/t/lib/TestApp.pm @@ -0,0 +1,11 @@ +package TestApp; +use strict; +use warnings; + +use Catalyst; + +__PACKAGE__->config( name => 'TestApp', ); + +__PACKAGE__->setup; + +1; diff --git a/t/lib/TestApp/Controller/Root.pm b/t/lib/TestApp/Controller/Root.pm new file mode 100644 index 0000000..7ea3f25 --- /dev/null +++ b/t/lib/TestApp/Controller/Root.pm @@ -0,0 +1,16 @@ +package TestApp::Controller::Root; + +use strict; +use warnings; +use base qw/Catalyst::Controller::REST/; + +sub foo : Local : ActionClass('REST') { + my ( $self, $c ) = @_; +} + +sub foo_GET { + my ( $self, $c ) = @_; + $self->status_ok( $c, entity => { status => 1 } ); +} + +1; |