Kubernetes-REST
view release on metacpan or search on metacpan
t/13_io_backends.t view on Meta::CPAN
my $ua = $io->ua;
my $opts = $ua->ssl_opts('verify_hostname');
is $opts, 1, 'verify_hostname enabled';
};
subtest 'LWPIO - ua SSL options without verify' => sub {
my $io = Kubernetes::REST::LWPIO->new(ssl_verify_server => 0);
my $ua = $io->ua;
my $opts = $ua->ssl_opts('verify_hostname');
is $opts, 0, 'verify_hostname disabled';
};
subtest 'LWPIO - call() makes HTTP request' => sub {
# We can't easily mock LWP::UserAgent, but we can subclass
my $io = Kubernetes::REST::LWPIO->new;
# Replace the ua with a mock
my $mock_ua = Test::MockLWP->new(
code => 200,
content => '{"kind":"Namespace","metadata":{"name":"default"}}',
);
$io->{ua} = $mock_ua;
my $req = Kubernetes::REST::HTTPRequest->new(
method => 'GET',
url => 'http://mock.local/api/v1/namespaces/default',
headers => { Authorization => 'Bearer test-token' },
);
my $res = $io->call($req);
isa_ok $res, 'Kubernetes::REST::HTTPResponse';
is $res->status, 200, 'status is 200';
like $res->content, qr/Namespace/, 'content contains Namespace';
};
subtest 'LWPIO - call() with POST body' => sub {
my $io = Kubernetes::REST::LWPIO->new;
my $mock_ua = Test::MockLWP->new(
code => 201,
content => '{"kind":"Namespace","metadata":{"name":"test","uid":"abc123"}}',
);
$io->{ua} = $mock_ua;
my $req = Kubernetes::REST::HTTPRequest->new(
method => 'POST',
url => 'http://mock.local/api/v1/namespaces',
headers => { 'Content-Type' => 'application/json' },
content => '{"metadata":{"name":"test"}}',
);
my $res = $io->call($req);
is $res->status, 201, 'POST returns 201';
like $res->content, qr/uid/, 'response contains uid';
# Verify the request body was passed
is $mock_ua->last_request->content, '{"metadata":{"name":"test"}}',
'request body preserved';
};
subtest 'LWPIO - call_streaming() with data callback' => sub {
my $io = Kubernetes::REST::LWPIO->new;
# For streaming, LWP calls the callback with chunks
my $mock_ua = Test::MockLWP->new(
code => 200,
content => '',
streaming_chunks => [
qq|{"type":"ADDED","object":{"kind":"Pod","metadata":{"name":"pod-1"}}}\n|,
qq|{"type":"MODIFIED","object":{"kind":"Pod","metadata":{"name":"pod-1"}}}\n|,
],
);
$io->{ua} = $mock_ua;
my $req = Kubernetes::REST::HTTPRequest->new(
method => 'GET',
url => 'http://mock.local/api/v1/namespaces/default/pods?watch=true',
headers => { Authorization => 'Bearer test-token' },
);
my @chunks;
my $res = $io->call_streaming($req, sub { push @chunks, $_[0] });
isa_ok $res, 'Kubernetes::REST::HTTPResponse';
is $res->status, 200, 'streaming returns 200';
is scalar @chunks, 2, 'received 2 chunks';
like $chunks[0], qr/ADDED/, 'first chunk is ADDED';
like $chunks[1], qr/MODIFIED/, 'second chunk is MODIFIED';
};
subtest 'LWPIO - call() with empty response' => sub {
my $io = Kubernetes::REST::LWPIO->new;
my $mock_ua = Test::MockLWP->new(
code => 204,
content => '',
);
$io->{ua} = $mock_ua;
my $req = Kubernetes::REST::HTTPRequest->new(
method => 'DELETE',
url => 'http://mock.local/api/v1/namespaces/test',
headers => {},
);
my $res = $io->call($req);
is $res->status, 204, 'status is 204';
};
subtest 'LWPIO - implements Role::IO' => sub {
my $io = Kubernetes::REST::LWPIO->new;
ok $io->does('Kubernetes::REST::Role::IO'), 'LWPIO does Role::IO';
};
# ============================================================================
# Test HTTPTinyIO backend
# ============================================================================
subtest 'HTTPTinyIO - construction with defaults' => sub {
use_ok('Kubernetes::REST::HTTPTinyIO');
my $io = Kubernetes::REST::HTTPTinyIO->new;
ok $io, 'HTTPTinyIO created';
is $io->ssl_verify_server, 1, 'ssl_verify_server defaults to true';
is $io->timeout, 310, 'timeout defaults to 310';
};
subtest 'HTTPTinyIO - construction with custom options' => sub {
my $io = Kubernetes::REST::HTTPTinyIO->new(
ssl_verify_server => 0,
ssl_cert_file => '/tmp/cert.pem',
ssl_key_file => '/tmp/key.pem',
ssl_ca_file => '/tmp/ca.pem',
timeout => 120,
);
is $io->ssl_verify_server, 0, 'ssl_verify_server set';
is $io->ssl_cert_file, '/tmp/cert.pem', 'ssl_cert_file set';
is $io->ssl_key_file, '/tmp/key.pem', 'ssl_key_file set';
is $io->ssl_ca_file, '/tmp/ca.pem', 'ssl_ca_file set';
is $io->timeout, 120, 'timeout set';
};
subtest 'HTTPTinyIO - ua is HTTP::Tiny' => sub {
my $io = Kubernetes::REST::HTTPTinyIO->new;
my $ua = $io->ua;
t/13_io_backends.t view on Meta::CPAN
status => 200,
content => '{"kind":"Namespace","metadata":{"name":"default"}}',
);
$io->{ua} = $mock_ua;
my $req = Kubernetes::REST::HTTPRequest->new(
method => 'GET',
url => 'http://mock.local/api/v1/namespaces/default',
headers => { Authorization => 'Bearer test-token' },
);
my $res = $io->call($req);
isa_ok $res, 'Kubernetes::REST::HTTPResponse';
is $res->status, 200, 'status is 200';
like $res->content, qr/Namespace/, 'content contains Namespace';
};
subtest 'HTTPTinyIO - call() with POST and content' => sub {
my $io = Kubernetes::REST::HTTPTinyIO->new;
my $mock_ua = Test::MockHTTPTiny->new(
status => 201,
content => '{"kind":"Namespace","metadata":{"name":"test","uid":"abc"}}',
);
$io->{ua} = $mock_ua;
my $req = Kubernetes::REST::HTTPRequest->new(
method => 'POST',
url => 'http://mock.local/api/v1/namespaces',
headers => { 'Content-Type' => 'application/json' },
content => '{"metadata":{"name":"test"}}',
);
my $res = $io->call($req);
is $res->status, 201, 'POST returns 201';
# Verify content was passed
my $last = $mock_ua->last_opts;
is $last->{content}, '{"metadata":{"name":"test"}}', 'content passed to UA';
};
subtest 'HTTPTinyIO - call() with empty content' => sub {
my $io = Kubernetes::REST::HTTPTinyIO->new;
my $mock_ua = Test::MockHTTPTiny->new(
status => 200,
content => undef,
);
$io->{ua} = $mock_ua;
my $req = Kubernetes::REST::HTTPRequest->new(
method => 'GET',
url => 'http://mock.local/api/v1/pods',
headers => {},
);
my $res = $io->call($req);
is $res->status, 200, 'status ok with no content';
};
subtest 'HTTPTinyIO - call_streaming() with data callback' => sub {
my $io = Kubernetes::REST::HTTPTinyIO->new;
my @delivered_chunks;
my $mock_ua = Test::MockHTTPTiny->new(
status => 200,
content => undef,
on_data_callback => sub {
my ($cb) = @_;
$cb->(qq|{"type":"ADDED","object":{"kind":"Pod"}}\n|);
$cb->(qq|{"type":"DELETED","object":{"kind":"Pod"}}\n|);
},
);
$io->{ua} = $mock_ua;
my $req = Kubernetes::REST::HTTPRequest->new(
method => 'GET',
url => 'http://mock.local/api/v1/pods?watch=true',
headers => {},
);
my @chunks;
my $res = $io->call_streaming($req, sub { push @chunks, $_[0] });
is $res->status, 200, 'streaming returns 200';
is scalar @chunks, 2, 'received 2 chunks';
like $chunks[0], qr/ADDED/, 'first chunk is ADDED';
like $chunks[1], qr/DELETED/, 'second chunk is DELETED';
};
# ============================================================================
# Test that REST.pm uses LWPIO by default
# ============================================================================
subtest 'REST default IO is LWPIO' => sub {
use Test::Kubernetes::Mock qw(mock_api);
# The mock API uses Mock::IO, so test by creating a non-mock one
require Kubernetes::REST;
require Kubernetes::REST::Server;
require Kubernetes::REST::AuthToken;
my $api = Kubernetes::REST->new(
server => Kubernetes::REST::Server->new(endpoint => 'http://test.local'),
credentials => Kubernetes::REST::AuthToken->new(token => 'test'),
resource_map_from_cluster => 0,
);
isa_ok $api->io, 'Kubernetes::REST::LWPIO', 'default IO is LWPIO';
};
subtest 'REST allows HTTPTinyIO override' => sub {
require Kubernetes::REST;
require Kubernetes::REST::Server;
require Kubernetes::REST::AuthToken;
require Kubernetes::REST::HTTPTinyIO;
my $api = Kubernetes::REST->new(
server => Kubernetes::REST::Server->new(endpoint => 'http://test.local'),
credentials => Kubernetes::REST::AuthToken->new(token => 'test'),
resource_map_from_cluster => 0,
io => Kubernetes::REST::HTTPTinyIO->new,
);
isa_ok $api->io, 'Kubernetes::REST::HTTPTinyIO', 'can override with HTTPTinyIO';
};
done_testing;
# ============================================================================
# Minimal mock LWP::UserAgent for testing LWPIO without network
# ============================================================================
package Test::MockLWP;
use strict;
use warnings;
sub new {
my ($class, %args) = @_;
bless {
code => $args{code} // 200,
content => $args{content} // '',
streaming_chunks => $args{streaming_chunks} // [],
last_request => undef,
}, $class;
}
sub request {
my ($self, $req, $content_cb) = @_;
$self->{last_request} = $req;
if ($content_cb && ref $content_cb eq 'CODE') {
# Streaming mode: deliver chunks via callback
for my $chunk (@{$self->{streaming_chunks}}) {
$content_cb->($chunk);
}
}
return Test::MockLWP::Response->new(
code => $self->{code},
content => $self->{content},
);
}
sub last_request { $_[0]->{last_request} }
package Test::MockLWP::Response;
use strict;
use warnings;
sub new {
my ($class, %args) = @_;
bless \%args, $class;
}
sub code { $_[0]->{code} }
sub decoded_content { $_[0]->{content} }
# ============================================================================
# Minimal mock HTTP::Tiny for testing HTTPTinyIO without network
# ============================================================================
package Test::MockHTTPTiny;
use strict;
use warnings;
sub new {
my ($class, %args) = @_;
bless {
status => $args{status} // 200,
content => $args{content},
on_data_callback => $args{on_data_callback},
last_opts => undef,
}, $class;
}
sub request {
my ($self, $method, $url, $opts) = @_;
$self->{last_opts} = $opts // {};
# If there's a data_callback in opts and we have streaming setup, invoke it
if ($opts->{data_callback} && $self->{on_data_callback}) {
$self->{on_data_callback}->($opts->{data_callback});
}
return {
status => $self->{status},
(defined $self->{content} ? (content => $self->{content}) : ()),
};
}
sub last_opts { $_[0]->{last_opts} }
( run in 0.656 second using v1.01-cache-2.11-cpan-600a1bdf6e4 )