Finance-Bank-Cahoot

 view release on metacpan or  search on metacpan

README  view on Meta::CPAN

NAME
    Finance::Bank::Cahoot - Check your Cahoot bank accounts from Perl

DESCRIPTION
    This module provides a rudimentary interface to the Cahoot online
    banking system at "https://www.cahoot.com/". You will need either
    "Crypt::SSLeay" or "IO::Socket::SSL" installed for HTTPS support to work
    with WWW::Mechanize.

SYNOPSIS
      my $cahoot = Finance::Bank::Cahoot->new(credentials => 'Constant',
                                              credentials_options => {
                                                 account => '12345678',
                                                 password => 'verysecret',
                                                 place => 'London',
                                                 date => '01/01/1906',
                                                 username => 'dummy',
                                                 maiden => 'Smith' } );

      my $accounts = $cahoot->accounts;
      $cahoot->set_account($accounts->[0]->{account});
      my $snapshot = $cahoot->snapshot;
      foreach my $row (@$snapshot) {
        print join ',', @$row; print "\n";
      }

METHODS
    new Create a new instance of a connection to the Cahoot server.

        "new" can be called in two different ways. It can take a single
        parameter, "credentials", which will accept an already created
        credentials object, of type
        "Finance::Bank::Cahoot::CredentialsProvider::*". Alternatively, it
        can take two parameters, "credentials" and "credentials_options". In
        this case "credentials" is the name of a credentials class to create
        an instance of, and "credentials_options" is a hash of the options
        to pass-through to the constructor of the chosen class.

        If the second form of "new" is being used, and the chosen class is
        *not* one of the ones supplied as standard then it will need to be
        "required" first.

        If any errors occur then "new" will "croak".

          my $cahoot = Finance::Bank::Cahoot->new(credentials => 'Constant',
                                                  credentials_options => {
                                                     account => '12345678',
                                                     password => 'verysecret',
                                                     place => 'London',
                                                     date => '01/01/1906',
                                                     username => 'dummy',
                                                     maiden => 'Smith' } );

          # Or create the credentials object ourselves
          my $credentials = Finance::Bank::Cahoot::CredentialsProvider::Constant->new(
             account => '12345678', password => 'verysecret', place => 'London',
             date => '01/01/1906', username => 'dummy', maiden => 'Smith' } );
          my $cahoot = Finance::Bank::Cahoot->new(credentials => $credentials);

    login
        Login to the Cahoot server using the credentials supplied to "new".
        This method is implicit for all data access methods, so typically
        does not need to be called explicitly. The method takes no arguments
        and will only call one of memorable place, date or mother's maiden
        name as expected by the Cahoot portal.

    accounts
        Returns a list reference containing a summary of any accounts
        available from the supplied credentials. If a login has yet to occur
        "accounts" will automatically do this.

          my $accounts = $cahoot->accounts;

        Each item in the list is a hash reference that holds summary
        information for a single account, and contains this data:

        name - the text name of the account
        account - the account number
        balance - the current balance of the account

examples/debits  view on Meta::CPAN

#! /usr/bin/perl

use strict;
use warnings;

use Finance::Bank::Cahoot;

my $cahoot = Finance::Bank::Cahoot->new(
               credentials => 'ReadLine',
	       credentials_options => { account => '12345678',
					username => 'acmeuser' });
$cahoot->login;
my $accounts = $cahoot->accounts;

foreach my $account (@$accounts) {
  next unless $account->{name} =~ /current/;
  $cahoot->set_account($account->{account});
}

my $debits = $cahoot->debits;

examples/snapshot  view on Meta::CPAN

#! /usr/bin/perl

use strict;
use warnings;

use Finance::Bank::Cahoot;

my $cahoot = Finance::Bank::Cahoot->new(
               credentials => 'ReadLine',
	       credentials_options => { account => '12345678',
					username => 'acmeuser' });
