App-karr

 view release on metacpan or  search on metacpan

t/72-claim-timeout.t  view on Meta::CPAN

use strict;
use warnings;
use Test::More;
use Time::Piece;

use App::karr::Task;

# Ticket #63: App::karr::Role::ClaimTimeout had no direct coverage at all.
# Mutating `return $1 * 60` to `$1 * 3600` (minutes parsed as hours) and
# replacing the whole of _claim_expired with `return 0` (no claim ever expires,
# so `karr pick` can never take over a stale claim) both left the suite green --
# the role is only exercised indirectly by pick/handoff, and only through claims
# that were fresh either way.

{
  package TimeoutConsumer;
  use Moo;
  # store is the role's one requirement since ticket #144: claim_timeout_secs
  # reads $self->store->effective_config, and until then the role declared
  # nothing, so this class composed by accident rather than by contract. The
  # stub can stay empty -- every method exercised below takes its timeout as an
  # argument and never asks the board for one. t/147-claim-timeout-requires.t is
  # that one-name list's own test.
  sub store { }
  # json and quiet joined the list in ticket #177, when check_claim began
  # recording the expired claims it lets through and expired_claim_report grew
  # the two output options every other warning in karr is gated on. Nothing
  # below reports, so the stubs stay empty here too.
  sub json  { }
  sub quiet { }
  with 'App::karr::Role::ClaimTimeout';
}

my $c = TimeoutConsumer->new;

subtest '_parse_timeout' => sub {
  is( $c->_parse_timeout('1h'),  3600,  '1h' );
  is( $c->_parse_timeout('2h'),  7200,  '2h' );
  is( $c->_parse_timeout('24h'), 86400, '24h' );

  # The mutation that survived: minutes are minutes, not hours.
  is( $c->_parse_timeout('1m'),  60,    '1m is 60 seconds, not 3600' );
  is( $c->_parse_timeout('30m'), 1800,  '30m' );
  is( $c->_parse_timeout('90m'), 5400,  '90m' );

  # Anything unparseable falls back to one hour rather than to zero, which
  # would make every claim instantly stealable.
  is( $c->_parse_timeout(undef), 3600, 'undef falls back to 1h' );
  is( $c->_parse_timeout(''),    3600, 'empty string falls back to 1h' );
  is( $c->_parse_timeout('0'),   3600, 'a bare 0 falls back to 1h' );
  is( $c->_parse_timeout('7d'),  3600, 'an unsupported unit falls back to 1h' );
  is( $c->_parse_timeout('1 h'), 3600, 'a malformed value falls back to 1h' );
  is( $c->_parse_timeout('h'),   3600, 'a unit with no number falls back to 1h' );
};

sub _task_claimed_secs_ago {
  my ($secs) = @_;
  my $ts = defined $secs ? gmtime( time - $secs )->datetime . 'Z' : undef;
  return App::karr::Task->new(
    id    => 1,
    title => 'Claimed card',
    ( defined $ts ? ( claimed_by => 'agent-fox', claimed_at => $ts ) : () ),
  );
}

subtest '_claim_expired' => sub {
  # Both sides of the decision, so `return 0` and `return 1` are each fatal.
  ok( !$c->_claim_expired( _task_claimed_secs_ago(60), 3600 ),
    'a claim one minute old is still live under a 1h timeout' );
  ok( $c->_claim_expired( _task_claimed_secs_ago(7200), 3600 ),
    'a claim two hours old has expired under a 1h timeout' );

  # And that the timeout argument is actually consulted.
  ok( $c->_claim_expired( _task_claimed_secs_ago(120), 60 ),
    'the same claim expires under a 1m timeout' );
  ok( !$c->_claim_expired( _task_claimed_secs_ago(120), 86400 ),
    'and does not under a 24h timeout' );

  # An unclaimed task is not "expired": pick treats has_claimed_at as the
  # question and would otherwise double-count it.
  ok( !$c->_claim_expired( _task_claimed_secs_ago(undef), 0 ),
    'a task that was never claimed is not expired' );

  # A stamp karr did not write must not silently read as expired either.
  my $bad = App::karr::Task->new(
    id => 2, title => 'Bad stamp',
    claimed_by => 'agent-fox', claimed_at => 'not-a-timestamp',
  );
  ok( !$c->_claim_expired( $bad, 0 ), 'an unparseable claimed_at is not treated as expired' );
};

# Ticket #57. Every stamp above is karr's own shape: no fractional seconds, and
# always UTC with a trailing Z. kanban-md, the implementation karr shares boards
# with, writes Go's time.RFC3339Nano off the agent's local clock -- verified



( run in 2.619 seconds using v1.01-cache-2.11-cpan-85d3896f969 )