TT#57652 Implement a Proof of Concept for the new API Test Framework

* includes basic scenarios and pagination tests
	  for Contracts

	* added threading support
	* a number of threads retrieve test cases
	  and run them in parallel, completely
	  isolated from the other test cases
	* deep comparison of data structures with
	  is_deeply
	* perl code in YAML for complex testing
	  scenarios
	* migrated test cases for Calls, BillingNetworks, Customer

Change-Id: I6a58b753309fce570544a7c3f28959ee07d9aeb6
changes/76/29576/5
Flaviu Mates 6 years ago
parent 263f97a0dd
commit 818f22c5c7

@ -0,0 +1,180 @@
package NGCP::TestFramework;
use strict;
use warnings;
use Cpanel::JSON::XS;
use Data::Walk;
use DateTime qw();
use DateTime::Format::Strptime qw();
use DateTime::Format::ISO8601 qw();
use Digest::MD5 qw/md5_hex/;
use Moose;
use Net::Domain qw(hostfqdn);
use Test::More;
use URI;
use YAML::XS qw(LoadFile);
use Data::Dumper;
use NGCP::TestFramework::RequestBuilder;
use NGCP::TestFramework::Client;
use NGCP::TestFramework::TestExecutor;
has 'file_path' => (
isa => 'Str',
is => 'ro'
);
sub run {
my ( $self ) = @_;
unless ( $self->file_path ) {
return;
}
$YAML::XS::DumpCode = 1;
$YAML::XS::LoadCode = 1;
$YAML::XS::UseCode = 1;
$YAML::XS::LoadBlessed = 1;
my $testing_data = LoadFile($self->file_path);
my $base_uri = $ENV{CATALYST_SERVER} || ('https://'.hostfqdn.':4443');
my $request_builder = NGCP::TestFramework::RequestBuilder->new({ base_uri => $base_uri });
my $client = NGCP::TestFramework::Client->new( { uri => $base_uri, log_debug => 0 } );
my $test_executor = NGCP::TestFramework::TestExecutor->new();
# initializing time to add to fields which need to be unique
my $retained = { unique_id => int(rand(100000)) };
foreach my $test ( @$testing_data ) {
next if ( $test->{skip} );
# build request
my $request = $request_builder->build({
method => $test->{method},
path => $test->{path},
header => $test->{header} || undef,
content => $test->{content} || undef,
retain => $retained
});
# handle separate types
if ( $test->{type} eq 'item' ) {
my $result = $client->perform_request($request);
if ( $test->{retain} ) {
$self->_get_retained_elements( $test->{retain}, $retained, $result );
}
if ( $test->{operations} ) {
$self->_perform_operations( $test->{operations}, $retained );
}
if ( $test->{perl_code} ){
my $sub = $test->{perl_code};
warn Dumper $sub;
$sub->( $retained );
}
$test_executor->run_tests( $test->{conditions}, $result, $retained, $test->{name} ) if ( $test->{conditions} );
}
elsif ( $test->{type} eq 'batch' ) {
foreach my $iteration ( 1..$test->{iterations} ) {
my $result = $client->perform_request($request);
if ( $test->{retain} ) {
$self->_get_retained_elements( $test->{retain}, $retained, $result );
}
if ( $test->{operations} ) {
$self->_perform_operations( $test->{operations}, $retained );
}
$test_executor->run_tests( $test->{conditions}, $result, $retained, $test->{name} ) if ( $test->{conditions} );
}
}
elsif ( $test->{type} eq 'pagination' ) {
my $nexturi = $test->{path};
do {
$request->uri( $base_uri.$nexturi );
my $result = $client->perform_request($request);
if ( $test->{retain} ) {
$self->_get_retained_elements( $test->{retain}, $retained, $result );
}
if ( $test->{operations} ) {
$self->_perform_operations( $test->{operations}, $retained );
}
my $body = decode_json( $result->decoded_content() );
#build default conditions for pagination
$test->{conditions}->{is}->{$nexturi} = $body->{_links}->{self}->{href};
my $colluri = URI->new($base_uri.$body->{_links}->{self}->{href});
my %q = $colluri->query_form;
$test->{conditions}->{ok}->{$q{page}} = 'defined';
$test->{conditions}->{ok}->{$q{rows}} = 'defined';
my $page = int($q{page});
my $rows = int($q{rows});
if($page == 1) {
$test->{conditions}->{ok}->{'${collection}._links.prev.href'} = 'undefined';
} else {
$test->{conditions}->{ok}->{'${collection}._links.prev.href'} = 'defined';
}
if(($retained->{collection}->{total_count} / $rows) <= $page) {
$test->{conditions}->{ok}->{'${collection}._links.next.href'} = 'undefined';
} else {
$test->{conditions}->{ok}->{'${collection}._links.next.href'} = 'defined';
}
$test_executor->run_tests( $test->{conditions}, $result, $retained, $test->{name} ) if ( $test->{conditions} );
if( $body->{_links}->{next}->{href} ) {
$nexturi = $body->{_links}->{next}->{href};
} else {
$nexturi = undef;
}
} while ( $nexturi )
}
}
}
sub _get_retained_elements {
my ( $self, $retain, $retained, $result ) = @_;
while ( my ( $retain_elem, $retain_value ) = each %{$retain} ) {
if ( $retain_value =~ /.+\..+/ ) {
my @splitted_values = split (/\./, $retain_value);
$retained->{$retain_elem} = $self->_retrieve_from_composed_key( $result, \@splitted_values, $retain_elem );
}
elsif ( $retain_value eq 'body' ) {
$retained->{$retain_elem} = decode_json( $result->decoded_content() );
}
else {
return {
success => 0,
message => 'Wrong retain instructions!'
}
}
}
}
sub _retrieve_from_composed_key {
my ( $self, $result, $splitted_values, $retain_elem ) = @_;
if ( $splitted_values->[0] eq 'header' ) {
my $value = $result->header(ucfirst $splitted_values->[1]);
if ( $retain_elem =~ /^.+_id$/ ) {
$value =~ /^.+\/(\d+)$/;
$value = $1;
}
return $value;
}
}
sub _variables_available {
my( $self, $retained, $test ) = @_;
return 0 if ( $test->{path} =~ /\$\{(.*)\}/ && !$retained->{$1} );
# substitute variables in content
if ( $test->{content} && ref $test->{content} eq 'HASH' ) {
foreach my $content_key (keys %{$test->{content}}) {
return 0 if ( $test->{content}->{$content_key} =~ /\$\{(.*)\}/ && !$retained->{$1} );
}
}
return 1;
}
1;

