#-------------------------------------------------------------------
# WebGUI is Copyright 2001-2009 Plain Black Corporation.
#-------------------------------------------------------------------
# Please read the legal notices (docs/legal.txt) and the license
# (docs/license.txt) that came with this distribution before using
# this software.
#-------------------------------------------------------------------
# http://www.plainblack.com                     info@plainblack.com
#-------------------------------------------------------------------

use FindBin;
use strict;
use lib "$FindBin::Bin/lib";

use WebGUI::Test;
use WebGUI::Session;
use WebGUI::Utility;
use WebGUI::Cache;
#use Exception::Class;

use WebGUI::User;
use WebGUI::ProfileField;
use WebGUI::Shop::AddressBook;

use Test::More tests => 237; # increment this value for each test you create
use Test::Deep;
use Data::Dumper;

my $session = WebGUI::Test->session;

my $testCache = WebGUI::Cache->new($session, 'myTestKey');
$testCache->flush;
WebGUI::Test->addToCleanup(sub { $testCache->flush; });

my $user;
my $lastUpdate;

#Let's try to create a new user and make sure we get an object back
my $userCreationTime = time();
ok(defined ($user = WebGUI::User->new($session,"new")), 'new("new") -- object reference is defined');
WebGUI::Test->addToCleanup($user);

#New does not return undef if something breaks, so we'll see if the _profile hash was set.
ok(exists $user->{_profile}, 'new("new") -- profile subhash exists');  

#The new user's profile privacy settings should be set based on their default values
is($user->getProfileFieldPrivacySetting('uiLevel'),'none','get a profile field privacy setting');

#Let's assign a username
$user->username("bill_lumberg");
$lastUpdate = time();
is($user->username, "bill_lumberg", 'username() method');
cmp_ok(abs($user->lastUpdated-$lastUpdate), '<=', 1, 'lastUpdated() -- username change');

#Let's check the UID and make sure it's sane
ok($user->userId =~ m/[A-Za-z0-9\-\_]{22}/, 'userId() returns sane value');

#Let's make sure the user was added to the correct groups;
foreach my $groupId (2,7) {
	ok($user->isInGroup($groupId), "User added to group $groupId by default");
}

################################################################
#
# enable/disable
#
################################################################

# NOTE: enable/disable replaces all functionality of the status() method.
# We're keeping status() until we can remove it later
# Enable/disable is tested by the status()

#Let's check the status method
$user->status('Active');
$lastUpdate = time();
is($user->status, "Active", 'status("Active")');
cmp_ok(abs($user->lastUpdated-$lastUpdate), '<=', 1, 'lastUpdated() -- status change');

$user->status('Selfdestructed');
is($user->status, "Selfdestructed", 'status("Selfdestructed")');
is($user->get('status'), "Selfdestructed", 'status("Selfdestructed") via get');


# Deactivation user deletes all sessions and scratches
my $newSession  = WebGUI::Session->open( WebGUI::Test->root, WebGUI::Test->file );
$newSession->user({ user => $user });
$newSession->scratch->set("hasStapler" => "no");

$user->status('Deactivated');
is($user->status, "Deactivated", 'status("Deactivated")');
is($user->get('status'), "Deactivated", 'status("Deactivated") via get');

ok( 
    !$session->db->quickScalar("SELECT COUNT(*) from userSession where userId=?",[$user->userId]),
    "Deactivating user deletes all sessions",
);

ok(
    !$session->db->quickScalar("SELECT COUNT(*) FROM userSessionScratch WHERE sessionId=?",[$newSession->getId]),
    "Deactivating user deletes all user session scratch",
);

$newSession->close;

################################################################
#
# get/update
#
################################################################

# NOTE: get/set replaces the following methods, but we're leaving
# the tests for the deprecated methods until they get removed, since
# they test the get/update methods thoroughly
# - authMethod
# - dateCreated
# - lastUpdated
# - profileField
# - referringAffiliate
# - status
# - updateProfileFields
# - username
# - getId

my $now = time;
$user->update({
    userId          => 'INEDU2COMEINSATURDYTHX',
    username        => "jlumbe",
    firstName       => "John",
    lastName        => "Lumbergh",
    lastUpdated     => $now,
});

isnt(
    $user->get('userId'), 'INEDU2COMEINSATURDYTHX',
    "update() does not allow changing userId",
);

is(
    $session->db->quickScalar("SELECT username FROM users WHERE userId=?",[$user->getId]),
    "jlumbe",
    "update() updates username",
);
is(
    $user->get('username'),
    "jlumbe",
    "update() updates get('username')",
);

is(
    $session->db->quickScalar("SELECT lastUpdated FROM users WHERE userId=?",[$user->getId]),
    $now,
    "update() updates lastUpdated",
);
is(
    $user->get('lastUpdated'),
    $now,
    "update() updates get('lastUpdated')",
);

is(
    $session->db->quickScalar("SELECT firstName FROM userProfileData WHERE userId=?",[$user->getId]),
    "John",
    "update() updates profile firstName",
);
is(
    $user->get('firstName'),
    "John",
    "update() updates get('firstName')",
);

is(
    $session->db->quickScalar("SELECT lastName FROM userProfileData WHERE userId=?",[$user->getId]),
    "Lumbergh",
    "update() updates profile lastName",
);
is(
    $user->get('lastName'),
    "Lumbergh",
    "update() updates get('lastName')",
);

sleep 1;
ok(
    eval { $user->update({ lastNameIsNotExistingInThisContext => "Lumberg" }); 1; },
    "update() doesn't die with invalid field",
);
ok(
    $session->db->quickScalar("SELECT lastUpdated FROM users WHERE userId=?",[$user->getId])
    > $now,
    "update() updates lastUpdated automatically",
);
ok(
    $user->get('lastUpdated') > $now,
    "update() updates get('lastUpdated') automatically",
);

