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

use strict;
use warnings;
use bignum;

use Test::More;
use Test::Deep;
use Test::MockTime;

use Data::Dumper;
use DateTime;
use DateTime::TimeZone;
use Time::Moment;
use Storable qw(dclone);

use Parse::Syslog::Line qw/:with_timezones/;

subtest 'If logdate is "in the future" it is actually "in the past"' => sub {

    # default settings
    Test::MockTime::set_fixed_time("2016-05-29T05:00:00Z");
    my $msg = parse_syslog_line(q|<11>Mar  27 01:59:59 11.22.33.44 dhcpd: DHCPINFORM from 172.16.2.137 via vlan3|);

    cmp_deeply($msg, superhashof({
        datetime_str => '2016-03-27T01:59:59',
        datetime_raw => 'Mar  27 01:59:59',
    }), 'date is "in the past" - intuitive behaviour');

    Test::MockTime::set_fixed_time("2016-02-29T05:41:00Z"); #
    $msg = parse_syslog_line(q|<11>Mar  27 01:59:59 11.22.33.44 dhcpd: DHCPINFORM from 172.16.2.137 via vlan3|);

    cmp_deeply($msg, superhashof({
        datetime_str => '2015-03-27T01:59:59',
        datetime_raw => 'Mar  27 01:59:59',
    }), 'date is "in the future" - HTTP::Date assumes it is in the past and finds a date match from previous year');

    done_testing();
};


subtest 'setting named timezone for syslog file' => sub {
    Test::MockTime::set_fixed_time("2016-05-29T05:00:00Z");

    set_syslog_timezone('EST');
    local $Parse::Syslog::Line::DateTimeCreate = 1;
    my $msg = parse_syslog_line(q|2015-09-30T06:26:06.779373-05:00 my-host my-script.pl: {"lunchTime":1443612366.442}|);
    cmp_deeply($msg, superhashof({
        datetime_str => '2015-09-30T06:26:06.779373-0500',
        datetime_raw => '2015-09-30T06:26:06.779373-05:00',
    }), 'By default we will discard timezone present in message.');
    is($msg->{datetime_obj}->iso8601(), '2015-09-30T06:26:06', "Also dt_object doesn't have it...");

    set_syslog_timezone('Europe/Warsaw');
    Test::MockTime::set_fixed_time("2016-09-01T05:00:00Z"); #TZ +02:00
    $msg = parse_syslog_line(q|2015-09-30T06:26:06.779373-05:00 my-host my-script.pl: {"lunchTime":1443612366.442}|);
    cmp_deeply($msg,
    superhashof({
        datetime_str => '2015-09-30T13:26:06.779373+0200',
        datetime_raw => '2015-09-30T06:26:06.779373-05:00',
    }), '...however with NormalizeToUTC we preserve it');
    is($msg->{datetime_obj}->strftime("%Y-%m-%dT%H:%M:%S%6N%z"), '2015-09-30T13:26:06779373+0200', 'but we remember them in dt object');

    $msg = parse_syslog_line(q|<11>Mar  27 01:59:59 11.22.33.44 dhcpd: DHCPINFORM from 172.16.2.137 via vlan3|);
    cmp_deeply($msg, superhashof({
        datetime_str => '2016-03-27T01:59:59+0100',
        datetime_raw => 'Mar  27 01:59:59',
    }), 'msg date is +01:00 despite local timezone being +02:00');

    $msg = parse_syslog_line(q|<11>Mar  27 03:00:01 11.22.33.44 dhcpd: DHCPINFORM from 172.16.2.137 via vlan3|);
    cmp_deeply($msg, superhashof({
        datetime_str => '2016-03-27T03:00:01+0200',
        datetime_raw => 'Mar  27 03:00:01',
    }), 'msg date is +02:00 same as local timezone because of DST (27 March 01:00 UTC - summer time start)');


    Test::MockTime::set_fixed_time("2016-02-29T05:41:00Z"); #TZ +01:00
    $msg = parse_syslog_line(q|<11>Mar  27 01:59:59 11.22.33.44 dhcpd: DHCPINFORM from 172.16.2.137 via vlan3|);
    cmp_deeply($msg, superhashof({
        datetime_str => '2015-03-27T01:59:59+0100',
        datetime_raw => 'Mar  27 01:59:59',
    }), 'msg date is +01:00 same as local timezone');

    $msg = parse_syslog_line(q|<11>Mar  27 03:00:01 11.22.33.44 dhcpd: DHCPINFORM from 172.16.2.137 via vlan3|);
    cmp_deeply($msg, superhashof({
        datetime_str => '2015-03-27T03:00:01+0100',
        datetime_raw => 'Mar  27 03:00:01',
    }), 'msg date is +02:00 but since it is in the future, it jumped back to 2015 where DST change ocurred 2015-03-29');

    Test::MockTime::restore_time();
    done_testing();
};