@ -0,0 +1,191 @@
package NGCP::TestFramework::Client;
use strict;
use warnings;
use Data::Dumper;
use Digest::MD5 qw/md5_hex/;
use IO::Uncompress::Unzip;
use LWP::UserAgent;
use Moose;
use Time::HiRes qw/gettimeofday tv_interval/;
has 'username' => (
isa => 'Str',
is => 'ro',
default => sub {
$ENV{API_USER} // 'administrator';
}
);
has 'password' => (
isa => 'Str',
is => 'ro',
default => sub {
$ENV{API_PASS} // 'administrator';
}
);
has 'role' => (
isa => 'Str',
is => 'ro',
default => 'admin',
);
has 'uri' => (
isa => 'Str',
is => 'ro',
default => sub { $ENV{CATALYST_SERVER}; },
);
has 'sub_uri' => (
isa => 'Str',
is => 'ro',
default => sub { $ENV{CATALYST_SERVER_SUB}; },
);
has '_uri' => (
isa => 'Maybe[Str]',
is => 'rw',
);
has 'verify_ssl' => (
isa => 'Int',
is => 'ro',
default => 0,
);
has 'log_debug' => (
isa => 'Bool',
is => 'rw',
default => 0,
);
has '_crt_path' => (
is => 'ro',
isa => 'Str',
lazy => 1,
default => sub {
my ($self) = @_;
return '/tmp/' . md5_hex($self->username) . ".crt";
}
);
has 'last_rtt' => (
is => 'rw',
isa => 'Num',
default => 0,
);
has '_ua' => (
isa => 'LWP::UserAgent',
is => 'ro',
lazy => 1,
builder => '_build_ua'
);
sub _build_ua {
my $self = shift;
my $ua = LWP::UserAgent->new(keep_alive => 1);
my $uri;
if($self->role eq "admin" || $self->role eq "reseller") {
$uri = $self->uri;
} else {
$uri = $self->sub_uri;
}
$self->_uri($uri);
$self->debug("client using uri $uri\n");
$uri =~ s/^https?:\/\///;
$self->debug("client using ip:port $uri\n");
my $realm;
if($self->role eq 'admin' || $self->role eq 'reseller') {
$realm = 'api_admin_http';
} elsif($self->role eq 'subscriber') {
$realm = 'api_subscriber_http';
}
$self->debug("client using realm $realm with user=" . $self->username . " and pass " . $self->password . "\n");
$ua->credentials($uri, $realm, $self->username, $self->password);
unless($self->verify_ssl) {
$ua->ssl_opts(
verify_hostname => 0,
SSL_verify_mode => 0,
);
}
if($self->role eq "admin" || $self->role eq "reseller") {
unless(-f $self->_crt_path) {
# we have to setup a new connection here, because if we're already connected,
# the connection will be re-used, thus no cert is used
my $tmpua = LWP::UserAgent->new;
$tmpua->credentials($uri, $realm, $self->username, $self->password);
unless($self->verify_ssl) {
$tmpua->ssl_opts(
verify_hostname => 0,
SSL_verify_mode => 0,
);
}
my $res = $tmpua->post(
$self->_uri . '/api/admincerts/',
Content_Type => 'application/json',
Content => '{}'
);
unless($res->is_success) {
die "Failed to fetch client certificate: " . $res->status_line . "\n";
}
my $zip = $res->decoded_content;
my $z = IO::Uncompress::Unzip->new(\$zip, MultiStream => 0, Append => 1);
my $data;
while(!$z->eof() && (my $hdr = $z->getHeaderInfo())) {
unless($hdr->{Name} =~ /\.pem$/) {
# wrong file, just read stream, clear buffer and try next
while($z->read($data) > 0) {}
$data = undef;
$z->nextStream();
next;
}
while($z->read($data) > 0) {}
last;
}
$z->close();
unless($data) {
die "Failed to find PEM file in client certificate zip file\n";
}
open my $fh, ">:raw", $self->_crt_path
or die "Failed to open " . $self->_crt_path . ": $!\n";
print $fh $data;
close $fh;
}
$ua->ssl_opts(
SSL_cert_file => $self->_crt_path,
SSL_key_file => $self->_crt_path,
);
}
return $ua;
}
sub perform_request {
my ($self, $req) = @_;
my $t0 = [gettimeofday];
$self->debug("content of " . $req->method . " request to " . $req->uri . ":\n");
$self->debug($req->content || "<empty request>");
$self->debug("\n");
my $res = $self->_ua->request($req);
my $rtt = tv_interval($t0);
$self->last_rtt($rtt);
$self->debug("content of response:\n");
$self->debug($res->decoded_content || "<empty response>");
$self->debug("\n");
return $res;
}
sub debug {
my ($self, $msg) = @_;
if($self->log_debug) {
print $msg;
}
}
1;