$user->update({ lastName => "Lumberg" }),
is( 
    $session->db->quickScalar("SELECT lastName FROM userProfileData WHERE userId=?",[$user->getId]),
    "Lumberg",
    "update() updates lastName again",
);
is(
    $user->get("lastName"),
    "Lumberg",
    "update() updates get('lastName') again",
);

# get w/o arguments returns hashref of everything
my $expectValues  = {
    username        => "jlumbe",
    firstName       => "John",
    lastName        => "Lumberg",
    status          => "Deactivated",
    timeZone        => 'America/Chicago',
    gender          => 'other',
    toolbar         => 'useLanguageDefault',
};

# expects all user properties and all profile fields
my @expectFields    = ( 
    $session->db->buildArray('DESCRIBE users'),
    $session->db->buildArray('SELECT fieldName FROM userProfileField'),
);

cmp_deeply(
    [keys %{$user->get}], bag(@expectFields), 
    "get() contains all properties and profileFields",
);

cmp_deeply(
    $user->get, superhashof($expectValues),
    "get() contains known correct values",
);

################################################################
#
# profileField
#
################################################################

#Let's get/set a profile field
$user->profileField("firstName", "Bill");
$lastUpdate = time();
is($user->profileField("firstName"), "Bill", 'profileField() get/set');
cmp_ok(abs($user->lastUpdated-$lastUpdate), '<=', 1, 'lastUpdated() -- profileField');

#Fetching a non-existant profile field returns undef
is($user->profileField('notAProfileField'), undef, 'getting non-existant profile fields returns undef');

##Check for valid profileField access, even if it is not cached in the user object.
my $newProfileField = WebGUI::ProfileField->create($session, 'testField', {dataDefault => 'this is a test', fieldType => 'Text'});
WebGUI::Test->addToCleanup($newProfileField);
is($user->profileField('testField'), 'this is a test', 'getting profile fields not cached in the user object returns the profile field default');

ok(!$user->profileField('wg_privacySettings'), '... wg_privacySettings may not be retrieved');
$user->profileField('wg_privacySettings', '{"email"=>"all"}');
ok(!$user->profileField('wg_privacySettings'), '... wg_privacySettings may not be set');

################################################################
#
# authMethods
#
################################################################

#Let's check the auth methods

#Default should be WebGUI
is($user->authMethod, "WebGUI", 'authMethod() -- default value is WebGUI');

#Try changing to LDAP
$lastUpdate = time();
$user->authMethod("LDAP");
is($user->authMethod, "LDAP", 'authMethod() -- set to LDAP');
$user->authMethod("WebGUI");
is($user->authMethod, "WebGUI", 'authMethod() -- set back to WebGUI');
cmp_ok(abs($user->lastUpdated-$lastUpdate), '<=', 1, 'lastUpdated() -- authmethod change');

#See if date created is correct
is($user->dateCreated, $userCreationTime, 'dateCreated()');

################################################################
#
# get/set karma
#
################################################################

my $oldKarma = $user->karma;
$user->karma('69');
$user->karma('69', 'wonder man');
is($user->karma, $oldKarma, 'karma() -- requires amount, source and description');
$user->karma('69', 'peter gibbons', 'test karma');
is($user->karma, $oldKarma+69, 'karma() -- get/set add amount');

my ($source, $description) = $session->db->quickArray("select source, description from karmaLog where userId=?",[$user->userId]);

is($source, 'peter gibbons', 'karma() -- get/set source');
is($description, 'test karma', 'karma() -- get/set description');

$oldKarma = $user->karma;
$user->karma('-69', 'peter gibbons', 'lumberg took test karma away');
is($user->karma, $oldKarma-69, 'karma() -- get/set subtract amount');

#Let's test referringAffiliate
$lastUpdate = time();
$user->referringAffiliate(10);
is($user->referringAffiliate, '10', 'referringAffiliate() -- get/set');
cmp_ok(abs($user->lastUpdated-$lastUpdate), '<=', 1, 'lastUpdated() -- referringAffiliate');

#Let's try adding this user to some groups.  Note, users are auto-added to 2 and 7 on creation
my @groups = qw|6 4|;
$user->addToGroups(\@groups);

my $result;
($result) = $session->db->quickArray("select count(*) from groupings where groupId=? and userId=?", [6, $user->userId]);
ok($result, 'addToGroups() -- added to first test group');

($result) = $session->db->quickArray("select count(*) from groupings where groupId=? and userId=?", [4, $user->userId]);
ok($result, 'addToGroups() -- added to second test group');

#Let's delete this user from our test groups
$user->deleteFromGroups(\@groups);

($result) = $session->db->quickArray("select count(*) from groupings where groupId=? and userId=?", [6, $user->userId]);
is($result, '0', 'deleteFromGroups() -- removed from first test group');

($result) = $session->db->quickArray("select count(*) from groupings where groupId=? and userId=?", [4, $user->userId]);
is($result, '0', 'deleteFromGroups() -- removed from second test group');

#Let's delete this user
my $userId = $user->userId;
$user->delete;

my ($count) = $session->db->quickArray("select count(*) from users where userId=?",[$userId]);
is($count, '0', 'delete() -- users table');

($count) = $session->db->quickArray("select count(*) from userProfileData where userId=?",[$userId]);
is($count, '0', 'delete() -- userProfileData table');

($count) = $session->db->quickArray("select count(*) from inbox where userId=?",[$userId]);
is($count, '0', 'delete() -- inbox table'); 

#Let's test new with an override uid
$user = WebGUI::User->new($session, "new", "ROYSUNIQUEUSERID000001");
is($user->userId, "ROYSUNIQUEUSERID000001", 'new() -- override user id');
$user->delete;

#Let's test new to retrieve an existing user
$user = WebGUI::User->new($session,3);
is($user->username, "Admin", 'new() -- retrieve existing user');
$user = "";

