App-karr

 view release on metacpan or  search on metacpan

t/84-skill-usage-exit.t  view on Meta::CPAN

use strict;
use warnings;
use Test::More;
use lib 't/lib';
use TestKarr ();

# Ticket #76, the rows that live in App::karr::Cmd::Skill: an invalid value is
# a usage error and must exit 2, not 1 (ADR 0002,
# docs/adr/0002-exit-code-contract.md).
#
# Probed pre-fix on this tree:
#   karr skill bogusaction            -> exit 1, "Unknown action: bogusaction ..."
#   karr skill check --agent bogus    -> exit 1, "Unknown agent: bogus ..."
#
# Neither is reachable by MooX::Options: the action is a positional and
# --agent's value is free-form, so App::karr::Role::ExitCodes' remap of
# option-parse failures never sees them. Both dies are therefore written with
# the leading "Usage:" marker bin/karr's central handler classifies on. When
# Role::ExitCodes gains a usage_error helper (the rest of #76), each becomes a
# one-line swap and this file keeps pinning the observable contract either way.

# In-process runner (t/lib/TestKarr.pm): this file's own run_karr never
# chdirs (there is no repository to isolate -- `skill` needs none), so '.' is
# passed as the cwd, a no-op chdir that matches the un-isolated subprocess
# this used to spawn. KARR_TEST_SUBPROC=1 restores the old open3 path.
sub run_karr {
    my (@argv) = @_;
    return TestKarr::run_karr( '.', @argv );
}

subtest 'an unknown skill action is a usage error' => sub {
    my $rv = run_karr( 'skill', 'bogusaction' );
    is $rv->{exit}, 2, 'exits 2, not 1' or diag "stderr: $rv->{stderr}";
    like $rv->{stderr}, qr/^Usage: karr skill /m, 'stderr opens with the usage line';
    like $rv->{stderr}, qr/\QUnknown action: bogusaction\E/,
        'and names the action it rejected';
    unlike $rv->{stderr}, qr/ at \S+ line \d+/, 'no file:line suffix';
};

subtest 'an unknown --agent value is a usage error' => sub {
    my $rv = run_karr( 'skill', 'check', '--agent', 'bogusagent' );
    is $rv->{exit}, 2, 'exits 2, not 1' or diag "stderr: $rv->{stderr}";
    like $rv->{stderr}, qr/^Usage: karr skill --agent /m, 'stderr opens with the usage line';
    like $rv->{stderr}, qr/\QUnknown agent: bogusagent\E/, 'and names the agent it rejected';
    like $rv->{stderr}, qr/claude-code, codex, cursor/, 'and lists the known agents';
    unlike $rv->{stderr}, qr/ at \S+ line \d+/, 'no file:line suffix';

    # One bad name in a comma list rejects the whole list.
    my $mixed = run_karr( 'skill', 'check', '--agent', 'codex,bogusagent' );
    is $mixed->{exit}, 2, 'a bad name among good ones still exits 2';
};

subtest 'valid skill usage is unaffected' => sub {
    my $show = run_karr( 'skill', 'show' );
    is $show->{exit}, 0, 'karr skill show still exits 0' or diag $show->{stderr};

    my $check = run_karr( 'skill', 'check', '--agent', 'codex', '--json' );
    isnt $check->{exit}, 2, 'a valid --agent is not treated as a usage error'
        or diag "stderr: $check->{stderr}";
};

done_testing;



( run in 1.141 second using v1.01-cache-2.11-cpan-d01c6094234 )