Catalyst-Plugin-OpenIDConnect

 view release on metacpan or  search on metacpan

t/03_plugin.t  view on Meta::CPAN

#!/usr/bin/perl
use strict;
use warnings;
use Test::More;
use FindBin;
use lib "$FindBin::Bin/../lib";

use Catalyst::Plugin::OpenIDConnect;
use Catalyst::Plugin::OpenIDConnect::Context;
use Catalyst::Plugin::OpenIDConnect::Utils::JWT;
use Catalyst::Plugin::OpenIDConnect::Utils::Store;
use Crypt::OpenSSL::RSA;
use File::Temp qw(tempfile);
use JSON::MaybeXS qw(encode_json);

# Generate test keys
my $rsa = Crypt::OpenSSL::RSA->generate_key(1024);

my $private_key = $rsa;
my $public_key = Crypt::OpenSSL::RSA->new_public_key(
    $rsa->get_public_key_string()
);

# Create JWT handler for testing context methods
my $jwt = Catalyst::Plugin::OpenIDConnect::Utils::JWT->new(
    private_key => $private_key,
    public_key  => $public_key,
    key_id      => 'test-key',
    issuer      => 'http://localhost:5000',
);

ok($jwt, 'JWT handler created');

# Create store for testing context methods
my $store = Catalyst::Plugin::OpenIDConnect::Utils::Store->new();
ok($store, 'Store created');

# Test _OpenIDConnectContext (the context object)
require_ok('Catalyst::Plugin::OpenIDConnect');

# Create a mock Catalyst object for testing the context
package MockCatalyst;
use Moose;

has config => (
    is      => 'ro',
    isa     => 'HashRef',
    default => sub { {} },
);

has _oidc_jwt => (
    is  => 'rw',
);

has _oidc_store => (
    is  => 'rw',
);

has log => (
    is      => 'ro',
    isa     => 'MockLogger',
    default => sub { MockLogger->new() },
);

sub uri_for {
    my ($self, $path) = @_;
    return bless { path => $path }, 'MockURI';
}

package MockLogger;
use Moose;

sub debug {
    my ($self, $msg) = @_;
    # Silently log or do nothing for testing
}

sub info {
    my ($self, $msg) = @_;
    # Silently log or do nothing for testing
}

sub warn {
    my ($self, $msg) = @_;
    # Silently log or do nothing for testing
}

t/03_plugin.t  view on Meta::CPAN


# Check algorithms
is_deeply(
    $discovery->{id_token_signing_alg_values_supported},
    ['RS256'],
    'RS256 algorithm supported for ID tokens'
);

# Check claims
my @claims = @{ $discovery->{claims_supported} };
ok(grep { $_ eq 'sub' } @claims, 'sub claim supported');
ok(grep { $_ eq 'email' } @claims, 'email claim supported');
ok(grep { $_ eq 'picture' } @claims, 'picture claim supported');

# Verify discovery contains required fields
ok($discovery->{response_types_supported}, 'response_types_supported present');
ok($discovery->{grant_types_supported}, 'grant_types_supported present');
ok($discovery->{subject_types_supported}, 'subject_types_supported present');

# Test get_discovery() with custom issuer URL
my $custom_issuer_context = Catalyst::Plugin::OpenIDConnect::Context->new(
    catalyst => MockCatalyst->new(
        config => {
            'Plugin::OpenIDConnect' => {
                issuer => {
                    url => 'https://auth.example.com',
                },
            },
        },
    ),
);

my $custom_discovery = $custom_issuer_context->get_discovery();
is($custom_discovery->{issuer}, 'https://auth.example.com', 'custom issuer URL in discovery');

# Test empty config handling
my $empty_context = Catalyst::Plugin::OpenIDConnect::Context->new(
    catalyst => MockCatalyst->new(
        config => {},
    ),
);

ok($empty_context->config, 'config() handles empty configuration');
is_deeply($empty_context->config, {}, 'empty config returns empty hashref');

# Test get_client with empty clients config
my $empty_client = $empty_context->get_client('any-client');
is($empty_client, undef, 'get_client() handles empty clients config');

# ---------------------------------------------------------------------------
# MED-3: JWT and Store instances are isolated per consuming application class
# ---------------------------------------------------------------------------

{
    my $app_a = bless {}, 'FakeAppAlpha';
    my $app_b = bless {}, 'FakeAppBeta';

    # Create a second distinct JWT instance for app_b
    my $rsa_b = Crypt::OpenSSL::RSA->generate_key(1024);
    my $jwt_b = Catalyst::Plugin::OpenIDConnect::Utils::JWT->new(
        private_key => $rsa_b,
        public_key  => Crypt::OpenSSL::RSA->new_public_key( $rsa_b->get_public_key_string() ),
        key_id      => 'key-b',
        issuer      => 'http://b.example.com',
    );

    Catalyst::Plugin::OpenIDConnect::_oidc_jwt( $app_a, $jwt );
    Catalyst::Plugin::OpenIDConnect::_oidc_jwt( $app_b, $jwt_b );

    is(
        Catalyst::Plugin::OpenIDConnect::_oidc_jwt($app_a), $jwt,
        'MED-3: FakeAppAlpha holds its own JWT instance',
    );
    is(
        Catalyst::Plugin::OpenIDConnect::_oidc_jwt($app_b), $jwt_b,
        'MED-3: FakeAppBeta holds its own JWT instance',
    );
    isnt(
        Catalyst::Plugin::OpenIDConnect::_oidc_jwt($app_a),
        Catalyst::Plugin::OpenIDConnect::_oidc_jwt($app_b),
        'MED-3: JWT instances are isolated between application classes',
    );
}

# ---------------------------------------------------------------------------
# MED-4: Implicit grant/response types removed from discovery document
# ---------------------------------------------------------------------------

{
    my $grant_types    = $discovery->{grant_types_supported};
    my $response_types = $discovery->{response_types_supported};

    ok(
        !grep { $_ eq 'implicit' } @$grant_types,
        'MED-4: implicit not in grant_types_supported',
    );
    ok(
        scalar( grep { $_ eq 'authorization_code' } @$grant_types ),
        'authorization_code still in grant_types_supported',
    );

    ok(
        !grep { $_ eq 'id_token' || $_ eq 'token' } @$response_types,
        'MED-4: implicit response types (id_token, token) not in response_types_supported',
    );
    ok(
        scalar( grep { $_ eq 'code' } @$response_types ),
        'code still in response_types_supported',
    );
}

done_testing();



( run in 0.338 second using v1.01-cache-2.11-cpan-13bb782fe5a )