Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 86 additions & 1 deletion t/unit/Test2/Harness.t
Original file line number Diff line number Diff line change
@@ -1,5 +1,90 @@
use Test2::V0 -target => 'Test2::Harness::Client';

skip_all "write me";
subtest 'constructor' => sub {
my $client = $CLASS->new;
ok($client->isa($CLASS), 'creates instance with no args');
};

subtest 'abstract methods die when not overridden' => sub {
my $client = $CLASS->new;
like(
dies { $client->ipc },
qr/ipc.*not implemented/i,
'ipc() dies in base class',
);
like(
dies { $client->connect },
qr/connect.*not implemented/i,
'connect() dies in base class',
);
};

subtest 'send_and_get — success path returns response' => sub {
my $client = $CLASS->new;

my $fake_con = mock {} => (
add => [
send_and_get => sub {
return {
api => {success => 1},
response => 'pong',
};
},
],
);

{
no warnings 'redefine';
local *Test2::Harness::Client::connect = sub { $fake_con };
my $result = $client->send_and_get('ping');
is($result, 'pong', 'send_and_get returns response on success');
}
};

subtest 'send_and_get — failure path croaks' => sub {
my $client = $CLASS->new;

my $fake_con = mock {} => (
add => [
send_and_get => sub {
return {
api => {success => 0, error => 'something failed'},
response => undef,
};
},
],
);

{
no warnings 'redefine';
local *Test2::Harness::Client::connect = sub { $fake_con };
like(
dies { $client->send_and_get('stop') },
qr/API Call failed/,
'send_and_get croaks on failure',
);
}
};

subtest 'ping delegates to send_and_get' => sub {
my $client = $CLASS->new;
my @calls;

my $fake_con = mock {} => (
add => [
send_and_get => sub {
push @calls, [@_];
return {api => {success => 1}, response => 'pong'};
},
],
);

{
no warnings 'redefine';
local *Test2::Harness::Client::connect = sub { $fake_con };
my $res = $client->ping;
is($res, 'pong', 'ping returns pong');
}
};

done_testing;
187 changes: 186 additions & 1 deletion t/unit/Test2/Harness/Instance.t
Original file line number Diff line number Diff line change
@@ -1,5 +1,190 @@
use Test2::V0 -target => 'Test2::Harness::Instance';
use File::Temp qw/tempfile/;

skip_all "write me";
# Build minimal mock collaborators for the Instance constructor.
# Instance requires: log_file, scheduler (with runner/set_runner), runner, ipc.

sub make_mock_runner {
return mock {} => (
add => [
terminated => sub { 0 },
terminate => sub { 1 },
process_list => sub { () },
overall_status => sub { [] },
abort => sub { 1 },
stop => sub { 1 },
kill => sub { 1 },
reload => sub { 0 },
blacklist => sub { {} },
job_update => sub { 1 },
set_runner => sub { },
runner => sub { undef },
],
);
}

sub make_mock_scheduler {
my ($runner_mock) = @_;
return mock {} => (
add => [
runner => sub { undef },
set_runner => sub { },
terminated => sub { 0 },
terminate => sub { 1 },
process_list => sub { () },
overall_status => sub { [] },
abort => sub { 1 },
stop => sub { 1 },
kill => sub { 1 },
advance => sub { 0 },
queue_run => sub { 1 },
start => sub { 1 },
job_update => sub { 1 },
],
);
}

sub make_mock_ipc {
return mock {} => (
add => [
terminate => sub { 1 },
protocol => sub { 'Test2::Harness::IPC::Protocol::AtomicPipe' },
callback => sub { sub {} },
],
);
}

sub make_instance {
my %extra = @_;
my ($fh, $log_file) = tempfile(UNLINK => 1);
close $fh;
my $runner = make_mock_runner();
my $scheduler = make_mock_scheduler();
my $ipc = make_mock_ipc();
return $CLASS->new(
log_file => $log_file,
scheduler => $scheduler,
runner => $runner,
ipc => $ipc,
%extra,
);
}

subtest 'required attributes' => sub {
my ($fh, $log_file) = tempfile(UNLINK => 1);
close $fh;
my $runner = make_mock_runner();
my $scheduler = make_mock_scheduler();
my $ipc = make_mock_ipc();

like(
dies {
$CLASS->new(scheduler => $scheduler, runner => $runner, ipc => $ipc)
},
qr/log_file.*required/i,
'log_file is required',
);
like(
dies {
$CLASS->new(log_file => $log_file, runner => $runner, ipc => $ipc)
},
qr/scheduler.*required/i,
'scheduler is required',
);
like(
dies {
$CLASS->new(log_file => $log_file, scheduler => $scheduler, ipc => $ipc)
},
qr/runner.*required/i,
'runner is required',
);
like(
dies {
$CLASS->new(log_file => $log_file, scheduler => $scheduler, runner => $runner)
},
qr/ipc.*required/i,
'ipc is required',
);
};

subtest 'basic construction' => sub {
my $inst = make_instance();
ok($inst->isa($CLASS), 'creates instance');
ok($inst->log_file, 'log_file accessible');
ok($inst->runner, 'runner accessible');
ok($inst->scheduler, 'scheduler accessible');
ok($inst->ipc, 'ipc accessible (wrapped in arrayref)');
};

subtest 'ipc wrapped in arrayref when scalar provided' => sub {
my $inst = make_instance();
ref_ok($inst->{ipc}, 'ARRAY', 'ipc stored as arrayref');
};