my $accounts = $cahoot->accounts;
foreach my $account (@$accounts) {
  next unless $account->{name} =~ /current/;
  $cahoot->set_account($account->{account});
  my $snapshot = $cahoot->snapshot;
  foreach my $transaction (@$snapshot) {
    print $transaction->date, q{,},
          $transaction->details, q{,},
          $transaction->credit || 0, q{,},

examples/statement  view on Meta::CPAN

#! /usr/bin/perl

use strict;
use warnings;

use Finance::Bank::Cahoot;

my $cahoot = Finance::Bank::Cahoot->new(
               credentials => 'ReadLine',
	       credentials_options => { account => '12345678',
					username => 'acmeuser' });
$cahoot->login;
my $accounts = $cahoot->accounts;

foreach my $account (@$accounts) {
  next unless $account->{name} =~ /current/;
  $cahoot->set_account($account->{account});
}

my $statements = $cahoot->statements;

lib/Finance/Bank/Cahoot.pm  view on Meta::CPAN

use HTML::TableExtract;
use WWW::Mechanize;

use Finance::Bank::Cahoot::Statement;
use Finance::Bank::Cahoot::DirectDebit;

sub new
{
  my ($class, %opts) = @_;

  croak 'Must provide a credentials handler' if not exists $opts{credentials};

  my $self = { _mech        => new WWW::Mechanize(autocheck => 1),
	       _credentials => $opts{credentials},
	       _connected   => 0,
	     };
  $self->{_mech}->agent_alias('Mac Safari');

  bless $self, $class;
  $self->_set_credentials(%opts);
  return $self;
}

sub _set_credentials
{
  my ($self, %opts) = @_;

  croak 'Must provide either a premade credentials object or a class name together with options'
      if not exists $opts{credentials};

  if (ref $opts{credentials}) {
    croak 'Not a valid credentials object'
      if not $self->_isa_credentials($opts{credentials});

    croak 'Can\'t accept credential options if supplying a premade credentials object'
	if exists $opts{credentials_options};

    $self->{_credentials} = $opts{credentials};
  } else {
    croak 'Must provide credential options unless suppying a premade credentials object'
	if not exists $opts{credentials_options};

    $self->{_credentials} =
      $self->_new_credentials($opts{credentials}, $opts{credentials_options});
  }
  return $self;
}

sub _new_credentials
{
  my ($self, $class, $options) = @_;

  croak 'Invalid class name'
    if $class !~ /^(?:\w|::)+$/;

  my $full_class = 'Finance::Bank::Cahoot::CredentialsProvider::'.$class;

  eval "local \$SIG{'__DIE__'}; local \$SIG{'__WARN__'}; require $full_class;";  ## no critic
  croak 'Not a valid credentials class - not found' if $EVAL_ERROR;

  my $credentials;
  {
    local $Carp::CarpLevel = $Carp::CarpLevel + 1;   ## no critic
    $credentials = $full_class->new(credentials => [@REQUIRED_SUBS],
				    options => $options);
  }
  croak 'Not a valid credentials class - incomplete' if not $self->_isa_credentials($credentials);
  return $credentials;
}

sub _isa_credentials
{
  my ($self, $credentials) = @_;

  foreach my $sub (@REQUIRED_SUBS) {
    return unless defined eval {
      local $SIG{'__DIE__'};       ## no critic
      local $SIG{'__WARN__'};      ## no critic
      $credentials->can($sub);
    };
  }

  return 1;
}

sub login
{
  my ($self) = @_;

  return if $self->{_connected};

  $self->_get('https://securebank.cahoot.com/AquariusSecurity/bks/web/en/core_banking/log_in/frameset_top_log_in.jsp');
  my %fields = (inputuserid => $self->{_credentials}->username());
  my $content = $self->{_mech}->content;
  foreach my $input ($self->{_mech}->find_all_inputs()) {
     my $name = $input->name();
     next if not defined $name;
     next if defined $fields{$name};
     $fields{$name} = $self->{_credentials}->place() if $content =~ /address or place:/i;
     $fields{$name} = $self->{_credentials}->date() if $content =~ /memorable year:/i;
     $fields{$name} = $self->{_credentials}->maiden() if $content =~ /maiden name:/i;
  }
  $self->_submit_form(fields => \%fields);

  my %chars;
  my $label;
  # Expect:
  #   <label for="passwordChar1">... select character #d ...</label>
  HTML::Parser->new(unbroken_text => 1,
		    report_tags   => [qw(label)],
		    start_h       => [ sub {

lib/Finance/Bank/Cahoot.pm  view on Meta::CPAN

		    text_h        => [ sub {
					 return if not defined $label;
					 $_[0] =~ /select character.*?(\d+)/;
					 $chars{$label} = $1 if defined $1;
				       }, 'dtext' ])->parse($self->{_mech}->content());

  {
    # We submit a form, modifying hidden fields - WWW::Mechanize does not like
    # this (see FAQ).
    local $^W = 0; ## no critic
    $self->_submit_form(fields => { passwordChar1Hidden => $self->{_credentials}->password($chars{1}),
				    passwordChar2Hidden => $self->{_credentials}->password($chars{2}),
				    passwordChar1 => q{*},
				    passwordChar2 => q{*} });
  }
  $self->{_connected} = 1;
  return $self;
}

sub _test_content_for_error
{
  my ($self) = @_;

lib/Finance/Bank/Cahoot.pm  view on Meta::CPAN


=head1 DESCRIPTION

This module provides a rudimentary interface to the Cahoot online
banking system at C<https://www.cahoot.com/>. You will need
either C<Crypt::SSLeay> or C<IO::Socket::SSL> installed for HTTPS
support to work with WWW::Mechanize.

=head1 SYNOPSIS

  my $cahoot = Finance::Bank::Cahoot->new(credentials => 'Constant',
                                          credentials_options => {
                                             account => '12345678',
                                             password => 'verysecret',
					     place => 'London',
					     date => '01/01/1906',
					     username => 'dummy',
					     maiden => 'Smith' } );

  my $accounts = $cahoot->accounts;
  $cahoot->set_account($accounts->[0]->{account});
  my $snapshot = $cahoot->snapshot;

lib/Finance/Bank/Cahoot.pm  view on Meta::CPAN


=head1 METHODS

=over 4 

=item B<new>

Create a new instance of a connection to the Cahoot server. 

C<new> can be called in two different ways. It can take a single parameter,
C<credentials>, which will accept an already created credentials object, of type 
C<Finance::Bank::Cahoot::CredentialsProvider::*>. Alternatively, it can take two
parameters, C<credentials> and C<credentials_options>. In this case 
C<credentials> is the name of a credentials class to create an instance of, and
C<credentials_options> is a hash of the options to pass-through to the
constructor of the chosen class.

If the second form of C<new> is being used, and the chosen class is I<not> one
of the ones supplied as standard then it will need to be C<required> first.

If any errors occur then C<new> will C<croak>.

  my $cahoot = Finance::Bank::Cahoot->new(credentials => 'Constant',
                                          credentials_options => {
                                             account => '12345678',
                                             password => 'verysecret',
					     place => 'London',
					     date => '01/01/1906',
					     username => 'dummy',
					     maiden => 'Smith' } );

  # Or create the credentials object ourselves
  my $credentials = Finance::Bank::Cahoot::CredentialsProvider::Constant->new(
     account => '12345678', password => 'verysecret', place => 'London',
     date => '01/01/1906', username => 'dummy', maiden => 'Smith' } );
  my $cahoot = Finance::Bank::Cahoot->new(credentials => $credentials);

=item B<login>

Login to the Cahoot server using the credentials supplied to C<new>. This method
is implicit for all data access methods, so typically does not need to be called
explicitly. The method takes no arguments and will only call one of memorable
place, date or mother's maiden name as expected by the Cahoot portal.

=item B<accounts>

Returns a list reference containing a summary of any accounts available from
the supplied credentials. If a login has yet to occur C<accounts> will
automatically do this.

  my $accounts = $cahoot->accounts;

Each item in the list is a hash reference that holds summary information for a
single account, and contains this data:

=over 4

=item B<name> - the text name of the account

lib/Finance/Bank/Cahoot/CredentialsProvider.pm  view on Meta::CPAN

use Carp qw(croak);
use English '-no_match_vars';

sub new
{
  my ($class, %args) = @_;

  croak 'Calling abstract base class constructor for '.__PACKAGE__.' is forbidden'
    if $class eq __PACKAGE__;

  croak 'Must provide a list of credentials'
    if not exists $args{credentials};

  croak 'credentials is not an array ref'
    if ref $args{credentials} ne 'ARRAY';

  if (exists $args{options}) {
    croak 'options must be a hash ref'
      if ref $args{options} ne 'HASH';
    my @o = keys %{$args{options}};
    croak 'Empty list of options'
      if $#o < 0;
  }

  my $self = { };
  bless $self, $class;
  $self->{_credentials} = $args{credentials};
  foreach my $credential (@{$args{credentials}}) {
    no strict 'refs'; ## no critic
    *{"${class}::$credential"} = sub { my ($self, $offset) = @_;
				       return $self->get($credential, $offset);
				     };
  }
  $self->_init($args{options});
  return $self;
}

sub get

lib/Finance/Bank/Cahoot/CredentialsProvider.pm  view on Meta::CPAN

}

1;

__END__

=for stopwords Connell Belka

=head1 NAME

 Finance::Bank::Cahoot::CredentialsProvider - Abstract base class for credentials providers

=head1 SYNOPSIS

  my $credentials = Finance::Bank::Cahoot::CredentialsProvider::Acme->new(
     credentials => [qw(account password)],
     options => {account => 'acmeuser'});

=head1 DESCRIPTION

Provides an abstract base class for deriving new credentials providers with a
defined interface. Derived classes B<MUST> implement C<_init> and C<get>
methods.

=head1 METHODS

=over 4

=item B<new>

Create a new instance of a credentials provider. Each credential is available
with its own access method of the same name. All methods may be optionally supplied a
character offset in the credentials value (first character is 0).

=item B<credentials> is an array ref of all the credentials types available via the
credentials provider.

=item B<options> is a hash ref of options for each credential. These are
used by the credentials provider in an implementation-defined manner.

=back

=head1 ABSTRACT METHODS

=over 4

=item B<_init>

Initialization routine for the derived class to implement. Called by C<new>
with the credentials options as a hash reference. In the following example,
taken from the the C<Constant> credentials provider, C<_init> simply stores
each option value in the class:

  sub _init
  {
    my ($self, $options) = @_;
    while (my ($credential, $value) = each %{$options}) {
      croak 'Invalid credential '.$credential.' supplied with callback'
        if not $self->can($credential);
      $self->{$credential} = $value;
    }
  }

=item B<get>

Public access method for the derived class to implement. Called with the
name of the credential to supply and an optional character offset (0 is
the first character). In the following example, taken from the the
C<Constant> credentials provider, the credential is simply returned from
class members created by the previous C<_init> method:

  sub get
  {
    my ($self, $credential, $offset) = @_;
    return substr ($self->{$credential}, $offset, 1)
      if defined $offset;
    return $self->{$credential};
  }

lib/Finance/Bank/Cahoot/CredentialsProvider/Callback.pm  view on Meta::CPAN

__END__

=for stopwords Connell Belka

=head1 NAME

 Finance::Bank::Cahoot::CredentialsProvider::Callback - Credentials provider that uses callbacks

=head1 SYNOPSIS

  my $credentials =  Finance::Bank::Cahoot::CredentialsProvider::Callback->new(
     credentials => [qw(account password)],
     options => { account => sub { return '12345678' },
                  username => sub { return 'username' },
                  password => sub { return substr('verysecret', $_[0]-1, 1) } });

=head1 DESCRIPTION

This module provides an implementation of a credentials provider where each of
the credentials is provided by a user-supplied callback. All callbacks return a
text string. Any callback may be optionally supplied a character offset in the
credentials value (first character is 0).

=head1 METHODS

=over 4

=item B<new>

Create a new instance of the credentials provider. All parameters are mandatory.

=item B<credentials> is an array ref of all the credentials types available via the
credentials provider.

=item B<options> is a hash ref of callback routines for each credential.

=item B<get>

Returns a credential value whose name is passed as the first parameter. An
optional  character offset (0 is the first character) may also be provided.

  my $password_char = $provider->password(5);

lib/Finance/Bank/Cahoot/CredentialsProvider/Constant.pm  view on Meta::CPAN

__END__

=for stopwords Connell Belka

=head1 NAME

 Finance::Bank::Cahoot::CredentialsProvider::Constant - Credentials provider for static data

=head1 SYNOPSIS

  my $credentials = Finance::Bank::Cahoot::CredentialsProvider::Constant->new(
     credentials => [qw(account password)],
     options => {account => 'acmeuser'});

=head1 DESCRIPTION

Provides a credentials provider that returns static data. Each credential is available
with its own access method of the same name. All methods may be optionally supplied a
character offset in the credentials value (first character is 1).

=head1 METHODS

=over 4

=item B<new>

Create a new instance of a static data credentials provider.

=item B<credentials> is an array ref of all the credentials types available via the
credentials provider.

=item B<options> is a hash ref of constant return values of credentials.

=item B<get>

Returns a credential value whose name is passed as the first parameter. An
optional  character offset (1 is the first character) may also be provided.

  my $password_char = $provider->password(5);

=back

lib/Finance/Bank/Cahoot/CredentialsProvider/CryptFile.pm  view on Meta::CPAN

      $self->{$k} = $v;
    }
    $fh->close;
  }

  if (defined $options->{fallback}) {
    my $fallback_class = 'Finance::Bank::Cahoot::CredentialsProvider::'.$options->{fallback};
    eval "use $fallback_class"; ## no critic
    croak 'Invalid fallback provider '.$options->{fallback} if $EVAL_ERROR;

    my $fallback_args = { credentials => $self->{_credentials},
			  options => $options->{fallback_options} };
    eval "\$self->{_fallback} = $fallback_class->new(\%{\$fallback_args})"; ## no critic
    croak 'Fallback provider '.$options->{fallback}.' failed to initialise' if $EVAL_ERROR;
  }

  my $do_update = 0;
  foreach my $credential (@{$self->{_credentials}}) {
    if (not defined $self->{$credential}) {
      croak 'No fallback provider given and '.$credential.' is not in keyfile'
	if not defined $self->{_fallback};
      $self->{$credential} = $self->{_fallback}->$credential;
      $do_update = 1;
    }
  }

  if ($do_update) {
    my $fh = new IO::File $keyfile, 'w'
      or croak "Can't open $keyfile for writing: $OS_ERROR";

    my @ciphers;
    foreach my $credential (@{$self->{_credentials}}) {
      push @ciphers, $credential."\t".$self->{$credential};
    }
    my $ciphertext = $cipher->encrypt(join "\n", @ciphers);
    $fh->print($ciphertext);
    $fh->close;
  }
  return $self;
}

