The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/perl

use strict;
use warnings;

use Yote::WebAppServer;

use Yote::AppRoot;
use Yote::YoteRoot;
use Yote::Test::TestAppNoLogin;
use Yote::Test::TestAppNeedsLogin;
use Yote::Test::TestDeepCloner;
use Yote::Test::TestNoDeepCloner;
use Yote::SQLiteIO;

use Data::Dumper;
use File::Temp qw/ :mktemp /;
use File::Spec::Functions qw( catdir updir );
use Test::More tests => 182;
use Test::Pod;


use Carp;
$SIG{ __DIE__ } = sub { Carp::confess( @_ ) };

BEGIN {
    for my $class (qw/Obj Hash SQLiteIO/) {
        use_ok( "Yote::$class" ) || BAIL_OUT( "Unable to load Yote::$class" );
    }
}

# -----------------------------------------------------
#               init
# -----------------------------------------------------

my( $fh, $name ) = mkstemp( "/tmp/SQLiteTest.XXXX" );
$fh->close();
Yote::ObjProvider::init(
    datastore      => 'Yote::SQLiteIO',
    sqlitefile     => $name,
    );
my $db = $Yote::ObjProvider::DATASTORE->database();
test_suite( $db );
done_testing();

unlink( $name );

exit( 0 );

sub query_line {
    my( $db, $query, @args ) = @_;
    my( @ret ) = $db->selectrow_array( $query, {}, @args );
}