#Let's test new to retrieve default user visitor with no params passed in
$user = WebGUI::User->new($session);
is($user->userId, '1', 'new() -- returns visitor with no args');
$user = "";

#Let's test new to retrieve a non-existing user
$user = WebGUI::User->new($session, 'xxYYxxYYxxYYxxYYxxYYxx');
isa_ok($user, 'WebGUI::User', 'non-existant ID returns valid user object');
$user = "";

$user = WebGUI::User->new($session, "new", "ROYSUNIQUEUSERID000002");
is($user->userId, "ROYSUNIQUEUSERID000002", 'new() -- override user id');
$user->authMethod("LDAP");
is($user->authMethod, "LDAP", 'authMethod() -- set to LDAP');

ok(WebGUI::User->validUserId($session, 1), 'Visitor has a valid userId');
ok(WebGUI::User->validUserId($session, 3), 'Admin has a valid userId');
ok(!WebGUI::User->validUserId($session, 'eeee'), 'random illegal Id #1');
ok(!WebGUI::User->validUserId($session, 37), 'random illegal Id #2');

$user->delete;

SKIP: {
  skip("uncache() -- Don't know how to test uncache()",1);
  ok(undef, "uncache");
}

my $cm = WebGUI::Group->new($session, 4);
is( $cm->name, "Content Managers", "content manager name check");
is( $cm->getId, 4, "content manager groupId check");

my $admin = WebGUI::User->new($session, 3);
is($admin->profileField('uiLevel'), 9, 'Admin default uiLevel = 9');
my $visitor = WebGUI::User->new($session, 1);
is($visitor->profileField('uiLevel'), 5, 'Visitor gets the default uiLevel of 5');

$session->db->write('update userSession set lastIP=? where sessionId=?',['194.168.0.101', $session->getId]);

($result) = $session->db->quickArray('select lastIP,sessionId from userSession where sessionId=?',[$session->getId]);
is ($result, '194.168.0.101', "userSession setup correctly");

ok (!$visitor->isInGroup($cm->getId), "Visitor is not member of group");
ok ($admin->isInGroup($cm->getId), "Admin is member of group");
ok($admin->isAdmin, "Admin user is in admins group");

my $origFilter = $cm->ipFilter;

$cm->ipFilter('194.168.0.0/24');

is( $cm->ipFilter, "194.168.0.0/24", "ipFilter assignment to local net, 194.168.0.0/24");

ok ($visitor->isInGroup($cm->getId), "Visitor is allowed in via IP");
ok ($visitor->isVisitor, "User checks out as visitor");
ok (!$visitor->isAdmin,"User that isn't an admin doesn't look like admin");

$session->db->write('update userSession set lastIP=? where sessionId=?',['193.168.0.101', $session->getId]);

$cm->clearCaches;

ok (!$visitor->isInGroup($cm->getId), "Visitor is not allowed in via IP");

##Restore original filter
$cm->ipFilter(defined $origFilter ? $origFilter : '');

##Test for group membership
$user = WebGUI::User->new($session, "new");
WebGUI::Test->addToCleanup($user);
ok($user->isInGroup(7), "addToGroups: New user is in group 7(Everyone)");
ok(!$user->isInGroup(1),  "New user not in group 1 (Visitors)");
ok($user->isRegistered, "User is not a visitor");
$user->addToGroups([3]);

ok($user->isInGroup(3), "addToGroups: New user is in group 3(Admin)");
ok($user->isInGroup(11), "New user is in group 11(Secondary Admins)");
ok($user->isInGroup(12), "New user is in group 12(Turn On Admin)");
ok($user->isInGroup(13), "New user is in group 13(Export Managers)");

$user->deleteFromGroups([3]);
ok(!$user->isInGroup(3), "deleteFromGroups: New user is not in group 3(Admin)");
ok(!$user->isInGroup(11), "New user not in group 11 (Secondary Admins)");
ok(!$user->isInGroup(12), "New user not in group 12 (Turn On Admin)");
ok(!$user->isInGroup(13), "New user not in group 13 (Export Managers)");
ok(!$user->isInGroup(14), "New user not in group 14 (Product Managers)");

ok(!$user->isInGroup(), "isInGroup, default group is 3");

$user->delete;

ok($visitor->isInGroup(1),  "Visitor is a member of group Visitor");
ok($visitor->isInGroup(7),  "Visitor is a member of group Everyone");
ok(!$visitor->isInGroup(2), "Visitor is a not member of group 2 (Registered Users)");

##remove Visitor from those groups, and make sure we can add him back in.
WebGUI::Group->new($session, '1')->deleteUsers([1]);
($result) = $session->db->quickArray("select count(*) from groupings where groupId=? and userId=?", [1, $user->userId]);
is($result, 0, 'deleteFromGroups() -- Visitor removed from Visitor group');
WebGUI::Group->new($session, '7')->deleteUsers([1]);
($result) = $session->db->quickArray("select count(*) from groupings where groupId=? and userId=?", [7, $user->userId]);
is($result, 0, 'deleteFromGroups() -- Visitor removed from Everyone group');

ok($visitor->isInGroup(1), "isInGroup: Visitor is in group Visitor, hardcoded");
ok($visitor->isInGroup(7), "isInGroup: Everyone is in group Everyone, hardcoded");

##Test for group membership in a non-existant group
ok(! $visitor->isInGroup('nonExistantGroup'), 'isInGroup: Checking for membership in a non-existant group');

##Add Visitor back to those groups
WebGUI::Group->new($session, '1')->addUsers([1]);
WebGUI::Group->new($session, '7')->addUsers([1]);

ok($visitor->isInGroup(1), "Visitor added back to group Visitor");
ok($visitor->isInGroup(7), "Visitor added back to group Everyone");

################################################################
#
# canUseAdminMode
#
################################################################

my $dude = WebGUI::User->new($session, "new");
WebGUI::Test->addToCleanup($dude);