@ -0,0 +1,655 @@
---
#check options
-
name: check OPTIONS for billingnetworks
type: item
method: OPTIONS
path: /api/billingnetworks/
conditions:
is:
code: 200
header:
Accept-Post: application/hal+json; profile=http://purl.org/sipwise/ngcp-api/#rel-billingnetworks
ok:
options:
- GET
- HEAD
- OPTIONS
- POST
perl_code: !!perl/code |
{
my ($retained) = @_;
$retained->{blocks} = [{ip=>'fdfe::5a55:caff:fefa:9089',mask=>128},
{ip=>'fdfe::5a55:caff:fefa:908a'},
{ip=>'fdfe::5a55:caff:fefa:908b',mask=>128},];
}
#POST test billingnetwork
-
name: POST test billingnetwork
type: item
method: POST
path: /api/billingnetworks/
header:
Content-Type: application/json
Prefer: return=representation
content:
name: test billing network ${unique_id}
description: test billing network description ${unique_id}
reseller_id: 1
blocks: ${blocks}
retain:
billingnetwork_path: header.location
conditions:
is:
code: 201
#GET billingnetwork
-
name: fetch POSTed billingnetwork
type: item
method: GET
path: '/${billingnetwork_path}'
retain:
billingnetwork: body
conditions:
is:
code: 200
#PUT test billingnetwork
-
name: PUT test billingnetwork
type: item
method: PUT
path: '/${billingnetwork_path}'
header:
Content-Type: application/json
Prefer: return=representation
content:
name: test billingnetwork PUT ${unique_id}
description: test billing network description PUT ${unique_id}
reseller_id: 1
blocks: ${blocks}
conditions:
is:
code: 200
#GET billingnetwork
-
name: fetch PUT test billingnetwork
type: item
method: GET
path: '/${billingnetwork_path}'
retain:
billingnetwork: body
conditions:
is:
code: 200
#PATCH test billingnetwork
-
name: PATCH test billingnetwork
type: item
method: PATCH
path: '/${billingnetwork_path}'
header:
Content-Type: application/json-patch+json
Prefer: return=representation
content:
-
op: replace
path: /name
value: test billingnetwork PATCH ${unique_id}
conditions:
is:
code: 200
#GET billingnetwork
-
name: fetch PATCHed test billingnetwork
type: item
method: GET
path: '/${billingnetwork_path}'
retain:
billingnetwork: body
conditions:
is:
code: 200
#DELETE billingnetwork
-
name: terminate test billingnetwork
type: item
method: DELETE
path: '${billingnetwork_path}'
conditions:
is:
code: 204
#GET billingnetwork
-
name: try to fetch terminated billingnetwork
type: item
method: GET
path: '/${billingnetwork_path}'
retain:
billingnetwork: body
conditions:
is:
code: 404
#POST test billingnetwork
-
name: POST test billingnetwork
type: item
method: POST
path: /api/billingnetworks/
header:
Content-Type: application/json
Prefer: return=representation
content: &content
name: test ipv6 billing network 1 ${unique_id}
description: test ipv6 billing network description 1 ${unique_id}
reseller_id: 1
blocks:
-
ip: 'fdfe::5a55:caff:fefa:9089'
mask: 128
status: active
retain:
billingnetwork_path: header.location
conditions:
is:
code: 201
#GET billingnetwork
-
name: fetch POSTed billingnetwork
type: item
method: GET
path: '/${billingnetwork_path}'
retain:
billingnetwork_ipv6_1: body
perl_code: !!perl/code |
{
my ($retained) = @_;
delete $retained->{billingnetwork_ipv6_1}->{id};
delete $retained->{billingnetwork_ipv6_1}->{_links};
}
conditions:
is:
code: 200
is_deeply:
'${billingnetwork_ipv6_1}': *content
#POST test billingnetwork
-
name: POST test billingnetwork
type: item
method: POST
path: /api/billingnetworks/
header:
Content-Type: application/json
Prefer: return=representation
content: &content
name: test ipv6 billing network 2 ${unique_id}
description: test ipv6 billing network description 2 ${unique_id}
reseller_id: 1
blocks:
-
ip: 'fdfe::5a55:caff:fefa:908a'
mask: null
status: active
retain:
billingnetwork_path: header.location
conditions:
is:
code: 201
#GET billingnetwork
-
name: fetch POSTed billingnetwork
type: item
method: GET
path: '/${billingnetwork_path}'
retain:
billingnetwork_ipv6_2: body
perl_code: !!perl/code |
{
my ($retained) = @_;
delete $retained->{billingnetwork_ipv6_2}->{id};
delete $retained->{billingnetwork_ipv6_2}->{_links};
}
conditions:
is:
code: 200
is_deeply:
'${billingnetwork_ipv6_2}': *content
#POST test billingnetwork
-
name: POST test billingnetwork
type: item
method: POST
path: /api/billingnetworks/
header:
Content-Type: application/json
Prefer: return=representation
content: &content
name: test ipv6 billing network 3 ${unique_id}
description: test ipv6 billing network description 3 ${unique_id}
reseller_id: 1
blocks:
-
ip: 'fdfe::5a55:caff:fefa:908b'
mask: 128
status: active
retain:
billingnetwork_path: header.location
conditions:
is:
code: 201
#GET billingnetwork
-
name: fetch POSTed billingnetwork
type: item
method: GET
path: '/${billingnetwork_path}'
retain:
billingnetwork_ipv6_3: body
perl_code: !!perl/code |
{
my ($retained) = @_;
delete $retained->{billingnetwork_ipv6_3}->{id};
delete $retained->{billingnetwork_ipv6_3}->{_links};
}
conditions:
is:
code: 200
is_deeply:
'${billingnetwork_ipv6_3}': *content
#POST test billingnetwork
-
name: POST test billingnetwork
type: item
method: POST
path: /api/billingnetworks/
header:
Content-Type: application/json
Prefer: return=representation
content: &content
name: test ipv6 billing network 4 ${unique_id}
description: test ipv6 billing network description 4 ${unique_id}
reseller_id: 1
blocks:
-
ip: 'fdfe::5a55:caff:fefa:9089'
mask: 128
-
ip: 'fdfe::5a55:caff:fefa:908a'
mask: null
-
ip: 'fdfe::5a55:caff:fefa:908b'
mask: 128
status: active
retain:
billingnetwork_path: header.location
conditions:
is:
code: 201
#GET billingnetwork
-
name: fetch POSTed billingnetwork
type: item
method: GET
path: '/${billingnetwork_path}'
retain:
billingnetwork_ipv6_4: body
perl_code: !!perl/code |
{
my ($retained) = @_;
delete $retained->{billingnetwork_ipv6_4}->{id};
delete $retained->{billingnetwork_ipv6_4}->{_links};
}
conditions:
is:
code: 200
is_deeply:
'${billingnetwork_ipv6_4}': *content
#POST test billingnetwork
-
name: POST test billingnetwork
type: item
method: POST
path: /api/billingnetworks/
header:
Content-Type: application/json
Prefer: return=representation
content: &content
name: test ipv4 billing network 1 ${unique_id}
description: test ipv4 billing network description 1 ${unique_id}
reseller_id: 1
blocks:
-
ip: '10.0.4.7'
mask: 26
status: active
retain:
billingnetwork_path: header.location
conditions:
is:
code: 201
#GET billingnetwork
-
name: fetch POSTed billingnetwork
type: item
method: GET
path: '/${billingnetwork_path}'
retain:
billingnetwork_ipv4_1: body
perl_code: !!perl/code |
{
my ($retained) = @_;
delete $retained->{billingnetwork_ipv4_1}->{id};
delete $retained->{billingnetwork_ipv4_1}->{_links};
}
conditions:
is:
code: 200
is_deeply:
'${billingnetwork_ipv4_1}': *content
#POST test billingnetwork
-
name: POST test billingnetwork
type: item
method: POST
path: /api/billingnetworks/
header:
Content-Type: application/json
Prefer: return=representation
content: &content
name: test ipv4 billing network 2 ${unique_id}
description: test ipv4 billing network description 2 ${unique_id}
reseller_id: 1
blocks:
-
ip: '10.0.4.99'
mask: 26
status: active
retain:
billingnetwork_path: header.location
conditions:
is:
code: 201
#GET billingnetwork
-
name: fetch POSTed billingnetwork
type: item
method: GET
path: '/${billingnetwork_path}'
retain:
billingnetwork_ipv4_2: body
perl_code: !!perl/code |
{
my ($retained) = @_;
delete $retained->{billingnetwork_ipv4_2}->{id};
delete $retained->{billingnetwork_ipv4_2}->{_links};
}
conditions:
is:
code: 200
is_deeply:
'${billingnetwork_ipv4_2}': *content
#POST test billingnetwork
-
name: POST test billingnetwork
type: item
method: POST
path: /api/billingnetworks/
header:
Content-Type: application/json
Prefer: return=representation
content: &content
name: test ipv4 billing network 3 ${unique_id}
description: test ipv4 billing network description 3 ${unique_id}
reseller_id: 1
blocks:
-
ip: '10.0.5.9'
mask: 24
status: active
retain:
billingnetwork_path: header.location
conditions:
is:
code: 201
#GET billingnetwork
-
name: fetch POSTed billingnetwork
type: item
method: GET
path: '/${billingnetwork_path}'
retain:
billingnetwork_ipv4_3: body
perl_code: !!perl/code |
{
my ($retained) = @_;
delete $retained->{billingnetwork_ipv4_3}->{id};
delete $retained->{billingnetwork_ipv4_3}->{_links};
}
conditions:
is:
code: 200
is_deeply:
'${billingnetwork_ipv4_3}': *content
#POST test billingnetwork
-
name: POST test billingnetwork
type: item
method: POST
path: /api/billingnetworks/
header:
Content-Type: application/json
Prefer: return=representation
content: &content
name: test ipv4 billing network 4 ${unique_id}
description: test ipv4 billing network description 4 ${unique_id}
reseller_id: 1
blocks:
-
ip: '10.0.6.9'
mask: 24
status: active
retain:
billingnetwork_path: header.location
conditions:
is:
code: 201
#GET billingnetwork
-
name: fetch POSTed billingnetwork
type: item
method: GET
path: '/${billingnetwork_path}'
retain:
billingnetwork_ipv4_4: body
perl_code: !!perl/code |
{
my ($retained) = @_;
delete $retained->{billingnetwork_ipv4_4}->{id};
delete $retained->{billingnetwork_ipv4_4}->{_links};
}
conditions:
is:
code: 200
is_deeply:
'${billingnetwork_ipv4_4}': *content
#POST test billingnetwork
-
name: POST test billingnetwork
type: item
method: POST
path: /api/billingnetworks/
header:
Content-Type: application/json
Prefer: return=representation
content: &content
name: test ipv4 billing network 5 ${unique_id}
description: test ipv4 billing network description 5 ${unique_id}
reseller_id: 1
blocks:
-
ip: '10.0.4.7'
mask: 26
-
ip: '10.0.4.99'
mask: 26
-
ip: '10.0.5.9'
mask: 24
-
ip: '10.0.6.9'
mask: 24
status: active
retain:
billingnetwork_path: header.location
conditions:
is:
code: 201
#GET billingnetwork
-
name: fetch POSTed billingnetwork
type: item
method: GET
path: '/${billingnetwork_path}'
retain:
billingnetwork_ipv4_5: body
perl_code: !!perl/code |
{
my ($retained) = @_;
delete $retained->{billingnetwork_ipv4_5}->{id};
delete $retained->{billingnetwork_ipv4_5}->{_links};
}
conditions:
is:
code: 200
is_deeply:
'${billingnetwork_ipv4_5}': *content
#compare filtered collection
-
name: compare filtered collection
type: item
method: GET
path: '/api/billingnetworks/?page=1&rows=5&ip=fdfe::5a55:caff:fefa:9089&name=%25${unique_id}'
retain:
collection: body
perl_code: !!perl/code |
{
my ($retained) = @_;
$retained->{expected} = [ $retained->{billingnetwork_ipv6_1}, $retained->{billingnetwork_ipv6_4} ];
map { delete $_->{id} } @{$retained->{collection}->{'_embedded'}->{'ngcp:billingnetworks'}};
map { delete $_->{_links} } @{$retained->{collection}->{'_embedded'}->{'ngcp:billingnetworks'}};
}
conditions:
is:
code: 200
is_deeply:
'${collection}._embedded.ngcp:billingnetworks': ${expected}
#compare filtered collection
-
name: compare filtered collection
type: item
method: GET
path: '/api/billingnetworks/?page=1&rows=5&ip=10.0.4.0&name=%25${unique_id}'
retain:
collection: body
perl_code: !!perl/code |
{
my ($retained) = @_;
$retained->{expected} = [ $retained->{billingnetwork_ipv4_1}, $retained->{billingnetwork_ipv4_5} ];
map { delete $_->{id} } @{$retained->{collection}->{'_embedded'}->{'ngcp:billingnetworks'}};
map { delete $_->{_links} } @{$retained->{collection}->{'_embedded'}->{'ngcp:billingnetworks'}};
}
conditions:
is:
code: 200
is_deeply:
'${collection}._embedded.ngcp:billingnetworks': ${expected}
#compare filtered collection
-
name: compare filtered collection
type: item
method: GET
path: '/api/billingnetworks/?page=1&rows=5&ip=10.0.4.64&name=%25${unique_id}'
retain:
collection: body
perl_code: !!perl/code |
{
my ($retained) = @_;
$retained->{expected} = [ $retained->{billingnetwork_ipv4_2}, $retained->{billingnetwork_ipv4_5} ];
map { delete $_->{id} } @{$retained->{collection}->{'_embedded'}->{'ngcp:billingnetworks'}};
map { delete $_->{_links} } @{$retained->{collection}->{'_embedded'}->{'ngcp:billingnetworks'}};
}
conditions:
is:
code: 200
is_deeply:
'${collection}._embedded.ngcp:billingnetworks': ${expected}
#compare filtered collection
-
name: compare filtered collection
type: item
method: GET
path: '/api/billingnetworks/?page=1&rows=5&ip=10.0.5.255&name=%25${unique_id}'
retain:
collection: body
perl_code: !!perl/code |
{
my ($retained) = @_;
$retained->{expected} = [ $retained->{billingnetwork_ipv4_3}, $retained->{billingnetwork_ipv4_5} ];
map { delete $_->{id} } @{$retained->{collection}->{'_embedded'}->{'ngcp:billingnetworks'}};
map { delete $_->{_links} } @{$retained->{collection}->{'_embedded'}->{'ngcp:billingnetworks'}};
}
conditions:
is:
code: 200
is_deeply:
'${collection}._embedded.ngcp:billingnetworks': ${expected}
#compare filtered collection
-
name: compare filtered collection
type: item
method: GET
path: '/api/billingnetworks/?page=1&rows=5&ip=10.0.6.255&name=%25${unique_id}'
retain:
collection: body
perl_code: !!perl/code |
{
my ($retained) = @_;
$retained->{expected} = [ $retained->{billingnetwork_ipv4_4}, $retained->{billingnetwork_ipv4_5} ];
map { delete $_->{id} } @{$retained->{collection}->{'_embedded'}->{'ngcp:billingnetworks'}};
map { delete $_->{_links} } @{$retained->{collection}->{'_embedded'}->{'ngcp:billingnetworks'}};
}
conditions:
is:
code: 200
is_deeply:
'${collection}._embedded.ngcp:billingnetworks': ${expected}