sub test_suite {
    my $db = shift;


# -----------------------------------------------------
#               start of yote tests
# -----------------------------------------------------


#                                      #
# ----------- simple object tests -----#
#                                      #
    Yote::YoteRoot->fetch_root();
    my( $o_count ) = query_line( $db, "SELECT count(*) FROM objects" );
    is( $o_count, 10, "number of objects before save root, since root is initiated automatically" );
    my $root = Yote::ObjProvider::fetch( 1 );
    is( ref( $root ), 'Yote::YoteRoot', 'correct root class type' );
    ok( $root->{ID} == 1, "Root has id of 1" );
    my $max_id = $Yote::ObjProvider::DATASTORE->max_id();
    is( $max_id, 10, "highest id in database is 10" );
    ( $o_count ) = query_line( $db, "SELECT count(*) FROM objects" );
    is( $o_count, 10, "number of objects after save root" ); # which also makes an account root automiatcially and has apps,emails,accounts,app_alias and library paths underneath it
    my( $f_count ) = query_line( $db, "SELECT count(*) FROM field" );
    is( $f_count, 9, "number of fields after yoteroot is called" );

#
# Save key value fields for simple scalars, arrays and hashes.
#                                                       # rows in fields total 
    $root->get_default( "DEFAULT" );                        # 1
    is( $root->set_first( "FRIST" ), "FRIST", "set_ returns value" ); # 1
    $root->get_default_array( ["DEFAULT ARRAY"] );          # 2
    $max_id = $Yote::ObjProvider::DATASTORE->max_id();
    is( $max_id, 11, "highest id in database 11" );
    $root->set_reallybig( "BIG" x 1.000);                    # 0
    $root->set_gross( 12 * 12 );                            # 1
    $root->set_array( ["THIS IS AN ARRAY"] );               # 2
    $max_id = $Yote::ObjProvider::DATASTORE->max_id();
    is( $max_id, 12, "highest id in database 12" );
    $root->get_default_hash( { "DEFKEY" => "DEFVALUE" } );  # 2
    $max_id = $Yote::ObjProvider::DATASTORE->max_id();
    is( $max_id, 13, "highest id in database 13" );
    my $newo = new Yote::Obj();
    $max_id = $Yote::ObjProvider::DATASTORE->max_id();
    is( $max_id, 14, "highest id in database 14" );
    my $somehash = {"preArray" => $newo};
    $newo->set_somehash( $somehash ); #testing for recursion
    $max_id = $Yote::ObjProvider::DATASTORE->max_id();
    is( $max_id, 15, "highest id in database 15" );
    $root->get_cool_hash( { "llamapre" => ["prethis",$newo,$somehash] } );  # 2 (7 after stow all)
    $max_id = $Yote::ObjProvider::DATASTORE->max_id();
    is( $max_id, 17, "highest id in database 17" );
    $root->set_hash( { "KEY" => "VALUE" } );                # 2
    $max_id = $Yote::ObjProvider::DATASTORE->max_id();
    is( $max_id, 18, "highest id in database 18" );
    Yote::ObjProvider::stow_all();
    $max_id = $Yote::ObjProvider::DATASTORE->max_id();
    is( $max_id, 18, "highest id in database still 18" );

    # added default_hash, { 'llama', ["this", new yote obj, "Array, and a new yote object bringing the object count to 7 + 6 = 13
    # the new max id should be 7 (root) + defalt_array 1,  array 1, default_hash 1, newobj 1, somehash 1, coolahash 1, arryincoolhash 1, hash 1
    $max_id = $Yote::ObjProvider::DATASTORE->max_id();
    is( $max_id, 18, "highest id in database is 18 after adding more objects" );

    # this resets the cool hash, overwriting what is there. 
    $root->set_cool_hash( { "llama" => ["this",new Yote::Obj(),{"Array",new Yote::Obj()}] } );  # 5 new objects
    my $recycled = $Yote::ObjProvider::DATASTORE->recycle_objects();
    is( $recycled, 5, "recycled 5 objects" );

    # the cool hash has been reset, resulting in 6 more objects, and 6 objects that no longer connect to the root
    

# 1 from accounts under root (default)
# 1 from apps under root
# 1 from alias_apps
    my $db_rows = $db->selectall_arrayref("SELECT * FROM field");

    BAIL_OUT("error saving after stow all") unless is( scalar(@$db_rows), 28, "Number of db rows saved to database with stow all" );

    $db_rows = $db->selectall_arrayref("SELECT * FROM objects WHERE recycled=0");
    is( scalar(@$db_rows), 18, "Number of db rows saved to database not recycled" ); 
    $db_rows = $db->selectall_arrayref("SELECT * FROM objects WHERE recycled=1");
    is( scalar(@$db_rows), 5, "Number of db rows recycled" ); 


    my $root_clone = Yote::ObjProvider::fetch( 1 );
    is( ref( $root_clone->get_cool_hash()->{llama} ), 'ARRAY', '2nd level array object' );
    is( ref( $root_clone->get_cool_hash()->{llama}->[2]->{Array} ), 'Yote::Obj', 'deep level yote object in hash' );
    is( ref( $root_clone->get_cool_hash()->{llama}->[1] ), 'Yote::Obj', 'deep level yote object in array' );



    is( ref( $root->get_cool_hash()->{llama} ), 'ARRAY', '2nd level array object (original root after save)' );
    is( ref( $root->get_cool_hash()->{llama}->[2]->{Array} ), 'Yote::Obj', 'deep level yote object in hash  (original root after save)' );
    is( ref( $root->get_cool_hash()->{llama}->[1] ), 'Yote::Obj', 'deep level yote object in array (original root after save)' );


    is_deeply( $root_clone, $root, "CLONE to ROOT");
    ok( $root_clone->{ID} == 1, "Reloaded Root has id of 1" );
    is( $root_clone->get_default(), "DEFAULT", "get scalar with default" );
    is( $root_clone->get_first(), "FRIST", "simple scalar" );
    is( length($root_clone->get_reallybig()), length("BIG" x 1.000), "Big String" );
    is( $root_clone->get_gross(), 144, "simple number" );
    is_deeply( $root_clone->get_default_array(), ["DEFAULT ARRAY"], "Simple default array" );
    is_deeply( $root_clone->get_array(), ["THIS IS AN ARRAY"], "Simple array" );
    is_deeply( $root_clone->get_default_hash(), {"DEFKEY"=>"DEFVALUE"}, "Simple default hash" );
    my( %simple_hash ) = %{$root_clone->get_hash()};
    is_deeply( \%simple_hash, {"KEY"=>"VALUE"}, "Simple hash" );
    

    #
    # Test dirtying of hash. Tests if tied,  put, clear and delete
    #
    my $clone_hash = $root_clone->get_hash();
    is_deeply( $clone_hash, { KEY => 'VALUE' }, "hash def" );
    my $hid = Yote::ObjProvider::get_id( $clone_hash );
    ok( !Yote::ObjProvider::__is_dirty( $clone_hash ), "hash not dirty" );
    is( ref(tied %$clone_hash),'Yote::Hash',"clone hash tied");
    # -- put
    $clone_hash->{fooh} = 'barh';
    ok( Yote::ObjProvider::__is_dirty( $clone_hash ), "Hash dirty after change" );
    my $fetched_hash = Yote::ObjProvider::fetch( $hid );
    is_deeply( $fetched_hash, { fooh => 'barh', KEY => 'VALUE' }, "hash after put" );
    is( $fetched_hash->{fooh}, 'barh', "changed hash works" );
    Yote::ObjProvider::stow( $fetched_hash );
    ok( !Yote::ObjProvider::__is_dirty( $clone_hash ), "hash not dirty after change and save" );
    # -- delete
    delete $clone_hash->{fooh};
    ok( Yote::ObjProvider::__is_dirty( $clone_hash ), "Hash dirty after delete" );
    $fetched_hash = Yote::ObjProvider::fetch( $hid );
    is_deeply( $fetched_hash, { KEY => 'VALUE' }, " hash after delete" );
    is( $fetched_hash->{fooh}, undef, " hash after deletion works" );
    Yote::ObjProvider::stow( $fetched_hash );
    ok( !Yote::ObjProvider::__is_dirty( $clone_hash ), "hash not dirty after delete and save" );
    # -- clear
    %$clone_hash = (); 
    ok( Yote::ObjProvider::__is_dirty( $clone_hash ), "Hash dirty after clear" );
    $fetched_hash = Yote::ObjProvider::fetch( $hid );
    is_deeply( $fetched_hash, {}, "Hash other reference also clear" );
    Yote::ObjProvider::stow( $fetched_hash );
    ok( !Yote::ObjProvider::__is_dirty( $clone_hash ), "Hash dirty after clear and save" );
    # -- reset simple hash
    $clone_hash->{KEY} = 'VALUE';

    #
    # Test dirtying of array. tests store, delete, clear, push, pop, shift, unshift, splice
    #
    my $def_arry = $root_clone->get_default_array();
    is_deeply( $def_arry, [ 'DEFAULT ARRAY' ], "default array def" );
    my $aid = Yote::ObjProvider::get_id( $def_arry );
    ok( !Yote::ObjProvider::__is_dirty( $def_arry ), "array not dirty" );
    is( ref(tied @$def_arry),'Yote::Array',"clone array tied");
    # - store
    $def_arry->[13] = "booya";  #14 
    $def_arry->[12] = "zoog";  
    ok( Yote::ObjProvider::__is_dirty( $def_arry ), "array dirty after store" );
    my $fetched_arry = Yote::ObjProvider::fetch( $aid );
    is_deeply( $fetched_arry, ['DEFAULT ARRAY',(map { undef } (1..11)),'zoog','booya'], "array after store" );
    is( $fetched_arry->[12], 'zoog', 'changed array works after store');
    Yote::ObjProvider::stow( $fetched_arry );
    ok( !Yote::ObjProvider::__is_dirty( $def_arry ), "array dirty after store and save" );
    # - delete
    delete $def_arry->[12];
    $fetched_arry = Yote::ObjProvider::fetch( $aid );
    is_deeply( $fetched_arry, ['DEFAULT ARRAY',(map { undef } (1..12)),'booya'], "array after delete" );
    ok( Yote::ObjProvider::__is_dirty( $def_arry ), "array dirty after delete" );
    ok( Yote::ObjProvider::__is_dirty( $fetched_arry ), "array dirty after delete" );
    Yote::ObjProvider::stow( $fetched_arry );
    ok( !Yote::ObjProvider::__is_dirty( $def_arry ), "array dirty after delete and save" );
    # - clear
    @{$def_arry} = ();
    $fetched_arry = Yote::ObjProvider::fetch( $aid );
    is_deeply( $fetched_arry,[], "array after clear" );
    ok( Yote::ObjProvider::__is_dirty( $def_arry ), "array dirty after clear" );
    ok( Yote::ObjProvider::__is_dirty( $fetched_arry ), "array dirty after clear" );
    Yote::ObjProvider::stow( $fetched_arry );
    ok( !Yote::ObjProvider::__is_dirty( $def_arry ), "array dirty after clear and save" );
    # - push 
    push @$def_arry, "one", "two", "tree";
    $fetched_arry = Yote::ObjProvider::fetch( $aid );
    is_deeply( $fetched_arry, ["one", "two", "tree"], "array after push" );
    ok( Yote::ObjProvider::__is_dirty( $def_arry ), "array dirty after push" );
    ok( Yote::ObjProvider::__is_dirty( $fetched_arry ), "array dirty after push" );
    Yote::ObjProvider::stow( $fetched_arry );
    ok( !Yote::ObjProvider::__is_dirty( $def_arry ), "array dirty after push and save" );    
    # - pop
    is( pop @$def_arry, "tree", "pop array value" );
    $fetched_arry = Yote::ObjProvider::fetch( $aid );
    is_deeply( $fetched_arry, ["one", "two"], "array after pop" );
    ok( Yote::ObjProvider::__is_dirty( $def_arry ), "array dirty after pop" );
    ok( Yote::ObjProvider::__is_dirty( $fetched_arry ), "array dirty after pop" );
    Yote::ObjProvider::stow( $fetched_arry );
    ok( !Yote::ObjProvider::__is_dirty( $def_arry ), "array dirty after pop and save" );    
    # - shift
    is( shift @$def_arry, "one", "shifted array value" );
    $fetched_arry = Yote::ObjProvider::fetch( $aid );
    is_deeply( $fetched_arry, ["two"], "array after shift" );
    ok( Yote::ObjProvider::__is_dirty( $def_arry ), "array dirty after shift" );
    ok( Yote::ObjProvider::__is_dirty( $fetched_arry ), "array dirty after shift" );
    Yote::ObjProvider::stow( $fetched_arry );
    ok( !Yote::ObjProvider::__is_dirty( $def_arry ), "array dirty after shift and save" );    
    # - unshift
    unshift @$def_arry, "newguy", "orange", "Lemon", "tango";
    $fetched_arry = Yote::ObjProvider::fetch( $aid );
    is_deeply( $fetched_arry, ["newguy", "orange", "Lemon", "tango", "two"], "array after unshift" );
    ok( Yote::ObjProvider::__is_dirty( $def_arry ), "array dirty after unshift" );
    ok( Yote::ObjProvider::__is_dirty( $fetched_arry ), "array dirty after unshift" );
    Yote::ObjProvider::stow( $fetched_arry );
    ok( !Yote::ObjProvider::__is_dirty( $def_arry ), "array dirty after unshift and save" );    
    # - splice
    my( @slice ) = splice @$def_arry, 1, 2, "Booga", "Boo", "Bobby";
    is_deeply( \@slice, ["orange","Lemon"], "spliced array value" );
    $fetched_arry = Yote::ObjProvider::fetch( $aid );
    is_deeply( $fetched_arry, ["newguy", "Booga", "Boo", "Bobby", "tango","two"], "array after splice" );
    ok( Yote::ObjProvider::__is_dirty( $def_arry ), "array dirty after splice" );
    ok( Yote::ObjProvider::__is_dirty( $fetched_arry ), "array dirty after splice" );
    Yote::ObjProvider::stow( $fetched_arry );
    ok( !Yote::ObjProvider::__is_dirty( $def_arry ), "array dirty after splice and save" );    
    # - set in place
    my $last_set = $fetched_arry;
    @{$fetched_arry} = ("This Is","new");
    ok( Yote::ObjProvider::__is_dirty( $last_set ), "array dirty after set in place" );
    Yote::ObjProvider::stow( $fetched_arry );
    $fetched_arry = Yote::ObjProvider::fetch( $aid );
    is_deeply( $fetched_arry, ["This Is","new"], "array after set in place" );
    

#                                      #
# ----------- deep container tests ----#
#                                      #

    my $simple_array = $root->get_array();
    push( @$simple_array, "With more than one thing" );
    my $simple_hash = $root->get_hash();
    $simple_hash->{FOO} = "bar";
    $simple_hash->{BZAZ} = [ "woof", "bOOf" ];
    Yote::ObjProvider::stow_all();

    my $root_2 = Yote::ObjProvider::fetch( 1 );
    ( %simple_hash ) = %{$root_2->get_hash()};
    delete $simple_hash{__ID__};
    is_deeply( \%simple_hash, {"KEY"=>"VALUE","FOO" => "bar", BZAZ => [ "woof", "bOOf" ]}, "Simple hash after reload" );

    is_deeply( $root, $root_2, "Root data after modifying array" );

    my( %shh ) = %{$root_2->get_hash()};
    delete $shh{__ID__};
    is_deeply( \%shh, \%simple_hash, 'simple hash after second save' );
    is_deeply( $simple_hash, $root_2->get_hash(), "the modified hash saved" );
    is_deeply( $simple_array, $root_2->get_array(), "the modified array saved" );

    Yote::ObjProvider::stow_all();

#                                          #
# ----------- objects in objects tests ----#
#                                          #
    $simple_hash->{BZAZ}[2] = $simple_hash;
    my $new_obj = new Yote::Obj;
    $new_obj->set_cow( "FIRSTY" );
    $root->set_obj( $new_obj );
    $root->add_once_to_array( "MORE STUFF", "MORE STUFF", "MORE STUFF" );

    $root->add_once_to_obj_array( $new_obj, $new_obj );
    is( scalar(@{$root->get_obj_array()}), 1, "add once works for references" );

    $simple_array = $root->get_array();
    is( scalar(@$simple_array), 3, "add_once_to test array count" );

    $root->add_to_array( "MORE STUFF" );
    $root->add_to_array( "MORE STUFF", "MORE STUFF" );

    Yote::ObjProvider::stow_all();

    $simple_array = $root->get_array();
    my $root_3 = Yote::ObjProvider::fetch( 1 );
    is_deeply( $root_3, $root, "recursive data structure" );

    is_deeply( $root_3->get_obj(), $new_obj, "setting object" );

    is( $root_3->count( 'array' ), 6, 'Array has 6 with count' );
    is_deeply( $root_3->_paginate_list( 'array', 3 ), [ 'THIS IS AN ARRAY', 'With more than one thing', 'MORE STUFF' ], 'paginate with one argument' );
    is_deeply( $root_3->_paginate_list_rev( 'array', 3 ), [ 'MORE STUFF', 'MORE STUFF', 'MORE STUFF' ], 'paginate reverse with one argument' );
    is_deeply( $root_3->_paginate_list( 'array', 1, 2 ), [ 'MORE STUFF' ], 'paginate with one argument' );
    is_deeply( $root_3->_paginate_list_rev( 'array', 1, 4 ), [ 'With more than one thing' ], 'paginate with one argument' );
    is_deeply( $root_3->_paginate_list( 'array', 3, 4 ), [ 'MORE STUFF','MORE STUFF' ], 'paginate with one argument' );
    is_deeply( $root_3->_paginate_list_rev( 'array', 3, 4 ), [ 'With more than one thing', 'THIS IS AN ARRAY' ], 'paginate with one argument' );

    is( scalar(@$simple_array), 6, "add_to test array count" );

    is_deeply( $root_3->get_array(), $simple_array, "add to test" );

    $root->remove_from_array( "MORE STUFF" );
    Yote::ObjProvider::stow_all();
    is( scalar(@$simple_array), 5, "add_to test array count after remove" );
    $root->remove_from_array( "MOREO STUFF" );
    $simple_array = $root_3->get_array();
    Yote::ObjProvider::stow_all();
    is( scalar(@$simple_array), 5, "add_to test array count after second remove" );
    $root->remove_all_from_array( "MORE STUFF" );
    Yote::ObjProvider::stow_all();
    $simple_array = $root_3->get_array();
    is_deeply( $root_3->get_array(), $simple_array, "add to test" );
    is( scalar(@$simple_array), 2, "add_to test array count after remove all" );

    $root->add_once_to_array( "MORE STUFF", "MORE STUFF 2", "MORE STUFF 3" );
    Yote::ObjProvider::stow_all();
    $simple_array = $root_3->get_array();
    is( scalar(@$simple_array), 5, "add_to test array count after remove all" );

    $root->remove_from_array( "MORE STUFF 3", "MORE STUFF", "MORE STUFF 2" );
    Yote::ObjProvider::stow_all();
    $simple_array = $root_3->get_array();
    is( scalar(@$simple_array), 2, "add_to test array count after remove all" );

    my $root_4 = Yote::ObjProvider::fetch( 1 );


    # test shallow and deep clone.
    my $target_obj = new Yote::Obj();
    my $deep_cloner = new Yote::Test::TestDeepCloner();
    $deep_cloner->set_ref_to_clone( $target_obj );
    $deep_cloner->set_num_value( 1234 );
    $deep_cloner->set_array( [ "array", { of => "Awsome" } ] );
    $deep_cloner->set_hash( { "woot" => "Biza" } );
    $deep_cloner->set_txt_value( "This is text" );
    $deep_cloner->set_big_txt_value( "BIG" x 1.000 );
    $target_obj->set_deep_cloner( $deep_cloner );
    my $shallow_cloner = new Yote::Test::TestNoDeepCloner();    
    $target_obj->set_shallow_cloner( $shallow_cloner );
    my $arry = $target_obj->get_reftest([]);
    $target_obj->set_reftest2( $arry );
    push( @$arry, "FOO" );
    is_deeply( $target_obj->get_reftest2(), $target_obj->get_reftest(), "ref test equivalency" );
    ok( Yote::Obj::_is( $target_obj->get_reftest2(), $target_obj->get_reftest() ), "ref test yote identity 2" );
    is( ''.$target_obj->get_reftest2(), ''.$target_obj->get_reftest(), "thingy identity" );
    
    my $deep_clone = Yote::ObjProvider::power_clone( $target_obj );
    ok( $deep_clone->get_shallow_cloner()->_is( $shallow_cloner ), "deep clone did not clone NO CLONE object" );
    ok( ! $deep_clone->get_deep_cloner()->_is( $deep_cloner ), "did clone internal reference" );
    ok( $deep_clone->_is( $deep_clone->get_deep_cloner()->get_ref_to_clone() ), "deep clone replaces old reference with clone reference" );

    is_deeply( $deep_clone->get_deep_cloner()->get_array(), $deep_cloner->get_array(), "arrays are separate but identical" );
    ok( $deep_clone->get_deep_cloner()->get_array()->[1] ne $deep_cloner->get_array()->[1], "arrays are separate but identical" );
    is_deeply( $deep_clone->get_deep_cloner()->get_hash(), $deep_cloner->get_hash(), "hashes are separate but identical" );
    ok( $deep_clone->get_deep_cloner()->get_hash() ne $deep_cloner->get_hash(), "hashes are separate but identical" );
    

#
#                                          #
# ------------- app serv tests ------------#
#
#                                          #
    $root = Yote::ObjProvider::fetch( 1 );
    Yote::ObjProvider::stow_all();
    eval { 
        $root->create_login();
        fail( "Able to create account without handle or password" );
    };
    like( $@, qr/no handle|password required/i, "no handle or password given for create account" );
    eval {
        $root->create_login( { h => 'root' } );
        fail( "Able to create account without password" );
    };
    like( $@, qr/password required/i, "no password given for create account " );
    my $res = $root->create_login( { h => 'root', p => 'toor', e => 'foo@bar.com' } );
    is( $res->{l}->get_handle(), 'root', "handle for created root account" );
    is( $res->{l}->get_email(), 'foo@bar.com', "handle for created root account" );
    Yote::ObjProvider::stow_all();
    my $root_acct = $root->_hash_fetch( "_handles", "root");
    unless( $root_acct ) {
        fail( "Root not loaded" );
        BAIL_OUT("cannot continue" );
    }
    is( $root->_count("_handles"), 1, "1 handle stored");
    is( $root_acct->get_handle(), 'root', 'handle set' );
    is( $root_acct->get_email(), 'foo@bar.com', 'email set' );
    isnt( $root_acct->get_password(), 'toor', 'password set' ); #password is encrypted
    ok( ! $root_acct->get__is_root(), 'first account is not root anyore' );

    eval {
        $root->create_login( { h => 'root', p => 'toor', e => 'baz@bar.com' } );
        fail( "Able to create login with same handle" );
    };
    like( $@, qr/handle already taken/i, "handle already taken" );
    eval {
        $root->create_login( { h => 'toot', p => 'toor', e => 'foo@bar.com' } );
        fail( "Able to create login with same email" );
    };
    like( $@, qr/email already taken/i, "email already taken" );
    $res = $root->create_login( { h => 'toot', p => 'toor', e => 'baz@bar.com' } );
    is( $res->{l}->get_handle(), 'toot', "second account created" );
    ok( $res->{t}, "second account created with token" );
    Yote::ObjProvider::stow_all();
    my $acct = $root->_hash_fetch( '_handles', 'toot' );
    ok( ! $acct->get__is_root(), 'second account not root' );

    my $rpass = Yote::ObjProvider::encrypt_pass( "realpass", 'realroot' );
    isnt( $rpass, "realpass", "password was encrypted" );
    $res = $root->_check_root( 'realroot', $rpass );
    eval {
	my $rrl = $root->login( { h => 'realroot', p => 'wrongpass' } );
    };
    like( $@, qr/incorrect login/i, "Wrong log in" );
    my $rrl = $root->login( { h => 'realroot', p => 'realpass' } );
    ok( $rrl->{t}, "Logged in got token" );

# ------ hello app test -----
    my $t = $root->login( { h => 'toot', p => 'toor' } );
    ok( $t->{t}, "Logged in got token" );
    ok( $t->{l}, "logged in with login object" );
    is( $t->{l}->get_handle(), 'toot', "logged in with login object with correct handle" );
    is( $t->{l}->get_email(), 'baz@bar.com', "logged in with login object with correct handle" );
    ok( $t->{t}, "logged in with token $t->{t}" );
    my( $hello_app ) = $root->fetch_app_by_class( 'Yote::Test::Hello' );
    is( $hello_app->hello( { name => 'toot' } ), "hello there 'toot'. I have said hello 1 times.", "Hello app works with given token" );
    my $as = new Yote::WebAppServer();
    ok( $as, "Yote::WebAppServer compiles" );


    my( $ta ) = $root->fetch_app_by_class( 'Yote::Test::TestAppNeedsLogin' );
    ok( $ta->get_yote_obj(), "test app created yote object automatically" );

    my $aaa = $ta->array( '', $t );


    is( $aaa->[0], 'A', 'first el' );
    is( ref( $aaa->[1] ), 'HASH', 'second el hash' );
    my $ina = $aaa->[1]{inner};
    is( $ina->[0], "Juan", "inner array el" );
    my $inh = $ina->[1];

    is( ref( $inh ), 'HASH', 'inner hash' );
    is( $inh->{peanut}, 'Butter', "scalar in inner hash" );
    my $ino = $inh->{ego};
    ok( $ino > 0, "Inner object" );
    is( $aaa->[2], $ino, "3rd element outer array" );


    $root->add_to_rogers( "an", "array", "test" );
    my $hf = $root->get_hashfoo( {} );
    $hf->{zort} = 'zot';

    $ta->give_obj( [ "Fooo obj" ], $acct );

    Yote::ObjProvider::stow_all();
    
    is( $root->_list_fetch( 'rogers', '1'), "array", "hash_fetch with array" );
    is( $root->_list_fetch( "hashfoo", "zort"), "zot", "hash_fetch with array" );

    Yote::ObjProvider::stow_all();
    my $app = $root->_hash_fetch( '_apps', 'Yote::Test::TestAppNeedsLogin' );
    $app->add_to_azzy( "A","B","C","D");
    Yote::ObjProvider::stow_all();
    ok( ref( $app ) eq 'Yote::Test::TestAppNeedsLogin', "hash fetch gets AppObj" );
    is(  $app->_hash_fetch( 'azzy', '2' ), 'C', "hash fetch from AppRoot object" );
    is(  $app->_hash_fetch( 'azzy', '0' ), 'A', "hash fetch from AppRoot object" );

    # test hash fetch insert, _paginate 
    $res = $app->_paginate_list( 'azzy' );
    is_deeply( $res, [ qw/A B C D/ ], 'paginate list without limits correct' );
    $res = $app->_paginate_list( 'azzy', 2, 0 );
    is_deeply( $res, [ qw/A B/ ], 'paginate limits from 0 with 2 are correct' );
    $res = $app->_paginate_list( 'azzy', 2, 1 );
    is_deeply( $res, [ qw/B C/  ], 'paginate limits from 1 with 2 are correct' );
    $res = $app->_paginate_list( 'azzy', 2, 4 );
    is_deeply( $res, [ ], 'paginate limits beyond last index are empty' );
    $res = $app->_list_insert( 'azzy', 'E', 4 );
    $res = $app->_paginate_list( 'azzy' );
    is_deeply( $res, [ qw/A B C D E/ ], 'paginate list without limits correct' );
    $res = $app->_paginate_list( 'azzy', 2, 4 );
    is_deeply( $res, [ 'E' ], 'just the last of the paginate limit' );
    
    $res = $app->_paginate_hash( 'azzy' );
    is_deeply( $res, { 0 => 'A', 1 => 'B', 2 => 'C', 3 => 'D', 4 => 'E' }, 'paginate hash without limits correct' );
    $res = $app->_paginate_hash( 'azzy', 2, 0 );
    is_deeply( $res, { 0 => 'A', 1 => 'B' }, 'paginate list limits from 0 with 2 are correct' );
    $res = $app->_paginate_hash( 'azzy', 2, 4 );
    is_deeply( $res, { 4 => 'E' }, 'just the last of the paginate limit' );
    
    $app->_list_delete( 'azzy', 2 );
    $res = $app->_paginate_hash( 'azzy' );
    is_deeply( $res, { 0 => 'A', 1 => 'B', 3 => 'D', 4 => 'E' }, 'paginate hash without limits correct after paginate_delete' );
    $res = $app->_paginate_list( 'azzy' );
    is_deeply( $res, [ qw/A B D E/ ], 'paginate list without limits correct after list_delete' );

    $app->_list_insert( 'azzy', 'foo/bar' );
    $res = $app->_paginate_list( 'azzy' );
    is_deeply( $res, [ qw(A B D E foo/bar ) ], 'added value with / in the name' );

    Yote::ObjProvider::stow_all();    

    my $hash = $app->get_hsh( {} );
    $hash->{'baz/bof'} = "FOOME";
    $hash->{Bingo} = "BARFO";
    Yote::ObjProvider::stow_all();
    $res = $app->_paginate_hash( 'hsh' );
    is_deeply( $res, { 'baz/bof' => "FOOME", 'Bingo' => "BARFO" }, ' paginate for hash, with one key having a slash in its name' );
    
    # delete with key that has slash in the name
    $app->_hash_delete( 'hsh', 'baz/bof' );    
    $res = $app->_paginate_hash( 'hsh' );
    is_deeply( $res, { 'Bingo' => "BARFO" }, 'delete with key having a slash in its name' );
    $app->_hash_insert( 'hsh', '/\\/yakk\\/zakk/bakk', 'gotta slashy for it' );
    $res = $app->_paginate_hash( 'hsh' );
    is_deeply( $res, { 'Bingo' => "BARFO", '/\\/yakk\\/zakk/bakk' => 'gotta slashy for it' }, 'paginate for hash, with one key having a slash in its name' );

    # test hash argument to new obj :
    my $o = new Yote::Obj( { foof => "BARBARBAR", zeeble => [ 1, 88, { nine => "ten" } ] } );
    is( $o->get_foof(), "BARBARBAR", "obj hash constructore" );
    is( $o->get_zeeble()->[2]{nine}, "ten", 'obj hash constructor deep value' );
    
    # recursion testing
    my $o2 = new Yote::Obj( { recurse => $o } );
    $o->add_to_curse( $o2 );
    $o->set_emptylist( [] );
    $root->add_to_rogers( $o );
    Yote::ObjProvider::stow_all();
    is( $o->count( 'emptylist' ), 0, "emptylist" );

} #test suite

__END__