sub get

lib/Finance/Bank/Cahoot/CredentialsProvider/CryptFile.pm  view on Meta::CPAN

__END__

=for stopwords Connell Belka Gariv passphrase keyfile crypto

=head1 NAME

 Finance::Bank::Cahoot::CredentialsProvider::CryptFile - Credentials provider for encrypted stored data

=head1 SYNOPSIS

  my $credentials = Finance::Bank::Cahoot::CredentialsProvider::CrpytFile->new(
     credentials => [qw(account password)],
     options => {key => 'verysecret', keyfile => '/etc/cahoot'});

=head1 DESCRIPTION

Provides a credentials provider that uses credentials stored in an encrypted file.
Each credential is available with its own access method of the same name. All methods
may be optionally supplied a character offset in the credentials value (first
character is 0).

=head1 METHODS

=over 4

=item B<new>

Create a new instance of a static data credentials provider.

=item B<credentials> is an array ref of all the credentials types available via the
credentials provider.

=item B<options> a hash ref of options for the credentials provider.

=over 4

=item B<key> Is the text passphrase for encrypting/decrypting the credentials store.

=item B<keyfile> is an optional path to the credentials store. The default store
is C<$HOME/.cahoot>.

=item B<fallback> is the name of a C<Finance::Bank::Cahoot::CredentialsProvider>
credentials provider to use for any credentials that are not present in
the encrypted store. Newly discovered credentials and encrypted and written
back to the store.