@ -0,0 +1,35 @@
---
#get a subscriber for testing
-
name: get a subscriber for testing
type: item
method: GET
path: '/api/subscribers/?page=1&rows=1&order_by=id&order_by_direction=desc'
retain:
subscriber: body
perl_code: !!perl/code |
{
my ($retained) = @_;
my $subscriber = $retained->{subscriber}->{'_embedded'}->{'ngcp:subscribers'}->[0];
$retained->{subscriber} = $subscriber;
$retained->{subscriber_id} = $subscriber->{id};
}
conditions:
is:
code: 200
ok:
'${subscriber}.id': defined
#fetch calls for subscriber
-
name: get a subscriber for testing
type: item
method: GET
path: '/api/calls/?page=1&rows=10&subscriber_id=${subscriber_id}'
retain:
calls: body
conditions:
is:
code: 200
ok:
'${calls}.total_count': defined

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -0,0 +1,105 @@
package NGCP::TestFramework::RequestBuilder;
use strict;
use warnings;
use HTTP::Request;
use Cpanel::JSON::XS;
use Moose;
use Data::Dumper;
has 'base_uri' => (
isa => 'Str',
is => 'ro'
);
sub build {
my ( $self, $args ) = @_;
my @methods = ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'HEAD', 'CONNECT', 'TRACE'];
if ( !$args->{method} && !grep { $_ eq $args->{method} } @methods ) {
return {
success => 0,
message => 'HTTP method missing or incorrect!'
};
}
if ( !$args->{path} ) {
return {
success => 0,
message => 'Path missing!'
};
}
$self->_replace_vars($args);
my $req = HTTP::Request->new( $args->{method}, $self->base_uri.$args->{path} );
grep { $req->header( $_ => $args->{header}->{$_} ) } keys %{$args->{header}} if $args->{header};
$req->content( encode_json( $args->{content} ) ) if $args->{content};
return $req;
}
sub _replace_vars {
my ( $self, $args ) = @_;
# substitute variables in path
if ( $args->{path} =~ /\$\{(.*)\}/ ) {
$args->{path} =~ s/\$\{(.*)\}/$args->{retain}->{$1}/;
}
# substitute variables in content
if ( $args->{content} ) {
if ( ref $args->{content} eq 'HASH' ) {
foreach my $content_key (keys %{$args->{content}}) {
if ( $args->{content}->{$content_key} && $args->{content}->{$content_key} =~ /\$\{(.*)\}$/ ) {
if ( ref $args->{retain}->{$1} eq 'ARRAY' || ref $args->{retain}->{$1} eq 'HASH' ) {
$args->{content}->{$content_key} = $args->{retain}->{$1};
}
else {
$args->{content}->{$content_key} =~ s/\$\{(.*)\}/$args->{retain}->{$1}/;
}
}
elsif ( $args->{content}->{$content_key} && $args->{content}->{$content_key} =~ /^\$\{(.*)\}\..+/ ) {
my @splitted_values = split (/\./, $args->{content}->{$content_key});
$args->{content}->{$content_key} = $self->_retrieve_from_composed_key( \@splitted_values, $args->{retain} );
}
}
}
elsif ( ref $args->{content} eq 'ARRAY' ) {
foreach my $content ( @{$args->{content}} ) {
foreach my $content_key (keys %$content) {
if ( $content->{$content_key} && $content->{$content_key} =~ /\$\{(.*)\}$/ ) {
if ( ref $args->{retain}->{$1} eq 'ARRAY' || ref $args->{retain}->{$1} eq 'HASH' ) {
$content->{$content_key} = $args->{retain}->{$1};
}
else {
$content->{$content_key} =~ s/\$\{(.*)\}/$args->{retain}->{$1}/;
}
}
elsif ( $content->{$content_key} && $content->{$content_key} =~ /^\$\{(.*)\}\..+/ ) {
my @splitted_values = split (/\./, $content->{$content_key});
$content->{$content_key} = $self->_retrieve_from_composed_key( \@splitted_values, $args->{retain} );
}
}
}
}
else {
if ( $args->{content} =~ /\$\{(.*)\}/ ) {
$args->{content} = $args->{retain}->{$1};
}
}
}
}
sub _retrieve_from_composed_key {
my ( $self, $splitted_values, $retained ) = @_;
if ( $splitted_values->[0] =~ /\$\{(.*)\}/ ) {
my $value = $retained->{$1};
grep { $value = $value->{$splitted_values->[$_]} } (1..(scalar @$splitted_values - 1));
return $value;
}
}
1;