subtest 'api_ping returns pong' => sub {
my $inst = make_instance();
is($inst->api_ping, 'pong', 'api_ping returns "pong"');
};

subtest 'api_pid returns current PID' => sub {
my $inst = make_instance();
is($inst->api_pid, $$, 'api_pid returns current PID');
};

subtest 'api_log_file returns log file path' => sub {
my $inst = make_instance();
is($inst->api_log_file, $inst->log_file, 'api_log_file returns log_file');
};

subtest 'handle_request — success path' => sub {
my $inst = make_instance();
require Test2::Harness::Instance::Request;
my $req = Test2::Harness::Instance::Request->new(
request_id => 'req-ping',
api_call => 'ping',
);
my $res = $inst->handle_request($req);
ok($res->isa('Test2::Harness::Instance::Response'), 'returns Response object');
is($res->success, 1, 'success is 1');
is($res->response, 'pong', 'response contains pong');
};

subtest 'handle_request — error path for unknown api_call' => sub {
my $inst = make_instance();
require Test2::Harness::Instance::Request;
my $req = Test2::Harness::Instance::Request->new(
request_id => 'req-bad',
api_call => 'no_such_api_call',
);
my $res = $inst->handle_request($req);
ok($res->isa('Test2::Harness::Instance::Response'), 'returns Response on error');
is($res->success, 0, 'success is 0 for unknown api call');
ok($res->api->{error}, 'error message present');
};

subtest 'parse_request_args' => sub {
my $inst = make_instance();

is([$inst->parse_request_args(undef)], [], 'undef returns empty list');
is([$inst->parse_request_args('hello')], ['hello'], 'scalar returned as list with one element');
is([$inst->parse_request_args([1, 2])], [1, 2], 'arrayref flattened to list');
is([$inst->parse_request_args({a => 1})], ['a', 1], 'hashref flattened to key-value pairs');
};

subtest 'terminate — first reason wins' => sub {
my $inst = make_instance();
ok(!$inst->terminated, 'not terminated initially');
$inst->terminate('first-reason');
is($inst->terminated, 1, 'terminated set after first call');
$inst->terminate('second-reason');
is($inst->terminated, 1, 'terminated not overwritten by second call');
};

subtest 'stop sets stop flag' => sub {
my $inst = make_instance();
ok(!$inst->{stop}, 'stop not set initially');
$inst->stop;
ok($inst->{stop}, 'stop set after calling stop()');
};

done_testing;
30 changes: 29 additions & 1 deletion t/unit/Test2/Harness/Instance/Message.t
Original file line number Diff line number Diff line change
@@ -1,5 +1,33 @@
use Test2::V0 -target => 'Test2::Harness::Instance::Message';

skip_all "write me";
subtest 'constructor and basic attributes' => sub {
my $msg = $CLASS->new(
ipc_meta => {seq => 1},
connection => 'con1',
terminate => 1,
run_complete => 1,
);
ok($msg->isa($CLASS), 'creates instance');
is($msg->ipc_meta, {seq => 1}, 'ipc_meta accessor');
is($msg->connection, 'con1', 'connection accessor');
is($msg->terminate, 1, 'terminate accessor');
is($msg->run_complete, 1, 'run_complete accessor');
};

subtest 'empty constructor' => sub {
my $msg = $CLASS->new;
ok($msg->isa($CLASS), 'constructs with no args');
};

subtest 'TO_JSON includes class field' => sub {
my $msg = $CLASS->new(
ipc_meta => {seq => 99},
terminate => 1,
);
my $json = $msg->TO_JSON;
ref_ok($json, 'HASH', 'TO_JSON returns hashref');
is($json->{class}, $CLASS, 'TO_JSON includes class key');
is($json->{terminate}, 1, 'TO_JSON includes terminate');
};

done_testing;
43 changes: 42 additions & 1 deletion t/unit/Test2/Harness/Instance/Request.t
Original file line number Diff line number Diff line change
@@ -1,5 +1,46 @@
use Test2::V0 -target => 'Test2::Harness::Instance::Request';

skip_all "write me";
subtest 'required attributes' => sub {
like(
dies { $CLASS->new(api_call => 'ping') },
qr/request_id.*required/i,
'request_id is required',
);
like(
dies { $CLASS->new(request_id => '123') },
qr/api_call.*required/i,
'api_call is required',
);
};

subtest 'valid construction' => sub {
my $req = $CLASS->new(request_id => 'req-1', api_call => 'ping');
ok($req->isa($CLASS), 'creates instance');
is($req->request_id, 'req-1', 'request_id accessor');
is($req->api_call, 'ping', 'api_call accessor');
};

subtest 'optional attributes' => sub {
my $req = $CLASS->new(
request_id => 'req-2',
api_call => 'stop',
args => [1, 2, 3],
do_not_respond => 1,
);
is($req->args, [1, 2, 3], 'args accessor');
is($req->do_not_respond, 1, 'do_not_respond accessor');
};

subtest 'inherits from Message' => sub {
require Test2::Harness::Instance::Message;
my $req = $CLASS->new(request_id => 'req-3', api_call => 'ping');
ok($req->isa('Test2::Harness::Instance::Message'), 'inherits from Message');
};

subtest 'TO_JSON includes class' => sub {
my $req = $CLASS->new(request_id => 'req-4', api_call => 'ping');
my $json = $req->TO_JSON;
is($json->{class}, $CLASS, 'TO_JSON sets class to Request package');
};

done_testing;
Loading
Loading