summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--eg/api.pl2
-rw-r--r--lib/Net/HTTP/Spore/Meta.pm1
-rw-r--r--lib/Net/HTTP/Spore/Meta/Class.pm2
-rw-r--r--lib/Net/HTTP/Spore/Meta/Method.pm34
-rw-r--r--lib/Net/HTTP/Spore/Meta/Method/Spore.pm6
-rw-r--r--lib/Net/HTTP/Spore/Middleware/Auth/OAuth.pm7
-rw-r--r--lib/Net/HTTP/Spore/Middleware/FileUpload.pm12
-rw-r--r--lib/Net/HTTP/Spore/Middleware/ParanoidAgent.pm13
-rw-r--r--lib/Net/HTTP/Spore/Middleware/Redirection.pm44
-rw-r--r--lib/Net/HTTP/Spore/Request.pm270
-rw-r--r--lib/Net/HTTP/Spore/Response.pm2
-rw-r--r--lib/Net/HTTP/Spore/Role/Debug.pm14
-rw-r--r--lib/Net/HTTP/Spore/Role/Request.pm33
-rw-r--r--lib/Net/HTTP/Spore/Role/UserAgent.pm1
-rw-r--r--t/specs/api.json17
-rw-r--r--t/spore-middleware/auth-oauth.t40
-rw-r--r--t/spore-middleware/redirection.t27
-rw-r--r--t/spore-request/form_data.t27
-rw-r--r--t/spore-request/headers.t37
19 files changed, 524 insertions, 65 deletions
diff --git a/eg/api.pl b/eg/api.pl
index 9fd3bee..60559ff 100644
--- a/eg/api.pl
+++ b/eg/api.pl
@@ -8,6 +8,6 @@ my $api = Net::HTTP::Spore->new_from_spec(shift);
 
 $api->enable('Net::HTTP::Spore::Middleware::Format::JSON');
 
-my ( $content, $result ) = $api->user_information( format => 'json', username => 'franckcuny' );
+my ( $content, $result ) = $api->get_info( format => 'json', username => 'franckcuny' );
 
 say "name => ". $content->{body}->{user}->{name};