@ -0,0 +1,115 @@
package NGCP::TestFramework::TestExecutor;
use strict;
use warnings;
use Cpanel::JSON::XS;
use Data::Walk;
use Moose;
use Test::More;
use Data::Dumper;
sub run_tests {
my ( $self, $conditions, $result, $retained, $test_name ) = @_;
foreach my $condition ( keys %$conditions ) {
if ( $condition eq 'is' ) {
while ( my ( $check_param, $check_value ) = each %{$conditions->{$condition}} ) {
if ( $check_value =~ /^\$\{(.*)\}$/ ) {
$check_value = $retained->{$1};
}
if ( $check_param =~ /^\$\{(.*)\}$/ ) {
$check_param = $retained->{$1};
}
if ( $check_param =~ /.+\..+/ ) {
my @splitted_values = split (/\./, $check_param);
$check_param = $self->_retrieve_from_composed_key( $result, \@splitted_values, $retained );
is ($check_param, $check_value, $test_name);
}
elsif ( $check_param eq 'code' ) {
is ($result->code, $check_value, $test_name);
}
elsif ( $check_param eq 'header' ) {
foreach my $header_condition ( keys %{$conditions->{$condition}->{$check_param}} ) {
is ($result->header($header_condition), $check_value->{$header_condition}, $test_name);
}
}
else {
is ($check_param, $check_value, $test_name);
}
}
}
elsif ( $condition eq 'ok' ) {
foreach my $check_param (keys %{$conditions->{$condition}}) {
if ( $check_param eq 'options' ) {
my $body = decode_json($result->decoded_content);
my @hopts = split /\s*,\s*/, $result->header('Allow');
ok(exists $body->{methods} && ref $body->{methods} eq "ARRAY", $test_name);
foreach my $opt(@{$conditions->{$condition}->{$check_param}}) {
ok(grep { /^$opt$/ } @hopts, $test_name);
ok(grep { /^$opt$/ } @{ $body->{methods} }, $test_name);
}
}
if ( $conditions->{$condition}->{$check_param} eq 'defined' || $conditions->{$condition}->{$check_param} eq 'undefined') {
if ( $check_param =~ /.+\..+/ ) {
my @splitted_values = split (/\./, $check_param);
my $check_value = $self->_retrieve_from_composed_key( $result, \@splitted_values, $retained );
$conditions->{$condition}->{$check_param} eq 'defined' ?
ok(defined $check_value, $test_name) : ok(!defined $check_value, $test_name);
}
}
}
}
elsif ( $condition eq 'like' ) {
foreach my $check_param (keys %{$conditions->{$condition}}) {
if ( $check_param =~ /.+\..+/ ) {
my @splitted_values = split (/\./, $check_param);
my $check_value = $self->_retrieve_from_composed_key( $result, \@splitted_values, $retained );
like ($check_value, qr/$conditions->{$condition}->{$check_param}/, $test_name);
}
}
}
elsif ( $condition eq 'is_deeply' ) {
foreach my $check_param (keys %{$conditions->{$condition}}) {
*replace_variables = sub {
if ( ref $_ eq 'HASH' ) {
while ( my ( $key, $value ) = each %$_ ) {
if ( $value && $value =~ /\$\{(.*)\}/ ) {
$_->{$key} = $retained->{$1};
}
}
}
};
walkdepth {wanted => \&replace_variables}, $conditions->{$condition}->{$check_param};
if ( $conditions->{$condition}->{$check_param} =~ /^\$\{(.*)\}$/ ) {
$conditions->{$condition}->{$check_param} = $retained->{$1};
}
my $check_value;
if ( $check_param=~ /^\$\{(.*)\}$/ ) {
$check_value = $retained->{$1};
}
if ( $check_param =~ /.+\..+/ ) {
my @splitted_values = split (/\./, $check_param);
$check_value = $self->_retrieve_from_composed_key( $result, \@splitted_values, $retained );
}
is_deeply ($check_value, $conditions->{$condition}->{$check_param}, $test_name);
}
}
}
}
sub _retrieve_from_composed_key {
my ( $self, $result, $splitted_values, $retained ) = @_;
if ( $splitted_values->[0] eq 'body' ) {
my $body = decode_json($result->decoded_content);
return $body->{$splitted_values->[1]};
}
elsif ( $splitted_values->[0] =~ /\$\{(.*)\}/ ) {
my $value = $retained->{$1};
grep { $value = $value->{$splitted_values->[$_]} } (1..(scalar @$splitted_values - 1));
return $value;
}
}
1;

