The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.
#!/usr/bin/env perl

# Test the RPC::XML::Client class

## no critic(Bangs::ProhibitNumberedNames)
## no critic(RequireBriefOpen);
## no critic(RequireInterpolationOfMetachars)

use strict;
use warnings;
use subs qw(start_server stop_server find_port);

use Carp qw(croak);
use Module::Load;
use Test::More;

use LWP;
use Digest::MD5 'md5_hex';
use File::Spec;

use RPC::XML::Server;
use RPC::XML::Client;

my ($dir, $vol, $srv, $child, $port, $cli, $res, $flag, $srv_url);

($vol, $dir, undef) = File::Spec->splitpath(File::Spec->rel2abs($0));
$dir = File::Spec->catpath($vol, $dir, q{});
require File::Spec->catfile($dir, 'util.pl');

plan tests => 33;

# The organization of the test suites is such that we assume anything that
# runs before the current suite is 100%. Thus, no consistency checks on
# any other classes are done, only on the data and return values of this
# class under consideration, RPC::XML::Client. In this particular case, this
# means that we can safely use RPC::XML::Server in creating a suitable test
# environment.

# Start with some very basic things, before actually firing up a live server.
$cli = RPC::XML::Client->new();
ok(! ref $cli, 'RPC::XML::Client::new without endpoint fails');
like($cli, qr/Missing location argument/, 'Correct error message set');

if (($port = find_port) == -1)
{
    croak 'No usable port found between 9000 and 11000, skipping';
}
$cli = RPC::XML::Client->new("http://localhost:$port");
$cli->timeout(5); # to prevent long waiting for non-existing server
isa_ok($cli, 'RPC::XML::Client', '$cli');

# With no server yet at that port, test the failure modes
ok((! $cli->simple_request('system.identity')) && $RPC::XML::ERROR,
   'Calling a server method without a server sets $RPC::XML::ERROR');
ok(! ref($cli->send_request('system.identity')),
   'send_request returns a non-ref value when there is no server');
$res = $cli->send_request();
ok(! ref $res, 'Call to send_request without a method name fails');
like($res, qr/No request object/, 'Correct error message set');
$res = $cli->send_request('bad^method');
ok(! ref $res, 'Call to send_request with a bad method name fails');
like($res, qr/Error creating RPC::XML::request object/,
     'Correct error message set');

# Test the error-handling callback
$cli->error_handler(sub { $res++ });
$res = 0;
$cli->simple_request('system.identity');
ok($res, 'error_handler callback system');

# Test clearing it
$cli->error_handler(undef);
$res = 0;
$cli->simple_request('system.identity');
ok(! $res, 'Clearing the error_handler callback system');

# Test setting and clearing both with combined_handler
$cli->combined_handler(sub { 1 });
ok($cli->error_handler() && ($cli->error_handler() eq $cli->fault_handler()),
   'combined_handler set both error_handler and fault_handler');

$cli->combined_handler(undef);
ok(! ($cli->error_handler() or $cli->fault_handler()),
   'combined_handler clears both error_handler and fault_handler');

# Check the getting/setting of the timeout() value on the underlying UA
is($cli->timeout(), $cli->useragent->timeout(),
   'Client timeout() method, fetching');
$cli->timeout(60);
is($cli->useragent->timeout(), 60, 'Client timeout() method, setting');

# Cool so far. Create and spawn the server.
$srv = RPC::XML::Server->new(host => 'localhost', port => $port);
if (! ref $srv)
{
    croak "Failed to create server: $srv, stopped";
}
# Due to issues with Strawberry Perl on Windows, have to explicitly set the
# endpoint to what the server object thinks it is. Also, because of threading
# issues with Strawberry, we need to save the URL value for later use while
# the server is running.
$srv_url = $srv->url;
$cli->uri($srv_url);

# Start the server...
$child = start_server $srv;

# NOW, this should work. Also, set $RPC::XML::ERROR to see if it clears
$RPC::XML::ERROR = 'foo';
is($cli->simple_request('system.identity'), $srv->product_tokens,
   'simple_request/system.identity returns correct value');
ok(! $RPC::XML::ERROR,
   'simple_request/system.identity left $RPC::XML::ERROR empty');

# Using send_request should yield an RPC::XML::string object with that value
$res = $cli->send_request('system.identity');
isa_ok($res, 'RPC::XML::string', 'system.identity response');
SKIP: {
    if (! ref $res)
    {
        skip 'Client response not a RPC::XML data object', 1;
    }

    is($res->value, $srv->product_tokens,
       'system.identity response is correct');
}

if (! ref $res)
{
    # Assume that if an error occurred, the server might be in a confused
    # state. Kill and restart it.
    stop_server $child;
    $child = start_server $srv;
}

