The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package authorizationcodegrant_tests;

use strict;
use warnings;

use Test::Most;
use Test::Exception;

sub callbacks {
	my ( $Grant ) = @_;
	return (
		verify_client_cb => sub { return Net::OAuth2::AuthorizationServer::AuthorizationCodeGrant::_verify_client( $Grant,@_ ) },
		store_auth_code_cb => sub { return Net::OAuth2::AuthorizationServer::AuthorizationCodeGrant::_store_auth_code( $Grant,@_ ) },
		verify_auth_code_cb => sub { return Net::OAuth2::AuthorizationServer::AuthorizationCodeGrant::_verify_auth_code( $Grant,@_ ) },
		store_access_token_cb => sub { return Net::OAuth2::AuthorizationServer::AuthorizationCodeGrant::_store_access_token( $Grant,@_ ) },
		verify_access_token_cb => sub { return Net::OAuth2::AuthorizationServer::AuthorizationCodeGrant::_verify_access_token( $Grant,@_ ) },
		login_resource_owner_cb => sub { return Net::OAuth2::AuthorizationServer::AuthorizationCodeGrant::_login_resource_owner( $Grant,@_ ) },
		confirm_by_resource_owner_cb => sub { return Net::OAuth2::AuthorizationServer::AuthorizationCodeGrant::_confirm_by_resource_owner( $Grant,@_ ) },
	);
}

sub clients {

	return {
		test_client => {
			client_secret => 'letmein',
			scopes => {
				eat   => 1,
				drink => 0,
				sleep => 1,
			},
		},
	};
}