@ -0,0 +1,81 @@
use lib '..';
use strict;
use warnings;
use NGCP::TestFramework;
use Test::More;
use threads;
use Thread::Queue;
use Data::Dumper;
my $server = $ARGV[0] || undef;
my $selected = $ARGV[1] || 'all';
if ( !$server ){
print "Usage: \$ perl testrunner.pl [<testsystem>] [<testset>]\n";
print "Usage example: \$ perl testrunner.pl 192.168.88.162\n";
print "Usage example: \$ perl testrunner.pl 192.168.88.162 fast\n";
print "Possible test set: all, stable, fast, t/lib/NGCP/TestFramework/Interface/Contracts.yaml\n";
print "Default test set: all\n";
exit(1);
}
my @test_files;
if ( $selected eq 'stable' ) {
print "Test selection: stable\n";
}
elsif ( $selected eq 'fast' ) {
print "Test selection: fast\n";
}
elsif ( $selected eq 'all' ) {
print "Test selection: all\n";
map { push @test_files, "TestFramework/Interface/$_" } `ls TestFramework/Interface`;
}
else{
print "Test selection: $selected\n";
push @test_files, $selected;
}
print "################################################################################\n";
print "Finished main setup, now running tests ...\n";
$ENV{CATALYST_SERVER_SUB}="https://$server:443";
$ENV{CATALYST_SERVER}="https://$server:1443";
$ENV{NGCP_SESSION_ID}=int(rand(1000)).time;
my @threads;
my $tests_queue = Thread::Queue->new();
for ( @test_files ) {
$tests_queue->enqueue($_);
}
$tests_queue->end();
for (1..2) {
push @threads, threads->create( {'context' => 'void'}, \&worker, $tests_queue );
}
foreach ( @threads ){
$_->join();
}
done_testing();
sub worker {
my ($tests_queue) = @_;
while ( my $test_file = $tests_queue->dequeue_nb() ) {
my $start_time = time;
print "Running tests from $test_file\n";
my $test_framework = NGCP::TestFramework->new( {file_path => $test_file} );
my $result_code = $test_framework->run();
my $total_time = time - $start_time;
print "Finished test execution for $test_file, test execution returned with exit code $result_code.\n";
print "Tests for $test_file took $total_time seconds.\n";
}
}
1;
Loading…
Cancel
Save