The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/env perl

#########################

use strict;
use Test::More;
use IO::Socket::UNIX qw( SOCK_STREAM SOMAXCONN );
use Data::Dumper;
use JSON::XS;

BEGIN {
  eval {require threads;};
  if ( $@ ) {
    plan skip_all => 'need threads support for testing a real socket'
  }
  elsif( $^O eq 'MSWin32' ) {
      plan skip_all => 'no sockets on windows';
  }
  else{
    plan tests => 109
  }
}

use File::Temp;
BEGIN { use_ok('Monitoring::Livestatus') };

#########################
# Normal Querys
#########################
my $line_seperator      = 10;
my $column_seperator    = 0;
my $test_data           = [ ["alias","name","contacts"],       # table header
                            ["alias1","host1","contact1"],     # row 1
                            ["alias2","host2","contact2"],     # row 2
                            ["alias3","host3","contact3"],     # row 3
                          ];
my $test_hostgroups     = [['']]; # test one row with no data

# expected results
my $selectall_arrayref1 = [ [ 'alias1', 'host1', 'contact1' ],
                            [ 'alias2', 'host2', 'contact2' ],
                            [ 'alias3', 'host3', 'contact3' ]
                          ];
my $selectall_arrayref2 = [
                            { 'contacts' => 'contact1', 'name' => 'host1', 'alias' => 'alias1' },
                            { 'contacts' => 'contact2', 'name' => 'host2', 'alias' => 'alias2' },
                            { 'contacts' => 'contact3', 'name' => 'host3', 'alias' => 'alias3' }
                          ];
my $selectall_hashref   = {
                            'host1' => { 'contacts' => 'contact1', 'name' => 'host1', 'alias' => 'alias1' },
                            'host2' => { 'contacts' => 'contact2', 'name' => 'host2', 'alias' => 'alias2' },
                            'host3' => { 'contacts' => 'contact3', 'name' => 'host3', 'alias' => 'alias3' }
                          };
my $selectcol_arrayref1 = [ 'alias1', 'alias2', 'alias3' ];
my $selectcol_arrayref2 = [ 'alias1', 'host1', 'alias2', 'host2', 'alias3', 'host3' ];
my $selectcol_arrayref3 = [ 'alias1', 'host1', 'contact1', 'alias2', 'host2', 'contact2', 'alias3', 'host3', 'contact3' ];
my @selectrow_array     = ( 'alias1', 'host1', 'contact1' );
my $selectrow_arrayref  = [ 'alias1', 'host1', 'contact1' ];
my $selectrow_hashref   = { 'contacts' => 'contact1', 'name' => 'host1', 'alias' => 'alias1' };

#########################
# Single Querys
#########################
my $single_statement    = "GET hosts\nColumns: alias\nFilter: name = host1";
my $selectscalar_value  = 'alias1';

#########################
# Stats Querys
#########################
my $stats_statement = "GET services\nStats: state = 0\nStats: state = 1\nStats: state = 2\nStats: state = 3";
my $stats_data      = [[4297,13,9,0]];

# expected results
my $stats_selectall_arrayref1 = [ [4297,13,9,0] ];
my $stats_selectall_arrayref2 = [ { 'state = 0' => '4297', 'state = 1' => '13', 'state = 2' => '9', 'state = 3' => 0 } ];
my $stats_selectcol_arrayref  = [ '4297' ];
my @stats_selectrow_array     = ( '4297', '13', '9', '0' );
my $stats_selectrow_arrayref  = [ '4297', '13', '9', '0' ];
my $stats_selectrow_hashref   = { 'state = 0' => '4297', 'state = 1' => '13', 'state = 2' => '9', 'state = 3' => 0 };

#########################
# Empty Querys
#########################
my $empty_statement = "GET services\nFilter: description = empty";

# expected results
my $empty_selectall_arrayref = [];
my $empty_selectcol_arrayref = [];
my @empty_selectrow_array;
my $empty_selectrow_arrayref;
my $empty_selectrow_hashref;


#########################
# get a temp file from File::Temp and replace it with our socket
my $fh = File::Temp->new(UNLINK => 0);
my $socket_path = $fh->filename;
unlink($socket_path);
my $thr1 = threads->create('create_socket', 'unix');
#########################
# get a temp file from File::Temp and replace it with our socket
my $server              = 'localhost:32987';
my $thr2 = threads->create('create_socket', 'inet');
sleep(1);

#########################
my $objects_to_test = {
  # create unix object with hash args
  'unix_hash_args' => Monitoring::Livestatus->new(
                                      verbose             => 0,
                                      socket              => $socket_path,
                                      line_seperator      => $line_seperator,
                                      column_seperator    => $column_seperator,
                                    ),

  # create unix object with a single arg
  'unix_single_arg' => Monitoring::Livestatus::UNIX->new( $socket_path ),

  # create inet object with hash args
  'inet_hash_args' => Monitoring::Livestatus->new(
                                      verbose             => 0,
                                      server              => $server,
                                      line_seperator      => $line_seperator,
                                      column_seperator    => $column_seperator,
                                    ),

  # create inet object with a single arg
  'inet_single_arg' => Monitoring::Livestatus::INET->new( $server ),

};

