diff --git a/.github/workflows/test-suite.yml b/.github/workflows/test-suite.yml new file mode 100644 index 0000000..89d2c1c --- /dev/null +++ b/.github/workflows/test-suite.yml @@ -0,0 +1,28 @@ +name: Test Suite + +on: + pull_request: + branches: + - main + - develop + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Perl + run: | + sudo apt-get update + sudo apt-get install -y perl + sudo apt-get install -y cpanminus + + - name: Install dependencies + run: sudo cpanm --installdeps --with-test . + + - name: Run tests + working-directory: ./tests + run: prove -r diff --git a/.perlcriticrc b/.perlcriticrc index 61ab588..ff1aed2 100644 --- a/.perlcriticrc +++ b/.perlcriticrc @@ -1,4 +1,7 @@ severity = 3 [-TestingAndDebugging::RequireUseStrict] -[-TestingAndDebugging::RequireUseWarnings] \ No newline at end of file +[-TestingAndDebugging::RequireUseWarnings] + +[TestingAndDebugging::ProhibitNoWarnings] +allow = once diff --git a/cpanfile b/cpanfile index 28a71c9..f447507 100644 --- a/cpanfile +++ b/cpanfile @@ -1,3 +1,11 @@ requires "Getopt::Long", "2.54"; requires "Mojo::JSON"; -requires "Mojo::UserAgent"; \ No newline at end of file +requires "Mojo::UserAgent"; + +on 'test' => sub { + requires "Test::More"; + requires "Test::Exception"; + requires "Test::MockObject"; + requires "Test::Output"; + requires "Capture::Tiny"; +}; diff --git a/tests/no-open-secret-scanning-alerts.t b/tests/no-open-secret-scanning-alerts.t index 0956440..2a42957 100644 --- a/tests/no-open-secret-scanning-alerts.t +++ b/tests/no-open-secret-scanning-alerts.t @@ -50,6 +50,7 @@ BEGIN { } } +no warnings 'once'; *Mojo::UserAgent::new = \&MockMojoUserAgent::new; subtest 'No open secret scanning alerts' => sub { diff --git a/tests/open-code-scanning-alerts-exceeding-limits.t b/tests/open-code-scanning-alerts-exceeding-limits.t index cbe062b..bec2895 100644 --- a/tests/open-code-scanning-alerts-exceeding-limits.t +++ b/tests/open-code-scanning-alerts-exceeding-limits.t @@ -39,10 +39,10 @@ subtest 'Open code scanning alerts exceeding limits' => sub { my $mock_response = Mojo::UserAgent -> set_mock_response(Test::MockObject -> new); $mock_response -> set_always('code', 200); - $mock_response -> set_always('json', [ - { state => 'open', rule => { severity => 'high' } }, - { state => 'open', rule => { severity => 'high' } }, - { state => 'open', rule => { severity => 'medium' } }, + $mock_response->set_always('json', [ + { state => 'open', rule => { security_severity_level => 'high' } }, + { state => 'open', rule => { security_severity_level => 'high' } }, + { state => 'open', rule => { security_severity_level => 'medium' } }, ]); my %severity_limits = ( diff --git a/tests/open-secret-scanning-alerts-exceeding-limits.t b/tests/open-secret-scanning-alerts-exceeding-limits.t index 9343b6e..2a7be62 100644 --- a/tests/open-secret-scanning-alerts-exceeding-limits.t +++ b/tests/open-secret-scanning-alerts-exceeding-limits.t @@ -50,6 +50,7 @@ BEGIN { } } +no warnings 'once'; *Mojo::UserAgent::new = \&MockMojoUserAgent::new; subtest 'Open secret scanning alerts exceeding limits' => sub { diff --git a/tests/open-secret-scanning-alerts-within-limits.t b/tests/open-secret-scanning-alerts-within-limits.t index 9deba7c..c6fb142 100644 --- a/tests/open-secret-scanning-alerts-within-limits.t +++ b/tests/open-secret-scanning-alerts-within-limits.t @@ -50,6 +50,7 @@ BEGIN { } } +no warnings 'once'; *Mojo::UserAgent::new = \&MockMojoUserAgent::new; subtest 'Open secret scanning alerts within limits' => sub { @@ -60,7 +61,7 @@ subtest 'Open secret scanning alerts within limits' => sub { ]); MockMojoUserAgent::setup_locations_response(200, [ - { path => 'file1.txt', start_line => 10 }, + { details => { path => 'file.txt', start_line => 10 } }, ]); my %severity_limits = ( @@ -73,7 +74,7 @@ subtest 'Open secret scanning alerts within limits' => sub { my $result; my $expected_output_part1 = qr/\[!\]\ Total\ of\ open\ secret\ scanning\ alerts:\ 1/xsm; my $expected_output_part2 = qr/\[-\]\ Alert\ 1\ found\ in\ the\ following\ locations:/xsm; - my $expected_output_part3 = qr/File:\ file1\.txt,\ Start\ line:\ 10/xsm; + my $expected_output_part3 = qr/File:\ file\.txt,\ Start\ line:\ 10/xsm; my $expected_output_part4_part1 = qr/\[-\]\ Number\ of\ secret\ scanning\ alerts\ \(/xsm; my $expected_output_part4_part2 = qr/1\)\ is\ within\ the\ acceptable\ limit\ \(/xsm; my $expected_output_part4_part3 = qr/1\)\./xsm; diff --git a/tests/secrets-api-error-handling.t b/tests/secrets-api-error-handling.t index 1b60aef..e15735d 100644 --- a/tests/secrets-api-error-handling.t +++ b/tests/secrets-api-error-handling.t @@ -50,6 +50,7 @@ BEGIN { } } +no warnings 'once'; *Mojo::UserAgent::new = \&MockMojoUserAgent::new; subtest 'API error handling' => sub { diff --git a/tests/security-gate.t b/tests/security-gate.t deleted file mode 100644 index 146bf85..0000000 --- a/tests/security-gate.t +++ /dev/null @@ -1,179 +0,0 @@ -#!/usr/bin/env perl - -use strict; -use warnings; -use Test::More; -use Test::Exception; -use Test::MockObject; -use Test::Output; -use Carp qw(croak); -use Capture::Tiny qw(capture); -use Mojo::JSON qw(encode_json); - -local $ENV{TEST_MODE} = 1; - -my $mock_ua = Test::MockObject -> new(); -$mock_ua -> fake_module('Mojo::UserAgent'); -$mock_ua -> fake_new('Mojo::UserAgent'); - -require_ok('../security-gate.pl'); - -subtest 'Command-line argument parsing' => sub { - local @ARGV = (); - stdout_like( - sub { main() }, - qr/Security\ Gate\ v0\.0\.3/x, - 'Help message displayed when no arguments provided' - ); -}; - -subtest 'Severity counting' => sub { - my $mock_response = Test::MockObject -> new(); - $mock_response -> set_always('code', 200); - $mock_response -> set_always('json', [ - { state => 'open', security_vulnerability => { severity => 'high' } }, - { state => 'open', security_vulnerability => { severity => 'critical' } }, - { state => 'open', security_vulnerability => { severity => 'medium' } }, - { state => 'closed', security_vulnerability => { severity => 'low' } }, - ]); - - my $mock_tx = Test::MockObject -> new(); - $mock_tx -> set_always('result', $mock_response); - - $mock_ua -> set_always('get', $mock_tx); - - local @ARGV = ('-t', 'test_token', '-r', 'test_repo'); - stdout_like( - sub { main() }, - qr/critical:\ 1.*high:\ 1.*medium:\ 1.*low:\ 0/xs, - 'Severity counts are correct' - ); -}; - -subtest 'Threshold checking' => sub { - my $mock_response = Test::MockObject -> new(); - $mock_response -> set_always('code', 200); - $mock_response -> set_always('json', [ - { state => 'open', security_vulnerability => { severity => 'critical' } }, - { state => 'open', security_vulnerability => { severity => 'critical' } }, - ]); - - my $mock_tx = Test::MockObject -> new(); - $mock_tx -> set_always('result', $mock_response); - - $mock_ua -> set_always('get', $mock_tx); - - local @ARGV = ('-t', 'test_token', '-r', 'test_repo', '-c', '1'); - is( - scalar(main()), - 1, - 'Script exits with non-zero code when threshold is exceeded' - ); - - local @ARGV = ('-t', 'test_token', '-r', 'test_repo', '-c', '2'); - is( - scalar(main()), - 0, - 'Script exits with zero code when threshold is not exceeded' - ); -}; - -subtest 'Output formatting' => sub { - my $mock_response = Test::MockObject -> new(); - $mock_response -> set_always('code', 200); - $mock_response -> set_always('json', [ - { state => 'open', security_vulnerability => { severity => 'high' } }, - { state => 'open', security_vulnerability => { severity => 'critical' } }, - ]); - - my $mock_tx = Test::MockObject -> new(); - $mock_tx -> set_always('result', $mock_response); - - $mock_ua -> set_always('get', $mock_tx); - - local @ARGV = ('-t', 'test_token', '-r', 'test_repo'); - - my $total_alerts_re = qr/\[!\]\ Total\ of\ security\ alerts:/x; - my $critical_alerts_re = qr/\[-\]\ critical:\ 1/x; - my $high_alerts_re = qr/\[-\]\ high:\ 1/x; - - stdout_like( - sub { main() }, - qr/$total_alerts_re.*$critical_alerts_re.*$high_alerts_re/xs, - 'Output is correctly formatted' - ); -}; - -subtest 'Invalid token or repository' => sub { - my $mock_response = Test::MockObject -> new(); - $mock_response -> set_always('code', 401); - - my $mock_tx = Test::MockObject -> new(); - $mock_tx -> set_always('result', $mock_response); - - $mock_ua -> set_always('get', $mock_tx); - - local @ARGV = ('-t', 'invalid_token', '-r', 'invalid_repo'); - is( - scalar(main()), - 1, - 'Script exits with non-zero code when token or repository is invalid' - ); -}; - -subtest 'Empty response from GitHub API' => sub { - my $mock_response = Test::MockObject -> new(); - $mock_response -> set_always('code', 200); - $mock_response -> set_always('json', []); - - my $mock_tx = Test::MockObject -> new(); - $mock_tx -> set_always('result', $mock_response); - - $mock_ua -> set_always('get', $mock_tx); - - local @ARGV = ('-t', 'test_token', '-r', 'test_repo'); - is( - scalar(main()), - 0, - 'Script exits with zero code when no alerts are found' - ); -}; - -subtest 'Multiple severity thresholds' => sub { - my $mock_response = Test::MockObject -> new(); - $mock_response -> set_always('code', 200); - $mock_response -> set_always('json', [ - { state => 'open', security_vulnerability => { severity => 'high' } }, - { state => 'open', security_vulnerability => { severity => 'critical' } }, - { state => 'open', security_vulnerability => { severity => 'medium' } }, - ]); - - my $mock_tx = Test::MockObject -> new(); - $mock_tx -> set_always('result', $mock_response); - - $mock_ua -> set_always('get', $mock_tx); - - local @ARGV = ('-t', 'test_token', '-r', 'test_repo', '-c', '0', '-h', '0', '-m', '0', '-l', '0'); - - my ($stdout, $stderr, $result) = capture { - main(); - }; - - diag("STDOUT: $stdout"); - diag("STDERR: $stderr"); - diag("Result: $result"); - - is( - $result, - 1, - 'Script exits with non-zero code when multiple thresholds are exceeded' - ); - - like( - $stdout, - qr/Total\ of\ security\ alerts:/x, - 'Output contains expected content' - ); -}; - -done_testing();