#!/usr/bin/env perl
use strict;
use warnings;
use File::Basename 'dirname';
use File::Spec;
use lib join '/', File::Spec->splitdir( dirname(__FILE__) ), 'lib';
use lib join '/', File::Spec->splitdir( dirname(__FILE__) ), '..', 'lib';
#-------------------------------------------------------------------
# Define custom service
package MyService;
use Mojo::Base 'MojoX::JSON::RPC::Service';
sub multiply {
my ( $self, @params ) = @_;
my $m = 1;
$m *= $_ for @params;
return $m;
}
sub echo {
my ( $self, @params ) = @_;
return $params[0];
}
sub echo_key {
my ( $self, $param ) = @_;
return exists $param->{key} ? $param->{key} : '?';
}
sub register {
my ($self) = @_;
return 'register can be used';
}
sub _rpcs {
my ($self) = @_;
return '_rpcs can be used';
}
sub substract {
my ( $self, $res, @params ) = @_;
$res = 0 if !defined $res;
$res -= $_ for @params;
return $res;
}
sub update {
my ( $self, @params ) = @_;
# notification only
return;
}
sub foobar {
my ($self) = @_;
# notification only
return;
}
__PACKAGE__->register_rpc_method_names(
'multiply', 'echo', 'echo_key', 'register',
'_rpcs', 'substract', 'update', 'foobar'
);
#-------------------------------------------------------------------
# Mojolicious app for testing
package MojoxJsonRpc;
use Mojo::Base 'Mojolicious';
use MojoX::JSON::RPC::Service;
# This method will run once at server start
sub startup {
my $self = shift;
$self->secret('Testing!');
# Load our test plugin
my $svc = MojoX::JSON::RPC::Service->new->register(
'sum',
sub {
my @params = @_;
my $sum = 0;
$sum += $_ for @params;
return $sum;
}
)->register(
'remote_address',
sub {
my $tx = shift;
return $tx->remote_address;
},
{ with_mojo_tx => 1 }
)->register(
'substract',
sub {
my $params = shift;
return $params->{minuend} - $params->{subtrahend};
},
)->register(
'subtract',
sub {
my ( $res, @params ) = @_;
$res = 0 if !defined $res;
$res -= $_ for @params;
return $res;
},
)->register(
'notify_hello',
sub {
return;
}
)->register(
'notify_sum',
sub {
return;
}
)->register(
'get_data',
sub {
return [ 'hello', 5 ];
}
)->register(
'test_with_self',
sub {
my $dispatcher = shift;
return ref $dispatcher;
},
{ with_self => 1 }
)->register(
'test_with_mojo_tx_and_self',
sub {
my $tx = shift;
my $dispatcher = shift;
return [ ref $tx, ref $dispatcher ];
},
{ with_mojo_tx => 1, with_self => 1 }
)->register(
'test_with_all',
sub {
my $svc = shift;
my $tx = shift;
my $dispatcher = shift;
return [ ref $svc, ref $tx, ref $dispatcher ];
},
{ with_svc_obj => 1, with_mojo_tx => 1, with_self => 1 }
);
# register multiple services
$self->plugin(
'json_rpc_dispatcher',
services => {
'/jsonrpc' => $svc,
'/jsonrpc2' => MyService->new
}
);
}
#-------------------------------------------------------------------
# Back to tests
package main;
use TestUts;
use Test::More tests => 36;
use Test::Mojo;
use_ok 'MojoX::JSON::RPC::Service';
use_ok 'MojoX::JSON::RPC::Client';
my $t = Test::Mojo->new('MojoxJsonRpc');
my $client = MojoX::JSON::RPC::Client->new( ua => $t->ua );
my $res;
TestUts::test_call(
$client,
'/jsonrpc',
{ id => 1,
method => 'sum',
params => [ 17, 25 ]
},
{ result => 42,
id => 1
},
'sum 1'
);
# Test second service url
TestUts::test_call(
$client,
'/jsonrpc2',
{ id => 2,
method => 'multiply',
params => [ 2, 3 ]
},
{ result => 6,
id => 2
},
'multiply 1'
);
# Can call register which is also defined in MojoX::JSON::RPC::Service
TestUts::test_call(
$client,
'/jsonrpc2',
{ id => 2,
method => 'register',
},
{ result => 'register can be used',
id => 2
},
'register 1'
);
TestUts::test_call(
$client,
'/jsonrpc2',
{ id => 2,
method => 'echo',
params => ['HEEEEEEEEEEEEEEEEELLLLLLLLLLLLLLLOOOOOOOOOOO!']
},
{ result => 'HEEEEEEEEEEEEEEEEELLLLLLLLLLLLLLLOOOOOOOOOOO!',
id => 2
},
'echo 1'
);
# params must be array!
TestUts::test_call(
$client,
'/jsonrpc2',
{ id => 2,
method => 'echo',
params => 'HEEEEEEEEEEEEEEEEELLLLLLLLLLLLLLLOOOOOOOOOOO!'
},
{ error => {
code => -32602,
message => 'Invalid params.',
data =>
'NOT array or hash: HEEEEEEEEEEEEEEEEELLLLLLLLLLLLLLLOOOOOOOOOOO!'
}
},
'echo 2'
);
TestUts::test_call(
$client,
'/jsonrpc2',
{ id => 2,
method => 'echo',
params => [ 'a' x 32768 ]
},
{ result => 'a' x 32768,
id => 2
},
'echo 3'
);
# Can call _rpcs which is an attribute of MojoX::JSON::RPC::Service
TestUts::test_call(
$client,
'/jsonrpc2',
{ id => 2,
method => '_rpcs',
},
{ result => '_rpcs can be used', },
'_rpcs 1'
);
# Test rpc call that get tx added on server side.
TestUts::test_call(
$client,
'/jsonrpc',
{ id => 2,
method => 'remote_address',
},
{ result => '127.0.0.1', },
'remote_address 1'
);
# Test hash param
TestUts::test_call(
$client,
'/jsonrpc2',
{ id => 2,
method => 'echo_key',
params => { key => 'HEEEEEEEEEEEEEEEEELLLLLLLLLLLLLLLOOOOOOOOOOO!' }
},
{ result => 'HEEEEEEEEEEEEEEEEELLLLLLLLLLLLLLLOOOOOOOOOOO!', },
'echo_key 1'
);
# GET test
TestUts::test_call(
$client, '/jsonrpc?method=sum;params=[2,3,5];id=1',
undef, { results => 10 },
'GET sum 1'
);
TestUts::test_call(
$client,
'/jsonrpc?',
undef,
{ error => {
code => -32600,
message => 'Invalid Request.'
}
},
'GET no method'
);
# Test client proxy
my $proxy = $client->prepare(
'/jsonrpc' => 'sum',
'/jsonrpc2' => [ 'multiply', 'echo' ]
);
is( $proxy->sum( 1, 2 ), 3, 'proxy sum 1' );
is( $proxy->multiply( 1, 2, 3 ), 6, 'proxy multiply 1' );
is( $proxy->echo('Testing 1 2 3'), 'Testing 1 2 3', 'proxy echo 1' );
### Test non blocking
my $loop_count = 1;
my $nb_test1;
my $nb_test2;
$client->call(
'/jsonrpc',
{ id => 2,
method => 'sum',
params => [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
},
sub {
my $res = pop;
$nb_test1 = $res->result;
if ( $loop_count-- == 0 ) { Mojo::IOLoop->stop; }
}
);
$client->call(
'/jsonrpc2',
{ id => 2,
method => 'echo',
params => ['..................................']
},
sub {
my $res = pop;
$nb_test2 = $res->result;
if ( $loop_count-- == 0 ) { Mojo::IOLoop->stop; }
}
);
Mojo::IOLoop->start;
is( $nb_test1, 45, 'Non blocking 1' );
is( $nb_test2, '..................................', 'Non blocking 2' );
######## Some extra tests from spec
#rpc call with positional parameters:
#--> {"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": 1}
#<-- {"jsonrpc": "2.0", "result": 19, "id": 1}
TestUts::test_call(
$client,
'/jsonrpc2',
{ id => 1,
method => 'substract',
params => [ 42, 23 ]
},
{ result => 19,
id => 1
},
'rpc call with positional parameters 1'
);
#--> {"jsonrpc": "2.0", "method": "subtract", "params": [23, 42], "id": 2}
#<-- {"jsonrpc": "2.0", "result": -19, "id": 2}
TestUts::test_call(
$client,
'/jsonrpc2',
{ id => 2,
method => 'substract',
params => [ 23, 42 ]
},
{ id => 2,
result => -19,
},
'rpc call with positional parameters 2'
);
#rpc call with named parameters:
#--> {"jsonrpc": "2.0", "method": "subtract", "params": {"subtrahend": 23, "minuend": 42}, "id": 3}
#<-- {"jsonrpc": "2.0", "result": 19, "id": 3}
TestUts::test_call(
$client,
'/jsonrpc',
{ id => 3,
method => 'substract',
params => {
minuend => 42,
subtrahend => 23,
}
},
{ id => 3,
result => 19,
},
'rpc call with named parameters 1'
);
#--> {"jsonrpc": "2.0", "method": "subtract", "params": {"minuend": 42, "subtrahend": 23}, "id": 4}
#<-- {"jsonrpc": "2.0", "result": 19, "id": 4}
TestUts::test_call(
$client,
'/jsonrpc',
{ id => 4,
method => 'substract',
params => {
minuend => 23,
subtrahend => 42,
}
},
{ id => 4,
result => -19,
},
'rpc call with named parameters 2'
);
#a Notification:
#--> {"jsonrpc": "2.0", "method": "update", "params": [1,2,3,4,5]}
TestUts::test_call(
$client,
'/jsonrpc2',
{ method => 'update',
params => [ 1, 2, 3, 4, 5 ]
},
{},
'a Notification 1'
);
#--> {"jsonrpc": "2.0", "method": "foobar"}
TestUts::test_call( $client, '/jsonrpc2', { method => 'foobar', },
{}, 'a Notification 2' );
#rpc call of non-existent method:
#--> {"jsonrpc": "2.0", "method": "foobar", "id": "1"}
#<-- {"jsonrpc": "2.0", "error": {"code": -32601, "message": "Procedure not found."}, "id": "1"}
TestUts::test_call(
$client,
'/jsonrpc',
{ id => 1,
method => 'foobar'
},
{ error => {
code => -32601,
message => 'Method not found.'
},
id => 1
},
'rpc call of non-existent method'
);
#rpc call with invalid JSON:
#--> {"jsonrpc": "2.0", "method": "foobar, "params": "bar", "baz]
#<-- {"jsonrpc": "2.0", "error": {"code": -32700, "message": "Parse error."}, "id": null}
TestUts::test_call(
$client,
'/jsonrpc',
q|{"jsonrpc": "2.0", "method": "foobar, "params": "bar", "baz]|,
{ error => {
code => -32700,
message => 'Parse error.'
},
id => undef
},
'rpc call with invalid JSON'
);
#rpc call with invalid Request object:
#--> {"jsonrpc": "2.0", "method": 1, "params": "bar"}
#<-- {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request."}, "id": null}
TestUts::test_call(
$client,
'/jsonrpc',
{ id => 1,
method => 1,
params => 'bar'
},
{ error => {
code => -32600,
message => 'Invalid Request.'
},
id => undef
},
'rpc call with invalid Request object'
);
#rpc call Batch, invalid JSON:
#--> [ {"jsonrpc": "2.0", "method": "sum", "params": [1,2,4], "id": "1"},{"jsonrpc": "2.0", "method" ]
#<-- {"jsonrpc": "2.0", "error": {"code": -32700, "message": "Parse error."}, "id": null}
TestUts::test_call(
$client,
'/jsonrpc',
q|[ {"jsonrpc": "2.0", "method": "sum", "params": [1,2,4], "id": "1"},{"jsonrpc": "2.0", "method" ]|,
{ error => {
code => -32700,
message => 'Parse error.'
},
id => undef
},
'rpc call Batch, invalid JSON'
);
#rpc call with an empty Array:
#--> []
#<-- {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request."}, "id": null}
TestUts::test_call(
$client,
'/jsonrpc',
q|[]|,
{ error => {
code => -32600,
message => 'Invalid Request.'
},
id => undef
},
'rpc call with an empty Array'
);
#rpc call with an invalid Batch (but not empty):
#--> [1]
#<-- [
# {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request."}, "id": null}
# ]
TestUts::test_call(
$client,
'/jsonrpc',
q|[1]|,
{ error => {
code => -32600,
message => 'Invalid Request.'
},
id => undef
},
'rpc call with an invalid Batch (but not empty)'
);
#rpc call with invalid Batch:
#--> [1,2,3]
#<-- [
# {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request."}, "id": null},
# {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request."}, "id": null},
# {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request."}, "id": null}
# ]
TestUts::test_call(
$client,
'/jsonrpc',
q|[1,2,3]|,
[ { error => {
code => -32600,
message => 'Invalid Request.'
},
id => undef
},
{ error => {
code => -32600,
message => 'Invalid Request.'
},
id => undef
},
{ error => {
code => -32600,
message => 'Invalid Request.'
},
id => undef
},
],
'rpc call with an invalid Batch'
);
#rpc call Batch:
#--> [
# {"jsonrpc": "2.0", "method": "sum", "params": [1,2,4], "id": "1"},
# {"jsonrpc": "2.0", "method": "notify_hello", "params": [7]},
# {"jsonrpc": "2.0", "method": "subtract", "params": [42,23], "id": "2"},
# {"foo": "boo"},
# {"jsonrpc": "2.0", "method": "foo.get", "params": {"name": "myself"}, "id": "5"},
# {"jsonrpc": "2.0", "method": "get_data", "id": "9"}
# ]
#<-- [
# {"jsonrpc": "2.0", "result": 7, "id": "1"},
# {"jsonrpc": "2.0", "result": 19, "id": "2"},
# {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request."}, "id": null},
# {"jsonrpc": "2.0", "error": {"code": -32601, "message": "Method not found."}, "id": "5"},
# {"jsonrpc": "2.0", "result": ["hello", 5], "id": "9"}
# ]
TestUts::test_call(
$client,
'/jsonrpc',
[ { method => 'sum', params => [ 1, 2, 4 ], id => '1' },
{ method => 'notify_hello', params => [7] },
{ method => 'subtract', params => [ 42, 23 ], id => '2' },
{ foo => 'boo' },
{ method => 'foo.get',
params => { name => 'myself' },
id => '5'
},
{ method => 'get_data', id => '9' }
],
[ { result => 7,
id => 1
},
{ result => 19,
id => 2
},
{ error => {
code => -32600,
message => 'Invalid Request.'
},
id => undef
},
{ error => {
code => -32601,
message => 'Method not found.'
},
id => 5
},
{ result => [ 'hello', 5 ],
id => 9
},
],
'rpc call Batch'
);
#rpc call Batch (all notifications):
#--> [
# {"jsonrpc": "2.0", "method": "notify_sum", "params": [1,2,4]},
# {"jsonrpc": "2.0", "method": "notify_hello", "params": [7]},
# ]
#<-- //Nothing is returned for all notification batches
TestUts::test_call(
$client,
'/jsonrpc',
[ { method => 'notify_sum', params => [ 1, 2, 4 ] },
{ method => 'notify_hello', params => [7] },
],
{},
'rpc call Batch (all notifications)'
);
# Test with_self option in Dispatcher
TestUts::test_call(
$client,
'/jsonrpc',
[ { id => 1, method => 'test_with_self', params => [] } ],
{ id => 1,
result => 'MojoX::JSON::RPC::Dispatcher',
},
'test with_self'
);
TestUts::test_call(
$client,
'/jsonrpc',
[ { id => 2, method => 'test_with_mojo_tx_and_self', params => [] } ],
{ id => 2,
result =>
[ 'Mojo::Transaction::HTTP', 'MojoX::JSON::RPC::Dispatcher' ]
},
'test with_mojo_tx + with_self'
);
TestUts::test_call(
$client,
'/jsonrpc',
[ { id => 3, method => 'test_with_all', params => [] } ],
{ id => 3,
result => [
'MojoX::JSON::RPC::Service', 'Mojo::Transaction::HTTP',
'MojoX::JSON::RPC::Dispatcher'
]
},
'test with_svc_obj + with_mojo_tx + with_self'
);
1;