=item B<fallback_options> is a hash ref that is passed to the fallback credentials
provider's constructor as C<options>.

  my $provider =
    Finance::Bank::Cahoot::CredentialsProvider::CryptFile->new(
      credentials => [qw(account username password)],
      options => { key => 'verysecret',
		   keyfile => '/etc/cahoot,
		   fallback => 'Constant',
		   fallback_options => { account => '12345678',
			 		 username => 'acmeuser',
				 	 password => 'secret' } });

=back

=item B<get>

lib/Finance/Bank/Cahoot/CredentialsProvider/ReadLine.pm  view on Meta::CPAN

}

1;

__END__

=for stopwords Connell Belka username online

=head1 NAME

 Finance::Bank::Cahoot::CredentialsProvider::ReadLine - Console-based credentials provider

=head1 SYNOPSIS

  my $credentials =  Finance::Bank::Cahoot::CredentialsProvider::ReadLine->new(
     account => '12345678', username => 'acmeuser',
     password_prompt => 'Enter character %d of your password: '
  );

=head1 DESCRIPTION

This module provides a C<Term::ReadLine> implementation of a credentials provider
for console entry of credentials. Each credentials method can be overridden by
a constant parameter to reduce the amount of console interaction with the user in
the case of less security sensitive data such as a username. In addition to the
value overrides, the text prompt for each readline method can also be overridden.

=head1 METHODS

=over 4

=item B<new>

Create a new instance of the credentials provider.

=item B<credentials> is an array ref of all the credentials types available via the
credentials provider.

=item B<options> is a hash ref of optional default values and prompts for each
credential. Prompts are provided in C<options> using keys of the form
C<credential_prompt>.

  my $credentials =  Finance::Bank::Cahoot::CredentialsProvider::ReadLine->new(
     account => '12345678', username => 'acmeuser',
     password_prompt => 'Enter character %d of your password: '
  );

