# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.

######################################
# Test for xmlrpc call to User.get() #
######################################

use 5.14.0;
use strict;
use warnings;

use FindBin qw($RealBin);
use lib "$RealBin/../lib", "$RealBin/../../local/lib/perl5";

use QA::Util;
use QA::Tests qw(PRIVATE_BUG_USER);
use Test::More tests => 330;
our ($config, @clients) = get_rpc_clients();

my $get_user = $config->{'unprivileged_user_login'};
my $canconfirm_user = $config->{'canconfirm_user_login'};
my $priv_user = $config->{PRIVATE_BUG_USER . '_user_login'};
my $disabled = $config->{'disabled_user_login'};
my $disabled_match = substr($disabled, 0, length($disabled) - 1);

# These are the basic tests. There are tests for include_fields
# and exclude_field below.

my @tests = (
    { args => { names => [$get_user] },
      test => "Logged-out user can get unprivileged user by name"
    },
    { args  => { match => [$get_user] },
      test  => 'Logged-out user cannot use the match argument',
      error => 'Logged-out users cannot use',
    },
    { args  => { ids => [1] },
      test  => 'Logged-out users cannot use the "ids" argument',
      error => 'Logged-out users cannot use',
    },

    # match & names
    { user => 'unprivileged',
      args => { names => [$get_user] },
      test => "Unprivileged user can get himself",
    },
    { user => 'unprivileged',
      args => { match => [$get_user] },
      test => 'Logged-in user can use the match argument',
    },
    { user => 'unprivileged',
      args => { match => [$get_user], names => [$get_user] },
      test => 'Specifying the same thing in "match" and "names"',
    },

    # include_disabled
    { user => 'unprivileged',
      args => { match => [$get_user, $disabled_match] },
      test => 'Disabled users are not normally returned'
    },
    { user => 'unprivileged',
      args => { match => [$disabled_match], include_disabled => 1 },
      test => 'Specifying include_disabled returns disabled users'
    },
    { user => 'unprivileged',
      args => { match => [$disabled] },
      test => 'Full match on a disabled user returns that user',
    },

    # groups and group_ids
    { args  => { groups => ['QA-Selenium-TEST'] },
      test  => 'Specifying just groups fails',
      error => 'one of the following parameters',
    },
    { args => { group_ids => [1] },
      test => 'Specifying just group ids fails',
      error => 'one of the following parameters',
    },
    { args => { names => [$get_user, $priv_user], groups => ['QA-Selenium-TEST'] },
      test => 'Limiting the return value to a group while being logged out fails',
      error => 'The group you specified, QA-Selenium-TEST, is not valid here',
    },
    { user => 'unprivileged',
      args => { names => [$get_user, $priv_user], groups => ['missing_group'] },
      test => 'Limiting the return value to a group which does not exist fails',
      error => 'The group you specified, missing_group, is not valid here',
    },
    { user => 'unprivileged',
      args => { names => [$get_user, $priv_user], groups => ['QA-Selenium-TEST'] },
      test => 'Limiting the return value to a group you do not belong to fails',
      error => 'The group you specified, QA-Selenium-TEST, is not valid here',
    },
    { user => 'editbugs',
      args => { names => [$get_user, $priv_user], groups => ['canconfirm', 'editbugs'] },
      test => 'Limiting the return value to some groups you do not belong to fails',
      error => 'The group you specified, canconfirm, is not valid here',
    },
    { user => 'admin',
      args => { names => [$canconfirm_user], groups => ['canconfirm', 'editbugs'] },
      test => 'Limiting the return value to groups you belong to',
    },

    # groups returned
    { user => 'admin',
      args => { names => [$get_user] },
      test => 'Admin can get user',
    },
    { user => 'admin',
      args => { names => [$canconfirm_user] },
      test => 'Admin can get user',
    },
    { user => 'canconfirm',
      args => { names => [$canconfirm_user] },
      test => 'Privileged user can get himself',
    },
    { user => 'editbugs',
      args => { names => [$canconfirm_user] },
      test => 'Privileged user can get another user',
    },
);

