App-karr

 view release on metacpan or  search on metacpan

t/177-expired-claim-takeover.t  view on Meta::CPAN


    for my $n ( 1 .. ( $opt{tasks} || 1 ) ) {
        my $rv = _run_karr( $repo, 'create', "Task $n" );
        is( $rv->{exit}, 0, "task $n created" ) or diag $rv->{stderr};
    }

    return $repo;
}

my $HOLDER  = 'agent-holder';
my $TAKEOVER = 'agent-takeover';

# The one sentence every command has to produce, whoever is stepping over the
# claim -- including the two that pass no claimant at all.
sub _trace_re {
    my ( $id, $holder ) = @_;
    return qr/task \Q$id\E: overriding the expired claim held by \Q$holder\E/;
}

subtest 'an expired claim taken over by another name is reported' => sub {
    my $repo = _setup_repo( claim_timeout => '1s', tasks => 6 );

    my $claim = _run_karr( $repo, 'move', '1,2,3,4,5,6', 'in-progress',
        '--claim', $HOLDER );
    is( $claim->{exit}, 0, 'all six tasks claimed by the holder' )
      or diag $claim->{stderr};

    # One sleep for the whole subtest: every claim above was stamped before it,
    # so every card below is expired by the same wait.
    sleep 2;

    subtest 'handoff under a different name' => sub {
        my $r = _run_karr( $repo, 'handoff', '1', '--claim', $TAKEOVER,
            '--note', 'x' );

        # Unchanged, and deliberately so: an expired claim must not start
        # blocking anybody just because it is now audible.
        is( $r->{exit}, 0, 'the handoff still succeeds' ) or diag $r->{stderr};
        like( $r->{stdout}, qr/Handed off task 1/, '...and still says so' );

        like( $r->{stderr}, _trace_re( 1, $HOLDER ),
            'STDERR names the holder the takeover stepped over' );

        my $show = _run_karr( $repo, 'show', '1' );
        like( $show->{stdout}, qr/^Claimed:\s+\Q$TAKEOVER\E$/m,
            'the card really did change hands' );
    };

    subtest 'archive, which passes no claimant at all' => sub {
        my $r = _run_karr( $repo, 'archive', '2' );
        is( $r->{exit}, 0, 'the archive still succeeds' ) or diag $r->{stderr};
        like( $r->{stderr}, _trace_re( 2, $HOLDER ),
            'a card archived out from under an expired claim says whose it was' );
    };

    subtest 'delete, which checks the claim twice' => sub {
        my $r = _run_karr( $repo, 'delete', '3', '--yes' );
        is( $r->{exit}, 0, 'the delete still succeeds' ) or diag $r->{stderr};

        # Delete applies the rule once outside the guard (to decide whether to
        # prompt) and again inside delete_task_guarded, against the revision it
        # removes. Two checks, one card, one line: the record is keyed by task
        # id and replaced, not appended to.
        my @lines = ( $r->{stderr} =~ /(overriding the expired claim)/g );
        is( scalar @lines, 1,
            'the two check_claim calls on the delete path report once, not twice' )
          or diag $r->{stderr};
        like( $r->{stderr}, _trace_re( 3, $HOLDER ), '...and it names the holder' );
    };

    subtest 'the holder continuing under its own name is not reported' => sub {
        # check_claim answers "the current claimant may always proceed" before
        # it ever asks about expiry, so a long-running agent that outlives its
        # own claim_timeout gets no warning about itself.
        my $r = _run_karr( $repo, 'move', '4', 'review', '--claim', $HOLDER );
        is( $r->{exit}, 0, 'the holder still moves its own card' )
          or diag $r->{stderr};
        unlike( $r->{stderr}, qr/expired claim/,
            'no trace: nothing changed hands' );
    };

    subtest '--quiet silences the human copy' => sub {
        my $r = _run_karr( $repo, 'move', '5', 'review', '--claim', $TAKEOVER,
            '--quiet' );
        is( $r->{exit}, 0, 'the move still succeeds under --quiet' )
          or diag $r->{stderr};
        unlike( $r->{stderr}, qr/expired claim/,
            '--quiet silences the takeover line like every other warning' );
    };

    subtest '--json carries the pair instead of the line' => sub {
        my $r = _run_karr( $repo, 'handoff', '6', '--claim', $TAKEOVER,
            '--note', 'x', '--json' );
        is( $r->{exit}, 0, 'the handoff still succeeds under --json' )
          or diag $r->{stderr};

        unlike( $r->{stderr}, qr/expired claim/,
            'nothing on STDERR: a JSON consumer would never read it there' );

        my $data = eval { decode_json( $r->{stdout} ) };
        ok( $data, 'STDOUT is one decodable JSON object' )
          or diag "stdout: $r->{stdout}";
        is( ref $data->{expired_claim}, 'HASH',
            'the payload carries an expired_claim object' )
          or return;

        # Structured, not a sentence: karr-foundation attributes stalls per
        # claim name, so the name it needs must not have to be parsed out of
        # English.
        is( $data->{expired_claim}{held_by}, $HOLDER,
            'expired_claim.held_by is the previous holder' );
        like( $data->{expired_claim}{claimed_at},
            qr/\A\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z\z/,
            'expired_claim.claimed_at is when that claim was stamped' );
    };
};

subtest 'a live claim is still refused, loudly and for every command' => sub {
    my $repo = _setup_repo( tasks => 4 );   # default claim_timeout: 1h

    my $claim = _run_karr( $repo, 'move', '1,2,3', 'in-progress',



( run in 0.695 second using v1.01-cache-2.11-cpan-800906f7e73 )