=item B<get>

Returns a credential value whose name is passed as the first parameter. An
optional  character offset (1 is the first character) may also be provided.

  my $password_char = $provider->password(5);

lib/Finance/Bank/Cahoot/DirectDebit.pm  view on Meta::CPAN


Finance::Bank::Cahoot::DirectDebit - Cahoot direct debit record

=head1 DESCRIPTION

This module describes describes the object that holds the information
contained in a single statement transaction.

=head1 SYNOPSIS

  my $cahoot = Finance::Bank::Cahoot->new(credentials => 'ReadLine');
  my @accounts = $cahoot->accounts;
  $cahoot->set_account($accounts->[0]->{account});
  my $debits = $cahoot->debits;
  foreach my $debit (@$debits) {
    print $debit->payee, q{,},
          $debit->reference || 0, qq{\n};
  }

=head1 METHODS

lib/Finance/Bank/Cahoot/Statement.pm  view on Meta::CPAN

Finance::Bank::Cahoot::Statement - Cahoot statement object

=head1 DESCRIPTION

This module describes describes the object that holds the information
contained in a single statement returned by the C<Finance::Bank::Cahoot>
C<statement> and C<snapshot> methods.

=head1 SYNOPSIS

  my $cahoot = Finance::Bank::Cahoot->new(credentials => 'ReadLine');
  my @accounts = $cahoot->accounts;
  $cahoot->set_account($accounts->[0]->{account});
  my $snapshot = $cahoot->snapshot;
  foreach my $transaction (@$snapshot) {
    print $transaction->date, q{,},
          $transaction->details, q{,},
          $transaction->credit || 0, q{,},
          $transaction->debit || 0, qq{\n};
  }

lib/Finance/Bank/Cahoot/Statement/Entry.pm  view on Meta::CPAN


Finance::Bank::Cahoot::Statement::Entry - Cahoot statement transaction object

=head1 DESCRIPTION

This module describes describes the object that holds the information
contained in a single statement transaction.

=head1 SYNOPSIS

  my $cahoot = Finance::Bank::Cahoot->new(credentials => 'ReadLine');
  my @accounts = $cahoot->accounts;
  $cahoot->set_account($accounts->[0]->{account});
  my $snapshot = $cahoot->snapshot;
  foreach my $transaction (@$snapshot) {
    print $transaction->date, q{,},
          $transaction->details, q{,},
          $transaction->credit || 0, q{,},
          $transaction->debit || 0, qq{\n};
  }

t/01-connection.t  view on Meta::CPAN

use Test::More tests => 31;
use Test::Exception;

use Mock::WWW::Mechanize;
my $cs = Mock::WWW::Mechanize->new('t/pages');

use_ok( 'Finance::Bank::Cahoot' );

dies_ok {
  my $c = Finance::Bank::Cahoot->new()
} 'no credentials supplied: expected to fail';

my %invalid_details = ('Must provide a credentials handler'
		       => {},

		       'Must provide credential options unless suppying a premade credentials object (1)'
		       => { credentials => 'Constant' },

		       'Must provide credential options unless suppying a premade credentials object (2)'
		       => { credentials => 'UnknownCP' },

		       'Not a valid credentials class - not found (1)'
		       => { credentials => 'UnknownCP', credentials_options => {} },

		       'Not a valid credentials object (1)'
		       => { credentials => bless {}, 'YetAnotherUnknownCP' },

		       'Not a valid credentials object (2)'
		       => { credentials => {} },

		       'Not a valid credentials object (3)'
		       => { credentials => {}, credentials_options => {} },

		       'Invalid class name'
		       => { credentials => ':bogus:', credentials_options =>{} },

		       'Must provide a credentials handler'
		       => { credentials_options => {} }
		      );
while (my ($message, $options) = each %invalid_details) {
  dies_ok {
    my $c = Finance::Bank::Cahoot->new(%{$options});
  } 'invalid credential parameters: expected to fail';
  my $re = $message;
  $re =~ s/\s*\(\d+\)$//;
  like($@, qr/$re at /, 'exception: '.$message);
}

dies_ok {
  my $provider = {};
  $INC{'Finance/Bank/Cahoot/CredentialsProvider/Bogus.pm'} = 1;
  bless $provider, 'Finance::Bank::Cahoot::CredentialsProvider::Bogus';
  package Finance::Bank::Cahoot::CredentialsProvider::Bogus;
  sub new {};
  package main;
  my $c = Finance::Bank::Cahoot->new(credentials => 'Bogus', credentials_options => {});
} 'incomplete pre-made credentials class: expected to fail';
like($@, qr/Not a valid credentials class - incomplete at /, 'exception: invalid credentials');

{
  my $cahoot;

  ok($cahoot = Finance::Bank::Cahoot->new(credentials => 'Constant',
					  credentials_options => { account => '12345678',
								   password => 'verysecret',
								   place => 'London',
								   date => '01/01/1906',
								   username => 'dummy',
								   maiden => 'Smith'
								 },
					 ),
     'valid credentials - getting ::Cahoot to create credentials object'
    );

  isa_ok($cahoot, 'Finance::Bank::Cahoot' );

  foreach my $method (qw(login set_account statement statements
			 set_statement snapshot accounts)) {
    can_ok($cahoot, $method);
  }
  foreach my $method (qw(password place date username maiden)) {
    no strict 'refs';
    undef *{"Finance::Bank::Cahoot::CredentialsProvider::Constant::$method"};
  }
}

