Claude-Agent-Code-Refactor
view release on metacpan or search on metacpan
t/01-basic.t view on Meta::CPAN
#!/usr/bin/env perl
use strict;
use warnings;
use Test::More;
use Test::Exception;
use_ok('Claude::Agent::Code::Refactor');
use_ok('Claude::Agent::Code::Refactor::Options');
use_ok('Claude::Agent::Code::Refactor::Result');
# Test exports
can_ok('Claude::Agent::Code::Refactor', qw(refactor refactor_issues refactor_until_clean));
# Test Options
subtest 'Options defaults' => sub {
my $opts = Claude::Agent::Code::Refactor::Options->new();
is($opts->max_iterations, 5, 'default max_iterations');
is($opts->max_turns_per_fix, 20, 'default max_turns_per_fix');
is($opts->stop_on_critical, 1, 'default stop_on_critical');
is($opts->min_severity, 'low', 'default min_severity');
is_deeply($opts->categories,
['bugs', 'security', 'style', 'performance', 'maintainability'],
'default categories');
is($opts->fix_one_at_a_time, 0, 'default fix_one_at_a_time');
is($opts->dry_run, 0, 'default dry_run');
is($opts->create_backup, 0, 'default create_backup');
is($opts->perlcritic, 0, 'default perlcritic');
is($opts->perlcritic_severity, 4, 'default perlcritic_severity');
is($opts->filter_false_positives, 1, 'default filter_false_positives');
is($opts->permission_mode, 'acceptEdits', 'default permission_mode');
};
subtest 'Options custom values' => sub {
my $opts = Claude::Agent::Code::Refactor::Options->new(
max_iterations => 10,
max_turns_per_fix => 30,
min_severity => 'medium',
categories => ['bugs', 'security'],
dry_run => 1,
perlcritic => 1,
perlcritic_severity => 2,
model => 'claude-sonnet-4-5',
);
is($opts->max_iterations, 10, 'custom max_iterations');
is($opts->max_turns_per_fix, 30, 'custom max_turns_per_fix');
is($opts->min_severity, 'medium', 'custom min_severity');
is_deeply($opts->categories, ['bugs', 'security'], 'custom categories');
is($opts->dry_run, 1, 'custom dry_run');
is($opts->perlcritic, 1, 'custom perlcritic');
is($opts->perlcritic_severity, 2, 'custom perlcritic_severity');
is($opts->model, 'claude-sonnet-4-5', 'custom model');
};
subtest 'Options validation' => sub {
throws_ok {
Claude::Agent::Code::Refactor::Options->new(min_severity => 'invalid');
} qr/Invalid min_severity/, 'rejects invalid min_severity';
throws_ok {
Claude::Agent::Code::Refactor::Options->new(categories => ['invalid']);
} qr/Invalid category/, 'rejects invalid category';
throws_ok {
Claude::Agent::Code::Refactor::Options->new(categories => []);
} qr/cannot be empty/, 'rejects empty categories';
throws_ok {
Claude::Agent::Code::Refactor::Options->new(max_iterations => 0);
} qr/must be >= 1/, 'rejects zero max_iterations';
throws_ok {
Claude::Agent::Code::Refactor::Options->new(max_turns_per_fix => 0);
} qr/must be >= 1/, 'rejects zero max_turns_per_fix';
throws_ok {
Claude::Agent::Code::Refactor::Options->new(perlcritic_severity => 6);
} qr/Invalid perlcritic_severity/, 'rejects invalid perlcritic_severity';
};
# Test Result
subtest 'Result defaults' => sub {
my $result = Claude::Agent::Code::Refactor::Result->new();
is($result->success, 0, 'default success');
is($result->iterations, 0, 'default iterations');
is($result->initial_issues, 0, 'default initial_issues');
is($result->final_issues, 0, 'default final_issues');
is($result->fixes_applied, 0, 'default fixes_applied');
is_deeply($result->files_modified, [], 'default files_modified');
is_deeply($result->history, [], 'default history');
is($result->duration_ms, 0, 'default duration_ms');
ok(!$result->has_error, 'no error by default');
};
subtest 'Result is_clean' => sub {
my $clean = Claude::Agent::Code::Refactor::Result->new(
success => 1,
final_issues => 0,
);
ok($clean->is_clean, 'is_clean when success and no final issues');
my $not_clean = Claude::Agent::Code::Refactor::Result->new(
success => 1,
final_issues => 5,
);
ok(!$not_clean->is_clean, 'not clean when final issues remain');
my $failed = Claude::Agent::Code::Refactor::Result->new(
success => 0,
final_issues => 0,
);
ok(!$failed->is_clean, 'not clean when not success');
};
subtest 'Result issues_fixed and fix_rate' => sub {
my $result = Claude::Agent::Code::Refactor::Result->new(
initial_issues => 10,
final_issues => 3,
);
is($result->issues_fixed, 7, 'issues_fixed calculation');
is($result->fix_rate, 70, 'fix_rate calculation');
my $all_fixed = Claude::Agent::Code::Refactor::Result->new(
initial_issues => 5,
final_issues => 0,
);
is($all_fixed->fix_rate, 100, 'fix_rate 100% when all fixed');
my $no_issues = Claude::Agent::Code::Refactor::Result->new(
initial_issues => 0,
final_issues => 0,
);
is($no_issues->fix_rate, 100, 'fix_rate 100% when no initial issues');
};
subtest 'Result add_iteration' => sub {
my $result = Claude::Agent::Code::Refactor::Result->new();
$result->add_iteration(
issues_found => 10,
issues_fixed => 5,
files_modified => ['lib/Foo.pm'],
);
is($result->iterations, 1, 'iteration count');
is($result->fixes_applied, 5, 'fixes_applied updated');
is_deeply($result->files_modified, ['lib/Foo.pm'], 'files_modified');
is(scalar @{$result->history}, 1, 'history has one entry');
is($result->history->[0]{iteration}, 1, 'history iteration number');
is($result->history->[0]{issues_found}, 10, 'history issues_found');
is($result->history->[0]{issues_fixed}, 5, 'history issues_fixed');
$result->add_iteration(
issues_found => 5,
issues_fixed => 3,
files_modified => ['lib/Foo.pm', 'lib/Bar.pm'],
);
is($result->iterations, 2, 'iteration count after second');
is($result->fixes_applied, 8, 'fixes_applied cumulative');
is(scalar @{$result->files_modified}, 2, 'files_modified deduplicated');
is(scalar @{$result->history}, 2, 'history has two entries');
};
subtest 'Result has_error' => sub {
my $no_error = Claude::Agent::Code::Refactor::Result->new();
ok(!$no_error->has_error, 'no error');
my $with_error = Claude::Agent::Code::Refactor::Result->new(
error => 'Something went wrong',
);
ok($with_error->has_error, 'has error');
};
subtest 'Result as_text' => sub {
my $result = Claude::Agent::Code::Refactor::Result->new(
success => 1,
iterations => 2,
initial_issues => 10,
final_issues => 0,
fixes_applied => 10,
files_modified => ['lib/Foo.pm'],
duration_ms => 5000,
);
my $text = $result->as_text;
like($text, qr/REFACTOR RESULT/, 'has header');
like($text, qr/SUCCESS/, 'shows success');
like($text, qr/Iterations: 2/, 'shows iterations');
like($text, qr/Initial issues: 10/, 'shows initial issues');
like($text, qr/Fix rate: 100%/, 'shows fix rate');
like($text, qr/lib\/Foo\.pm/, 'shows modified files');
};
subtest 'Result to_hash' => sub {
my $result = Claude::Agent::Code::Refactor::Result->new(
success => 1,
iterations => 3,
initial_issues => 15,
final_issues => 0,
fixes_applied => 15,
);
my $hash = $result->to_hash;
is($hash->{success}, 1, 'hash success');
is($hash->{iterations}, 3, 'hash iterations');
is($hash->{initial_issues}, 15, 'hash initial_issues');
is($hash->{final_issues}, 0, 'hash final_issues');
is($hash->{fix_rate}, 100, 'hash fix_rate');
ok(!exists $hash->{error}, 'hash omits error when not set');
};
done_testing();
( run in 1.251 second using v1.01-cache-2.11-cpan-71847e10f99 )