package NGCP::API::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 Log::Log4perl qw(:easy); use LWP::Protocol::http; push @LWP::Protocol::http::EXTRA_SOCK_OPTS, MaxHeaderLines => 256; use Moose; use Net::Domain qw(hostfqdn); use Test::More; use URI; use YAML::XS qw(LoadFile); use Data::Dumper; use NGCP::API::TestFramework::RequestBuilder; use NGCP::API::TestFramework::Client; use NGCP::API::TestFramework::TestExecutor; has 'file_path' => ( isa => 'Str', is => 'rw' ); has 'unique_id' => ( isa => 'Str', is => 'rw' ); sub run { my ( $self ) = @_; my $test_case_name = $self->file_path; $test_case_name =~ s/.+\/(\w+).yaml/$1/; chomp $test_case_name; Log::Log4perl->easy_init( { level => $DEBUG, file => ">>/results/$test_case_name.log", category => "NGCP::API::TestFramework::Client", layout => '%d - %F{1} line %L - %M: %m%n' }, { level => $DEBUG, file => ">>/results/$test_case_name.log", category => "NGCP::API::TestFramework::RequestBuilder", layout => '%d - %F{1} line %L - %M: %m%n' }, { level => $DEBUG, file => ">>/results/$test_case_name.log", category => "NGCP::API::TestFramework::TestExecutor", layout => '%d - %F{1} line %L - %M: %m%n' }, { level => $DEBUG, file => ">/results/$test_case_name.log", layout => '%d - %F{1} line %L - %M: %m%n' } ); INFO( "Started running the Test TestFramework!" ); unless ( $self->file_path ) { return; } $YAML::XS::DumpCode = 1; $YAML::XS::LoadCode = 1; $YAML::XS::UseCode = 1; $YAML::XS::LoadBlessed = 1; INFO( "Loading YAML file." ); my $testing_data = LoadFile($self->file_path); INFO( "Setting up the base URI." ); my $base_uri = $ENV{CATALYST_SERVER} || ('https://'.hostfqdn.':4443'); INFO( "Setting up the RequestBuilder." ); my $request_builder = NGCP::API::TestFramework::RequestBuilder->new({ base_uri => $base_uri }); INFO( "Setting up the Client." ); my $client = NGCP::API::TestFramework::Client->new( { uri => $base_uri } ); INFO( "Setting up the Test Executor." ); my $test_executor = NGCP::API::TestFramework::TestExecutor->new(); # If $self->{retained} is already defined, use its previously collected variables. # Otherwise, initialize it. # In any case, update with received "unique_id" for fields that need to be unique. my $retained = $self->{retained} //= {}; $retained->{unique_id} = $self->unique_id; my $test_case_result = { success => 1, error_count => 0 }; foreach my $test ( @$testing_data ) { $self->process_test( $test_case_name, $base_uri, $request_builder, $client, $test_executor, $retained, $test_case_result, $test ); } return $test_case_result; } sub process_test{ my ( $self, $test_case_name, $base_uri, $request_builder, $client, $test_executor, $retained, $test_case_result, $test ) = @_; DEBUG( "In test '".$test->{name}."'" ); return if ( $test->{skip} ); if ( !$self->_variables_available( $retained, $test ) ) { my $warning = "Warning: Required variable not found for test: $test->{name}.\n"; WARN( $warning ); print $warning; push @{$test_case_result->{warnings}}, $warning; return; } if ( $test->{type} eq 'include' ) { if ( $test->{perl_code} ){ INFO( "Executing perl code." ); my $sub = $test->{perl_code}; $sub->( $retained ); } INFO( "Loading include YAML file." ); my $include_testing_data = LoadFile("./t/api-rest2/Utils/".$test->{file}); foreach my $include_test ( @$include_testing_data ) { $self->process_test( $include_test->{name}, $base_uri, $request_builder, $client, $test_executor, $retained, $test_case_result, $include_test ); } return; } # build request my $request = $request_builder->build({ method => $test->{method}, path => $test->{path}, header => $test->{header} || undef, content => $test->{content} || undef, retain => $retained }); DEBUG ( "Request: ".Dumper $request ); # handle separate types if ( $test->{type} eq 'item' ) { INFO( "Performing request." ); my $result = $client->perform_request($request); DEBUG ( "Result: ".Dumper $result ); if ( $test->{retain} ) { INFO( "Storing retained variables." ); $self->_get_retained_elements( $test->{retain}, $retained, $result ); } if ( $test->{perl_code} ){ INFO( "Executing perl code." ); my $sub = $test->{perl_code}; $sub->( $retained ); } if ( $test->{conditions} ) { INFO( "Running tests." ); my $tests_result = $test_executor->run_tests( $test->{conditions}, $result, $retained, $test->{name} ); if ( !$tests_result->{success} ) { map { print $_." in test case '$test_case_name'\n" } @{$tests_result->{errors}}; $test_case_result->{success} = 0; $test_case_result->{error_count} += scalar @{$tests_result->{errors}}; } if ( $tests_result->{warnings} && scalar @{$tests_result->{warnings}} ) { my @warnings = @{$tests_result->{warnings}}; WARN( @warnings ); print @warnings; push @{$test_case_result->{warnings}}, @warnings; return; } } } elsif ( $test->{type} eq 'batch' ) { foreach my $iteration ( 1..$test->{iterations} ) { INFO( "Performing request for iteration $iteration." ); my $result = $client->perform_request($request); DEBUG ( "Result: ".Dumper $result ); if ( $test->{retain} ) { INFO( "Storing retained variables." ); $self->_get_retained_elements( $test->{retain}, $retained, $result ); } if ( $test->{perl_code} ){ INFO( "Executing perl code." ); my $sub = $test->{perl_code}; $sub->( $retained ); } if ( $test->{conditions} ) { INFO( "Running tests." ); my $tests_result = $test_executor->run_tests( $test->{conditions}, $result, $retained, $test->{name} ); if ( !$tests_result->{success} ) { map { print $_." in test case '$test_case_name'\n" } @{$tests_result->{errors}}; $test_case_result->{success} = 0; $test_case_result->{error_count} += scalar @{$tests_result->{errors}}; } } } } elsif ( $test->{type} eq 'pagination' ) { my $nexturi = $test->{path}; do { INFO( "Performing request for pagination." ); $request->uri( $base_uri.$nexturi ); my $result = $client->perform_request($request); DEBUG ( "Result: ".Dumper $result ); if ( $test->{retain} ) { INFO( "Storing retained variables." ); $self->_get_retained_elements( $test->{retain}, $retained, $result ); } 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'; } if ( $test->{conditions} ) { INFO( "Running tests." ); my $tests_result = $test_executor->run_tests( $test->{conditions}, $result, $retained, $test->{name} ); if ( !$tests_result->{success} ) { map { print $_." in test case '$test_case_name'\n" } @{$tests_result->{errors}}; $test_case_result->{success} = 0; $test_case_result->{error_count} += scalar @{$tests_result->{errors}}; } } if( $body->{_links}->{next}->{href} ) { $nexturi = $body->{_links}->{next}->{href}; } else { $nexturi = undef; } } while ( $nexturi ) } return $test_case_result; } 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} && $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} && $test->{content}->{$content_key} =~ /\$\{(.*)\}/ && !$retained->{$1} ); } } return 1; } 1;