diff --git a/lib/Net/HTTP/Spore/Meta.pm b/lib/Net/HTTP/Spore/Meta.pm
index ec773b6..14b4266 100644
--- a/lib/Net/HTTP/Spore/Meta.pm
+++ b/lib/Net/HTTP/Spore/Meta.pm
@@ -36,6 +36,7 @@ sub init_meta {
         for   => $for,
         roles => [
             qw/
+              Net::HTTP::Spore::Role::Debug
               Net::HTTP::Spore::Role::Description
               Net::HTTP::Spore::Role::UserAgent
               Net::HTTP::Spore::Role::Request
diff --git a/lib/Net/HTTP/Spore/Meta/Class.pm b/lib/Net/HTTP/Spore/Meta/Class.pm
index 4ddd5c6..7571305 100644
--- a/lib/Net/HTTP/Spore/Meta/Class.pm
+++ b/lib/Net/HTTP/Spore/Meta/Class.pm
@@ -4,7 +4,7 @@ package Net::HTTP::Spore::Meta::Class;
 
 use Moose::Role;
 
-with qw/Net::HTTP::Spore::Meta::Method::Spore/;
+with qw/Net::HTTP::Spore::Meta::Method::Spore Net::HTTP::Spore::Role::Debug/;
 
 1;
 
diff --git a/lib/Net/HTTP/Spore/Meta/Method.pm b/lib/Net/HTTP/Spore/Meta/Method.pm
index 4353b55..db990ff 100644
--- a/lib/Net/HTTP/Spore/Meta/Method.pm
+++ b/lib/Net/HTTP/Spore/Meta/Method.pm
@@ -76,6 +76,11 @@ has formats => (
     isa       => ArrayRef [Str],
     predicate => 'has_formats',
 );
+has headers => (
+    is        => 'ro',
+    isa       => HashRef [Str],
+    predicate => 'has_headers',
+);
 has expected_status => (
     traits     => ['Array'],
     is         => 'ro',
@@ -195,19 +200,24 @@ sub wrap {
                 ? ''
                 : $base_url->path
             ),
-            PATH_INFO              => $method->path,
-            REQUEST_URI            => '',
-            QUERY_STRING           => '',
-            HTTP_USER_AGENT        => $self->api_useragent->agent,
-            'spore.expected_status'       => [ $method->expected_status ],
-            'spore.authentication' => $authentication,
-            'spore.params'         => $params,
-            'spore.payload'        => $payload,
-            'spore.errors'         => *STDERR,
-            'spore.url_scheme'     => $base_url->scheme,
+            PATH_INFO               => $method->path,
+            REQUEST_URI             => '',
+            QUERY_STRING            => '',
+            HTTP_USER_AGENT         => $self->api_useragent->agent,
+            'spore.expected_status' => [ $method->expected_status ],
+            'spore.authentication'  => $authentication,
+            'spore.params'          => $params,
+            'spore.payload'         => $payload,
+            'spore.errors'          => *STDERR,
+            'spore.url_scheme'      => $base_url->scheme,
             'spore.formats'         => $formats,
         };
 
+        $env->{'spore.form_data'} = $method->form_data
+          if $method->has_form_data;
+
+        $env->{'spore.headers'} = $method->headers if $method->has_headers;
+
         my $response = $self->http_request($env);
         my $code = $response->status;
 
@@ -218,6 +228,10 @@ sub wrap {
     };
     $args{body} = $code;
 
+    if ($args{'form-data'}){
+        $args{'form_data'} = delete $args{'form-data'};
+    }
+
     $class->SUPER::wrap(%args);
 }
 
diff --git a/lib/Net/HTTP/Spore/Meta/Method/Spore.pm b/lib/Net/HTTP/Spore/Meta/Method/Spore.pm
index 1cea574..b61e2de 100644
--- a/lib/Net/HTTP/Spore/Meta/Method/Spore.pm
+++ b/lib/Net/HTTP/Spore/Meta/Method/Spore.pm
@@ -54,6 +54,12 @@ sub add_spore_method {
 
     my $code = delete $options{code};
 
+#    $meta->_trace_msg( '-> attach ' 
+#          . $name . ' ('
+#          . $options{method} . ' => '
+#          . $options{path}
+#          . ')' );
+
     $meta->add_method(
         $name,
         Net::HTTP::Spore::Meta::Method->wrap(
diff --git a/lib/Net/HTTP/Spore/Middleware/Auth/OAuth.pm b/lib/Net/HTTP/Spore/Middleware/Auth/OAuth.pm
index 3fb5bf0..524205d 100644
--- a/lib/Net/HTTP/Spore/Middleware/Auth/OAuth.pm
+++ b/lib/Net/HTTP/Spore/Middleware/Auth/OAuth.pm
@@ -44,6 +44,9 @@ sub call {
 =head1 SYNOPSIS
 
     my $client = Net::HTTP::Spore->new_from_spec('twitter.json');
+
+    $client->enable('Format::JSON');
+
     $client->enable(
         'Auth::OAuth',
         consumer_key    => 'xxx',
@@ -52,6 +55,10 @@ sub call {
         token_secret    => '456'
     );
 
+    print $client->friends_timeline(
+      format => 'json'
+    )->body->[0]->{text};
+
 =head1 DESCRIPTION
 
 Net::HTTP::Spore::Middleware::Auth::OAuth is a middleware to handle OAuth mechanism. This middleware should be loaded as the last middleware, because it requires all parameters to be setted to calculate the signature.
diff --git a/lib/Net/HTTP/Spore/Middleware/FileUpload.pm b/lib/Net/HTTP/Spore/Middleware/FileUpload.pm
new file mode 100644
index 0000000..6677e54
--- /dev/null
+++ b/lib/Net/HTTP/Spore/Middleware/FileUpload.pm
@@ -0,0 +1,12 @@
+package Net::HTTP::Spore::Middleware::FileUpload;
+
+use Moose;
+extends 'Net::HTTP::Spore::Middleware';
+
+use LWP::MediaTypes qw/read_media_types/;
+
+sub call {
+    my ($self, $request) = @_;
+}
+
+1;
diff --git a/lib/Net/HTTP/Spore/Middleware/ParanoidAgent.pm b/lib/Net/HTTP/Spore/Middleware/ParanoidAgent.pm
new file mode 100644
index 0000000..c1cce0c
--- /dev/null
+++ b/lib/Net/HTTP/Spore/Middleware/ParanoidAgent.pm
@@ -0,0 +1,13 @@
+package Net::HTTP::Spore::Middleware::ParanoidAgent;
+
+use Moose;
+extends 'Net::HTTP::Spore::Middleware';
+
+has black_list => ();
+has white_list => ();
+
+sub call {
+    my ($self, $request) = @_;
+}
+
+1;
diff --git a/lib/Net/HTTP/Spore/Middleware/Redirection.pm b/lib/Net/HTTP/Spore/Middleware/Redirection.pm
new file mode 100644
index 0000000..07046d8
--- /dev/null
+++ b/lib/Net/HTTP/Spore/Middleware/Redirection.pm
@@ -0,0 +1,44 @@
+package Net::HTTP::Spore::Middleware::Redirection;
+
+use Moose;
+
+extends 'Net::HTTP::Spore::Middleware';
+
+with 'Net::HTTP::Spore::Role::Request', 'Net::HTTP::Spore::Role::UserAgent';
+
+has max_redirect => ( is => 'rw', isa => 'Int', lazy => 1, default => 5 );
+
+sub call {
+    my ( $self, $req ) = @_;
+
+    my $nredirect = 0;
+
+    return $self->response_cb(
+        sub {
+            my $res      = shift;
+            while ( $nredirect < $self->max_redirect ) {
+                my $location = $res->header('location');
+                my $status   = $res->status;
+                if (
+                    $location
+                    and (  $status == 301
+                        or $status == 302
+                        or $status == 303
+                        or $status == 307 )
+                  )
+                {
+                    my $uri = URI->new($location);
+                    $req->env->{HTTP_HOST} = $uri->host;
+                    $req->env->{PATH_INFO} = $uri->path;
+                    $res = $self->_request($req);
+                    $nredirect++;
+                }else{
+                    last;
+                }
+            }
+            return $res;
+        }
+    );
+}
+
+1;
diff --git a/lib/Net/HTTP/Spore/Request.pm b/lib/Net/HTTP/Spore/Request.pm
index 267ec0b..8604f35 100644
--- a/lib/Net/HTTP/Spore/Request.pm
+++ b/lib/Net/HTTP/Spore/Request.pm
@@ -2,44 +2,179 @@ package Net::HTTP::Spore::Request;
 
 # ABSTRACT: Net::HTTP::Spore::Request - Portable HTTP request object from SPORE env hash
 
-use strict;
-use warnings;
-
+use Moose;
 use Carp ();
 use URI;
 use HTTP::Headers;
 use HTTP::Request;
 use URI::Escape;
-use Hash::MultiValue;
-
+use MIME::Base64;
 use Net::HTTP::Spore::Response;
 
-sub new {
-    my ( $class, $env ) = @_;
+has env => (
+    is       => 'rw',
+    isa      => 'HashRef',
+    required => 1,
+    traits   => ['Hash'],
+    handles  => {
+        set_to_env   => 'set',
+        get_from_env => 'get',
+    }
+);
+
+has path => (
+    is      => 'rw',
+    isa     => 'Str',
+    lazy    => 1,
+    default => sub { $_[0]->path_info }
+);
+
+has headers => (
+    is      => 'rw',
+    isa     => 'HTTP::Headers',
+    lazy    => 1,
+    handles => {
+        header => 'header',
+    },
+    default => sub {
+        my $self = shift;
+        my $env  = $self->env;
+        my $h    = HTTP::Headers->new(
+            map {
+                ( my $field = $_ ) =~ s/^HTTPS?_//;
+                ( $field => $env->{$_} );
+              } grep { /^(?:HTTP|CONTENT)/i } keys %$env
+        );
+        return $h;
+    },
+);
+
+sub BUILDARGS {
+    my $class = shift;
+
+    if (@_ == 1 && !exists $_[0]->{env}) {
+        return {env => $_[0]};
+    }
+    return @_;
+}
+
+sub method {
+    my ( $self, $value ) = @_;
+    if ($value) {
+        $self->set_to_env( 'REQUEST_METHOD', $value );
+    }
+    else {
+        return $self->get_from_env('REQUEST_METHOD');
+    }
+}
+
+sub port {
+    my ( $self, $value ) = @_;
+    if ($value) {
+        $self->set_to_env( 'SERVER_PORT', $value );
+    }
+    else {
+        return $self->get_from_env('SERVER_PORT');
+    }
+}
+
+sub script_name {
+    my ( $self, $value ) = @_;
+    if ($value) {
+        $self->set_to_env( 'SCRIPT_NAME', $value );
+    }
+    else {
+        return $self->get_from_env('SCRIPT_NAME');
+    }
+}
+
+sub request_uri {
+    my ($self, $value) = @_;
+    if ($value) {
+        $self->set_to_env( 'REQUEST_URI', $value );
+    }
+    else {
+        return $self->get_from_env('REQUEST_URI');
+    }    
+}
+
+sub scheme {
+    my ($self, $value) = @_;
+    if ($value) {
+        $self->set_to_env( 'spore.scheme', $value );
+    }
+    else {
+        return $self->get_from_env('spore.scheme');
+    }    
+}
+
+sub logger {
+    my ($self, $value) = @_;
+    if ($value) {
+        $self->set_to_env( 'sporex.logger', $value );
+    }
+    else {
+        return $self->get_from_env('sporex.logger');
+    }
+}
+
+sub body {
+    my ($self, $value) = @_;
+    if ($value) {
+        $self->set_to_env( 'spore.payload', $value );
+    }
+    else {
+        return $self->get_from_env('spore.payload');
+    }
+}
+
+sub input   { (shift)->body(@_) }
+sub content { (shift)->body(@_) }
+sub secure  { $_[0]->scheme eq 'https' }
+
+# stolen from HTTP::Request::Common
+sub _boundary {
+    my ( $self, $size ) = @_;
+
+    return "xYzZy" unless $size;
 
-    Carp::croak('$env is required') unless defined $env && ref($env) eq 'HASH';
-    bless { env => $env }, $class;
+    my $b =
+      MIME::Base64::encode( join( "", map chr( rand(256) ), 1 .. $size * 3 ),
+        "" );
+    $b =~ s/[\W]/X/g;
+    return $b;
 }
 
-sub env         { $_[0]->{env} }
-sub method      { $_[0]->{env}->{REQUEST_METHOD} }
-sub port        { $_[0]->{env}->{SERVER_PORT} }
-sub script_name { $_[0]->{env}->{SCRIPT_NAME} }
-sub path        { $_[0]->path_info }
-sub request_uri { $_[0]->{env}->{REQUEST_URI} }
-sub scheme      { $_[0]->{env}->{'spore.scheme'} }
-sub logger      { $_[0]->{env}->{'sporex.logger'} }
-sub secure      { $_[0]->scheme eq 'https' }
-sub content     { $_[0]->{env}->{'spore.payload'} }
-sub body        { $_[0]->{env}->{'spore.payload'} }
-sub input       { $_[0]->{env}->{'spore.payload'} }
+sub _form_data {
+    my ( $self, $data ) = @_;
+
+    my $form_data;
+    foreach my $k ( keys %$data ) {
+        push @$form_data,
+            'Content-Disposition: form-data; name="' 
+          . $k
+          . '"'."\r\n\r\n"
+          . $data->{$k};
+    }
+
+    my $b = $self->_boundary(10);
+    my $t = [];
+    foreach (@$form_data) {
+        push @$t, '--', $b, "\r\n", $_, "\r\n";
+    }
+    push @$t, '--', $b, , '--', "\r\n";
+    my $content = join("", @$t);
+    return ($content, $b);
+}
 
+# TODO
 sub path_info {
     my $self = shift;
     my ($path) = $self->_path;
     $path;
 }
 
+# TODO
 sub _path {
     my $self = shift;
 
@@ -65,28 +200,14 @@ sub _path {
     return ( $path, $query_string );
 }
 
+# TODO
 sub query_string {
     my $self = shift;
     my ( undef, $query_string ) = $self->_path;
     $query_string;
 }
 
-sub headers {
-    my $self = shift;
-    if ( !defined $self->{headers} ) {
-        my $env = $self->env;
-        $self->{headers} = HTTP::Headers->new(
-            map {
-                ( my $field = $_ ) =~ s/^HTTPS?_//;
-                ( $field => $env->{$_} );
-              } grep { /^(?:HTTP|CONTENT)/i } keys %$env
-        );
-    }
-    $self->{headers};
-}
-
-sub header {shift->headers->header(@_)}
-
+# TODO
 sub uri {
     my $self = shift;
 
@@ -113,16 +234,18 @@ sub uri {
     return URI->new( $base . $path )->canonical;
 }
 
-# retourner les query parameters ? vu qu'on a pas encore peuple l'url, on gere comment ?
+# TODO retourner les query parameters ? vu qu'on a pas encore peuple l'url, on gere comment ?
 sub query_parameters {
     my $self = shift;
 }
 
+# TODO
 sub base {
     my $self = shift;
     URI->new( $self->_uri_base )->canonical;
 }
 
+# TODO
 sub _uri_base {
     my $self = shift;
     my $env  = $self->env;
@@ -147,18 +270,75 @@ sub new_response {
 sub finalize {
     my $self = shift;
 
-    my ($path_info, $query_string) = $self->_path;
+    my $path_info = $self->env->{PATH_INFO};
+
+    my $form_data = $self->env->{'spore.form_data'};
+    my $headers   = $self->env->{'spore.headers'};
+    my $params    = $self->env->{'spore.params'} || [];
+
+    my $query = [];
+    my $form  = {};
+
+    for ( my $i = 0 ; $i < scalar @$params ; $i++ ) {
+        my $k = $params->[$i];
+        my $v = $params->[++$i];
+        my $modified = 0;
+
+        if ($path_info =~ s/\:$k/$v/) {
+            $modified++;
+        }
+        
+        foreach my $f_k (keys %$form_data) {
+            my $f_v = $form_data->{$f_k};
+            if ($f_v =~ s/^\:$k/$v/) {
+                $form->{$f_k} = $f_v;
+                $modified++;
+            }
+        }
+
+        foreach my $h_k (keys %$headers) {
+            my $h_v = $headers->{$h_k};
+            if ($h_v =~ s/^\:$k/$v/) {
+                $self->header($h_k => $h_v);
+                $modified++;
+            }
+        }
 
-    $self->env->{PATH_INFO} = $path_info;
-    $self->env->{QUERY_STRING} = $query_string || '';
+        if ($modified == 0) {
+            push @$query, $k.'='.$v;
+        }
+    }
+
+    my $query_string;
+    if (scalar @$query) {
+        $query_string = join('&', @$query);
+    }
+
+    $self->env->{PATH_INFO}    = $path_info;
+    $self->env->{QUERY_STRING} = $query_string;
 
     my $uri = $self->uri($path_info, $query_string || '');
+    
+    my $request = HTTP::Request->new(
+        $self->method => $uri, $self->headers
+    );
+
+    if ( keys %$form_data ) {
+        $self->env->{'spore.form_data'} = $form;
+        my ( $content, $b ) = $self->_form_data($form);
+        $request->content($content);
+        $request->header('Content-Length' => length($content));
+        $request->header(
+            'Content-Type' => 'multipart/form-data; boundary=' . $b );
+    }
 
-    my $request =
-      HTTP::Request->new( $self->method => $uri, $self->headers );
+    if ( my $payload = $self->content ) {
+        $request->content($payload);
+        $request->header(
+            'Content-Type' => 'application/x-www-form-urlencoded' );
+    }
 
-    $request->content($self->content) if ($self->content);
-    $request;
+    return $request;
 }
 
 1;
diff --git a/lib/Net/HTTP/Spore/Response.pm b/lib/Net/HTTP/Spore/Response.pm
index 03ab2e7..b667332 100644
--- a/lib/Net/HTTP/Spore/Response.pm
+++ b/lib/Net/HTTP/Spore/Response.pm
@@ -5,7 +5,7 @@ package Net::HTTP::Spore::Response;
 use strict;
 use warnings;
 
-use overload '@{}' => \&finalize;
+use overload '@{}' => \&finalize, fallback => 1;
 
 use HTTP::Headers;
 
diff --git a/lib/Net/HTTP/Spore/Role/Debug.pm b/lib/Net/HTTP/Spore/Role/Debug.pm
new file mode 100644
index 0000000..772373a
--- /dev/null
+++ b/lib/Net/HTTP/Spore/Role/Debug.pm
@@ -0,0 +1,14 @@
+package Net::HTTP::Spore::Role::Debug;
+
+use Moose::Role;
+
+has trace => (
+    is      => 'rw',
+    isa     => 'Bool',
+    lazy    => 1,
+    default => sub { $ENV{SPORE_TRACE} ? 1 : 0; }
+);
+
+sub _trace_msg { print STDOUT $_[1]."\n" if $_[0]->trace; }
+
+1;
diff --git a/lib/Net/HTTP/Spore/Role/Request.pm b/lib/Net/HTTP/Spore/Role/Request.pm
index ddd52e6..a0d61b1 100644
--- a/lib/Net/HTTP/Spore/Role/Request.pm
+++ b/lib/Net/HTTP/Spore/Role/Request.pm
@@ -33,22 +33,41 @@ sub http_request {
         }
     }
 
-    if (defined $response) {
-        map { $_->($response) } reverse @middlewares;
-        return $response;
+    return
+      $self->_execute_middlewares_on_response( $response, @middlewares )
+      if defined $response;
+
+    $response = $self->_request($request);
+
+    return $self->_execute_middlewares_on_response( $response, @middlewares );
+}
+
+sub _execute_middlewares_on_response {
+    my ($self, $response, @middlewares) = @_;
+
+    foreach my $mw ( reverse @middlewares ) {
+        my $res = $mw->($response);
+        $response = $res
+          if ( defined $res
+            && Scalar::Util::blessed($res)
+            && $res->isa('Net::HTTP::Spore::Response') );
     }
 
+    $response;
+}
+
+sub _request {
+    my ($self, $request) = @_;
+
     my $result = $self->request($request->finalize);
 
-    $response = $request->new_response(
+    my $response = $request->new_response(
         $result->code,
         $result->headers,
         $result->content,
     );
 
-    map { $_->($response) } reverse @middlewares;
-
-    $response;
+    return $response;
 }
 
 1;
diff --git a/lib/Net/HTTP/Spore/Role/UserAgent.pm b/lib/Net/HTTP/Spore/Role/UserAgent.pm
index 6bfaa5a..9b99ab8 100644
--- a/lib/Net/HTTP/Spore/Role/UserAgent.pm
+++ b/lib/Net/HTTP/Spore/Role/UserAgent.pm
@@ -15,6 +15,7 @@ has api_useragent => (
         my $ua = LWP::UserAgent->new();
         $ua->agent( "Net::HTTP::Spore v" . $Net::HTTP::Spore::VERSION . " (Perl)" );
         $ua->env_proxy;
+        $ua->max_redirect(0);
         return $ua;
     }
 );
diff --git a/t/specs/api.json b/t/specs/api.json
index 42e3c25..a89d147 100644
--- a/t/specs/api.json
+++ b/t/specs/api.json
@@ -19,6 +19,23 @@
             ],
             "path" : "/show",
             "method" : "GET"
+        },
+      "add_email" : {
+        "path" : "/email",
+        "method" : "POST",
+        "form-data" : {
+          "email" : ":email"
+        },
+        "required_params" : [
+          "email"
+        ]
+      },
+      "attach_file" : {
+        "path":"/file",
+        "method":"POST",
+        "headers": {
+          "content-type":":content_type"
         }
+      }
     }
 }
diff --git a/t/spore-middleware/auth-oauth.t b/t/spore-middleware/auth-oauth.t
new file mode 100644
index 0000000..a704e24
--- /dev/null
+++ b/t/spore-middleware/auth-oauth.t
@@ -0,0 +1,40 @@
+use strict;
+use warnings;
+
+use Test::More;
+
+plan tests => 3;
+
+use NET::HTTP::Spore;
+use JSON;
+
+my $api = {
+    base_url => "http://term.ie/oauth/example",
+    name     => "term.ie",
+    methods  => {
+        echo => {
+            path            => "/echo_api.php",
+            method          => "GET",
+            expected_status => [200],
+            authentication => 1,
+        }
+    },
+};
+
+SKIP: {
+    skip "require RUN_HTTP_TEST", 3 unless $ENV{RUN_HTTP_TEST};
+
+    my $client = Net::HTTP::Spore->new_from_string( JSON::encode_json($api) );
+
+    $client->enable(
+        'Auth::OAuth',
+        consumer_key    => 'key',
+        consumer_secret => 'secret',
+        token           => 'accesskey',
+        token_secret    => 'accesssecret',
+    );
+
+    ok my $r = $client->echo(method => 'foo', bar => 'baz');
+    is $r->status, 200;
+    like $r->body, qr/bar=baz&method=foo/;
+}
diff --git a/t/spore-middleware/redirection.t b/t/spore-middleware/redirection.t
new file mode 100644
index 0000000..fe239f9
--- /dev/null
+++ b/t/spore-middleware/redirection.t
@@ -0,0 +1,27 @@
+use strict;
+use warnings;
+
+use Test::More;
+
+plan tests => 2;
+
+use Net::HTTP::Spore;
+
+SKIP: {
+    skip "require RUN_HTTP_TEST", 2 unless $ENV{RUN_HTTP_TEST};
+    my $client = Net::HTTP::Spore->new_from_string(
+        '{
+    "base_url" : "http://fperrad.googlepages.com",
+      "name"   : "googlepages",
+      "methods"
+      : { "get_home"
+        : { "path" : "/home", "method" : "GET", "expected_status" : [200] } }
+    }');
+
+    $client->enable('Redirection');
+
+    my $r = $client->get_home();
+    is $r->status, 200;
+    is $r->request->uri,
+      'http://sites.google.com/site/fperrad/home';
+}
diff --git a/t/spore-request/form_data.t b/t/spore-request/form_data.t
new file mode 100644
index 0000000..6400465
--- /dev/null
+++ b/t/spore-request/form_data.t
@@ -0,0 +1,27 @@
+use strict;
+use warnings;
+use Test::More;
+
+use Net::HTTP::Spore;
+
+my $mock_server = {
+    '/email' => sub {
+        my $req   = shift;
+        my $final = $req->finalize;
+        like $final->header('Content-Type'),
+          qr/multipart\/form-data; boundary=/;
+        $req->new_response( 200, [ 'Content-Type' => 'text/html' ], 'ok' );
+    },
+};
+
+ok my $client =
+  Net::HTTP::Spore->new_from_spec( 't/specs/api.json',
+    base_url => 'http://localhost' );
+
+$client->enable( 'Mock', tests => $mock_server );
+
+my $res = $client->add_email( email => 'foo@bar.com' );
+is $res->[0],   200;
+like $res->[2], qr/ok/;
+
+done_testing;
diff --git a/t/spore-request/headers.t b/t/spore-request/headers.t
new file mode 100644
index 0000000..1d0e3f5
--- /dev/null
+++ b/t/spore-request/headers.t
@@ -0,0 +1,37 @@
+use strict;
+use warnings;
+
+use Test::More tests => 7;
+use Net::HTTP::Spore::Request;
+use Net::HTTP::Spore;
+
+my $env = { 'HTTP_CONTENT_TYPE' => 'text/html', };
+
+ok my $request = Net::HTTP::Spore::Request->new($env);
+
+isa_ok $request->headers, 'HTTP::Headers';
+is $request->header('Content-Type'), 'text/html';
+ok $request->header( 'Content-Type' => 'application/json' );
+is $request->header('Content-Type'), 'application/json';
+
+my $mock_server = {
+    '/file' => sub {
+        my $req   = shift;
+        my $final = $req->finalize;
+        if ( $final->header('Content-Type') eq 'image/png' ) {
+            return $req->new_response( 200, [ 'Content-Type' => 'text/html' ],
+                'ok' );
+        }
+        $req->new_response( 500, [ 'Content-Type' => 'text/html' ], 'nok' );
+    },
+};
+
+ok my $client =
+  Net::HTTP::Spore->new_from_spec( 't/specs/api.json',
+    base_url => 'http://localhost', );
+
+$client->enable( 'Mock', tests => $mock_server );
+
+my $res = $client->attach_file( file => 'foo', 'content_type' => 'image/png' );
+is $res->[0], 200;
+