ok(!$dude->canUseAdminMode, 'canUseAdminMode: newly created users cannot');

$dude->addToGroups([12]);

ok($dude->isInGroup(12), 'user successfully added to group 12');

ok($dude->canUseAdminMode, 'canUseAdminMode: with no subnets set, user canUseAdminMode');

$dude->deleteFromGroups([12]);

##Spoof the IP address to test subnet level access control to adminMode
my $origEnvHash = $session->env->{_env};
my %newEnv = ( REMOTE_ADDR => '194.168.0.2' );
$session->env->{_env} = \%newEnv;
WebGUI::Test->originalConfig('adminModeSubnets');
$session->config->set('adminModeSubnets', ['194.168.0.0/24']);

ok(!$dude->isInGroup(12), 'user is not in group 12');
ok(!$dude->canUseAdminMode, 'canUseAdminMode: just being in the subnet does not allow adminMode access');

$dude->addToGroups([12]);

ok($dude->canUseAdminMode, 'canUseAdminMode: with no subnets set, user canUseAdminMode');

$newEnv{REMOTE_ADDR} = '10.0.0.2';

ok(!$dude->canUseAdminMode, 'canUseAdminMode: even with the right group permission, user must be in subnet if subnet is set');

##Check for multiple IP settings
$session->config->set('adminModeSubnets', ['10.0.0.0/24', '192.168.0.0/24', ]);
ok($dude->canUseAdminMode, 'canUseAdminMode: multiple IP settings, first IP range');

$newEnv{REMOTE_ADDR} = '192.168.0.127';
ok($dude->canUseAdminMode, 'canUseAdminMode: multiple IP settings, second IP range');

##restore the original session variables
$session->env->{_env} = $origEnvHash;
$session->config->delete('adminModeSubnets');

################################################################
#
# newByEmail
#
################################################################

my $originalVisitorEmail = $visitor->profileField('email');
WebGUI::Test->addToCleanup(sub {$visitor->profileField('email', $originalVisitorEmail); });
$visitor->profileField('email', 'visitor@localdomain');
$dude->profileField('email', 'dude@aftery2k.com');

my $clone = WebGUI::User->newByEmail($session, 'visitor@localdomain');
is($clone, undef, 'newByEmail returns undef if you look up the Visitor');

$clone = WebGUI::User->newByEmail($session, 'noone@localdomain');
is($clone, undef, 'newByEmail returns undef if email address cannot be found');

$dude->username('');

$clone = WebGUI::User->newByEmail($session, 'dude@aftery2k.com');
is($clone, undef, 'newByEmail returns undef if the user does not have a username');

$dude->username('dude');

$clone = WebGUI::User->newByEmail($session, 'dude@aftery2k.com');
isa_ok($clone, 'WebGUI::User', 'newByEmail returns a valid user object');
is($clone->username, 'dude', '... and it has the right username');

################################################################
#
# newByUsername
#
################################################################

my $useru = WebGUI::User->newByUsername($session, 'Visitor');
is($useru, undef, 'newByUsername returns undef if you look up the Visitor');

$useru = WebGUI::User->newByUsername($session, 'NotHomeRightNow');
is($useru, undef, 'newByUsername returns undef if username cannot be found');

$dude->username('');

$useru = WebGUI::User->newByUsername($session, '');
is($useru, undef, 'newByUsername returns undef if the user does not have a username');

$dude->username('dude');

$useru = WebGUI::User->newByUsername($session, 'dude');
isa_ok($useru, 'WebGUI::User', 'newByUsername returns a valid user object');
is($useru->userId, $dude->userId, '... and it is the right user object');


################################################################
#
# new, cached user profile data and overrides
#
################################################################

my $buster = WebGUI::User->new($session, "new");
WebGUI::Test->addToCleanup($buster);
is( $buster->profileField('timeZone'), 'America/Chicago', 'buster received original user profile on user creation');

my $profileField = WebGUI::ProfileField->new($session, 'timeZone');
my %originalFieldData = %{ $profileField->get() };
WebGUI::Test->addToCleanup(sub { $profileField->set(\%originalFieldData); });
my %copiedFieldData = %originalFieldData;
$copiedFieldData{'dataDefault'} = "'America/Hillsboro'";
$profileField->set(\%copiedFieldData);

is($profileField->get('dataDefault'), "'America/Hillsboro'", 'default timeZone set to America/Hillsboro');

# now let's make sure it has an extras field, and that we can get/set it.
$profileField->set( { extras => '<!-- hello world -->' } );
is($profileField->getExtras, '<!-- hello world -->', 'extras field for profileField');
$profileField->set( { extras => '' } );


my $busterCopy = WebGUI::User->new($session, $buster->userId);
is( $busterCopy->profileField('timeZone'), 'America/Hillsboro', 'busterCopy received updated user profile because there is no username set in his cached user object');

$profileField->set(\%originalFieldData);

my $aliasProfile = WebGUI::ProfileField->new($session, 'alias');
my %originalAliasProfile = %{ $aliasProfile->get() };
WebGUI::Test->addToCleanup(sub { $profileField->set(\%originalAliasProfile); });
my %copiedAliasProfile = %originalAliasProfile;
$copiedAliasProfile{'dataDefault'} = "'aliasAlias'"; ##Non word characters;
$aliasProfile->set(\%copiedAliasProfile);

my $buster3 = WebGUI::User->new($session, $buster->userId);
WebGUI::Test->addToCleanup($buster);
is($buster3->profileField('alias'), 'aliasAlias', 'default alias set');

$copiedAliasProfile{'dataDefault'} = "'....^^^^....'"; ##Non word characters;
$aliasProfile->set(\%copiedAliasProfile);
$buster->uncache();

$buster->username('mythBuster');

$buster3 = WebGUI::User->new($session, $buster->userId);
is($buster3->profileField('alias'), 'mythBuster', 'alias set to username since the default alias has only non-word characters');