{
  my $creds = Finance::Bank::Cahoot::CredentialsProvider::Constant->new(credentials => [qw(password place date username maiden)],
									options => { account => '12345678',
										     password => 'verysecret',
										     place => 'London',
										     date => '01/01/1906',
										     username => 'dummy',
										     maiden => 'Smith' });

  ok(my $cahoot = Finance::Bank::Cahoot->new(credentials => $creds),
     'valid credentials - providing premade credentials object');

  $cahoot->login();
  ok($cahoot->{_connected}, 'Logged in successfully');
}

t/02-constant.t  view on Meta::CPAN

dies_ok {
  my $provider = Finance::Bank::Cahoot::CredentialsProviderProvider->new;
} 'invalid base constructor: expected to fail';

{
  package Finance::Bank::Cahoot::CredentialsProvider::Broken;
  use base qw(Finance::Bank::Cahoot::CredentialsProvider);
  sub _init {};
  package main;

  my $provider = Finance::Bank::Cahoot::CredentialsProvider::Broken->new(credentials => []);
  dies_ok {
    $provider->get;
  } 'get method not overridden: expected to fail';
  like($@, qr/Calling abstract base class get method for Finance::Bank::Cahoot::CredentialsProvider::Broken is forbidden at/,
     'exception: Calling abstract base class get method');
}

use_ok('Finance::Bank::Cahoot::CredentialsProvider::Constant');

dies_ok {
  my $provider = new Finance::Bank::Cahoot::CredentialsProvider;
} 'construct abstract base: expected to fail';

my %invalid_details = ('Must provide a list of credentials'
		       => { },

		       'credentials is not an array ref'
		       => { credentials => { } },

		       'Empty list of options'
		       => { credentials => [qw(account password username)],
			    options => { } },

		       'options must be a hash ref'
		       => { credentials => [qw(account password username)],
			    options => '' },

		       'Invalid credential bogus supplied with callback'
		       => { credentials => [qw(account password username)],
			    options => { bogus => '' } },
		      );

while (my ($message, $credentials) = each %invalid_details) {
  dies_ok {
    my $provider =
      Finance::Bank::Cahoot::CredentialsProvider::Constant->new(%{$credentials});
  } 'invalid credentials: expected to fail';
  like($@, qr/$message at/, 'exception: '.$message);
  foreach (qw(account password username)) {
    no strict 'refs';
    undef *{"Finance::Bank::Cahoot::CredentialsProvider::Constant::$_"};
  }
}