for my $key (keys %{$objects_to_test}) {
    my $ml = $objects_to_test->{$key};
    isa_ok($ml, 'Monitoring::Livestatus');

    # we dont need warnings for testing
    $ml->warnings(0);

    ##################################################
    # test settings
    my $rt = $ml->verbose(1);
    is($rt, '0', 'enable verbose');
    $rt = $ml->verbose(0);
    is($rt, '1', 'disable verbose');

    $rt = $ml->errors_are_fatal(0);
    is($rt, '1', 'disable errors_are_fatal');
    $rt = $ml->errors_are_fatal(1);
    is($rt, '0', 'enable errors_are_fatal');

    ##################################################
    # do some sample querys
    my $statement = "GET hosts";

    #########################
    my $ary_ref  = $ml->selectall_arrayref($statement);
    is_deeply($ary_ref, $selectall_arrayref1, 'selectall_arrayref($statement)')
        or diag("got: ".Dumper($ary_ref)."\nbut expected ".Dumper($selectall_arrayref1));

    #########################
    $ary_ref  = $ml->selectall_arrayref($statement, { Slice => {} });
    is_deeply($ary_ref, $selectall_arrayref2, 'selectall_arrayref($statement, { Slice => {} })')
        or diag("got: ".Dumper($ary_ref)."\nbut expected ".Dumper($selectall_arrayref2));

    #########################
    my $hash_ref = $ml->selectall_hashref($statement, 'name');
    is_deeply($hash_ref, $selectall_hashref, 'selectall_hashref($statement, "name")')
        or diag("got: ".Dumper($hash_ref)."\nbut expected ".Dumper($selectall_hashref));

    #########################
    $ary_ref  = $ml->selectcol_arrayref($statement);
    is_deeply($ary_ref, $selectcol_arrayref1, 'selectcol_arrayref($statement)')
        or diag("got: ".Dumper($ary_ref)."\nbut expected ".Dumper($selectcol_arrayref1));

    #########################
    $ary_ref = $ml->selectcol_arrayref($statement, { Columns=>[1,2] });
    is_deeply($ary_ref, $selectcol_arrayref2, 'selectcol_arrayref($statement, { Columns=>[1,2] })')
        or diag("got: ".Dumper($ary_ref)."\nbut expected ".Dumper($selectcol_arrayref2));

    $ary_ref = $ml->selectcol_arrayref($statement, { Columns=>[1,2,3] });
    is_deeply($ary_ref, $selectcol_arrayref3, 'selectcol_arrayref($statement, { Columns=>[1,2,3] })')
        or diag("got: ".Dumper($ary_ref)."\nbut expected ".Dumper($selectcol_arrayref3));

    #########################
    my @row_ary  = $ml->selectrow_array($statement);
    is_deeply(\@row_ary, \@selectrow_array, 'selectrow_array($statement)')
        or diag("got: ".Dumper(\@row_ary)."\nbut expected ".Dumper(\@selectrow_array));

    #########################
    $ary_ref  = $ml->selectrow_arrayref($statement);
    is_deeply($ary_ref, $selectrow_arrayref, 'selectrow_arrayref($statement)')
        or diag("got: ".Dumper($ary_ref)."\nbut expected ".Dumper($selectrow_arrayref));

    #########################
    $hash_ref = $ml->selectrow_hashref($statement);
    is_deeply($hash_ref, $selectrow_hashref, 'selectrow_hashref($statement)')
        or diag("got: ".Dumper($hash_ref)."\nbut expected ".Dumper($selectrow_hashref));

    ##################################################
    # stats querys
    ##################################################
    $ary_ref  = $ml->selectall_arrayref($stats_statement);
    is_deeply($ary_ref, $stats_selectall_arrayref1, 'selectall_arrayref($stats_statement)')
        or diag("got: ".Dumper($ary_ref)."\nbut expected ".Dumper($stats_selectall_arrayref1));

    $ary_ref  = $ml->selectall_arrayref($stats_statement, { Slice => {} });
    is_deeply($ary_ref, $stats_selectall_arrayref2, 'selectall_arrayref($stats_statement, { Slice => {} })')
        or diag("got: ".Dumper($ary_ref)."\nbut expected ".Dumper($stats_selectall_arrayref2));

    $ary_ref  = $ml->selectcol_arrayref($stats_statement);
    is_deeply($ary_ref, $stats_selectcol_arrayref, 'selectcol_arrayref($stats_statement)')
        or diag("got: ".Dumper($ary_ref)."\nbut expected ".Dumper($stats_selectcol_arrayref));

    @row_ary = $ml->selectrow_array($stats_statement);
    is_deeply(\@row_ary, \@stats_selectrow_array, 'selectrow_arrayref($stats_statement)')
        or diag("got: ".Dumper(\@row_ary)."\nbut expected ".Dumper(\@stats_selectrow_array));

    $ary_ref  = $ml->selectrow_arrayref($stats_statement);
    is_deeply($ary_ref, $stats_selectrow_arrayref, 'selectrow_arrayref($stats_statement)')
        or diag("got: ".Dumper($ary_ref)."\nbut expected ".Dumper($stats_selectrow_arrayref));

    $hash_ref = $ml->selectrow_hashref($stats_statement);
    is_deeply($hash_ref, $stats_selectrow_hashref, 'selectrow_hashref($stats_statement)')
        or diag("got: ".Dumper($hash_ref)."\nbut expected ".Dumper($stats_selectrow_hashref));

    my $scal = $ml->selectscalar_value($single_statement);
    is($scal, $selectscalar_value, 'selectscalar_value($single_statement)')
        or diag("got: ".Dumper($scal)."\nbut expected ".Dumper($selectscalar_value));

    ##################################################
    # empty querys
    ##################################################
    $ary_ref  = $ml->selectall_arrayref($empty_statement);
    is_deeply($ary_ref, $empty_selectall_arrayref, 'selectall_arrayref($empty_statement)')
        or diag("got: ".Dumper($ary_ref)."\nbut expected ".Dumper($empty_selectall_arrayref));

    $ary_ref  = $ml->selectcol_arrayref($empty_statement);
    is_deeply($ary_ref, $empty_selectcol_arrayref, 'selectcol_arrayref($empty_statement)')
        or diag("got: ".Dumper($ary_ref)."\nbut expected ".Dumper($empty_selectcol_arrayref));

    @row_ary = $ml->selectrow_array($empty_statement);
    is_deeply(\@row_ary, \@empty_selectrow_array, 'selectrow_arrayref($empty_statement)')
        or diag("got: ".Dumper(\@row_ary)."\nbut expected ".Dumper(\@empty_selectrow_array));

    $ary_ref  = $ml->selectrow_arrayref($empty_statement);
    is_deeply($ary_ref, $empty_selectrow_arrayref, 'selectrow_arrayref($empty_statement)')
        or diag("got: ".Dumper($ary_ref)."\nbut expected ".Dumper($empty_selectrow_arrayref));

    $hash_ref = $ml->selectrow_hashref($empty_statement);
    is_deeply($hash_ref, $empty_selectrow_hashref, 'selectrow_hashref($empty_statement)')
        or diag("got: ".Dumper($hash_ref)."\nbut expected ".Dumper($empty_selectrow_hashref));
        
    ##################################################
    # empty rows and columns
    ##################################################
    my $empty_hostgroups_stm = "GET hostgroups\nColumns: members";
    $ary_ref  = $ml->selectall_arrayref($empty_hostgroups_stm);
    is_deeply($ary_ref, $test_hostgroups, 'selectall_arrayref($empty_hostgroups_stm)')
        or diag("got: ".Dumper($ary_ref)."\nbut expected ".Dumper($test_hostgroups));

}