sub run_tests {
	my ( $Grant,$args ) = @_;

	$args //= {};

	can_ok(
		$Grant,
		qw/
			clients
		/
	);

	ok( $Grant->login_resource_owner,'login_resource_owner' );
	my ( $confirmed,$confirm_error,$scopes_ref ) = $Grant->confirm_by_resource_owner(
		client_id => 'test_client',
		scopes => [ qw/ eat sleep / ],
	);
	
	ok( $confirmed,'confirm_by_resource_owner' );
	ok( !$confirm_error,' ... no error' );
	cmp_deeply( $scopes_ref,[ qw/ eat sleep / ],' ... returned scopes ref' );

	note( "verify_client" );

	my %valid_client = (
		client_id => 'test_client',
		scopes    => $scopes_ref,
	);

	my ( $res,$error ) = $Grant->verify_client( %valid_client );

	ok( $res,'->verify_client, allowed scopes' );
	ok( ! $error,'has no error' );

	foreach my $t (
		[ { scopes => [ qw/ eat sleep yawn / ] },'invalid_scope','invalid scopes' ],
		[ { client_id => 'another_client' },'unauthorized_client','invalid client' ],
	) {
		( $res,$error ) = $Grant->verify_client( %valid_client,%{ $t->[0] } );

		ok( ! $res,'->verify_client, ' . $t->[2] );
		is( $error,$t->[1],'has error' );
	}

    foreach my $t (
		[ { scopes => [ qw/ eat sleep drink / ] },[ qw / eat sleep / ],'disallowed scopes' ],
    ) {
        my $scopes;
        ( $res, $error, $scopes ) = $Grant->verify_client( %valid_client,%{ $t->[0] } );

        ok ( $res, '->verify_client, ' . $t->[2] );
        cmp_deeply( $scopes, $t->[1], 'has reduced scopes' );
    }

	note( "store_auth_code" );

	ok( my $auth_code = $Grant->token(
		client_id    => 'test_client',
		scopes       => $scopes_ref,
		type         => 'auth',
		redirect_uri => 'https://come/back',
		user_id      => 1,
	),'->token (auth code)' );

	$args->{token_format_tests}->( $auth_code,'auth' )
		if $args->{token_format_tests};

	ok( $Grant->store_auth_code(
		client_id    => 'test_client',
		auth_code    => $auth_code,
		redirect_uri => 'https://come/back',
		scopes       => $scopes_ref,
	),'->store_auth_code' );

	note( "verify_auth_code" );

	my %valid_auth_code = (
		client_id     => 'test_client',
		client_secret => 'letmein',
		auth_code     => $auth_code,
		redirect_uri  => 'https://come/back',
	);

	my ( $client,$vac_error,$scopes ) = $Grant->verify_auth_code( %valid_auth_code );

	ok( $client,'->verify_auth_code, correct args' );
	ok( ! $vac_error,'has no error' );
	cmp_deeply( $scopes,[ qw/ eat sleep / ],'has scopes' );

	foreach my $t (
		[ { client_id => 'another_client' },'unauthorized_client','invalid client' ],
		[ { client_secret => 'bad secret' },'invalid_grant','bad client secret' ],
		[ { redirect_uri => 'http://not/this' },'invalid_grant','bad redirect uri' ],
	) {
		( $client,$vac_error,$scopes ) = $Grant->verify_auth_code(
			%valid_auth_code,%{ $t->[0] },
		);

		ok( ! $client,'->verify_auth_code, ' . $t->[2] );
		is( $vac_error,$t->[1],'has error' );
		ok( ! $scopes,'has no scopes' );
	}

	my $og_auth_code = $auth_code;
	chop( $auth_code );

	( $client,$vac_error,$scopes ) = $Grant->verify_auth_code(
		%valid_auth_code,
		auth_code => $auth_code,
	);

	ok( ! $client,'->verify_auth_code, token fiddled with' );
	is( $vac_error,'invalid_grant','has error' );
	ok( ! $scopes,'has no scopes' );

	note( "store_access_token" );

	ok( my $access_token = $Grant->token(
		client_id    => 'test_client',
		scopes       => $scopes_ref,
		type         => 'access',
		user_id      => 1,
	),'->token (access token)' );

	$args->{token_format_tests}->( $access_token,'access' )
		if $args->{token_format_tests};

	ok( my $refresh_token = $Grant->token(
		client_id    => 'test_client',
		scopes       => $scopes_ref,
		type         => 'refresh',
		user_id      => 1,
	),'->token (refresh token)' );

	$args->{token_format_tests}->( $refresh_token,'refresh' )
		if $args->{token_format_tests};

	ok( $Grant->store_access_token(
		client_id     => 'test_client',
		auth_code     => $og_auth_code,
		access_token  => $access_token,
		refresh_token => $refresh_token,
		scopes        => $scopes_ref,
	),'->store_access_token' );

	note( "verify_access_token" );

	( $res,$error ) = $Grant->verify_access_token(
		access_token     => $access_token,
		scopes           => $scopes_ref,
		is_refresh_token => 0,
	);

	ok( $res,'->verify_access_token, valid access token' );
	ok( ! $error,'has no error' );

	( $res,$error ) = $Grant->verify_access_token(
		access_token     => $refresh_token,
		scopes           => $scopes_ref,
		is_refresh_token => 1,
	);

	ok( $res,'->verify_access_token, valid refresh token' );
	ok( ! $error,'has no error' );

	( $res,$error ) = $Grant->verify_access_token(
		access_token     => $access_token,
		scopes           => [ qw/ drink / ],
		is_refresh_token => 0,
	);

	ok( ! $res,'->verify_access_token, invalid scope' );
	is( $error,'invalid_grant','has error' );

	( $res,$error ) = $Grant->verify_access_token(
		access_token     => $access_token,
		scopes           => [ qw/ drink / ],
		is_refresh_token => 1,
	);

	ok( ! $res,'->verify_access_token, refresh token is not access token' );
	is( $error,'invalid_grant','has error' );

	( $res,$error ) = $Grant->verify_token_and_scope(
		auth_header      => "Bearer $access_token",
		scopes           => $scopes_ref,
		is_refresh_token => 0,
	);

	ok( $res,'->verify_token_and_scope, valid access token' );
	ok( ! $error,'has no error' );

	( $res,$error ) = $Grant->verify_token_and_scope(
		auth_header   => "Bearer $access_token",
		scopes        => $scopes_ref,
		refresh_token => $refresh_token,
	);

	ok( $res,'->verify_token_and_scope, valid refresh token' );
	ok( ! $error,'has no error' );

	my $og_access_token = $access_token;
	chop( $access_token );

	( $res,$error ) = $Grant->verify_access_token(
		access_token     => $access_token,
		scopes           => $scopes_ref,
		is_refresh_token => 0,
	);

	ok( ! $res,'->verify_access_token, token fiddled with' );
	is( $error,'invalid_grant','has error' );

	unless ( $args->{cannot_revoke} ) {
		note( "verify_auth_code" );
		( $client,$vac_error,$scopes ) = $Grant->verify_auth_code( %valid_auth_code );

		ok( ! $client,'->verify_auth_code, correct args but second time' );
		is( $vac_error,'invalid_grant','has no error' );
		ok( ! $scopes,'has no scopes' );

		( $res,$error ) = $Grant->verify_access_token(
			access_token     => $access_token,
			scopes           => $scopes_ref,
			is_refresh_token => 0,
		);

		ok( ! $res,'->verify_access_token, access token revoked' );
		is( $error,'invalid_grant','has error' );
	}

	return $og_access_token;
}

1;