$aliasProfile->set(\%originalAliasProfile);

my %listProfile = %copiedAliasProfile;
$listProfile{'fieldName'} = 'listProfile';
$listProfile{'dataDefault'} = "['alpha', 'delta', 'tango']";
my $listProfileField = WebGUI::ProfileField->create($session, 'listProfile', \%listProfile);
WebGUI::Test->addToCleanup($listProfileField);

$buster->uncache;
$buster3 = WebGUI::User->new($session, $buster->userId);
is($buster3->profileField('listProfile'), 'alpha,delta,tango', 'profile field with default data value that is a list returns a string with all values as CSV');

################################################################
#
# Attempt to eval userProfileData
#
################################################################

my %evalProfile = %copiedAliasProfile;
$evalProfile{'fieldName'} = 'evalProfile';
$evalProfile{'dataDefault'} = q!$session->scratch->set('hack','true'); 1;!;
my $evalProfileField = WebGUI::ProfileField->create($session, 'evalProfile', \%evalProfile);
WebGUI::Test->addToCleanup($evalProfileField);

$buster->uncache;
my $buster4 = WebGUI::User->new($session, $buster->userId);
is($session->scratch->get('hack'), undef, 'userProfile dataDefault is not executed when creating users');

################################################################
#
# getGroups
#
################################################################

##Set up a group that has expired.

my $expiredGroup = WebGUI::Group->new($session, 'new');
WebGUI::Test->addToCleanup($expiredGroup);
$expiredGroup->name('Group that expires users automatically');
$expiredGroup->expireOffset(-1000);

$dude->addToGroups([$expiredGroup->getId]);

my $dudeGroups = $dude->getGroups();
cmp_bag($dudeGroups, [12, 2, 7, $expiredGroup->getId], 'Dude belongs to Registered Users, Everyone and T.O.A');

##Group lookups are cached, so we'll clear the cache by removing Dude from T.O.A.
$dude->deleteFromGroups([12]);
$dudeGroups = $dude->getGroups(1);  ##This is the original call to getGroups;
cmp_bag($dudeGroups, [2, 7], 'Dude belongs to Registered Users, Everyone as unexpired group memberships');

##Safe copy check
push @{ $dudeGroups }, 'not a groupId';
cmp_bag($dude->getGroups(1), [2, 7], 'Accessing the list of groups does not change the cached value');

my $dudeGroups2 = $dude->getGroups(1); ##This call gets a cached version.
push @{ $dudeGroups2 }, 'still not a groupId';
cmp_bag($dude->getGroups(1), [2, 7], 'Accessing the cached list of groups does not change the cached value');

################################################################
#
# getFirstName
#
################################################################

my $friend = WebGUI::User->new($session, 'new');
WebGUI::Test->addToCleanup($friend);
is($friend->getFirstName, undef, 'with no profile settings, getFirstName returns undef');

$friend->username('friend');
is($friend->getFirstName, 'friend', 'username is the lower priority profile setting for getFirstName');
$friend->profileField('alias', 'Friend');
is($friend->getFirstName, 'Friend', 'alias is the middle priority profile setting for getFirstName');
$friend->profileField('firstName', 'Mr');
is($friend->getFirstName, 'Mr', 'firstName is the highest priority profile setting for getFirstName');

################################################################
#
# getWholeName
#
################################################################

my $neighbor = WebGUI::User->new($session, 'new');
WebGUI::Test->addToCleanup($neighbor);

is($neighbor->getWholeName, undef, 'with no profile settings, getWholeName returns undef');
$neighbor->username('neighbor');
is($neighbor->getWholeName, 'neighbor', 'username is the lower priority profile setting for getWholeName');
$neighbor->profileField('alias', 'neighbor-man');
is($neighbor->getWholeName, 'neighbor-man', 'alias is the middle priority profile setting for getWholeName');

$neighbor->profileField('firstName', 'Mr');
is($neighbor->getWholeName, 'neighbor-man', 'must have firstName and lastName to override alias');
$neighbor->profileField('lastName', 'Rogers');
is($neighbor->getWholeName, 'Mr Rogers', 'If firstName and lastName are true, wholeName is the concatenation of the both');

################################################################
#
# isOnline
#
################################################################

is ($neighbor->isOnline, 0, 'neighbor is not onLine (no userSession entry)');
$session->user({user => $neighbor});
is ($neighbor->isOnline, 1, 'neighbor is onLine');
$session->db->write('update userSession set lastPageView=?',[time-599]);
is ($neighbor->isOnline, 1, 'neighbor is onLine (lastPageViews=599)');
$session->db->write('update userSession set lastPageView=?',[time-601]);
is ($neighbor->isOnline, 0, 'neighbor is not onLine (lastPageViews=601)');
$session->user({userId => 1});

################################################################
#
# identifier
#
################################################################

is($neighbor->identifier, undef, 'identifier: by default, new users have an undefined password with created through the API');
is($neighbor->identifier('neighborhood'), 'neighborhood', 'identifier: setting the identifier returns the new identifier');
is($neighbor->identifier, 'neighborhood', 'identifier: testing fetch of newly set password');

################################################################
#
# friends
#
################################################################

my $friendsGroup  = $neighbor->friends();
isa_ok($friendsGroup, 'WebGUI::Group', 'friends returns a Group object');
my $friendsGroup2 = $neighbor->friends();
cmp_deeply($friendsGroup, $friendsGroup2, 'second fetch returns the cached group object from the user object');

my $neighborClone = WebGUI::User->new($session, $neighbor->userId);
my $friendsGroup3 = $neighborClone->friends();
is ($friendsGroup->getId, $friendsGroup3->getId, 'friends: fetching group object when group exists but is not cached');

undef $friendsGroup2;
undef $friendsGroup3;
undef $neighborClone;

################################################################
#
# friend reciprication
#
################################################################
#

