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

use warnings;
use strict;
use Test::More;
use Test::Fatal;
use Redis::Fast;
use lib 't/tlib';
use Test::SpawnRedisServer;

my ($c, $srv) = redis(timeout => 3); # redis connection timeouts within 3 seconds.
END { $c->() if $c }

# Reconnect once
# when
## 1. reconnect is greater than 0
## 2. and reconnect_on_error returns a value greater than and equal to 0
## 3. and a specified time seconds elapsed

my $sync_call_hset = sub {
    my ($client, $hint)  = @_;
    like (exception { $client->hset(1,1) },
        qr{ERR wrong number of arguments for 'hset' command},
        'syntax error')
        or diag "hint=$hint";
    return 'sync';
};
my $async_call_hset = sub {
    my ($client, $hint)  = @_;
    my $_cb_call_count = 0;
    my $_cb = sub {
        my ($ret, $error) = @_;
        is $error, "ERR wrong number of arguments for 'hset' command"
            or diag "_cb_call_count=$_cb_call_count, hint=$hint";
        ok !$ret
            or diag "_cb_call_count=$_cb_call_count, hint=$hint";
        $_cb_call_count++;
    };
    $client->hset(1,1,$_cb);
    $client->hset(2,2,$_cb);
    $client->wait_all_responses;
    is $_cb_call_count, 2;
    return 'async';
};


# Check a condition 1.

subtest 'reconnect option is 0: reconnect_on_error is not called' => sub {
    for my $call_hset ($sync_call_hset, $async_call_hset) {
        my $cb_call_count = 0;
        my $r = Redis::Fast->new(
            reconnect          => 0, # do not trigger reconnection.
            server             => $srv,
            reconnect_on_error => sub { $cb_call_count++ },
        );
        my $hint = $call_hset->($r, "reconnect is 0");
        is $cb_call_count, 0, 'reconnect_on_error is not called'
            or diag "call=$hint";
    }
};

subtest 'reconnect option is 1: reconnect_on_error is called once' => sub {
    for my $call_hset ($sync_call_hset, $async_call_hset) {
        my $cb_call_count = 0;
        my $r = Redis::Fast->new(
            reconnect          => 1, # trigger reconnection until 1 second elapsed.
            server             => $srv,
            reconnect_on_error => sub {
                my ($error, $ret, $cmd) = @_;

                # increment a counter to test it later.
                $cb_call_count++;

                # tests each argument.
                is $error, "ERR wrong number of arguments for 'hset' command";
                ok !$ret;
                is $cmd, 'HSET';
                return 0; # 0 means invake reconnection as soon as possible.
            },
        );
        my $hint = $call_hset->($r, "reconnect is 1");
        is $cb_call_count, 1, 'reconnect_on_error is called once'
            or diag "call=$hint";
    }
};


# Check a condition 2.

subtest "reconnect_on_error returns -1: redis ERR doesn't trigger reconnection" => sub {
    for my $call_hset ($sync_call_hset, $async_call_hset) {
        my $connect_count = 0;
        my $cb_call_count = 0;
        my $r = Redis::Fast->new(
            reconnect          => 1,
            server             => $srv,
            on_connect         => sub { $connect_count++ },
            reconnect_on_error => sub {
                $cb_call_count++;
                return -1; # reconnect_on_error returns -1
            },
        );
        my $hint = $call_hset->($r, "reconnect_on_error returns -1");
        is $connect_count, 1, "redis ERR doesn't trigger reconnection";
        if ($hint eq 'async') {
            is $cb_call_count, 2, 'call reconnect_on_error each async call for hset'
                or diag "cb_return_value=-1 hint=$hint";
        } else {
            is $cb_call_count, 1, 'call reconnect_on_error once'
                or diag "cb_return_value=-1, hint=$hint";
        }
    }
};

subtest "reconnect_on_error returns 0: redis ERR triggers reconnection" => sub {
    for my $call_hset ($sync_call_hset, $async_call_hset) {
        my $connect_count = 0;
        my $cb_call_count = 0;
        my $r = Redis::Fast->new(
            reconnect          => 1,
            server             => $srv,
            on_connect         => sub { $connect_count++ },
            reconnect_on_error => sub {
                $cb_call_count++;
                return 0; # reconnect_on_error returns 0
            },
        );
        my $hint = $call_hset->($r, "reconnect_on_error returns 0");
        is $connect_count, 2, "redis ERR triggers reconnection";

        # If $cb_return_value is 0 and then $self->need_reconnect is set,
        # calling the reconnect_on_error cb again is useless cost.
        is $cb_call_count, 1, 'call reconnect_on_error once'
            or diag "cb_return_value=-1, hint=$hint";
    }
};


# Check a condition 3.

subtest "reconnection will not be triggered until specified seconds elapsed." => sub {
    for my $call_hset ($sync_call_hset, $async_call_hset) {
        my $hint;
        my $cb_call_count = 0;
        my $cb_return_value = 0;
        my $r = Redis::Fast->new(
            reconnect          => 1,
            server             => $srv,
            reconnect_on_error => sub {
                $cb_call_count++;
                return $cb_return_value;
            },
        );

        # reconnect if the redis returns ERR,
        # and next reconnection will be triggered.
        $cb_return_value = 0;

        $hint = $call_hset->($r, $cb_return_value);
        is $cb_call_count, 1, 'call reconnect_on_error once'
            or diag "cb_return_value=$cb_return_value, call=$hint";

        # reconnect if the redis returns ERR,
        # and next reconnection will not be triggered until 1 second elapsed.
        $cb_return_value = 1;

        $hint = $call_hset->($r, $cb_return_value);
        is $cb_call_count, 2, 'call reconnect_on_error twice'
            or diag "cb_return_value=$cb_return_value, call=$hint";

        # reconnection is not triggered
        # because $cb_return_value seconds have not passed since the last reconnection.
        $hint = $call_hset->($r, $cb_return_value);
        is $cb_call_count, 2, 'call reconnect_on_error twice'
            or diag "cb_return_value=$cb_return_value, call=$hint";
    }
};

subtest "reconnection will be triggered after specified seconds elapsed." => sub {
    for my $call_hset ($sync_call_hset, $async_call_hset) {
        my $hint;
        my $cb_call_count = 0;
        my $cb_return_value = 0;
        my $r = Redis::Fast->new(
            reconnect          => 1,
            server             => $srv,
            reconnect_on_error => sub {
                $cb_call_count++;
                return $cb_return_value;
            },
        );

        # reconnect if the redis returns ERR,
        # and next reconnection will be triggered.
        $cb_return_value = 0;

        $hint = $call_hset->($r, $cb_return_value);
        is $cb_call_count, 1, 'call reconnect_on_error once'
            or diag "cb_return_value=$cb_return_value, call=$hint";

        # reconnect if the redis returns ERR,
        # and next reconnection will not be triggered until 1 second elapsed.
        $cb_return_value = 1;

        $hint = $call_hset->($r, $cb_return_value);
        is $cb_call_count, 2, 'call reconnect_on_error twice'
            or diag "cb_return_value=$cb_return_value, call=$hint";

        # wait for $cb_return_value seconds to pass since the last reconnection.
        # +1 second is a buffer.
        sleep $cb_return_value + 1;

        # reconnection is triggered
        # because $cb_return_value seconds have passed since the last reconnection.
        $hint = $call_hset->($r, $cb_return_value);
        is $cb_call_count, 3, 'call reconnect_on_error twice'
            or diag "cb_return_value=$cb_return_value, call=$hint";
    }
};

done_testing();