{
  my $provider =
    Finance::Bank::Cahoot::CredentialsProvider::Constant->new(credentials => [qw(account username password maiden)],
							      options => { account => '12345678',
									   username => 'acmeuser',
									   password => 'secret',
									   maiden => 'Smith' });

  isa_ok($provider, 'Finance::Bank::Cahoot::CredentialsProvider::Constant');

  is($provider->account, '12345678', 'account name');
  is($provider->username, 'acmeuser', 'user name');
  is($provider->password(1), 's', 'password character 1');

t/03-callback.t  view on Meta::CPAN

#!/usr/bin/perl -w

use strict;
use Test::More tests => 20;
use Test::Exception;
use Carp;

use_ok('Finance::Bank::Cahoot::CredentialsProvider::Callback');

my %invalid_details = ('Must provide a list of credentials'
		       => { },

		       'credentials is not an array ref'
		       => { credentials => { } },

		       'Empty list of options'
		       => { credentials => [qw(account password username)],
			    options => { } },

		       'options must be a hash ref'
		       => { credentials => [qw(account password username)],
			    options => '' },

		       'Invalid credential bogus supplied with callback'
		       => { credentials => [qw(account password username)],
			    options => { bogus => sub {} } },

		       'Callback for account is not a code ref'
		       => { credentials => [qw(account password username)],
			    options => { account => '', 'password' => sub {}, username => sub {} } }
		      );

while (my ($message, $credentials) = each %invalid_details) {
  dies_ok {
    my $provider =
      Finance::Bank::Cahoot::CredentialsProvider::Callback->new(%{$credentials});
  } 'invalid credentials: expected to fail';
  like($@, qr/$message at/, 'exception: '.$message);
  foreach (qw(account password username)) {
    no strict 'refs';
    undef *{"Finance::Bank::Cahoot::CredentialsProvider::Callback::$_"};
  }
}

{
  my $provider =
    Finance::Bank::Cahoot::CredentialsProvider::Callback->new(
		credentials => [qw(account username password maiden)],
		options => { account => sub { return '12345678' },
			     username => sub { return 'username' },
			     password => sub { return defined $_[0]
						 ? substr('secret', $_[0]-1, 1) : 'secret' },
			     maiden => sub { return 'Smith' } });

  is($provider->account, '12345678', 'account name');
  is($provider->username, 'username', 'user name');
  is($provider->password(1), 's', 'password character 1');
  is($provider->password(2), 'e', 'password character 2');

t/04-readline.t  view on Meta::CPAN

use Test::MockObject;
use Carp;

my $term = new Test::MockObject;
$term->fake_module('Term::ReadLine');
$term->fake_new('Term::ReadLine');
$term->mock('readline', sub { return $_[1].'bogus data' });

use_ok('Finance::Bank::Cahoot::CredentialsProvider::ReadLine');

my %invalid_details = ('Must provide a list of credentials'
		       => { },

		       'credentials is not an array ref'
		       => { credentials => { } },

		       'Empty list of options'
		       => { credentials => [qw(account password username)],
			    options => { } },

		       'options must be a hash ref'
		       => { credentials => [qw(account password username)],
			    options => '' },

		       'Invalid credential bogus supplied with callback'
		       => { credentials => [qw(account password username)],
			    options => { bogus => '' } },

		       'Prompt for unknown credential bogus'
		       => { credentials => [qw(account password username)],
			    options => { bogus_prompt => '' } }
		      );

while (my ($message, $credentials) = each %invalid_details) {
  dies_ok {
    my $provider =
      Finance::Bank::Cahoot::CredentialsProvider::ReadLine->new(%{$credentials});
  } 'invalid credentials: expected to fail';
  like($@, qr/$message at/, 'exception: '.$message);
  foreach (qw(account password username)) {
    no strict 'refs';
    undef *{"Finance::Bank::Cahoot::CredentialsProvider::ReadLine::$_"};
  }
}

{
  my $provider =
    Finance::Bank::Cahoot::CredentialsProvider::ReadLine->new(
		credentials => [qw(account username password maiden date)],
		options => { date => '10/01/70',
			     account_prompt  => '::account::',
			     password_prompt => '::password::',
			     maiden_prompt   => '::maiden::' });
  is($provider->date, '10/01/70', 'constant value');
  is($provider->account, '::account::bogus data', 'account method');
  is($provider->username, 'Enter username: bogus data', 'username method');
  is($provider->password, '::password::bogus data', 'password method');
  is($provider->maiden, '::maiden::bogus data', 'maiden method');

t/05-cryptfile.t  view on Meta::CPAN


use strict;
use warnings;
use Test::More tests => 32;
use Test::Exception;
use Test::MockObject;
use Carp;

use_ok('Finance::Bank::Cahoot::CredentialsProvider::CryptFile');

my %invalid_details = ('Must provide a list of credentials'
		       => { },

		       'credentials is not an array ref'
		       => { credentials => { } },

		       'Empty list of options'
		       => { credentials => [qw(account password username)],
			    options => { } },

		       'options must be a hash ref'
		       => { credentials => [qw(account password username)],
			    options => '' },

		       'No key provided'
		       => { credentials => [qw(account password username)],
			    options => { bogus => '' } },

		       'Can\'t open .* for writing: .*'
		       => { credentials => [qw(account password username)],
			    options => { key => 'test', keyfile => '/W^%$#/@%W$S', fallback => 'Constant',
					 fallback_options => { account => '12345678',
							       username => 'acmeuser',
							       password => 'secret' } } },
		       'Can\'t open .* for reading: .*'
		       => { credentials => [qw(account password username)],
			    options => { key => 'test', keyfile => 'temp_keyfile' } },

		       'Invalid fallback provider bogus (1)'
		       => { credentials => [qw(account password username)],
			    options => { key => 'test', fallback => 'bogus' } },

		       'Invalid fallback provider bogus (2)'
		       => { credentials => [qw(account password username)],
			    options => { key => 'test', keyfile => 'temp_keyfile', fallback => 'bogus' } },

		       'No fallback provider given and account is not in keyfile'
		       => { credentials => [qw(account password username)],
			    options => { key => 'test', keyfile => 'temp_keyfile'} },

		       'Fallback provider Constant failed to initialise (1)'
		       => { credentials => [qw(account password username)],
			    options => { key => 'test', keyfile => 'temp_keyfile', fallback => 'Constant',
					 fallback_options => { bogus => 1 } } }
		      );

while (my ($message, $credentials) = each %invalid_details) {
  if ($message =~ /open.*reading/) {
    new IO::File $credentials->{options}->{keyfile}, 'w';
    chmod 000, $credentials->{options}->{keyfile};
  } else {
    unlink 'temp_keyfile';
  }
  dies_ok {
    local $^W = 0;  ## supress UNIVERSAL::can warning from Crypt::CBC
    my $provider =
      Finance::Bank::Cahoot::CredentialsProvider::CryptFile->new(%{$credentials});
  } $message.': expected to fail';
  my $re = $message;
  $re =~ s/\s*\(\d+\)$//;
  like($@, qr/$re at/, 'exception: '.$message);
  foreach (qw(account password username)) {
    no strict 'refs';
    undef *{"Finance::Bank::Cahoot::CredentialsProvider::CryptFile::$_"};
    undef *{"Finance::Bank::Cahoot::CredentialsProvider::Constant::$_"};
  }
}

{
  unlink 'temp_keyfile';
  my $provider =
    Finance::Bank::Cahoot::CredentialsProvider::CryptFile->new(
	credentials => [qw(account username password)],
	options => { key => 'verysecret',
		     keyfile => 'temp_keyfile',
		     fallback => 'Constant',
		     fallback_options => { account => '12345678',
					   username => 'acmeuser',
					   password => 'secret' } });
  is($provider->account, '12345678', 'account method via constant fallback');
  is($provider->username, 'acmeuser', 'username method via constant fallback');
  is($provider->password, 'secret', 'password method via constant fallback');

  foreach my $method (qw(account username password)) {
    no strict 'refs';
    undef *{"Finance::Bank::Cahoot::CredentialsProvider::CryptFile::$method"};
    undef *{"Finance::Bank::Cahoot::CredentialsProvider::Constant::$method"};
  }
  undef $provider;

  my $provider2 =
    Finance::Bank::Cahoot::CredentialsProvider::CryptFile->new(
	credentials => [qw(account username password)],
	options => { key => 'verysecret', keyfile => 'temp_keyfile' });
  is($provider2->account, '12345678', 'account method via autosaved cryptfile');
  is($provider2->username, 'acmeuser', 'username method via autosaved cryptfile');
  is($provider2->password(0), 's', 'password character 0 method via autosaved cryptfile');
  is($provider2->password(1), 'e', 'password character 1 method via autosaved cryptfile');
  is($provider2->password(2), 'c', 'password character 2 method via autosaved cryptfile');
  is($provider2->password(3), 'r', 'password character 3 method via autosaved cryptfile');
}

unlink 'temp_keyfile';

t/06-snapshot.t  view on Meta::CPAN

use Test::Deep;

use Mock::WWW::Mechanize;
my $cs = Mock::WWW::Mechanize->new('t/pages');

use_ok('Finance::Bank::Cahoot');
use_ok('Finance::Bank::Cahoot::CredentialsProvider::Constant');

{
  my $creds = Finance::Bank::Cahoot::CredentialsProvider::Constant->new(
	credentials => [qw(account password place date username maiden)],
	options => { account => '12345678',
		     password => 'verysecret',
		     place => 'London',
		     date => '01/01/1906',
		     username => 'dummy',
		     maiden => 'Smith' });

  ok(my $c = Finance::Bank::Cahoot->new(credentials => $creds),
     'valid credentials - providing premade credentials object');

  $c->login();
  my $accounts = $c->accounts();
  is_deeply($accounts,
	    [ { name => 'current account', account => '12345678',
                account_index => 0,
		balance => '847.83', available => '1847.83' },
	      { name => 'flexible loan', account => '87654321',
                account_index => 1,
		balance => '0.00', available => '1000.00' },

t/07-statement.t  view on Meta::CPAN

  dies_ok {
    my $row = Finance::Bank::Cahoot::Statement->new('bogus')
  } 'invalid data row to constructor: expected to fail';
  like($@, qr/statement is not an array ref at /,
       'exception: invalid statement to constructor');

}

{
  my $creds = Finance::Bank::Cahoot::CredentialsProvider::Constant->new(
	credentials => [qw(account password place date username maiden)],
	options => { account => '12345678',
		     password => 'verysecret',
		     place => 'London',
		     date => '01/01/1906',
		     username => 'dummy',
		     maiden => 'Smith' });

  ok(my $c = Finance::Bank::Cahoot->new(credentials => $creds),
     'valid credentials - providing premade credentials object');

  $c->login();
  my $accounts = $c->accounts();
  is_deeply($accounts,
	    [ { name => 'current account', account => '12345678',
		account_index => 0, balance => '847.83', available => '1847.83' },
	    { name => 'flexible loan', account => '87654321',
	      account_index => 1, balance => '0.00', available => '1000.00' },
	    ],
	    'got expected account summary (list)' );

t/07-statement.t  view on Meta::CPAN

	       ],
	       'got correct statement');
  }
  foreach my $method (qw(account password place date username maiden)) {
    no strict 'refs';
    undef *{"Finance::Bank::Cahoot::CredentialsProvider::Constant::$method"};
  }
}

{
  my $c = Finance::Bank::Cahoot->new(credentials => 'Constant',
				     credentials_options => { account => '12345678',
							      password => 'verysecret',
							      place => 'London',
							      date => '01/01/1906',
							      username => 'dummy',
							      maiden => 'Smith' });
  dies_ok {
    $c->set_statement()
  } 'select undef statement: expected to fail';
  like($@, qr/No statement selected for set_statement/, 'exception: no statement selected');
  dies_ok {

t/07-statement.t  view on Meta::CPAN

  } 'get statement with no account: expected to fail';
  like($@, qr/No account currently selected/, 'exception: no account selected');
  foreach my $method (qw(account password place date username maiden)) {
    no strict 'refs';
    undef *{"Finance::Bank::Cahoot::CredentialsProvider::Constant::$method"};
  }
}

{
  my $creds = Finance::Bank::Cahoot::CredentialsProvider::Constant->new(
	credentials => [qw(account password place date username maiden)],
	options => { account => '12345678',
		     password => 'verysecret',
		     place => 'London',
		     date => '01/01/1906',
		     username => 'dummy',
		     maiden => 'Smith' });

  my $c = Finance::Bank::Cahoot->new(credentials => $creds);
  my $accounts = $c->accounts();
  $c->set_account($accounts->[0]->{account});
  my $statements = $c->statements;
  is_deeply($statements,
            [ { 'description' => '16/01/08 - 15/02/08',
                'end' => 1203033600,
                'start' => 1200441600 },
              { 'description' => '16/12/07 - 15/01/08',
                'end' => 1200355200,
                'start' => 1197763200 },

t/12-directdebit.t  view on Meta::CPAN


  dies_ok {
    my $row = Finance::Bank::Cahoot::DirectDebit->new('bogus')
  } 'invalid data row to constructor: expected to fail';
  like($@, qr/row data is not an array ref at /,
       'exception: invalid data row to constructor');
}

{
  my $creds = Finance::Bank::Cahoot::CredentialsProvider::Constant->new(
        credentials => [qw(account password place date username maiden)],
        options => { account => '12345678',
                     password => 'verysecret',
                     place => 'London',
                     date => '01/01/1906',
                     username => 'dummy',
                     maiden => 'Smith' });

  ok(my $c = Finance::Bank::Cahoot->new(credentials => $creds),
     'valid credentials - providing premade credentials object');

  $c->login();
  my $accounts = $c->accounts();
  $c->set_account($accounts->[0]->{account});
  my $debits = $c->debits();
  cmp_deeply($debits,
             array_each(isa('Finance::Bank::Cahoot::DirectDebit')),
             'got an array of direct debits');
  cmp_deeply($debits,
             [ methods(payee => 'ACME WATER CO',



( run in 0.376 second using v1.01-cache-2.11-cpan-4d50c553e7e )