my $buddy1 = WebGUI::User->new($session, 'new');
WebGUI::Test->addToCleanup($buddy1);

my $buddy2 = WebGUI::User->new($session, 'new');
WebGUI::Test->addToCleanup($buddy2);

WebGUI::Friends->new( $session, $buddy1 )->add( [ $buddy2->userId ] );

ok( exists $buddy1->friends->getUserList->{ $buddy2->userId }, 'buddy1 has buddy2 in his friends list' );
ok( exists $buddy2->friends->getUserList->{ $buddy1->userId }, 'buddy2 has buddy1 in his friends list' );

################################################################
#
# acceptsPrivateMessages
#
################################################################
is ($visitor->acceptsPrivateMessages($visitor->userId), 0, 'acceptsPrivateMessages: visitor cannot receive private messages');
is ($friend->acceptsPrivateMessages($friend->userId), 0, '... never accept private messages from yourself');

$friend->profileField('allowPrivateMessages', 'all');
is ($friend->acceptsPrivateMessages($neighbor->userId), 1, '... when allowPrivateMessages=all, anyone can send messages');
is ($friend->acceptsPrivateMessages(1), 0, 'acceptsPrivateMessages: when allowPrivateMessages=all, visitor can\'t send messages');
$friend->profileField('allowPrivateMessages', 'none');
is ($friend->acceptsPrivateMessages($neighbor->userId), 0, '... when allowPrivateMessages=none, no one can send messages');

$neighbor->profileField('allowPrivateMessages', 'friends');
is ($neighbor->acceptsPrivateMessages($friend->userId), 0, '... when allowPrivateMessages=friends, only friends can send me messages');
$friend->addToGroups([$neighbor->friends->getId]);
is ($neighbor->acceptsPrivateMessages($friend->userId), 1, '... add $friend to $neighbor friendsGroup, now he can send me messages');

$friend->deleteFromGroups([$neighbor->friends->getId]);
$neighbor->profileField('allowPrivateMessages', 'not a valid choice');
is ($neighbor->acceptsPrivateMessages($friend->userId), 0, '... illegal profile field doesn\'t allow messages to be received from anyone');

################################################################
#
# getGroupIdsRecursive
#
################################################################

##Build two sets of groups, which share one group in common
my %groupSet;
foreach my $groupName (qw/red pink orange blue turquoise lightBlue purple/) {
    $groupSet{$groupName} = WebGUI::Group->new($session, 'new');
    $groupSet{$groupName}->name($groupName);
}
WebGUI::Test->addToCleanup(values %groupSet);

$groupSet{blue}->expireOffset(-1500);

$groupSet{purple}->addGroups( [ map { $groupSet{$_}->getId } qw/red blue pink/ ] );
$groupSet{lightBlue}->addGroups( [ map { $groupSet{$_}->getId } qw/blue/ ] );
$groupSet{turquoise}->addGroups( [ map { $groupSet{$_}->getId } qw/blue/ ] );
$groupSet{pink}->addGroups(   [ map { $groupSet{$_}->getId } qw/red/ ] );
$groupSet{orange}->addGroups( [ map { $groupSet{$_}->getId } qw/red/ ] );

my $newFish = WebGUI::User->new($session, 'new');
WebGUI::Test->addToCleanup($newFish);
$newFish->addToGroups([ $groupSet{red}->getId, $groupSet{blue}->getId ]);

cmp_bag(
    $newFish->getGroupIdsRecursive,
    [ 2, 7, map { $_->getId } @groupSet{qw/red pink orange purple/} ],
    'getGroupIdsRecursive returns the correct set of groups, ignoring expire date and not duplicating groups'
);



################################################################
#
# create
#
################################################################

SKIP: {
    eval{ require Test::Exception; import Test::Exception };
    skip 1, 'Test::Exception not found' if $@;

    throws_ok( sub{ WebGUI::User->create }, 'WebGUI::Error::InvalidObject', 
        'create() throws if no session passed'
    );

    throws_ok( sub{ WebGUI::User->create($user) }, 'WebGUI::Error::InvalidObject', 
        '... and if an object other than a Session is passed'
    );

};

ok( my $newCreateUser = WebGUI::User->create( $session ),
    'create() returns something'
);
WebGUI::Test->addToCleanup($newCreateUser);
isa_ok( $newCreateUser, 'WebGUI::User', 'create() returns a WebGUI::User' );

################################################################
#
# getProfileUrl
#
################################################################
$session->setting->set('preventProxyCache', 0);

WebGUI::Test->originalConfig('profileModuleIdentifier');
my $profileModuleId = $session->config->get('profileModuleIdentifier');
is(
    $newFish->getProfileUrl('cellblock'),
    "cellblock?op=account;module=$profileModuleId;do=view;uid=".$newFish->userId,
    'getProfileUrl: passing a page'
);
$session->config->set('profileModuleIdentifier', 'someOtherThing');
is(
    $newFish->getProfileUrl('cellblock'),
    "cellblock?op=account;module=someOtherThing;do=view;uid=".$newFish->userId,
    '... uses profileModuleIdentifier to pick the right Account module'
);
$session->config->set('profileModuleIdentifier', $profileModuleId);

$session->asset(WebGUI::Asset->getDefault($session));
is(
    $newFish->getProfileUrl(),
    "/home?op=account;module=$profileModuleId;do=view;uid=".$newFish->userId,
    '... uses session->url->page if no URL is passed in'
);

$session->setting->set('preventProxyCache', 1);
my $newFishId = $newFish->userId;

like(
    $newFish->getProfileUrl(),
    qr{/home\?noCache=\d+:\d+;op=account;module=$profileModuleId;do=view;uid=$newFishId},
    '... handles preventProxyCache correctly'
);

$session->setting->set('preventProxyCache', 0);

################################################################
#
# hasFriends
#
################################################################