Test::MockTime::set_fixed_time("2016-01-01T00:00:01Z");
my %utc_syslogs = (
    'Cisco NX-OS'               => q|2015-01-01T11:09:36+02:00 hostname.company.tld : $year Jan  1 11:09:36.290 CET: %ETHPORT-5-IF_DOWN_CFG_CHANGE: Interface Ethernet121/1/1 is down(Config change)|,
    'ISO8601 with micro'        => q|2015-09-30T06:26:06.779373-05:00 my-host my-script.pl: {"lunchTime":1443612366.442}|,
    'ISO8601 with micro - zulu' => q|2015-09-30T06:26:06.779373Z my-host my-script.pl: {"lunchTime":1443612366.442}|,
);
my %expects = (
    'Cisco NX-OS'               => {
        _set_tz => 'EET',
        date => '2015-01-01', 'time' => '11:09:36', offset => '+0200',
        datetime_str => "2015-01-01T11:09:36+0200",
        datetime_utc => '2015-01-01T09:09:36Z',
        epoch        => Time::Moment->from_string("2015-01-01T11:09:36+02:00")->strftime("%s"),
    },
    'ISO8601 with micro'        => {
        _set_tz => 'EST',
        date => '2015-09-30', 'time' => '06:26:06.779373', offset => '-0500',
        datetime_str => "2015-09-30T06:26:06.779373-0500",
        datetime_utc => '2015-09-30T11:26:06.779373Z',
        epoch        => Time::Moment->from_string("2015-09-30T06:26:06.779373-05:00")->epoch . '.77937',
    },
    'ISO8601 with micro - zulu' => {
        date => '2015-09-30', 'time' => '06:26:06.779373', offset => 'Z',
        datetime_str => "2015-09-30T06:26:06.779373Z",
        datetime_utc => '2015-09-30T06:26:06.779373Z',
        epoch        => Time::Moment->from_string("2015-09-30T06:26:06.779373Z")->epoch . '.77937',
    },
);

subtest 'Millisecond resolution' => sub {
    while (my ($case_name, $msg) = each %utc_syslogs) {
        my $exp = dclone($expects{$case_name});
        my $tz  = exists $exp->{_set_tz} ? delete $exp->{_set_tz} : 'UTC';
        delete $exp->{datetime_utc};
        set_syslog_timezone($tz);
        cmp_deeply(
            parse_syslog_line($msg),
            superhashof($exp),
            $case_name,
        );
    }
    done_testing();
};

subtest 'config switching' => sub {
    use_utc_syslog();
    is($Parse::Syslog::Line::NormalizeToUTC, 1, 'config variable OK');
    is(get_syslog_timezone, 'UTC');

    while (my ($case_name, $msg) = each %utc_syslogs) {
        my $exp = dclone($expects{$case_name});
        # Adjust expected to the UTC values
        delete $exp->{_set_tz};
        $exp->{datetime_str} = delete $exp->{datetime_utc};
        $exp->{time}    = ($exp->{datetime_str} =~ /[ T](\d{2}(?::\d{2}){2}(?:\.\d+)?)/)[0];
        $exp->{offset}  = 'Z';
        use YAML;
        cmp_deeply(
            parse_syslog_line($msg),
            superhashof($exp),
            $case_name,
        ) || Dump parse_syslog_line($msg);
    }
    done_testing();
};

done_testing;

sub _set_test_config{
    my ( $config ) = @_;

    # set config values
    while (my ($name, $value) = each %{$config}) {
        no strict 'refs';
        ${"Parse::Syslog::Line::$name"} = $value;
    };

    return;
};

__END__