App-karr

 view release on metacpan or  search on metacpan

t/185-foundation-ticket-mode.t  view on Meta::CPAN


  # The crashed-agent case the mode exists for: a claim older than the board's
  # claim_timeout no longer holds the card, or a board whose last open ticket
  # carries one would go quiet for ever -- nothing would run, so nothing would
  # ever reap the claim.
  my $store = App::karr::BoardStore->new(
    git => App::karr::Git->new( dir => "$repo" ) );
  my $stale = $store->find_task( 5 );
  $stale->claimed_at( gmtime( time - 7200 )->datetime . 'Z' );
  $store->save_task( $stale );

  is $f->_select_ticket( $repo ), 5,
    'an expired claim is taken over, and critical outranks high';
};

# ---------------------------------------------------------------------------
# Unit: how the assignment reaches the agent
# ---------------------------------------------------------------------------

subtest 'the ticket travels in $PROMPT and $KARR_TASK' => sub {
  my $f = App::karr::Foundation->new( _config_data => {} );

  my $plain = $f->_prompt_for( {} );
  unlike $plain, qr/task for this run/, 'no assignment without a ticket';

  my $assigned = $f->_prompt_for( {}, 42 );
  like $assigned, qr/task for this run is #42/, 'the id is in the prompt';
  unlike $assigned, qr/pick the next actionable task/,
    'the built-in default no longer tells the agent to choose its own work';

  my $custom = $f->_prompt_for( { prompt => 'CUSTOM' }, 42 );
  like $custom, qr/\ACUSTOM\n\nThe task for this run is #42/,
    'a configured prompt is kept, with the assignment appended after it';

  my $rdir = tempdir( CLEANUP => 1 );
  my ( undef, $out ) = $f->_run_command( $rdir, {}, 'printf "[%s]" "$KARR_TASK"' );
  is $out, '[]', 'KARR_TASK is empty outside ticket mode';
  my ( undef, $out2 ) = $f->_run_command( $rdir, {}, 'printf "[%s]" "$KARR_TASK"', 7 );
  is $out2, '[7]', 'and carries the id in it';
};

# Ticket #281 / ADR 0005: the runner exports KARR_CLAIM into the child, so
# every nested `karr` the agent spawns defaults its claim to the same name.
# The value is `karr agent-name --unique` for this checkout -- the checkout's
# own directory name plus a per-run suffix, minted once and stable for the run.
subtest 'the claim name travels in $KARR_CLAIM' => sub {
  my $f = App::karr::Foundation->new( _config_data => {} );

  my $rdir = tempdir( CLEANUP => 1 );
  my $base = agent_name( dir => "$rdir" );   # same checkout, no suffix
  ok length $base, 'the checkout has a claim-safe base name';

  my ( undef, $out ) = $f->_run_command( $rdir, {}, 'printf "[%s]" "$KARR_CLAIM"' );
  like $out, qr/\A\[\Q$base\E-[a-z0-9]{3}\]\z/,
    'KARR_CLAIM is the checkout name with a per-run suffix';

  # Minted once per child and inherited by every nested call: the two reads a
  # single run makes see one value, not a fresh one each time.
  my ( undef, $twice ) =
    $f->_run_command( $rdir, {}, 'printf "[%s][%s]" "$KARR_CLAIM" "$KARR_CLAIM"' );
  like $twice, qr/\A\[(\Q$base\E-[a-z0-9]{3})\]\[\g{1}\]\z/,
    'the same value within one run, not re-minted per reference';
};

# ---------------------------------------------------------------------------
# Integration: one run, one ticket, and back
# ---------------------------------------------------------------------------

subtest 'ticket mode runs the agent once, on the card it named' => sub {
  my $repo = make_git_repo();
  seed_board( $repo,
    { status => 'todo', priority => 'low' },
    { status => 'todo', priority => 'high' },
    { status => 'todo', priority => 'low' },
  );
  my $agent = write_fake_agent( $repo );

  my $f = App::karr::Foundation->new( _config_data => {} );
  local $ENV{KARR_FAKE_MODE} = 'assigned';
  my $res = $f->_drain_repo( $repo,
    { command => $agent, max_runtime => 60, mode => 'ticket' } );

  is $res->{ticket},  2,          'the run reports which ticket it was about';
  is $res->{outcome}, 'progress', 'the ticket moved';

  my @runs = runs_of( $repo );
  is scalar @runs, 1, 'exactly one agent run, board still actionable or not';
  is $runs[0], '2', 'the agent was handed the id foundation picked';

  is task_by_id( $repo, 2 )->status, 'done', 'the assigned card is done';
  is task_by_id( $repo, 1 )->status, 'todo', 'the rest of the board is untouched';
  is task_by_id( $repo, 3 )->status, 'todo', 'no drain happened';

  like path( $repo )->child('prompt.txt')->slurp_utf8, qr/task for this run is #2/,
    'the prompt the agent saw named the card';
};

# ---------------------------------------------------------------------------
# Integration: nothing to assign is not a reason to start an agent
# ---------------------------------------------------------------------------

subtest 'no assignable card means no agent run at all' => sub {
  my $repo = make_git_repo();
  seed_board( $repo, { status => 'done' }, { status => 'todo', blocked => 'why' } );
  my $agent = write_fake_agent( $repo );

  my $f = App::karr::Foundation->new( _config_data => {} );
  local $ENV{KARR_FAKE_MODE} = 'assigned';
  my $res = $f->_drain_repo( $repo,
    { command => $agent, max_runtime => 60, mode => 'ticket' } );

  is $res->{outcome}, 'idle',  'idle, not an error';
  is $res->{ticket},  undef,   'and no ticket to report';
  ok !path( $repo )->child('runs.log')->exists, 'the agent was never started';
  like path( $repo )->child('.karr.log')->slurp_utf8, qr/TICKET none assignable/,
    'the log says why nothing ran';
};

# ---------------------------------------------------------------------------
# Integration: the run is judged by its own card
# ---------------------------------------------------------------------------



( run in 0.507 second using v1.01-cache-2.11-cpan-99fa8243a42 )