# See what comes back from bad (but successful) calls
$res = $cli->simple_request('system.bad');
isa_ok($res, 'HASH', 'simple_request/system.bad response');
SKIP: {
    if (! ref $res)
    {
        skip 'Client response was not a RPC::XML data object', 2;
    }

    is(join(q{,} => sort keys %{$res}), 'faultCode,faultString',
       'simple_request/system.bad hashref has correct keys');
    like($res->{faultString}, qr/Unknown method/,
         'simple_request/system.bad set correct faultString');
}

if (! ref $res)
{
    # Assume that if an error occurred, the server might be in a confused
    # state. Kill and restart it.
    stop_server $child;
    $child = start_server $srv;
}

# As opposed to a fault object:
$res = $cli->send_request('system.bad');
isa_ok($res, 'RPC::XML::fault', 'send_request/system.bad response');
SKIP: {
    if (! ref $res)
    {
        skip 'Client response not a RPC::XML data object', 1;
    }

    like($res->string, qr/Unknown method/,
         'send_request/system.bad set correct string() property');
}

if (! ref $res)
{
    # Assume that if an error occurred, the server might be in a confused
    # state. Kill and restart it.
    stop_server $child;
    $child = start_server $srv;
}

# Give the fault handler a whirl -- note the return value is the fault object
$cli->fault_handler(
    sub {
        if ((ref($_[0]) eq 'RPC::XML::fault') &&
                ($_[0]->string =~ /Unknown method/))
        {
            $flag++;
        }

        $_[0]
    }
);
$flag = 0;
$res = $cli->send_request('system.bad');
# Did the callback run correctly?
ok($flag, 'fault_handler correctly set $flag');
# Is the value returned correct?
isa_ok($res, 'RPC::XML::fault', 'fault_handler returned value');
SKIP: {
    if (! ref $res)
    {
        skip 'Client response not a RPC::XML data object', 1;
    }

    like($res->string, qr/Unknown method/,
         'fault_handler object has correct faultString');
}

if (! ref $res)
{
    # Assume that if an error occurred, the server might be in a confused
    # state. Kill and restart it.
    stop_server $child;
    $child = start_server $srv;
}

# Last tests-- is the uri() method working?
is($cli->uri, $srv_url,
   'RPC::XML::Client::uri method return value is correct');

# does calling it as an accesor change it at all?
$cli->uri('http://www.oreilly.com/RPC');
is($cli->uri, 'http://www.oreilly.com/RPC',
   'RPC::XML::Client::uri changes as expected');

# Kill the server long enough to add a new method
stop_server $child;

# Restore the server URL in the client. Due to some threading issues seen in
# Strawberry Perl, must do this while $srv is not running.
$cli->uri($srv->url);

SKIP: {
    if ($LWP::VERSION <= 5.800)
    {
        skip 'Message-to-file spooling broken with LWP < 5.801', 4;
    }

    $srv->add_method(
        {
            name => 'cmpImg',
            signature => [ 'boolean base64 base64' ],
            code => sub {
                my ($self, $img1, $img2) = @_;

                return (md5_hex($img1) eq md5_hex($img2));
            }
        }
    );
    $child = start_server $srv;

  SKIP: {
        my ($fh1, $fh2);

        if (! (open $fh1, '<', File::Spec->catfile($dir, 'svsm_text.gif')))
        {
            skip "Error opening svsm_text.gif: $!", 4;
        }
        if (! (open $fh2, '<', File::Spec->catfile($dir, 'svsm_text.gif')))
        {
            skip "Error opening svsm_text.gif: $!", 4;
        }

        # Setting the size threshhold to the size of the GIF will guarantee a
        # file spool, since we're sending the GIF twice.
        $cli->message_file_thresh(-s $fh1);
        $cli->message_temp_dir($dir);

        $res = $cli->send_request(cmpImg =>
                                  RPC::XML::base64->new($fh1),
                                  RPC::XML::base64->new($fh2));
        isa_ok($res, 'RPC::XML::boolean', 'cmpImg return value');
      SKIP: {
            if (! ref $res)
            {
                skip 'Client response not a RPC::XML data object', 1;
            }

            ok($res->value, 'cmpImg, file spooling, correct return');
        }

        # Force the compression threshhold down, to test that branch
        $cli->compress_requests(1);
        $cli->compress_thresh(-s $fh1);
        $res = $cli->send_request(cmpImg =>
                                  RPC::XML::base64->new($fh1),
                                  RPC::XML::base64->new($fh2));
        isa_ok($res, 'RPC::XML::boolean', 'cmpImg return value (compression)');
      SKIP: {
            if (! ref $res)
            {
                skip 'Client response not a RPC::XML data object', 1;
            }

            ok($res->value,
               'cmpImg, file spooling+compression, correct return');
        }
    }

    # Kill the server before exiting
    stop_server $child, 'final';
}

exit;