sub post_success {
    my ($call, $t) = @_;

    my $result = $call->result;
    is(scalar @{ $result->{users} }, 1, "Got exactly one user");
    my $item = $result->{users}->[0];
    my $user = $t->{user} || '';

    if ($user eq 'admin') {
        ok(exists $item->{email} && exists $item->{can_login}
           && exists $item->{email_enabled} && exists $item->{login_denied_text},
           'Admin correctly gets all user fields');
    }
    elsif ($user) {
        ok(exists $item->{can_login},
           'Logged-in user correctly gets can_login');
        ok(!exists $item->{email_enabled}
           && !exists $item->{login_denied_text},
           "Non-admin user doesn't get email_enabled and login_denied_text");
    }
    else {
        my @item_keys = sort keys %$item;
        is_deeply(\@item_keys, ['id', 'name', 'real_name'],
            'Only id, name, and real_name are returned to logged-out users');
        return;
    }

    my $username = $config->{"${user}_user_login"};
    # FIXME We have no way to create a saved search or a saved report from
    # the WebService, so we cannot test that the correct data is returned
    # if the user is accessing his own account.
    if ($username eq $item->{name}) {
        ok(exists $item->{saved_searches} && exists $item->{saved_reports},
           'Users can get the list of saved searches and reports for their own account');
    }
    else {
        ok(!exists $item->{saved_searches} && !exists $item->{saved_reports},
           "Users cannot get the list of saved searches and reports from someone else's acccount");
    }

    my @groups = map { $_->{name} } @{$item->{groups}};
    # Admins can see all groups a user belongs to (assuming they inherited
    # membership for all groups). Same for a user querying his own account.
    if ($username eq $item->{name} || $user eq 'admin') {
        if ($username eq $get_user) {
            ok(!scalar @groups, "The unprivileged user doesn't belong to any group");
        }
        elsif ($username eq $canconfirm_user) {
            ok(grep($_ eq 'canconfirm', @groups), "Group 'canconfirm' returned");
        }
    }
    else {
        ok(!scalar @groups, "No groups are visible to users without bless privs");
    }
}

foreach my $rpc (@clients) {
    $rpc->bz_run_tests(tests => \@tests, method => 'User.get',
                       post_success => \&post_success);

    #############################
    # Include and Exclude Tests #
    #############################

    my $include_nothing = $rpc->bz_call_success('User.get', {
        names => [$get_user], include_fields => ['asdfasdfsdf'],
    }, 'User.get including only invalid fields');
    is(scalar keys %{ $include_nothing->result->{users}->[0] }, 0,
       'No fields returned for user');

    my $include_one = $rpc->bz_call_success('User.get', {
        names => [$get_user], include_fields => ['id'],
    }, 'User.get including only id');
    is(scalar keys %{ $include_one->result->{users}->[0] }, 1,
       'Only one field returned for user');

    my $exclude_none = $rpc->bz_call_success('User.get', {
        names => [$get_user], exclude_fields => ['asdfasdfsdf'],
    }, 'User.get excluding only invalid fields');
    is(scalar keys %{ $exclude_none->result->{users}->[0] }, 3,
       'All fields returned for user');

    my $exclude_one = $rpc->bz_call_success('User.get', {
        names => [$get_user], exclude_fields => ['id'],
    }, 'User.get excluding id');
    is(scalar keys %{ $exclude_one->result->{users}->[0] }, 2,
       'Only two fields returned for user');

    my $override = $rpc->bz_call_success('User.get', {
        names => [$get_user], include_fields => ['id', 'name'],
        exclude_fields => ['id']
    }, 'User.get with both include and exclude');
    is(scalar keys %{ $override->result->{users}->[0] }, 1,
       'Only one field returned');
    ok(exists $override->result->{users}->[0]->{name},
       '...and that field is the "name" field');
}