ok(! $neighbor->hasFriends, 'hasFriends, user has no friends');
$friend->addToGroups([$neighbor->friends->getId]);
ok(  $neighbor->hasFriends, 'hasFriends, user has a friend');
$friend->deleteFromGroups([$neighbor->friends->getId]);

################################################################
#
# acceptsFriendsRequests
#
################################################################

ok(! $neighbor->acceptsFriendsRequests(), 'acceptsFriendsRequests: returns 0 unless you give it an object');
ok(! $neighbor->acceptsFriendsRequests($session), '... returns 0 unless you give it a user object');
ok(! $visitor->acceptsFriendsRequests($neighbor), '... visitor cannot have friends');
ok(! $neighbor->acceptsFriendsRequests($visitor), '... visitor cannot be a friend');
ok(! $neighbor->acceptsFriendsRequests($neighbor), '... cannot be your own friend');
$friend->addToGroups([$neighbor->friends->getId]);
ok(! $neighbor->acceptsFriendsRequests($friend), '... cannot accept requests if you are already a friend');
$friend->deleteFromGroups([$neighbor->friends->getId]);
$neighbor->profileField('ableToBeFriend', 0);
ok(! $neighbor->acceptsFriendsRequests($friend), '... follows ableToBeFriend=0');
$neighbor->profileField('ableToBeFriend', 1);
ok(  $neighbor->acceptsFriendsRequests($friend), '... follows ableToBeFriend=1');

################################################################
#
# profileIsViewable
#
################################################################

ok(  $visitor->can('profileIsViewable'), 'profileIsViewable: is a WebGUI::User method');
my $originalVisitorPublicProfile = $visitor->profileField('publicProfile');
WebGUI::Test->addToCleanup(sub { $visitor->profileField('publicProfile', $originalVisitorPublicProfile); });
$visitor->profileField('publicProfile', 'all');
ok(! $visitor->profileIsViewable, '... visitors profile is not viewable, even if publicProfile=all');
ok(! $visitor->profileIsViewable($visitor), '... visitor cannot see his own profile');

my $originalNeighborPublicProfile = $neighbor->profileField('publicProfile');
$neighbor->profileField('publicProfile', 'none');
ok(  $neighbor->profileIsViewable($neighbor), '... you may always see your own profile field');
ok(! $neighbor->profileIsViewable($friend), '... visitor permission follows publicProfile=none');
ok(! $neighbor->profileIsViewable($admin), '... visitor permission follows publicProfile=none, even admin');
$neighbor->profileField('publicProfile', 'all');
ok(  $neighbor->profileIsViewable($friend), '... visitor permission follows publicProfile=all');
ok(! $neighbor->profileIsViewable($visitor), '... visitor may not see profiles, regardless of privacy setting');
$neighbor->profileField('publicProfile', 'friends');
ok(! $neighbor->profileIsViewable($friend), '... visitor permission follows publicProfile=friend, not a friend');
$friend->addToGroups([$neighbor->friends->getId]);
ok(  $neighbor->profileIsViewable($friend), '... visitor permission follows publicProfile=friend, now a friend');
$friend->deleteFromGroups([$neighbor->friends->getId]);

$neighbor->profileField('publicProfile', $originalNeighborPublicProfile);

################################################################
#
# setProfileFieldPrivacySetting, getProfileFieldPrivacySetting
#
################################################################

isa_ok($neighbor->getProfileFieldPrivacySetting, 'HASH', 'getProfileFieldPrivacySetting: returns a HASH if called with no params');
is($neighbor->setProfileFieldPrivacySetting(), undef, '...with no argument, it returns undef');
is($neighbor->setProfileFieldPrivacySetting({}), undef, '...with an empty hashref, it returns undef');
isa_ok($neighbor->setProfileFieldPrivacySetting({email => 'none'}), 'HASH', 'setProfileFieldPrivacySetting: returns a HASH if called with valid params');
is($neighbor->getProfileFieldPrivacySetting('email'), 'none', '...get and set 1 profile field privacy setting');
$neighbor->setProfileFieldPrivacySetting({email => 'only Tony'});
is($neighbor->getProfileFieldPrivacySetting('email'), 'none', '...set will not set invalid profile settings');

is($admin->getProfileFieldPrivacySetting('publicEmail'), 'all', '...get on a user with existing settings');
is($neighbor->getProfileFieldPrivacySetting('wg_privacySettings'), 'none', '...the privacy field always returns "none"');

my $recentProfileField = WebGUI::ProfileField->create($session, 'recentField', {
    fieldName             => 'recentField',
    defaultPrivacySetting => 'all',
    visible               => 1,
    label                 => 'recentField',
});
WebGUI::Test->addToCleanup($recentProfileField);
is($neighbor->getProfileFieldPrivacySetting('recentField'), 'all', '...if a field is added and the user does not have privacy data for it, check the original field');

################################################################
#
# updateProfileFields
#
################################################################

$neighbor->updateProfileFields({ firstName => 'Andy', lastName => 'Dufresne'});
is($neighbor->profileField('firstName'), 'Andy', 'updateProfileFields: set firstName');
is($neighbor->profileField('lastName'), 'Dufresne', '... set lastName, too');

################################################################
#
# canViewField
#
################################################################

