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

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

=pod
x   SADD
x   SCARD
x   SDIFF
x   SDIFFSTORE
x   SINTER
x   SINTERSTORE
x   SISMEMBER
x   SMEMBERS
x   SMOVE
x   SPOP
x   SRANDMEMBER
x   SREM
x   SUNION
x   SUNIONSTORE
=cut

ok(my $r = Test::Mock::Redis->new, 'pretended to connect to our test redis-server');
my @redi = ($r);

my ( $guard, $srv );
if( $ENV{RELEASE_TESTING} ){
    use_ok("Redis");
    use_ok("Test::SpawnRedisServer");
    ($guard, $srv) = redis();
    ok(my $r = Redis->new(server => $srv), 'connected to our test redis-server');
    $r->flushall;
    unshift @redi, $r
}

my @members = (qw/foo bar baz qux quux quuux/);

foreach my $r (@redi){
    diag("testing $r") if $ENV{RELEASE_TESTING};


    is        $r->srandmember('noset'),         undef, "srandmember for a set that doesn't exist returns undef";
    is        $r->spop('noset'),                undef, "spop for a set that doesn't exist returns undef";
    is        $r->scard('noset'),                   0, "scard for a set that doesn't exist returns 0";
    is        $r->srem('noset', 'foo'),             0, "srem for a set that doesn't exist returns 0";
    is        $r->smove('noset', 'set', 'foo'),     0, "smove for sets that don't exist returns 0";
    is        $r->sismember('noset', 'foo'),        0, "sismember for a set that doesn't exist returns 0";
    is_deeply [$r->smembers('noset')],             [], "smembers for a set that doesn't exist returns an empty array";

    is $r->sadd('set', 'foo'),  1, "sadd returns 1 when element is new to the set";
    is $r->sadd('set', 'foo'),  0, "sadd returns 0 when element is already in the set";
    is $r->scard('set'),        1, "scard returns size of set";

    is $r->sadd('set', 'bar'),  1, "sadd returns 1 when element is new to the set";
    is $r->scard('set'),        2, "scard returns size of set";

    is $r->sismember('set', 'foo'), 1, "sismember returns 1 for a set element that exists";
    is $r->sismember('set', 'baz'), 0, "sismember returns 0 for a set element that doesn't exist";


    is_deeply [sort $r->smembers('set')], [qw/bar foo/], "smembers returns all members of the set";

    is $r->srem('set', 'bar'), 1, "srem returns 1 when it removes an element";

    is $r->sadd('set', $_), 1, "srem returns 1 when it adds a new element to the set"
        for (grep { $_ ne 'foo'} @members);

    is $r->type('set'), 'set', "our set has type set";

    my $randmember = $r->srandmember('set');
    ok $randmember, "srandmember returned something";
    ok grep { $_ eq $randmember } $r->smembers('set'), "srandmember returned a member";

    while($r->scard('set')){
        my $popped = $r->spop('set');
        ok $popped, "spopped something";
        ok grep { $_ eq $popped } @members, "spopped a member";
        is $r->sismember('set', $popped), 0, "spop removed $popped";
    }

    # set has been emptied.  Put some stuff in it again
    is $r->sadd('set', $_), 1, "srem returns 1 when it adds a new element to the set"
        for (@members);

    is $r->sadd('otherset', $_), 1, "srem returns 1 when it adds a new element to the set"
        for (qw/foo bar baz/);

    is $r->sadd('anotherset', $_), 1, "srem returns 1 when it adds a new element to the set"
        for (qw/bar baz qux/);

    is_deeply [sort $r->sinter('set', 'otherset')], [qw/bar baz foo/], "sinter returns all members in common";

    is_deeply [sort $r->sinter('set', 'otherset', 'anotherset')], [qw/bar baz/],
        "sinter returns all members in common for multiple sets";

    is_deeply [$r->sinter('set', 'emptyset')], [], "sinter returns empty list";
    is_deeply [$r->sinter('set', 'otherset', 'emptyset')], [], "sinter returns empty list with multiple sets";

    is $r->sinterstore('destset', 'set', 'otherset'), 3, "sinterstore returns cardinality of intersection";
    is_deeply [sort $r->smembers('destset')], [sort $r->sinter('set', 'otherset')], "sinterstore stored the correct result";

    is $r->sinterstore('destset', 'set', 'emptyset'), 0, "cardinality of empty intersection is zero";
    is_deeply [sort $r->smembers('destset')], [sort $r->sinter('set', 'emptyset')], "sinterstore stored the correct result";

    is $r->sinterstore('destset', 'set', 'otherset', 'anotherset'), 2, "sinterstore returns cardinality of intersection";
    is_deeply [sort $r->smembers('destset')], [sort $r->sinter('set', 'otherset', 'anotherset')], "sinterstore stored the correct result";

    is $r->sadd('otherset', $_), 1, "srem returns 1 when it adds a new element to the set"
        for (qw/oink bah neigh/);

    is_deeply [sort $r->sunion('set', 'otherset')],   [sort @members, qw/oink bah neigh/], "sunion returns all members of two sets";
    is_deeply [sort $r->sunion('set', 'anotherset')], [sort @members], "sunion returns all members of two sets";

    is $r->sunionstore('destset', 'set', 'otherset'), @members + 3, "sunionstore returns cardinality of union";
    is_deeply [sort $r->smembers('destset')], [sort $r->sunion('set', 'otherset')], "sunionstore stored the correct result";

    is $r->sunionstore('destset', 'set', 'emptyset'), @members, "cardinality of empty union is same as carindality of set";
    is_deeply [sort $r->smembers('destset')], [sort $r->sunion('set', 'emptyset')], "sunionstore stored the correct result";

    is $r->sunionstore('destset', 'set', 'otherset', 'anotherset'), @members + 3, "sunion returns cardinality of union";
    is_deeply [sort $r->smembers('destset')], [sort $r->sunion('set', 'otherset', 'anotherset')], "sunionstore stored the correct result";

    is_deeply [sort $r->sdiff('set', 'otherset')], [qw/quuux quux qux/], "sdiff removed members correctly";
    is_deeply [sort $r->sdiff('set', 'otherset', 'anotherset')], [qw/quuux quux/], "sdiff removed members correctly";

    is $r->sdiffstore('destset', 'set', 'otherset'), 3, "sdiffstore returnes cardinality of difference";
    is_deeply [sort $r->smembers('destset')], [sort $r->sdiff('set', 'otherset')], "sdiffstore stored the correct result";

    is $r->sdiffstore('destset', 'set', 'otherset', 'anotherset'), 2, "sdiffstore returnes cardinality of difference";
    is_deeply [sort $r->smembers('destset')], [sort $r->sdiff('set', 'otherset', 'anotherset')], "sdiffstore stored the correct result";

    # cardinality of the difference with the empty set is the same as what we started with
    is $r->sdiffstore('destset', 'set', 'emptyset'), $r->scard('set'), "sdiffstore returnes cardinality of difference";
    is_deeply [sort $r->smembers('destset')], [sort $r->smembers('set')], "sdiffstore stored the correct result";

    is $r->smove('otherset', 'set', 'oink'), 1, "smove returns true if it moved an element succesfully";
    is $r->sismember('set', 'oink'), 1, "oink moved to set";
    is $r->sismember('otherset', 'oink'), 0, "oink removed from otherset";

    is $r->smove('otherset', 'set', 'meow'), 0, "smove returns false if it failed to move an element";

    is $r->smove('notaset', 'otherset', 'foo'), 0, "smove returns false when source doesn't exist";

    $r->set('justakey', 'foobar');

    like exception { $r->smove('justakey', 'set', 'foo') },
        qr/^\Q[smove] ERR Operation against a key holding the wrong kind of value\E/,
         "smove dies when source isn't a set";

    like exception { $r->smove('set', 'justakey', 'foo') },
        qr/^\Q[smove] ERR Operation against a key holding the wrong kind of value\E/,
         "smove dies when dest isn't a set";

    is $r->smove('otherset', 'newset', 'foo'), 1, "smove returns true when destination doesn't exist";
    is $r->type('newset'), 'set', "newset sprang into existence";
}

done_testing();