##################################################
# exit threads
$thr1->kill('KILL')->detach();
$thr2->kill('KILL')->detach();
exit;


#########################
# SUBS
#########################
# test socket server
sub create_socket {
    my $type = shift;
    my $listener;

    $SIG{'KILL'} = sub { threads->exit(); };

    if($type eq 'unix') {
      print "creating unix socket\n";
      $listener = IO::Socket::UNIX->new(
                                        Type    => SOCK_STREAM,
                                        Listen  => SOMAXCONN,
                                        Local   => $socket_path,
                                      ) or die("failed to open $socket_path as test socket: $!");
    }
    elsif($type eq 'inet') {
            print "creating tcp socket\n";
      $listener = IO::Socket::INET->new(
                                        LocalAddr  => $server,
                                        Proto      => 'tcp',
                                        Listen     => 1,
                                        Reuse      => 1,
                                      ) or die("failed to listen on $server: $!");
    } else {
      die("unknown type");
    }
    while( my $socket = $listener->accept() or die('cannot accept: $!') ) {
        my $recv = "";
        while(<$socket>) { $recv .= $_; last if $_ eq "\n" }
        my $data;
        my $status = 200;
        if($recv =~ m/^GET .*?\s+Filter:.*?empty/m) {
            $data = '';
        }
        elsif($recv =~ m/^GET hosts\s+Columns: alias/m) {
            my @data = @{$test_data}[1..3];
            $data = encode_json(\@data)."\n";
        }
        elsif($recv =~ m/^GET hosts\s+Columns: name/m) {
            $data = encode_json(\@{$test_data}[1..3])."\n";
        }
        elsif($recv =~ m/^GET hosts/) {
            $data = encode_json($test_data)."\n";
        }
        elsif($recv =~ m/^GET hostgroups/) {
            $data = encode_json(\@{$test_hostgroups})."\n";
        }
        elsif($recv =~ m/^GET services/ and $recv =~ m/Stats:/m) {
            $data = encode_json(\@{$stats_data})."\n";
        }
        my $content_length = sprintf("%11s", length($data));
        print $socket $status." ".$content_length."\n";
        print $socket $data;
        close($socket);
    }
    unlink($socket_path);
}