ok(! $neighbor->canViewField(), 'canViewField: returns 0 unless you pass it nothing');
ok(! $neighbor->canViewField('email'), '... returns 0 unless you pass it a fieldName and no user object');
ok(  $neighbor->canViewField('email', $neighbor), '... user can always view their own fields');
ok(  $neighbor->canViewField('toilet', $neighbor), '... even if they do not exist');
$friend->addToGroups([$neighbor->friends->getId]);
$neighbor->setProfileFieldPrivacySetting({email => 'only Red'});
ok(! $neighbor->canViewField('toilet', $friend), '... returns 0 unless the field has a valid privacy setting');
$neighbor->setProfileFieldPrivacySetting({email => 'all'});
ok(  $neighbor->canViewField('email', $friend), "... returns 1 when the field's privacy setting is all");
ok(  $neighbor->canViewField('email', $visitor), "... returns 1 when the field's privacy setting is all, even for visitor");
ok(  $neighbor->canViewField('email', $buster), "... returns 1 when the field's privacy setting is all, even for some other user");
$neighbor->setProfileFieldPrivacySetting({email => 'none'});
ok(! $neighbor->canViewField('email', $friend), "... returns 0 when the field's privacy setting is none for a friend");
ok(! $neighbor->canViewField('email', $admin), "... returns 0 when the field's privacy setting is none, even for admin");
ok(! $neighbor->canViewField('email', $buster), "... returns 0 when the field's privacy setting is none, even for some other user");
$neighbor->setProfileFieldPrivacySetting({email => 'friends'});
ok(  $neighbor->canViewField('email', $friend), "... returns 1 when the field's privacy setting is friends, for a friend");
ok(! $neighbor->canViewField('email', $admin), "... returns 0 when the field's privacy setting is friends, even for admin");
ok(! $neighbor->canViewField('email', $buster), "... returns 0 when the field's privacy setting is friends, even for some other user");
$friend->deleteFromGroups([$neighbor->friends->getId]);

################################################################
#
# getInboxNotificationAddresses
#
################################################################

$session->setting->set('smsGateway', '');
my $inmate = WebGUI::User->create($session);
WebGUI::Test->addToCleanup($inmate);
$inmate->profileField('email',     '');
$inmate->profileField('cellPhone', '');
$inmate->profileField('receiveInboxEmailNotifications', 0);
$inmate->profileField('receiveInboxSmsNotifications',   0);
is ($inmate->getInboxNotificationAddresses, '', 'getInboxNotificationAddresses: with no profile info, returns blank');

$inmate->profileField('receiveInboxEmailNotifications', 1);
is ($inmate->getInboxNotificationAddresses, '', '... with receiveInboxEmailNotifications=1, but not email address, returns blank');

$inmate->profileField('email', 'andy@shawshank.com');
is ($inmate->getInboxNotificationAddresses, 'andy@shawshank.com', '... email address only');

$inmate->profileField('receiveInboxSmsNotifications',   1);
is ($inmate->getInboxNotificationAddresses, 'andy@shawshank.com', '... receive only email address, with receiveInboSMSNotifications=1 but no other profile info');

$inmate->profileField('cellPhone', '37927');
is ($inmate->getInboxNotificationAddresses, 'andy@shawshank.com', '... receive only email address, with receiveInboSMSNotifications=1 and cell phone but no gateway');

$inmate->profileField('cellPhone', '');
$session->setting->set('smsGateway', 'textme.com');
is ($inmate->getInboxNotificationAddresses, 'andy@shawshank.com', '... receive only email address, with receiveInboSMSNotifications=1 and gateway but no cell phone');

################################################################
#
# getInboxSmsNotificationAddress
#
################################################################
$inmate->profileField('receiveInboxSmsNotifications',   0);
$inmate->profileField('cellPhone', '');
$session->setting->set('smsGateway', '');

is($inmate->getInboxSmsNotificationAddress, undef, 'getInboxSmsNotificationAddress: returns undef with notifications off, no cell phone and no SMS gateway');

$session->setting->set('smsGateway', 'textme.com');
$inmate->profileField('cellPhone', '37927');
is($inmate->getInboxSmsNotificationAddress, undef, '... returns undef with notifications off, but cell phone and gateway set');

$inmate->profileField('receiveInboxSmsNotifications',   1);
is($inmate->getInboxSmsNotificationAddress, '37927@textme.com', '... returns cellphone@gateway');

$inmate->profileField('cellPhone', '(555)-555.5555');
is($inmate->getInboxSmsNotificationAddress, '5555555555@textme.com', '... strips non digits from cellphone');

################################################################
#
# isDuplicateEmail
#
################################################################

$newFish->profileField( 'email' => 'tommy@shawshank.com' );
ok( $inmate->isDuplicateEmail( 'tommy@shawshank.com' ), 'isDuplicateEmail triggers for duplicate email' );
$inmate->profileField( 'email' => 'andy@shawshank.com' );
ok( !$inmate->isDuplicateEmail( 'andy@shawshank.com' ), "isDuplicateEmail doesn't trigger for our email" );

################################################################
#
# validateProfileDataFromForm
#
################################################################

my $profileData = {
    email       => 'tommy@shawshank.com',
    language    => 'SmoothBankerTalk',
};
$session->request->setup_body( $profileData );

# Also check for firstName, which is not in request
my @fields = map { WebGUI::ProfileField->new( $session, $_ ); } ( keys %$profileData, "firstName" );
my $reply = $inmate->validateProfileDataFromForm( \@fields );

cmp_deeply( $reply->{errorFields}, [ "language" ], "language isn't found, errors" );
is( scalar( @{$reply->{errors}} ), 1, "error messages contains one message" );
cmp_deeply( $reply->{warningFields}, [ "email" ], "email is duplicate, warns" );
is( scalar( @{$reply->{warnings}} ), 1, "warnings messages contains one message" );
cmp_deeply( $reply->{profile}, $profileData, "profile data makes it through" );

################################################################
#
# delete
#
################################################################

##Specifically, cleaning up Address books

my $shopUser = WebGUI::User->create($session);
WebGUI::Test->addToCleanup($shopUser);
$session->user({user => $shopUser});
my $book = WebGUI::Shop::AddressBook->create($session);
is ($book->get('userId'), $shopUser->userId, 'delete: Address book created with proper user');
my $bookId = $book->getId;
$shopUser->delete;
undef $book;
eval { $book = WebGUI::Shop::AddressBook->new($session, $bookId); };
my $e = Exception::Class->caught();
isa_ok($e, 'WebGUI::Error::ObjectNotFound', '... cleans up the address book');
