From 9b6ec1f545da1cc4088ddf9cc117747954e58e65 Mon Sep 17 00:00:00 2001 From: David Lawrence Date: Fri, 26 Feb 2016 17:57:55 +0000 Subject: Bug 1069799 - move the QA repository into the main repository r=LpSolit --- xt/webservice/bug_add_attachment.t | 231 +++++++++ xt/webservice/bug_add_comment.t | 173 +++++++ xt/webservice/bug_attachments.t | 155 ++++++ xt/webservice/bug_comments.t | 178 +++++++ xt/webservice/bug_create.t | 243 ++++++++++ xt/webservice/bug_fields.t | 223 +++++++++ xt/webservice/bug_get.t | 150 ++++++ xt/webservice/bug_history.t | 33 ++ xt/webservice/bug_legal_values.t | 104 ++++ xt/webservice/bug_search.t | 211 +++++++++ xt/webservice/bug_update.t | 705 ++++++++++++++++++++++++++++ xt/webservice/bug_update_see_also.t | 86 ++++ xt/webservice/bugzilla.t | 49 ++ xt/webservice/group_create.t | 101 ++++ xt/webservice/jsonp.t | 34 ++ xt/webservice/product_create.t | 167 +++++++ xt/webservice/product_get.t | 113 +++++ xt/webservice/user_create.t | 118 +++++ xt/webservice/user_get.t | 222 +++++++++ xt/webservice/user_login_logout.t | 128 +++++ xt/webservice/user_offer_account_by_email.t | 63 +++ 21 files changed, 3487 insertions(+) create mode 100644 xt/webservice/bug_add_attachment.t create mode 100644 xt/webservice/bug_add_comment.t create mode 100644 xt/webservice/bug_attachments.t create mode 100644 xt/webservice/bug_comments.t create mode 100644 xt/webservice/bug_create.t create mode 100644 xt/webservice/bug_fields.t create mode 100644 xt/webservice/bug_get.t create mode 100644 xt/webservice/bug_history.t create mode 100644 xt/webservice/bug_legal_values.t create mode 100644 xt/webservice/bug_search.t create mode 100644 xt/webservice/bug_update.t create mode 100644 xt/webservice/bug_update_see_also.t create mode 100644 xt/webservice/bugzilla.t create mode 100644 xt/webservice/group_create.t create mode 100644 xt/webservice/jsonp.t create mode 100644 xt/webservice/product_create.t create mode 100644 xt/webservice/product_get.t create mode 100644 xt/webservice/user_create.t create mode 100644 xt/webservice/user_get.t create mode 100644 xt/webservice/user_login_logout.t create mode 100644 xt/webservice/user_offer_account_by_email.t (limited to 'xt/webservice') diff --git a/xt/webservice/bug_add_attachment.t b/xt/webservice/bug_add_attachment.t new file mode 100644 index 000000000..f08e42c6c --- /dev/null +++ b/xt/webservice/bug_add_attachment.t @@ -0,0 +1,231 @@ +# 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. + +use 5.10.1; +use strict; +use warnings; + +use FindBin qw($RealBin); +use lib "$RealBin/../lib"; + +use QA::Util; +use MIME::Base64 qw(encode_base64 decode_base64); +use Test::More tests => 187; +my ($config, $xmlrpc, $jsonrpc, $jsonrpc_get) = get_rpc_clients(); + +use constant INVALID_BUG_ID => -1; +use constant INVALID_BUG_ALIAS => random_string(20); +use constant PRIVS_USER => 'QA_Selenium_TEST'; + +sub attach { + my ($id, $override) = @_; + my %fields = ( + ids => [$id], + data => 'data-' . random_string(100), + file_name => 'file_name-' . random_string(60), + summary => 'summary-' . random_string(100), + content_type => 'text/plain', + comment => 'comment-' . random_string(100), + ); + + foreach my $key (keys %{ $override || {} }) { + my $value = $override->{$key}; + if (defined $value) { + $fields{$key} = $value; + } + else { + delete $fields{$key}; + } + } + return \%fields; +} + +my ($public_bug, $private_bug) = + $xmlrpc->bz_create_test_bugs('private'); +my $public_id = $public_bug->{id}; +my $private_id = $private_bug->{id}; + +my @tests = ( + # Permissions + { args => attach($public_id), + error => 'You must log in', + test => 'Logged-out user cannot add an attachment to a public bug', + }, + { args => attach($private_id), + error => "You must log in", + test => 'Logged-out user cannot add an attachment to a private bug', + }, + { user => 'editbugs', + args => attach($private_id), + error => "not authorized to access", + test => "Editbugs user can't add an attachment to a private bug", + }, + + # Test ID parameter + { user => 'unprivileged', + args => attach(undef, { ids => undef }), + error => 'a ids argument', + test => 'Failing to pass the "ids" param fails', + }, + { user => 'unprivileged', + args => attach(INVALID_BUG_ID), + error => "not a valid bug number", + test => 'Passing invalid bug id returns error "Invalid Bug ID"', + }, + { user => 'unprivileged', + args => attach(''), + error => "You must enter a valid bug number", + test => 'Passing empty bug id returns error "Invalid Bug ID"', + }, + { user => 'unprivileged', + args => attach(INVALID_BUG_ALIAS), + error => "nor an alias to a bug", + test => 'Passing invalid bug alias returns error "Invalid Bug Alias"', + }, + + # Test Comment parameter + { user => 'unprivileged', + args => attach($public_id, { data => undef }), + error => 'a data argument', + test => 'Failing to pass the "data" parameter fails', + }, + { user => 'unprivileged', + args => attach($public_id, { data => '' }), + error => "The file you are trying to attach is empty", + test => 'Passing empty data fails', + }, + { user => 'unprivileged', + args => attach($public_id, { data => random_string(300_000) }), + error => "Attachments cannot be more than", + test => "Passing an attachment that's too large fails", + }, + + # Test the private parameter + { user => 'unprivileged', + args => attach($public_id, { is_private => 1 }), + error => 'attachments as private', + test => 'Unprivileged user cannot add a private attachment' + }, + + # Content-type + { user => 'unprivileged', + args => attach($public_id, { content_type => 'foo/bar' }), + error => "Valid types must be of the form", + test => "Well-formed but invalid content type fails", + }, + { user => 'unprivileged', + args => attach($public_id, { content_type => undef }), + error => 'Valid types must be of the form', + test => "Failing to pass content_type fails", + }, + { user => 'unprivileged', + args => attach($public_id, { content_type => '' }), + error => 'Valid types must be of the form', + test => "Empty content type fails", + }, + + # Summary + { user => 'unprivileged', + args => attach($public_id, { summary => undef }), + error => 'You must enter a description for the attachment', + test => "Failing to pass summary fails", + }, + { user => 'unprivileged', + args => attach($public_id, { summary => '' }), + error => 'You must enter a description for the attachment', + test => "Empty summary fails", + }, + + # Filename + { user => 'unprivileged', + args => attach($public_id, { file_name => undef }), + error => 'You did not specify a file to attach', + test => "Failing to pass file_name fails", + }, + { user => 'unprivileged', + args => attach($public_id, { file_name => '' }), + error => 'You did not specify a file to attach', + test => "Empty file_name fails", + }, + + # Success tests + { user => 'unprivileged', + args => attach($public_id), + test => 'Unprivileged user can add an attachment to a public bug', + }, + { user => 'unprivileged', + args => attach($public_id, { is_patch => 1, content_type => undef }), + test => 'Attaching a patch with no content type works', + }, + { user => 'unprivileged', + args => attach($public_id, { is_patch => 1, + content_type => 'application/octet-stream' }), + test => 'Attaching a patch with a bad content_type works', + }, + { user => PRIVS_USER, + args => attach($private_id), + test => 'Privileged user can add an attachment to a private bug', + }, + { user => PRIVS_USER, + args => attach($public_id, { is_private => 1 }), + test => 'Insidergroup user can add a private attachment', + }, +); + +$jsonrpc_get->bz_call_fail('Bug.add_attachment', attach($public_id), + 'must use HTTP POST', 'add_attachment fails over GET'); + +foreach my $rpc ($jsonrpc, $xmlrpc) { + $rpc->bz_run_tests(tests => \@tests, method => 'Bug.add_attachment', + post_success => \&post_success, pre_call => \&pre_call); +} + +# We have to encode data manually when using JSON-RPC, else it fails. +sub pre_call { + my ($t, $rpc) = @_; + return if !$rpc->isa('QA::RPC::JSONRPC'); + return if !defined $t->{args}->{data}; + + $t->{args}->{data} = encode_base64($t->{args}->{data}, ''); +} + +sub post_success { + my ($call, $t, $rpc) = @_; + + my $ids = $call->result->{ids}; + $call = $rpc->bz_call_success("Bug.attachments", {attachment_ids => $ids}); + my $attachments = $call->result->{attachments}; + + foreach my $id (keys %$attachments) { + my $attachment = $attachments->{$id}; + if ($t->{args}->{is_private}) { + ok($attachment->{is_private}, + $rpc->TYPE . ": Attachment $id is private"); + } + else { + ok(!$attachment->{is_private}, + $rpc->TYPE . ": Attachment $id is NOT private"); + } + + if ($t->{args}->{is_patch}) { + is($attachment->{content_type}, 'text/plain', + $rpc->TYPE . ": Patch $id content type is text/plain"); + } + else { + is($attachment->{content_type}, $t->{args}->{content_type}, + $rpc->TYPE . ": Attachment $id content type is correct"); + } + + if ($rpc->isa('QA::RPC::JSONRPC')) { + # We encoded data in pre_call(), so we have to restore it to its original content. + $t->{args}->{data} = decode_base64($t->{args}->{data}); + $attachment->{data} = decode_base64($attachment->{data}); + } + is($attachment->{data}, $t->{args}->{data}, + $rpc->TYPE . ": Attachment $id data is correct"); + } +} diff --git a/xt/webservice/bug_add_comment.t b/xt/webservice/bug_add_comment.t new file mode 100644 index 000000000..6f234b37a --- /dev/null +++ b/xt/webservice/bug_add_comment.t @@ -0,0 +1,173 @@ +# 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 Bug.add_comment() # +############################################# + +use 5.10.1; +use strict; +use warnings; + +use FindBin qw($RealBin); +use lib "$RealBin/../lib"; + +use QA::Util; +use Test::More tests => 141; + +my ($config, $xmlrpc, $jsonrpc, $jsonrpc_get) = get_rpc_clients(); + +use constant INVALID_BUG_ID => -1; +use constant INVALID_BUG_ALIAS => 'aaaaaaa12345'; +use constant PRIVS_USER => 'QA_Selenium_TEST'; +use constant TIMETRACKING_USER => 'admin'; + +use constant TEST_COMMENT => '--- Test Comment From QA Tests ---'; +use constant TOO_LONG_COMMENT => 'a' x 100000; + +my @tests = ( + # Permissions + { args => { id => 'public_bug', comment => TEST_COMMENT }, + error => 'You must log in', + test => 'Logged-out user cannot comment on a public bug', + }, + { args => { id => 'private_bug', comment => TEST_COMMENT }, + error => "You must log in", + test => 'Logged-out user cannot comment on a private bug', + }, + { user => 'unprivileged', + args => { id => 'private_bug', comment => TEST_COMMENT }, + error => "not authorized to access", + test => "Unprivileged user can't comment on a private bug", + }, + + # Test ID parameter + { user => 'unprivileged', + args => { comment => TEST_COMMENT }, + error => 'a id argument', + test => 'Failing to pass the "id" param fails', + }, + { user => 'unprivileged', + args => { id => INVALID_BUG_ID, comment => TEST_COMMENT }, + error => "not a valid bug number", + test => 'Passing invalid bug id returns error "Invalid Bug ID"', + }, + { user => 'unprivileged', + args => { id => '', comment => TEST_COMMENT }, + error => "You must enter a valid bug number", + test => 'Passing empty bug id param returns error "Invalid Bug ID"', + }, + { user => 'unprivileged', + args => { id => INVALID_BUG_ALIAS, comment => TEST_COMMENT }, + error => "nor an alias to a bug", + test => 'Passing invalid bug alias returns error "Invalid Bug Alias"', + }, + + # Test Comment parameter + { user => 'unprivileged', + args => { id => 'public_bug' }, + error => 'a comment argument', + test => 'Failing to pass the "comment" parameter fails', + }, + { user => 'unprivileged', + args => { id => 'public_bug', comment => '' }, + error => "a comment argument", + test => 'Passing an empty comment fails', + }, + { user => 'unprivileged', + args => { id => 'public_bug', comment => ' ' }, + error => 'a comment argument', + test => 'Passing only a space for comment fails', + }, + { user => 'unprivileged', + args => { id => 'public_bug', comment => " \t\n\n\r\n\r\n\r " }, + error => 'a comment argument', + test => 'Passing only whitespace (including newlines) fails', + }, + { user => 'unprivileged', + args => { id => 'public_bug', comment => TOO_LONG_COMMENT }, + error => "cannot be longer than", + test => "Passing a comment that's too long fails", + }, + + # Testing the "private" parameter happens in the tests for Bug.comments + + # Test work_time parameter + # FIXME Should be testing permissions on the work_time parameter, + # but we currently have no way to verify whether or not time was + # added to the bug, and there's no error thrown if you lack perms. + { user => 'admin', + args => { id => 'public_bug', comment => TEST_COMMENT, + work_time => 'aaa' }, + error => "is not a numeric value", + test => "Passing a non-numeric work_time fails", + }, + { user => 'admin', + args => { id => 'public_bug', comment => TEST_COMMENT, + work_time => '1234567890' }, + error => 'more than the maximum', + test => 'Passing too large of a work_time fails', + }, + { user => 'admin', + args => { id => 'public_bug', comment => '', + work_time => '1.0' }, + error => 'a comment argument', + test => 'Passing a work_time with an empty comment fails', + }, + + # Success tests + { user => 'unprivileged', + args => { id => 'public_bug', comment => TEST_COMMENT }, + test => 'Unprivileged user can add a comment to a public bug', + }, + { user => 'unprivileged', + args => { id => 'public_bug', comment => " \n" . TEST_COMMENT }, + test => 'Can add a comment to a bug where the first line is whitespace', + }, + { user => 'QA_Selenium_TEST', + args => { id => 'private_bug', comment => TEST_COMMENT }, + test => 'Privileged user can add a comment to a private bug', + check_privacy => 1, + }, + { user => 'QA_Selenium_TEST', + args => { id => 'public_bug', comment => TEST_COMMENT, + is_private => 1 }, + test => 'Insidergroup user can add a private comment', + check_privacy => 1, + }, + { user => 'admin', + args => { id => 'public_bug', comment => TEST_COMMENT, + work_time => '1.5' }, + test => 'Timetracking user can add work_time to a bug', + }, + # FIXME Need to verify that the comment added actually has work_time. +); + +$jsonrpc_get->bz_call_fail('Bug.add_comment', + { id => 'public_bug', comment => TEST_COMMENT }, + 'must use HTTP POST', 'add_comment fails over GET'); + +foreach my $rpc ($jsonrpc, $xmlrpc) { + $rpc->bz_run_tests(tests => \@tests, method => 'Bug.add_comment', + post_success => \&post_success); +} + +sub post_success { + my ($call, $t, $rpc) = @_; + return unless $t->{check_privacy}; + + my $comment_id = $call->result->{id}; + my $result = $rpc->bz_call_success('Bug.comments', {comment_ids => [$comment_id]}); + if ($t->{args}->{is_private}) { + ok($result->result->{comments}->{$comment_id}->{is_private}, + $rpc->TYPE . ": Comment $comment_id is private"); + } + else { + ok(!$result->result->{comments}->{$comment_id}->{is_private}, + $rpc->TYPE . ": Comment $comment_id is NOT private"); + } +} diff --git a/xt/webservice/bug_attachments.t b/xt/webservice/bug_attachments.t new file mode 100644 index 000000000..d5283685d --- /dev/null +++ b/xt/webservice/bug_attachments.t @@ -0,0 +1,155 @@ +# 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. + +use 5.10.1; +use strict; +use warnings; + +use FindBin qw($RealBin); +use lib "$RealBin/../lib"; + +use QA::Util; +use QA::Tests qw(STANDARD_BUG_TESTS PRIVATE_BUG_USER); +use Data::Dumper; +use List::Util qw(first); +use MIME::Base64; +use Test::More tests => 313; +my ($config, @clients) = get_rpc_clients(); + +################ +# Bug ID Tests # +################ + +our %attachments; + +sub post_bug_success { + my ($call, $t) = @_; + + my $bugs = $call->result->{bugs}; + is(scalar keys %$bugs, 1, "Got exactly one bug") + or diag(Dumper($call->result)); + + my $bug_attachments = (values %$bugs)[0]; + # Collect attachment ids + foreach my $alias (qw(public_bug private_bug)) { + foreach my $is_private (0, 1) { + my $find_desc = "${alias}_${is_private}"; + my $attachment = first { $_->{summary} eq $find_desc } + reverse @$bug_attachments; + if ($attachment) { + $attachments{$find_desc} = $attachment->{id}; + } + } + } +} + +foreach my $rpc (@clients) { + $rpc->bz_run_tests(tests => STANDARD_BUG_TESTS, method => 'Bug.attachments', + post_success => \&post_bug_success); +} + +foreach my $alias (qw(public_bug private_bug)) { + foreach my $is_private (0, 1) { + ok($attachments{"${alias}_${is_private}"}, + "Found attachment id for ${alias}_${is_private}"); + } +} + +#################### +# Attachment Tests # +#################### + +my $content_file = $config->{bugzilla_path} . '/xt/config/generate_test_data.pl'; +open(my $fh, '<', $content_file) or die "$content_file: $!"; +my $content; +{ local $/; $content = <$fh>; } +close($fh); + +# Access tests for public/private stuff, and also validate that the +# format of each return value is correct. + +my @tests = ( + # Logged-out user + { args => { attachment_ids => [$attachments{'public_bug_0'}] }, + test => 'Logged-out user can access public attachment on public' + . ' bug by id', + }, + { args => { attachment_ids => [$attachments{'public_bug_1'}] }, + test => 'Logged-out user cannot access private attachment on public bug', + error => 'Sorry, you are not authorized', + }, + { args => { attachment_ids => [$attachments{'private_bug_0'}] }, + test => 'Logged-out user cannot access attachments by id on private bug', + error => 'You are not authorized to access', + }, + { args => { attachment_ids => [$attachments{'private_bug_1'}] }, + test => 'Logged-out user cannot access private attachment on ' + . ' private bug', + error => 'You are not authorized to access', + }, + + # Logged-in, unprivileged user. + { user => 'unprivileged', + args => { attachment_ids => [$attachments{'public_bug_0'}] }, + test => 'Logged-in user can see a public attachment on a public bug by id', + }, + { user => 'unprivileged', + args => { attachment_ids => [$attachments{'public_bug_1'}] }, + test => 'Logged-in user cannot access private attachment on public bug', + error => 'Sorry, you are not authorized', + }, + { user => 'unprivileged', + args => { attachment_ids => [$attachments{'private_bug_0'}] }, + test => 'Logged-in user cannot access attachments by id on private bug', + error => "You are not authorized to access", + }, + { user => 'unprivileged', + args => { attachment_ids => [$attachments{'private_bug_1'}] }, + test => 'Logged-in user cannot access private attachment on private bug', + error => "You are not authorized to access", + }, + + # User who can see private bugs and private attachments + { user => PRIVATE_BUG_USER, + args => { attachment_ids => [$attachments{'public_bug_1'}] }, + test => PRIVATE_BUG_USER . ' can see private attachment on public bug', + }, + { user => PRIVATE_BUG_USER, + args => { attachment_ids => [$attachments{'private_bug_1'}] }, + test => PRIVATE_BUG_USER . ' can see private attachment on private bug', + }, +); + +sub post_success { + my ($call, $t, $rpc) = @_; + is(scalar keys %{ $call->result->{attachments} }, 1, + "Got exactly one attachment"); + my $attachment = (values %{ $call->result->{attachments} })[0]; + + cmp_ok($attachment->{last_change_time}, '=~', $rpc->DATETIME_REGEX, + "last_change_time is in the right format"); + cmp_ok($attachment->{creation_time}, '=~', $rpc->DATETIME_REGEX, + "creation_time is in the right format"); + is($attachment->{is_obsolete}, 0, 'is_obsolete is 0'); + cmp_ok($attachment->{bug_id}, '=~', qr/^\d+$/, "bug_id is an integer"); + cmp_ok($attachment->{id}, '=~', qr/^\d+$/, "id is an integer"); + is($attachment->{content_type}, 'application/x-perl', + "content_type is correct"); + cmp_ok($attachment->{file_name}, '=~', qr/^\w+\.pl$/, + "filename is in the expected format"); + is($attachment->{creator}, $config->{QA_Selenium_TEST_user_login}, + "creator is the correct user"); + my $data = $attachment->{data}; + $data = decode_base64($data) if $rpc->isa('QA::RPC::JSONRPC'); + is($data, $content, 'data is correct'); + is($attachment->{size}, length($data), "size matches data's size"); +} + +foreach my $rpc (@clients) { + $rpc->bz_run_tests(method => 'Bug.attachments', tests => \@tests, + post_success => \&post_success); +} diff --git a/xt/webservice/bug_comments.t b/xt/webservice/bug_comments.t new file mode 100644 index 000000000..d66e445cf --- /dev/null +++ b/xt/webservice/bug_comments.t @@ -0,0 +1,178 @@ +# 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 Bug.comments() # +########################################## + +use 5.10.1; +use strict; +use warnings; + +use FindBin qw($RealBin); +use lib "$RealBin/../lib"; + +use DateTime; +use QA::Util; +use QA::Tests qw(STANDARD_BUG_TESTS PRIVATE_BUG_USER); +use Test::More tests => 331; +my ($config, @clients) = get_rpc_clients(); + +# These gets populated when we call Bug.add_comment. +our $creation_time; +our %comments = ( + public_comment_public_bug => 0, + public_comment_private_bug => 0, + private_comment_public_bug => 0, + private_comment_private_bug => 0, +); + +sub test_comments { + my ($comments_returned, $call, $t, $rpc) = @_; + + my $comment = $comments_returned->[0]; + ok($comment->{bug_id}, "bug_id exists"); + # FIXME At some point we should test attachment_id here. + + if ($t->{args}->{comment_ids}) { + my $expected_id = $t->{args}->{comment_ids}->[0]; + is($comment->{id}, $expected_id, "comment id is correct"); + + my %reverse_map = reverse %comments; + my $expected_text = $reverse_map{$expected_id}; + is($comment->{text}, $expected_text, "comment has the correct text"); + + my $priv_login = $rpc->bz_config->{PRIVATE_BUG_USER . '_user_login'}; + is($comment->{creator}, $priv_login, "comment creator is correct"); + + my $creation_day; + if ($rpc->isa('QA::RPC::XMLRPC')) { + $creation_day = $creation_time->ymd(''); + } + else { + $creation_day = $creation_time->ymd; + } + like($comment->{time}, qr/^\Q${creation_day}\ET\d\d:\d\d:\d\d/, + "comment time has the right format"); + } + else { + foreach my $field (qw(id text creator time)) { + ok(defined $comment->{$field}, "$field is defined"); + } + } +} + +################ +# Bug ID Tests # +################ + +sub post_bug_success { + my ($call, $t) = @_; + my @bugs = values %{ $call->result->{bugs} }; + is(scalar @bugs, 1, "Got exactly one bug"); + my @comments = map { @{ $_->{comments} } } @bugs; + test_comments(\@comments, @_); +} + +foreach my $rpc (@clients) { + $rpc->bz_run_tests(tests => STANDARD_BUG_TESTS, method => 'Bug.comments', + post_success => \&post_bug_success); +} + +#################### +# Comment ID Tests # +#################### + +# First, create comments using add_comment. +my @add_comment_tests; +foreach my $key (keys %comments) { + $key =~ /^([a-z]+)_comment_(\w+)$/; + my $is_private = ($1 eq 'private' ? 1 : 0); + my $bug_alias = $2; + push(@add_comment_tests, { args => { id => $bug_alias, comment => $key, + private => $is_private }, + test => "Add comment: $key", + user => PRIVATE_BUG_USER }); +} + +# Set the comment id for each comment that we add, so we can test getting +# them back, later. +sub post_add { + my ($call, $t) = @_; + my $key = $t->{args}->{comment}; + $comments{$key} = $call->result->{id}; +} + +$creation_time = DateTime->now(); +# We only need to create these comments once, with one of the interfaces. +$clients[0]->bz_run_tests( + tests => \@add_comment_tests, method => 'Bug.add_comment', + post_success => \&post_add); + +# Now check access on each private and public comment + +my @comment_tests = ( + # Logged-out user + { args => { comment_ids => [$comments{'public_comment_public_bug'}] }, + test => 'Logged-out user can access public comment on public bug by id', + }, + { args => { comment_ids => [$comments{'private_comment_public_bug'}] }, + test => 'Logged-out user cannot access private comment on public bug', + error => 'is private', + }, + { args => { comment_ids => [$comments{'public_comment_private_bug'}] }, + test => 'Logged-out user cannot access comments by id on private bug', + error => 'You are not authorized to access', + }, + { args => { comment_ids => [$comments{'private_comment_private_bug'}] }, + test => 'Logged-out user cannot access private comment on private bug', + error => 'You are not authorized to access', + }, + + # Logged-in, unprivileged user. + { user => 'unprivileged', + args => { comment_ids => [$comments{'public_comment_public_bug'}] }, + test => 'Logged-in user can see a public comment on a public bug by id', + }, + { user => 'unprivileged', + args => { comment_ids => [$comments{'private_comment_public_bug'}] }, + test => 'Logged-in user cannot access private comment on public bug', + error => 'is private', + }, + { user => 'unprivileged', + args => { comment_ids => [$comments{'public_comment_private_bug'}] }, + test => 'Logged-in user cannot access comments by id on private bug', + error => "You are not authorized to access", + }, + { user => 'unprivileged', + args => { comment_ids => [$comments{'private_comment_private_bug'}] }, + test => 'Logged-in user cannot access private comment on private bug', + error => "You are not authorized to access", + }, + + # User who can see private bugs and private comments + { user => PRIVATE_BUG_USER, + args => { comment_ids => [$comments{'private_comment_public_bug'}] }, + test => PRIVATE_BUG_USER . ' can see private comment on public bug', + }, + { user => PRIVATE_BUG_USER, + args => { comment_ids => [$comments{'private_comment_private_bug'}] }, + test => PRIVATE_BUG_USER . ' can see private comment on private bug', + }, +); + +sub post_comments { + my ($call) = @_; + my @comments = values %{ $call->result->{comments} }; + is(scalar @comments, 1, "Got exactly one comment"); + test_comments(\@comments, @_); +} + +foreach my $rpc (@clients) { + $rpc->bz_run_tests(tests => \@comment_tests, method => 'Bug.comments', + post_success => \&post_comments); +} diff --git a/xt/webservice/bug_create.t b/xt/webservice/bug_create.t new file mode 100644 index 000000000..6d7c8e14a --- /dev/null +++ b/xt/webservice/bug_create.t @@ -0,0 +1,243 @@ +# 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 Bug.create() # +######################################## + +use 5.10.1; +use strict; +use warnings; + +use FindBin qw($RealBin); +use lib "$RealBin/../lib"; + +use Storable qw(dclone); +use Test::More tests => 293; +use QA::Util; +use QA::Tests qw(create_bug_fields PRIVATE_BUG_USER); + +my ($config, $xmlrpc, $jsonrpc, $jsonrpc_get) = get_rpc_clients(); + +######################## +# Bug.create() testing # +######################## + +my $bug_fields = create_bug_fields($config); + +# hash to contain all the possible $bug_fields values that +# can be passed to createBug() +my $fields = { + summary => { + undefined => { + faultstring => 'You must enter a summary for this bug', + value => undef + }, + }, + + product => { + undefined => { faultstring => 'You must select/enter a product.', value => undef }, + invalid => + { faultstring => 'does not exist', value => 'does-not-exist' }, + }, + + component => { + undefined => { + faultstring => 'you must first choose a component', + value => undef + }, + invalid => { + faultstring => "There is no component named 'does-not-exist'", + value => 'does-not-exist' + }, + }, + + version => { + undefined => + { faultstring => 'You must select/enter a version.', value => undef }, + invalid => { + faultstring => "There is no version named 'does-not-exist' in the", + value => 'does-not-exist' + }, + }, + platform => { + undefined => + { faultstring => 'You must select/enter a Hardware.', + value => '' }, + invalid => { + faultstring => "There is no Hardware named 'does-not-exist'.", + value => 'does-not-exist' + }, + }, + + status => { + invalid => { + faultstring => "There is no status named 'does-not-exist'", + value => 'does-not-exist' + }, + }, + + severity => { + undefined => + { faultstring => 'You must select/enter a Severity.', + value => '' }, + invalid => { + faultstring => "There is no Severity named 'does-not-exist'.", + value => 'does-not-exist' + }, + }, + + priority => { + undefined => + { faultstring => 'You must select/enter a Priority.', + value => '' }, + invalid => { + faultstring => "There is no Priority named 'does-not-exist'.", + value => 'does-not-exist' + }, + }, + + op_sys => { + undefined => { + faultstring => 'You must select/enter a OS.', + value => '' + }, + invalid => { + faultstring => "There is no OS named 'does-not-exist'.", + value => 'does-not-exist' + }, + }, + + cc => { + invalid => { + faultstring => 'not a valid username', + value => ['nonuserATbugillaDOTorg'] + }, + }, + + assigned_to => { + invalid => { + faultstring => "There is no user named 'does-not-exist'", + value => 'does-not-exist' + }, + }, + qa_contact => { + invalid => { + faultstring => "There is no user named 'does-not-exist'", + value => 'does-not-exist' + }, + }, + alias => { + long => { + faultstring => 'Bug aliases cannot be longer than 40 characters', + value => 'MyyyyyyyyyyyyyyyyyyBugggggggggggggggggggggg' + }, + existing => { + faultstring => 'already taken the alias', + value => 'public_bug' + }, + numeric => { + faultstring => 'aliases cannot be merely numbers', + value => '12345' + }, + commma_or_space_separated => { + faultstring => 'contains one or more commas or spaces', + value => ['Bug 12345'] + }, + + }, + groups => { + non_existent => { + faultstring => 'either this group does not exist, or you are not allowed to restrict bugs to this group', + value => [random_string(20)], + }, + }, + comment_is_private => { + invalid => { + faultstring => 'you are not allowed to.+comments.+private', + value => 1, + } + }, +}; + +$jsonrpc_get->bz_call_fail('Bug.create', $bug_fields, + 'must use HTTP POST', 'create fails over GET'); + +my @tests = ( + { args => $bug_fields, + error => "You must log in", + test => "Cannot file bugs as a logged-out user", + }, + { user => PRIVATE_BUG_USER, + args => { %$bug_fields, product => 'QA-Selenium-TEST', + component => 'QA-Selenium-TEST', + target_milestone => 'QAMilestone', + version => 'QAVersion', + groups => ['QA-Selenium-TEST'], + # These are set here because we can't actually set them, + # and we need the values to be correct for post_success. + qa_contact => $config->{PRIVATE_BUG_USER . '_user_login'}, + status => 'UNCONFIRMED' }, + test => "Authorized user can file a bug against a group", + }, + { user => PRIVATE_BUG_USER, + args => { %$bug_fields, comment_is_private => 1, + # These are here because PRIVATE_BUG_USER can't set them + # and we need their values to be correct for post_success. + assigned_to => $config->{'permanent_user'}, + qa_contact => '', + status => 'UNCONFIRMED' }, + test => "Insider can create a private description" + }, + { user => 'editbugs', + args => $bug_fields, + test => "Creating a bug with standard values succeeds", + }, +); + +# Convert the $fields tests into standard bz_run_tests format. +foreach my $field (sort keys %$fields) { + my $test_values = $fields->{$field}; + foreach my $test_name (sort keys %$test_values) { + my $input_fields = dclone($bug_fields); + my $check_value = $test_values->{$test_name}->{value}; + my $error = $test_values->{$test_name}->{faultstring}; + $input_fields->{$field} = $check_value; + my $test = { user => 'editbugs', args => $input_fields, + error => $error, + test => "$field $test_name: fails as expected" }; + push(@tests, $test); + } +} + +sub post_success { + my ($call, $t, $rpc) = @_; + + my $id = $call->result->{id}; + ok($id, $rpc->TYPE . ": Result has an id: $id"); + + my $get_call = $rpc->bz_call_success('Bug.get', { ids => [$id] }); + my $bug = $get_call->result->{bugs}->[0]; + + my $expect = dclone $t->{args}; + + my $comment_is_private = delete $expect->{comment_is_private}; + $expect->{creator} = $rpc->bz_config->{$t->{user} . '_user_login'}; + + my @fields = keys %$expect; + $rpc->bz_test_bug(\@fields, $bug, $expect, $t); + + my $comment_call = $rpc->bz_call_success('Bug.comments', { ids => [$id] }); + my $comment = $comment_call->result->{bugs}->{$id}->{comments}->[0]; + is($comment->{is_private} ? 1 : 0, $comment_is_private ? 1 : 0, + $rpc->TYPE . ": comment privacy is correct"); +} + +foreach my $rpc ($jsonrpc, $xmlrpc) { + $rpc->bz_run_tests(tests => \@tests, method => 'Bug.create', + post_success => \&post_success); +} diff --git a/xt/webservice/bug_fields.t b/xt/webservice/bug_fields.t new file mode 100644 index 000000000..097a607f5 --- /dev/null +++ b/xt/webservice/bug_fields.t @@ -0,0 +1,223 @@ +# 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. + +use 5.10.1; +use strict; +use warnings; + +use FindBin qw($RealBin); +use lib "$RealBin/../lib"; + +use Data::Dumper; +use Test::More; +use List::Util qw(first); +use QA::Util; + +my ($config, @clients) = get_rpc_clients(); +plan tests => ($config->{test_extensions} ? 1338 : 1320); + +use constant INVALID_FIELD_NAME => 'invalid_field'; +use constant INVALID_FIELD_ID => -1; +sub GLOBAL_GENERAL_FIELDS { + my @fields = qw( + attach_data.thedata + attachments.description + attachments.filename + attachments.isobsolete + attachments.ispatch + attachments.isprivate + attachments.mimetype + attachments.submitter + + flagtypes.name + requestees.login_name + setters.login_name + + alias + assigned_to + blocked + bug_file_loc + bug_group + bug_id + cc + cclist_accessible + classification + commenter + content + creation_ts + days_elapsed + delta_ts + dependson + everconfirmed + keywords + longdesc + longdescs.isprivate + owner_idle_time + product + qa_contact + reporter + reporter_accessible + see_also + short_desc + status_whiteboard + + deadline + estimated_time + percentage_complete + remaining_time + work_time + ); + push(@fields, 'votes') if QA::Util::get_config()->{test_extensions}; + + return @fields; +} + +use constant STANDARD_SELECT_FIELDS => + qw(bug_severity bug_status op_sys priority rep_platform resolution); + +use constant ALL_SELECT_FIELDS => (STANDARD_SELECT_FIELDS, + qw(cf_qa_status cf_single_select)); +use constant PRODUCT_FIELDS => qw(version target_milestone component); +use constant ALL_FIELDS => (GLOBAL_GENERAL_FIELDS, ALL_SELECT_FIELDS, + PRODUCT_FIELDS); +use constant MANDATORY_FIELDS => qw(short_desc product version component); + +use constant PUBLIC_PRODUCT => 'Another Product'; +use constant PRIVATE_PRODUCT => 'QA-Selenium-TEST'; + +sub get_field { + my ($fields, $field) = @_; + return first { $_->{name} eq $field } @$fields; +} + +sub get_products_from_field { + my $field = shift; + my %products; + foreach my $value (@{ $field->{values} }) { + foreach my $vis_value (@{ $value->{visibility_values} }) { + $products{$vis_value} = 1; + } + } + return \%products; +} + +our %field_ids; +foreach my $rpc (@clients) { + my $call = $rpc->bz_call_success('Bug.fields'); + my $fields = $call->result->{fields}; + foreach my $field (ALL_FIELDS) { + my $field_data = get_field($fields, $field); + ok($field_data, "$field is in the returned result") + or diag(Dumper($fields)); + $field_ids{$field} = $field_data->{id}; + + if (grep($_ eq $field, MANDATORY_FIELDS)) { + ok($field_data->{is_mandatory}, "$field is mandatory"); + } + else { + ok(!$field_data->{is_mandatory}, "$field is not mandatory"); + } + } + + foreach my $field (ALL_SELECT_FIELDS, PRODUCT_FIELDS) { + my $field_data = get_field($fields, $field); + ok(defined $field_data->{visibility_values}, + "$field has visibility_values defined"); + my $field_vis_undefs = grep { !defined $_ } + @{ $field_data->{visibility_values} }; + is($field_vis_undefs, 0, "$field.visibility_values has no undefs") + or diag(Dumper($field_data->{visibility_values})); + + ok(defined $field_data->{values}, + "$field has 'values' defined"); + my $num_values = scalar @{ $field_data->{values} }; + ok($num_values, "$field has $num_values values"); + # The first bug status is a fake one and has no name, so we choose the 2nd item. + my $first_value = $field_data->{values}->[1]; + ok(defined $first_value->{name}, 'The first value has a name') + or diag(Dumper($field_data->{values})); + # The sortkey for milestones can be negative. + cmp_ok($first_value->{sortkey}, '=~', qr/^-?\d+$/, + "The first value has a numeric sortkey"); + + ok(defined $first_value->{visibility_values}, + "$field has visibilty_values defined on its first value") + or diag(Dumper($field_data->{values})); + my @value_visibility_values = map { @{ $_->{visibility_values} } } + @{ $field_data->{values} }; + my $undefs = grep { !defined $_ } @value_visibility_values; + is($undefs, 0, + "$field.values.visibility_values has no undefs"); + } + + foreach my $field (PRODUCT_FIELDS) { + my $field_data = get_field($fields, $field); + is($field_data->{value_field}, 'product', + "The value_field for $field is 'product'"); + my $products = get_products_from_field($field_data); + ok($products->{+PUBLIC_PRODUCT}, + "$field values are returned for the public product"); + ok(!$products->{+PRIVATE_PRODUCT}, + "No $field values are returned for the private product"); + } +} + +my @all_tests = ( + { args => { ids => [values %field_ids], + names => [ALL_FIELDS] }, + test => 'Getting all fields by name and id simultaneously', + count => scalar ALL_FIELDS + }, + { args => { names => [INVALID_FIELD_NAME] }, + error => "There is no field named", + test => 'Invalid field name' + }, + { args => { ids => [INVALID_FIELD_ID] }, + error => 'must be numeric', + test => 'Invalid field id' + }, + { user => 'QA_Selenium_TEST', + args => { names => [PRODUCT_FIELDS] }, + test => 'Getting product-specific fields as a privileged user', + count => scalar PRODUCT_FIELDS, + product_private_values => 1 + }, +); + +foreach my $field (ALL_FIELDS) { + push(@all_tests, + { args => { names => [$field] }, + test => "Logged-out users can get the $field field by name" }); + push(@all_tests, + { args => { ids => [$field_ids{$field}] }, + test => "Logged-out users can get the $field by id" }); +} + +sub post_success { + my ($call, $t) = @_; + my $fields = $call->result->{fields}; + my $count = $t->{count}; + $count = 1 if !defined $count; + is(scalar @$fields, $count, "Exactly $count field(s) returned"); + + if ($t->{product_private_values}) { + foreach my $field (@$fields) { + my $name = $field->{name}; + my $field_data = get_field($fields, $name); + my $products = get_products_from_field($field_data); + ok($products->{+PUBLIC_PRODUCT}, + "$name values are returned for the public product"); + ok($products->{+PRIVATE_PRODUCT}, + "$name values are returned for the private product"); + } + } +} + +foreach my $rpc (@clients) { + $rpc->bz_run_tests(tests => \@all_tests, method => 'Bug.fields', + post_success => \&post_success); +} diff --git a/xt/webservice/bug_get.t b/xt/webservice/bug_get.t new file mode 100644 index 000000000..e05fe2cb2 --- /dev/null +++ b/xt/webservice/bug_get.t @@ -0,0 +1,150 @@ +# 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 Bug.get() # +########################################### + +use 5.10.1; +use strict; +use warnings; + +use FindBin qw($RealBin); +use lib "$RealBin/../lib"; + +use Data::Dumper; +use DateTime; +use QA::Util; +use QA::Tests qw(bug_tests PRIVATE_BUG_USER); +use Test::More tests => 988; +my ($config, @clients) = get_rpc_clients(); + +my $xmlrpc = $clients[0]; +our $creation_time = DateTime->now(); +our ($public_bug, $private_bug) = $xmlrpc->bz_create_test_bugs('private'); +my $private_id = $private_bug->{id}; +my $public_id = $public_bug->{id}; + +my $base_url = $config->{browser_url} . "/" + . $config->{bugzilla_installation} . '/'; + +# Set a few fields on the private bug, including setting up +# a dependency relationship. +$xmlrpc->bz_log_in(PRIVATE_BUG_USER); +$xmlrpc->bz_call_success('Bug.update', { + ids => [$private_id], + blocks => { set => [$public_id] }, + dupe_of => $public_id, + is_creator_accessible => 0, + keywords => { set => ['test-keyword-1', 'test-keyword-2'] }, + see_also => { add => ["${base_url}show_bug.cgi?id=$public_id", + "http://landfill.bugzilla.org/show_bug.cgi?id=123456"] }, + cf_qa_status => ['in progress', 'verified'], + cf_single_select => 'two', +}, 'Update the private bug'); +$xmlrpc->bz_call_success('User.logout'); + +$private_bug->{blocks} = [$public_id]; +$private_bug->{dupe_of} = $public_id; +$private_bug->{status} = 'RESOLVED'; +$private_bug->{is_open} = 0; +$private_bug->{resolution} = 'DUPLICATE'; +$private_bug->{is_creator_accessible} = 0; +$private_bug->{is_cc_accessible} = 1; +$private_bug->{keywords} = ['test-keyword-1', 'test-keyword-2']; +$private_bug->{see_also} = ["${base_url}show_bug.cgi?id=$public_id", + "http://landfill.bugzilla.org/show_bug.cgi?id=123456"]; +$private_bug->{cf_qa_status} = ['in progress', 'verified']; +$private_bug->{cf_single_select} = 'two'; + +$public_bug->{depends_on} = [$private_id]; +$public_bug->{dupe_of} = undef; +$public_bug->{resolution} = ''; +$public_bug->{is_open} = 1; +$public_bug->{is_creator_accessible} = 1; +$public_bug->{is_cc_accessible} = 1; +$public_bug->{keywords} = []; +# Local Bugzilla bugs are automatically updated. +$public_bug->{see_also} = ["${base_url}show_bug.cgi?id=$private_id"]; +$public_bug->{cf_qa_status} = []; +$public_bug->{cf_single_select} = '---'; + +# Fill in the timetracking fields on the public bug. +$xmlrpc->bz_log_in('admin'); +$xmlrpc->bz_call_success('Bug.update', { + ids => [$public_id], + deadline => '2038-01-01', + estimated_time => '10.0', + remaining_time => '5.0', +}); +$xmlrpc->bz_call_success('User.logout'); + +# Populate other fields. +$public_bug->{classification} = 'Unclassified'; +$private_bug->{classification} = 'Unclassified'; +$private_bug->{groups} = ['QA-Selenium-TEST']; +$public_bug->{groups} = []; + +# The user filing $private_bug doesn't have permission to set the status +# or qa_contact, so they differ from normal $public_bug values. +$private_bug->{qa_contact} = $config->{PRIVATE_BUG_USER . '_user_login'}; + +sub post_success { + my ($call, $t, $rpc) = @_; + + is(scalar @{ $call->result->{bugs} }, 1, "Got exactly one bug"); + my $bug = $call->result->{bugs}->[0]; + + if ($t->{user} && $t->{user} eq 'admin') { + ok(exists $bug->{estimated_time} && exists $bug->{remaining_time}, + 'Admin correctly gets time-tracking fields'); + is($bug->{deadline}, '2038-01-01', 'deadline is correct'); + cmp_ok($bug->{estimated_time}, '==', '10.0', + 'estimated_time is correct'); + cmp_ok($bug->{remaining_time}, '==', '5.0', + 'remaining_time is correct'); + } + else { + ok(!exists $bug->{estimated_time} && !exists $bug->{remaining_time}, + 'Time-tracking fields are not returned to non-privileged users'); + } + + if ($t->{user}) { + ok($bug->{update_token}, 'Update token returned for logged-in user'); + } + else { + ok(!exists $bug->{update_token}, + 'Update token not returned for logged-out users'); + } + + my $expect = $bug->{id} == $private_bug->{id} ? $private_bug : $public_bug; + + my @fields = sort keys %$expect; + push(@fields, 'creation_time', 'last_change_time'); + + $rpc->bz_test_bug(\@fields, $bug, $expect, $t, $creation_time); +} + +my @tests = ( + @{ bug_tests($public_id, $private_id) }, + { args => { ids => [$public_id], + include_fields => ['id', 'summary', 'groups'] }, + test => 'include_fields', + }, + { args => { ids => [$public_id], + exclude_fields => ['assigned_to', 'cf_qa_status'] }, + test => 'exclude_fields' }, + { args => { ids => [$public_id], + include_fields => ['id', 'summary', 'groups'], + exclude_fields => ['summary'] }, + test => 'exclude_fields overrides include_fields' }, +); + +foreach my $rpc (@clients) { + $rpc->bz_run_tests(tests => \@tests, method => 'Bug.get', + post_success => \&post_success); +} diff --git a/xt/webservice/bug_history.t b/xt/webservice/bug_history.t new file mode 100644 index 000000000..02ec1c11a --- /dev/null +++ b/xt/webservice/bug_history.t @@ -0,0 +1,33 @@ +# 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 Bug.history() # +######################################### + +use 5.10.1; +use strict; +use warnings; + +use FindBin qw($RealBin); +use lib "$RealBin/../lib"; + +use QA::Util; +use QA::Tests qw(STANDARD_BUG_TESTS); +use Test::More tests => 114; +my ($config, @clients) = get_rpc_clients(); + +sub post_success { + my ($call, $t) = @_; + is(scalar @{ $call->result->{bugs} }, 1, "Got exactly one bug"); + isa_ok($call->result->{bugs}->[0]->{history}, 'ARRAY', "Bug's history"); +} + +foreach my $rpc (@clients) { + $rpc->bz_run_tests(tests => STANDARD_BUG_TESTS, + method => 'Bug.history', post_success => \&post_success); +} diff --git a/xt/webservice/bug_legal_values.t b/xt/webservice/bug_legal_values.t new file mode 100644 index 000000000..2f775e528 --- /dev/null +++ b/xt/webservice/bug_legal_values.t @@ -0,0 +1,104 @@ +# 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 Bug.legal_values() # +############################################## + +use 5.10.1; +use strict; +use warnings; + +use FindBin qw($RealBin); +use lib "$RealBin/../lib"; + +use Test::More tests => 269; +use QA::Util; +my ($config, @clients) = get_rpc_clients(); + +use constant INVALID_PRODUCT_ID => -1; +use constant INVALID_FIELD_NAME => 'invalid_field'; +use constant GLOBAL_FIELDS => + qw(bug_severity bug_status op_sys priority rep_platform resolution + cf_qa_status cf_single_select); +use constant PRODUCT_FIELDS => qw(version target_milestone component); + + +my $products = $clients[0]->bz_get_products(); +my $public_product = $products->{'Another Product'}; +my $private_product = $products->{'QA-Selenium-TEST'}; + +my @all_tests; + +for my $field (GLOBAL_FIELDS) { + push(@all_tests, + { args => { field => $field }, + test => "Logged-out user can get $field values" }); +} + +for my $field (PRODUCT_FIELDS) { + my @tests = ( + { args => { field => $field }, + error => "argument was not set", + test => "$field can't be accessed without a value for 'product'", + }, + { args => { product_id => INVALID_PRODUCT_ID, field => $field }, + error => "does not exist", + test => "$field cannot be accessed with an invalid product id", + }, + + { args => { product_id => $private_product, field => $field }, + error => "you don't have access", + test => "Logged-out user cannot access $field in private product" + }, + { args => { product_id => $public_product, field => $field }, + test => "Logged-out user can access $field in a public product", + }, + + { user => 'unprivileged', + args => { product_id => $private_product, field => $field }, + error => "you don't have access", + test => "Unprivileged user cannot access $field in private product", + }, + { user => 'unprivileged', + args => { product_id => $public_product, field => $field }, + test => "Logged-in user can access $field in public product", + }, + + { user => 'QA_Selenium_TEST', + args => { product_id => $private_product, field => $field }, + test => "Privileged user can access $field in a private product", + }, + ); + + push(@all_tests, @tests); +} + +my @extra_tests = ( + { args => { product_id => $private_product, }, + error => "requires a field argument", + test => "Passing product_id without 'field' throws an error", + }, + { args => { field => INVALID_FIELD_NAME }, + error => "Can't use \"" . INVALID_FIELD_NAME . "\" as a field name", + test => 'Invalid field name' + }, +); + +push(@all_tests, @extra_tests); + +sub post_success { + my ($call) = @_; + + cmp_ok(scalar @{ $call->result->{'values'} }, '>', 0, + 'Got one or more values'); +} + +foreach my $rpc (@clients) { + $rpc->bz_run_tests(tests => \@all_tests, method => 'Bug.legal_values', + post_success => \&post_success); +} diff --git a/xt/webservice/bug_search.t b/xt/webservice/bug_search.t new file mode 100644 index 000000000..93a517e24 --- /dev/null +++ b/xt/webservice/bug_search.t @@ -0,0 +1,211 @@ +# 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 Bug.search() # +######################################## + +use 5.10.1; +use strict; +use warnings; + +use FindBin qw($RealBin); +use lib "$RealBin/../lib"; + +use QA::Util; +use QA::Tests qw(PRIVATE_BUG_USER); +use DateTime; +use List::MoreUtils qw(uniq); +use Test::More; +use Data::Dumper; + +my ($config, @clients) = get_rpc_clients(); +plan tests => $config->{test_extensions} ? 531 : 522; + +my ($public_bug, $private_bug) = $clients[0]->bz_create_test_bugs('private'); + +# Add aliases to both bugs +$public_bug->{alias} = random_string(40); +$private_bug->{alias} = random_string(40); +my $alias_tests = [ + { user => 'editbugs', + args => { ids => [ $public_bug->{id} ], alias => $public_bug->{alias} }, + test => 'Add alias to public bug' }, + { user => PRIVATE_BUG_USER, + args => { ids => [ $private_bug->{id} ], + cc => { add => [ $config->{'editbugs_user_login'} ] } }, + test => 'Add editusers to cc of private bug' }, + { user => 'editbugs', + args => { ids => [ $private_bug->{id} ], alias => $private_bug->{alias} }, + test => 'Add alias to private bug' }, + { user => PRIVATE_BUG_USER, + args => { ids => [ $private_bug->{id} ], + cc => { remove => [ $config->{'editbugs_user_login'} ] } }, + test => 'Remove editusers from cc of private bug' }, +]; +$clients[0]->bz_run_tests(tests => $alias_tests, method => 'Bug.update'); + +my @tests; +foreach my $field (keys %$public_bug) { + next if ($field eq 'cc' or $field eq 'description'); + my $test = { args => { $field => $public_bug->{$field} }, + test => "Search by $field" }; + if ( grep($_ eq $field, qw(alias whiteboard summary)) ) { + $test->{exactly} = 1; $test->{bugs} = 1; + } + push(@tests, $test); +} + +push(@tests, ( + { args => { offset => 1 }, + test => "Offset without limit fails", + error => 'requires a limit argument', + }, + + { args => { alias => $private_bug->{alias} }, + test => 'Logged-out cannot find a private_bug by alias', + bugs => 0, + }, + + { args => { creation_time => '19700101T00:00:00' }, + test => 'Get all bugs by creation time', + }, + { args => { creation_time => '20380101T00:00:00' }, + test => 'Get no bugs, by creation time', + bugs => 0, + }, + { args => { last_change_time => '19700101T00:00:00' }, + test => 'Get all bugs by last_change_time', + }, + { args => { last_change_time => '20380101T00:00:00' }, + test => 'Get no bugs by last_change_time', + bugs => 0, + }, + + { args => { reporter => $config->{editbugs_user_login} }, + test => 'Search by reporter', + }, + { args => { resolution => '' }, + test => 'Search for empty resolution', + }, + { args => { resolution => 'NO_SUCH_RESOLUTION' }, + test => 'Search for invalid resolution', + bugs => 0, + }, + { args => { summary => substr($public_bug->{summary}, 0, 50) }, + test => 'Search by partial summary', + bugs => 1, exactly => 1 + }, + { args => { summary => random_string() . ' ' . random_string() }, + test => 'Summary search that returns no results', + bugs => 0, + }, + { args => { summary => [split(/\s/, $public_bug->{summary})] }, + test => 'Summary search using multiple terms', + }, + + { args => { whiteboard => substr($public_bug->{whiteboard}, 0, 50) }, + test => 'Search by partial whiteboard', + bugs => 1, exactly => 1, + }, + { args => { whiteboard => random_string(100) }, + test => 'Whiteboard search that returns no results', + bugs => 0, + }, + { args => { whiteboard => [split(/\s/, $public_bug->{whiteboard})] }, + test => 'Whiteboard search using multiple terms', + bugs => 1, exactly => 1, + }, + + { args => { product => $public_bug->{product}, + component => $public_bug->{component}, + last_change_time => '19700101T00:00:00' }, + test => 'Search by multiple arguments', + }, + + # Logged-in user who can see private bugs + { user => PRIVATE_BUG_USER, + args => { alias => [$public_bug->{alias}, $private_bug->{alias}] }, + test => 'Search using two aliases (including one private)', + bugs => 2, exactly => 1, + }, + { user => PRIVATE_BUG_USER, + args => { product => [$public_bug->{product}, $private_bug->{product}], + limit => 1 }, + test => 'Limit 1', + bugs => 1, exactly => 1, + }, + { user => PRIVATE_BUG_USER, + args => { product => [$public_bug->{product}, $private_bug->{product}], + limit => 1, offset => 1 }, + test => 'Limit 1 Offset 1', + bugs => 1, exactly => 1, + }, + + # include_fields ane exclude_fields + { args => { id => $public_bug->{id}, + include_fields => ['id', 'alias', 'summary', 'groups'] }, + test => 'include_fields', + }, + { args => { id => $public_bug->{id}, + exclude_fields => ['assigned_to', 'cf_qa_status'] }, + test => 'exclude_fields' }, + { args => { id => $public_bug->{id}, + include_fields => ['id', 'alias', 'summary', 'groups'], + exclude_fields => ['summary'] }, + test => 'exclude_fields overrides include_fields' }, +)); + +push(@tests, + { args => { votes => 1 }, + test => 'Search by votes', + bugs => -1, # We don't care how many it returns, for now. + }) if $config->{test_extensions}; + +sub post_success { + my ($call, $t) = @_; + my $bugs = $call->result->{bugs}; + + my $expected_count = $t->{bugs}; + $expected_count = 1 if !defined $expected_count; + if ($expected_count) { + my $operator = $t->{exactly} ? '==' : '>='; + cmp_ok(scalar @$bugs, $operator, $expected_count, + 'The right number of bugs are returned'); + unless ($t->{user} and $t->{user} eq PRIVATE_BUG_USER) { + ok(!grep($_->{alias} && $_->{alias} eq $private_bug->{alias}, @$bugs), + 'Result does not contain the private bug'); + } + + my @include = @{ $t->{args}->{include_fields} || [] }; + my @exclude = @{ $t->{args}->{exclude_fields} || [] }; + if (@include or @exclude) { + my @check_fields = uniq (keys %$public_bug, @include); + foreach my $field (sort @check_fields) { + next if $field eq 'description'; + if ((@include and !grep { $_ eq $field } @include ) + or (@exclude and grep { $_ eq $field } @exclude)) + { + ok(!exists $bugs->[0]->{$field}, "$field is not included") + or diag Dumper($bugs); + } + else { + ok(exists $bugs->[0]->{$field}, "$field is included"); + } + } + } + + } + else { + is(scalar @$bugs, 0, 'No bugs returned'); + } +} + +foreach my $rpc (@clients) { + $rpc->bz_run_tests(tests => \@tests, + method => 'Bug.search', post_success => \&post_success); +} diff --git a/xt/webservice/bug_update.t b/xt/webservice/bug_update.t new file mode 100644 index 000000000..dfc2f89e1 --- /dev/null +++ b/xt/webservice/bug_update.t @@ -0,0 +1,705 @@ +# 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. + +use 5.10.1; +use strict; +use warnings; + +use FindBin qw($RealBin); +use lib "$RealBin/../lib"; + +use Data::Dumper; +use QA::Util; +use QA::Tests qw(PRIVATE_BUG_USER STANDARD_BUG_TESTS); +use Storable qw(dclone); +use Test::More tests => 937; + +use constant NONEXISTENT_BUG => 12_000_000; + +############### +# Subroutines # +############### + +# We have to generate different values for each RPC client, so we +# have a function to generate the tests for each client. +sub get_tests { + my ($config, $rpc) = @_; + + # update doesn't support logged-out users. + my @tests = grep { $_->{user} } @{ STANDARD_BUG_TESTS() }; + + my ($public_bug, $second_bug) = $rpc->bz_create_test_bugs(); + my ($public_id, $second_id) = ($public_bug->{id}, $second_bug->{id}); + + # Add aliases to both bugs + $public_bug->{alias} = random_string(40); + $second_bug->{alias} = random_string(40); + my $alias_tests = [ + { user => 'editbugs', + args => { ids => [ $public_id ], alias => $public_bug->{alias} }, + test => 'Add alias to public bug' }, + { user => 'editbugs', + args => { ids => [ $second_id ], alias => $second_bug->{alias} }, + test => 'Add alias to second bug' }, + ]; + $rpc->bz_run_tests(tests => $alias_tests, method => 'Bug.update'); + + my $comment_call = $rpc->bz_call_success( + 'Bug.comments', { ids => [$public_id, $second_id] }); + $public_bug->{comment} = + $comment_call->result->{bugs}->{$public_id}->{comments}->[0]; + $second_bug->{comment} = + $comment_call->result->{bugs}->{$second_id}->{comments}->[0]; + + push(@tests, ( + { args => { ids => [$public_id] }, + error => 'You must log in', + test => 'Logged-out users cannot call update' }, + + # FIXME We need a permissions test for canedit, but it's so uncommonly + # used that it's not a high priority. + )); + + my %valid = valid_values($config, $public_bug, $second_bug); + my $valid_value_tests = valid_values_to_tests(\%valid, $public_bug); + push(@tests, @$valid_value_tests); + + my %invalid = invalid_values($public_bug, $second_bug); + my $invalid_value_tests = invalid_values_to_tests(\%invalid, $public_bug); + push(@tests, @$invalid_value_tests); + + return \@tests; +} + +sub valid_values { + my ($config, $public_bug, $second_bug) = @_; + + my $admin = $config->{'admin_user_login'}; + my $second_id = $second_bug->{id}; + my $comment_id = $public_bug->{comment}->{id}; + my $bug_uri = $config->{browser_url} . '/' + . $config->{bugzilla_installation} . '/show_bug.cgi?id='; + + my %values = ( + alias => [ + { value => random_string(20) }, + ], + assigned_to => [ + { value => $config->{'unprivileged_user_login'} } + ], + blocks => [ + { value => { set => [$second_id] }, + added => $second_id, + test => 'set to second bug' }, + { value => { remove => [$second_id] }, + added => '', removed => $second_id, + test => 'remove second bug' }, + { value => { add => [$second_id] }, + added => $second_id, removed => '', + test => 'add second bug' }, + { value => { set => [] }, + added => '', removed => $second_id, + test => 'set to nothing' }, + ], + + cc => [ + { value => { add => [$admin] }, + added => $admin, removed => '', + test => 'add admin' }, + { value => { remove => [$admin] }, + added => '', removed => $admin, + test => 'remove admin' }, + { value => { remove => [$admin] }, + test => "removing user who isn't on the list works", + no_changes => 1 }, + ], + + is_cc_accessible => [ + { value => 0, test => 'set to 0' }, + { value => 1, test => 'set to 1' }, + ], + + comment => [ + { value => { body => random_string(100) }, test => 'public' }, + { value => { body => random_string(100), is_private => 1 }, + user => PRIVATE_BUG_USER, test => 'private' }, + ], + + comment_is_private => [ + { value => { $comment_id => 1 }, + user => PRIVATE_BUG_USER, test => 'make description private' }, + { value => { $comment_id => 0 }, + user => PRIVATE_BUG_USER, test => 'make description public' }, + ], + + component => [ + { value => 'c2' } + ], + + deadline => [ + { value => '2037-01-01' }, + { value => '', removed => '2037-01-01', test => 'remove' }, + ], + + dupe_of => [ + { value => $second_id }, + ], + + estimated_time => [ + { value => '10.0' }, + { value => '0.0', removed => '10.0', test => 'set to zero' }, + ], + + groups => [ + { value => { add => ['Master'] }, + user => 'admin', added => 'Master', test => 'add Master' }, + { value => { remove => ['Master'] }, + user => 'admin', added => '', removed => 'Master', + test => 'remove Master' }, + ], + + keywords => [ + { value => { add => ['test-keyword-1'] }, + test => 'add one', added => 'test-keyword-1' }, + { value => { set => ['test-keyword-1', 'test-keyword-2'] }, + test => 'set two', added => 'test-keyword-2' }, + { value => { remove => ['test-keyword-1'] }, + removed => 'test-keyword-1', added => '', + test => 'remove one' }, + { value => { set => [] }, + removed => 'test-keyword-2', added => '', + test => 'set to empty' }, + { value => { remove => ['test-keyword-2'] }, + test => 'removing removed keyword does nothing', + no_changes => 1 }, + ], + + op_sys => [ + { value => 'All' }, + ], + + platform => [ + { value => 'All' }, + ], + + priority => [ + { value => 'Normal' }, + ], + + product => [ + { value => 'C2 Forever', + extra => { + component => 'Helium', version => 'unspecified', + target_milestone => '---', + }, + test => 'move to C2 Forever' + }, + # This also tests that the extra fields transfer over properly + # when they have identical names in both products. + { value => $public_bug->{product}, + extra => { component => $public_bug->{component} }, + test => 'move back to original product' }, + ], + + qa_contact => [ + { value => $admin }, + { value => '', test => 'set blank', removed => $admin }, + # Reset to the original so that reset_qa_contact can also be tested. + { value => $public_bug->{qa_contact} }, + ], + + remaining_time => [ + { value => '1000.50' }, + { value => 0 }, + ], + + reset_assigned_to => [ + { value => 1, field => 'assigned_to', + added => $config->{permanent_user} }, + ], + + reset_qa_contact => [ + { value => 1, field => 'qa_contact', added => '' }, + ], + + resolution => [ + { value => 'FIXED', extra => { status => 'RESOLVED' }, + test => 'to RESOLVED FIXED' }, + { value => 'INVALID', test => 'just resolution' }, + ], + + see_also => [ + { value => { add => [$bug_uri . $second_id] }, + added => $bug_uri . $second_id, removed => '', + test => 'add local bug URI' }, + { value => { remove => [$bug_uri . $second_id] }, + removed => $bug_uri . $second_id, added => '', + test => 'remove local bug URI' }, + { value => { remove => ['http://landfill.bugzilla.org/bugzilla-tip/show_bug.cgi?id=1'] }, + no_changes => 1, + test => 'removing non-existent URI works' }, + { value => { add => [''] }, + no_changes => 1, + test => 'adding an empty string to see_also does nothing' }, + { value => { add => [undef] }, + no_changes => 1, + test => 'adding a null to see_also does nothing' }, + ], + + status => [ + # At this point, due to previous tests, the status is RESOLVED, + # so changing to CONFIRMED is our only real option if we want to + # test a simple open status. + { value => 'CONFIRMED' }, + ], + + severity => [ + { value => 'critical' }, + ], + + summary => [ + { value => random_string(100) }, + ], + + target_milestone => [ + { value => 'AnotherMS2' }, + ], + + url => [ + { value => 'http://' . random_string(20) . '/' }, + ], + + version => [ + { value => 'Another2' }, + ], + + whiteboard => [ + { value => random_string(1000) }, + ], + + work_time => [ + # FIXME: work_time really needs to start showing up in the changes + # hash. + { value => '1.2', no_changes => 1 }, + { value => '-1.2', test => 'negative value', no_changes => 1 }, + ], + ); + + $values{depends_on} = $values{blocks}; + $values{is_creator_accessible} = $values{is_cc_accessible}; + + return %values; +}; + +sub valid_values_to_tests { + my ($valid_values, $public_bug) = @_; + + my @tests; + foreach my $field (sort keys %$valid_values) { + my @tests_valid = @{ $valid_values->{$field} }; + foreach my $item (@tests_valid) { + my $desc = $item->{test} || 'valid value'; + my %args = ( + ids => [$public_bug->{id}], + $field => $item->{value}, + %{ $item->{extra} || {} }, + ); + my %test = ( user => 'editbugs', args => \%args, field => $field, + test => "$field: $desc" ); + foreach my $item_field (qw(no_changes added removed field user)) { + next if !exists $item->{$item_field}; + $test{$item_field} = $item->{$item_field}; + } + push(@tests, \%test); + } + } + + return \@tests; +} + +sub invalid_values { + my ($public_bug, $second_bug) = @_; + + my $public_id = $public_bug->{id}; + my $second_id = $second_bug->{id}; + + my $comment_id = $public_bug->{comment}->{id}; + my $second_comment_id = $second_bug->{comment}->{id}; + + my %values = ( + alias => [ + { value => random_string(41), + error => 'aliases cannot be longer than', + test => 'alias cannot be too long' }, + { value => $second_bug->{alias}, + error => 'has already taken the alias', + test => 'duplicate alias fails' }, + { value => 123456, + error => 'at least one letter', + test => 'numeric alias fails' }, + { value => random_string(20), ids => [$public_id, $second_id], + error => 'aliases when modifying multiple', + test => 'setting alias on multiple bugs fails' }, + ], + + assigned_to => [ + { value => random_string(20), + error => 'There is no user named', + test => 'changing assigned_to to invalid user fails' }, + { value => '', + error => 'you must provide an address for the new assignee', + test => 'empty assigned_to fails' }, + # FIXME Also check strict_isolation at some point in the future, + # perhaps. + ], + + blocks => [ + { value => { add => [NONEXISTENT_BUG] }, + error => 'does not exist', + test => 'Non-existent bug number fails in deps' }, + { value => { add => [$public_id] }, + error => 'block itself or depend on itself', + test => "can't add this bug itself in a dep field" }, + # FIXME Could use strict_isolation checks at some point. + # FIXME Could use a dependency_loop_multi test. + ], + + cc => [ + { value => { add => [random_string(20)] }, + error => 'There is no user named', + test => 'adding invalid user to cc fails' }, + { value => { remove => [random_string(20)] }, + error => 'There is no user named', + test => 'removing invalid user from cc fails' }, + ], + + comment => [ + { value => { body => random_string(100_000) }, + error => 'cannot be longer', + test => 'comment too long' }, + { value => { body => random_string(100), is_private => 1 }, + error => 'comments or attachments as private', + test => 'normal user cannot add private comments' }, + ], + + comment_is_private => [ + { value => { $comment_id => 1 }, + error => 'comments or attachments as private', + test => 'normal user cannot make a comment private' }, + { value => { $second_comment_id => 1 }, + error => 'You tried to modify the privacy of comment', + user => PRIVATE_BUG_USER, + test => 'cannot change privacy on a comment on another bug' }, + ], + + component => [ + { value => '', + error => 'you must first choose a component', + test => 'empty component fails' }, + { value => random_string(20), + error => 'There is no component named', + test => 'invalid component fails' }, + ], + + deadline => [ + { value => random_string(20), + error => 'is not a legal date', + test => 'Non-date fails in deadline' }, + { value => '2037', + error => 'is not a legal date', + test => 'year alone fails in deadline' }, + ], + + dupe_of => [ + { value => undef, + error => 'dup_id was not defined', + test => 'undefined dupe_of fails' }, + { value => NONEXISTENT_BUG, + error => 'does not exist', + test => 'Cannot dup to a nonexistent bug' }, + { value => $public_id, + error => 'as a duplicate of itself', + test => 'Cannot dup bug to itself' }, + ], + + estimated_time => [ + { value => -1, + error => 'less than the minimum allowable value', + test => 'negative estimated_time fails' }, + { value => 100_000_000, + error => 'more than the maximum allowable value', + test => 'too-large estimated_time fails' }, + { value => random_string(20), + error => 'is not a numeric value', + test => 'non-numeric estimated_time fails' }, + # We use PRIVATE_BUG_USER because he can modify the bug, but + # can't change time-tracking fields. + { value => '100', user => PRIVATE_BUG_USER, + error => 'only a user with the required permissions', + test => 'non-timetracker can not set estimated_time' }, + ], + + groups => [ + { value => { add => ['Master'] }, + error => 'either this group does not exist, or you are not allowed to restrict bugs to this group', + test => "adding group we don't have access to but is valid fails" }, + { value => { add => ['QA-Selenium-TEST'] }, + error => 'either this group does not exist, or you are not allowed to restrict bugs to this group', + test => 'adding valid group that is not in this product fails' }, + { value => { add => [random_string(20)] }, + error => 'either this group does not exist, or you are not allowed to restrict bugs to this group', + test => 'adding non-existent group fails' }, + { value => { remove => [random_string(20)] }, + error => 'either this group does not exist, or you are not allowed to remove bugs from this group', + test => 'removing non-existent group fails' }, + ], + + keywords => [ + { value => { add => [random_string(20)] }, + error => 'See the list of available keywords', + test => 'adding invalid keyword fails' }, + { value => { remove => [random_string(20)] }, + error => 'See the list of available keywords', + test => 'removing invalid keyword fails' }, + { value => { set => [random_string(20)] }, + error => 'See the list of available keywords', + test => 'setting invalid keyword fails' }, + ], + + op_sys => [ + { value => random_string(20), + error => 'There is no', + test => 'invalid op_sys fails' }, + { value => '', + error => 'You must select/enter', + test => 'blank op_sys fails' }, + ], + + product => [ + { value => random_string(60), + error => "does not exist or you aren't authorized", + test => 'invalid product fails' }, + { value => '', + error => 'You must select/enter a product', + test => 'moving to blank product fails' }, + { value => 'TestProduct', + error => 'There is no component named', + test => 'moving products without other fields fails' }, + { value => 'QA-Selenium-TEST', + extra => { component => 'QA-Selenium-TEST' }, + error => "does not exist or you aren't authorized", + test => 'moving to inaccessible product fails' }, + { value => 'QA Entry Only', + error => "does not exist or you aren't authorized", + test => 'moving to product where ENTRY is denied fails' }, + ], + + qa_contact => [ + { value => random_string(20), + error => 'There is no user named', + test => 'changing qa_contact to invalid user fails' }, + ], + + remaining_time => [ + { value => -1, + error => 'less than the minimum allowable value', + test => 'negative remaining_time fails' }, + { value => 100_000_000, + error => 'more than the maximum allowable value', + test => 'too-large remaining_time fails' }, + { value => random_string(20), + error => 'is not a numeric value', + test => 'non-numeric remaining_time fails' }, + # We use PRIVATE_BUG_USER because he can modify the bug, but + # can't change time-tracking fields. + { value => '100', user => PRIVATE_BUG_USER, + error => 'only a user with the required permissions', + test => 'non-timetracker can not set remaining_time' }, + ], + + # We do all the failing resolution tests on the second bug, + # because we want to be sure that we're starting from an open + # status. + resolution => [ + { value => random_string(20), ids => [$second_id], + extra => { status => 'RESOLVED' }, + error => 'There is no Resolution named', + test => 'invalid resolution fails' }, + { value => 'FIXED', ids => [$second_id], + error => 'You cannot set a resolution for open bugs', + test => 'setting resolution on open bug fails' }, + { value => 'DUPLICATE', ids => [$second_id], + extra => { status => 'RESOLVED' }, + error => 'id to mark this bug as a duplicate', + test => 'setting DUPLICATE without dup_id fails' }, + { value => '', ids => [$second_id], + extra => { status => 'RESOLVED' }, + error => 'A valid resolution is required', + test => 'blank resolution fails with closed status' }, + ], + + see_also => [ + { value => { add => [random_string(20)] }, + error => 'is not a valid bug number nor an alias', + test => 'random string fails in see_also' }, + { value => { add => ['http://landfill.bugzilla.org/'] }, + error => 'See Also URLs should point to one of', + test => 'no show_bug.cgi in see_also URI' }, + ], + + status => [ + { value => random_string(20), + error => 'There is no status named', + test => 'invalid status fails' }, + { value => '', + error => 'You must select/enter a status', + test => 'blank status fails' }, + # We use the second bug for this because we can guarantee that + # it is open. + { value => 'VERIFIED', ids => [$second_id], + extra => { resolution => 'FIXED' }, + error => 'You are not allowed to change the bug status from', + test => 'invalid transition fails' }, + ], + + summary => [ + { value => random_string(300), + error => 'The text you entered in the Summary field is too long', + test => 'too-long summary fails' }, + { value => '', + error => 'You must enter a summary for this bug', + test => 'blank summary fails' }, + ], + + work_time => [ + { value => 100_000_000, + error => 'more than the maximum allowable value', + test => 'too-large work_time fails' }, + { value => random_string(20), + error => 'is not a numeric value', + test => 'non-numeric work_time fails' }, + # We use PRIVATE_BUG_USER because he can modify the bug, but + # can't change time-tracking fields. + { value => '10', user => PRIVATE_BUG_USER, + error => 'only a user with the required permissions', + test => 'non-timetracker can not set work_time' }, + ], + ); + + $values{depends_on} = $values{blocks}; + + foreach my $field (qw(platform priority severity target_milestone version)) + { + my $tests = dclone($values{op_sys}); + foreach my $test (@$tests) { + $test->{test} =~ s/op_sys/$field/g; + } + $values{$field} = $tests; + } + + return %values; +} + +sub invalid_values_to_tests { + my ($invalid_values, $public_bug) = @_; + + my @tests; + foreach my $field (sort keys %$invalid_values) { + my @tests_invalid = @{ $invalid_values->{$field} }; + foreach my $item (@tests_invalid) { + my %args = ( + ids => $item->{ids} || [$public_bug->{id}], + $field => $item->{value}, + %{ $item->{extra} || {} }, + ); + push(@tests, { user => $item->{user} || 'editbugs', + args => \%args, + error => $item->{error}, + test => $item->{test} }); + } + } + + return \@tests; +} + +############### +# Main Script # +############### + +my ($config, $xmlrpc, $jsonrpc, $jsonrpc_get) = get_rpc_clients(); + +$jsonrpc_get->bz_call_fail('Bug.update', + { ids => ['public_bug'] }, + 'must use HTTP POST', 'update fails over GET'); + +sub post_success { + my ($call, $t, $rpc) = @_; + return if $t->{no_changes}; + my $field = $t->{field}; + return if !$field; + + my @bugs = @{ $call->result->{bugs} }; + foreach my $bug (@bugs) { + if ($field =~ /^comment/) { + _check_comment($bug, $field, $t, $rpc); + } + else { + _check_changes($bug, $field, $t); + } + } +} + +sub _check_changes { + my ($bug, $field, $t) = @_; + + my $changes = $bug->{changes}->{$field}; + ok(defined $changes, "$field was changed") + or diag Dumper($bug, $t); + + my $new_value = $t->{added}; + $new_value = $t->{args}->{$field} if !defined $new_value; + _test_value($changes->{added}, $new_value, $field, 'added'); + + if (defined $t->{removed}) { + _test_value($changes->{removed}, $t->{removed}, $field, 'removed'); + } +} + +sub _test_value { + my ($got, $expected, $field, $type) = @_; + if ($field eq 'estimated_time' or $field eq 'remaining_time') { + cmp_ok($got, '==', $expected, "$field: $type is correct"); + } + else { + is($got, $expected, "$field: $type is correct"); + } +} + +sub _check_comment { + my ($bug, $field, $t, $rpc) = @_; + my $bug_id = $bug->{id}; + my $call = $rpc->bz_call_success('Bug.comments', { ids => [$bug_id] }); + my $comments = $call->result->{bugs}->{$bug_id}->{comments}; + + if ($field eq 'comment_is_private') { + my $first_private = $comments->[0]->{is_private}; + my ($expected) = values %{ $t->{args}->{comment_is_private} }; + cmp_ok($first_private, '==', $expected, + 'description privacy is correct'); + } + else { + my $last_comment = $comments->[-1]; + my $expected = $t->{args}->{comment}->{body}; + is($last_comment->{text}, $expected, 'comment added correctly'); + } + +} + +foreach my $rpc ($jsonrpc, $xmlrpc) { + $rpc->bz_run_tests(tests => get_tests($config, $rpc), + method => 'Bug.update', post_success => \&post_success); +} diff --git a/xt/webservice/bug_update_see_also.t b/xt/webservice/bug_update_see_also.t new file mode 100644 index 000000000..79c3b5ea8 --- /dev/null +++ b/xt/webservice/bug_update_see_also.t @@ -0,0 +1,86 @@ +# 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 Bug.update_see_also() # +################################################# + +use 5.10.1; +use strict; +use warnings; + +use FindBin qw($RealBin); +use lib "$RealBin/../lib"; + +use QA::Util; +use QA::Tests qw(PRIVATE_BUG_USER STANDARD_BUG_TESTS); +use Test::More tests => 117; +my ($config, $xmlrpc, $jsonrpc, $jsonrpc_get) = get_rpc_clients(); + +my $bug_url = 'http://landfill.bugzilla.org/bugzilla-tip/show_bug.cgi?id=100'; + +# update_see_also doesn't support logged-out users. +my @tests = grep { $_->{user} } @{ STANDARD_BUG_TESTS() }; +foreach my $t (@tests) { + $t->{args}->{add} = $t->{args}->{remove} = []; +} + +push(@tests, ( + { user => 'unprivileged', + args => { ids => ['public_bug'], add => [$bug_url] }, + error => 'only the assignee or reporter of the bug, or a user', + test => 'Unprivileged user cannot add a URL to a bug', + }, + + { user => 'admin', + args => { ids => ['public_bug'], add => ['asdfasdfasdf'] }, + error => 'asdf', + test => 'Admin cannot add an invalid URL', + }, + { user => 'admin', + args => { ids => ['public_bug'], remove => ['asdfasdfasdf'] }, + test => 'Invalid URL silently ignored', + }, + + { user => 'admin', + args => { ids => ['public_bug'], add => [$bug_url] }, + test => 'Admin can add a URL to a public bug', + }, + { user => 'unprivileged', + args => { ids => ['public_bug'], remove => [$bug_url] }, + error => 'only the assignee or reporter of the bug, or a user', + test => 'Unprivileged user cannot remove a URL from a bug', + }, + { user => 'admin', + args => { ids => ['public_bug'], remove => [$bug_url] }, + test => 'Admin can remove a URL from a public bug', + }, + + { user => PRIVATE_BUG_USER, + args => { ids => ['private_bug'], add => [$bug_url] }, + test => PRIVATE_BUG_USER . ' can add a URL to a private bug', + }, + { user => PRIVATE_BUG_USER, + args => { ids => ['private_bug'], remove => [$bug_url] }, + test => PRIVATE_BUG_USER . ' can remove a URL from a private bug', + }, + +)); + +sub post_success { + my ($call, $t) = @_; + isa_ok($call->result->{changes}, 'HASH', "Changes"); +} + +$jsonrpc_get->bz_call_fail('Bug.update_see_also', + { ids => ['public_bug'], add => [$bug_url] }, + 'must use HTTP POST', 'update_see_also fails over GET'); + +foreach my $rpc ($jsonrpc, $xmlrpc) { + $rpc->bz_run_tests(tests => \@tests, method => 'Bug.update_see_also', + post_success => \&post_success); +} diff --git a/xt/webservice/bugzilla.t b/xt/webservice/bugzilla.t new file mode 100644 index 000000000..2ddb13092 --- /dev/null +++ b/xt/webservice/bugzilla.t @@ -0,0 +1,49 @@ +# 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 functions in Bugzilla.pm # +################################################## + +use 5.10.1; +use strict; +use warnings; + +use FindBin qw($RealBin); +use lib "$RealBin/../lib"; + +use Test::More tests => 11 * 3; +use QA::Util; +my ($config, @clients) = get_rpc_clients(); + +foreach my $rpc (@clients) { + my $vers_call = $rpc->bz_call_success('Bugzilla.version'); + my $version = $vers_call->result->{version}; + ok($version, "Bugzilla.version returns $version"); + + my $tz_call = $rpc->bz_call_success('Bugzilla.timezone'); + my $tz = $tz_call->result->{timezone}; + ok($tz, "Bugzilla.timezone retuns $tz"); + + my $ext_call = $rpc->bz_call_success('Bugzilla.extensions'); + my $extensions = $ext_call->result->{extensions}; + isa_ok($extensions, 'HASH', 'extensions'); + + # There is always at least the QA extension enabled. + my $cmp = $config->{test_extensions} ? '>' : '=='; + my @ext_names = keys %$extensions; + my $desc = scalar(@ext_names) . ' extension(s) returned: ' . join(', ', @ext_names); + cmp_ok(scalar(@ext_names), $cmp, 1, $desc); + ok(grep($_ eq 'QA', @ext_names), 'The QA extension is enabled'); + + my $time_call = $rpc->bz_call_success('Bugzilla.time'); + my $time_result = $time_call->result; + foreach my $type (qw(db_time web_time)) { + cmp_ok($time_result->{$type}, '=~', $rpc->DATETIME_REGEX, + "Bugzilla.time returns a datetime for $type"); + } +} diff --git a/xt/webservice/group_create.t b/xt/webservice/group_create.t new file mode 100644 index 000000000..e46546a31 --- /dev/null +++ b/xt/webservice/group_create.t @@ -0,0 +1,101 @@ +# 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 Group.create() # +########################################## + +use 5.10.1; +use strict; +use warnings; + +use FindBin qw($RealBin); +use lib "$RealBin/../lib"; + +use Test::More tests => 77; +use QA::Util; + +use constant DESCRIPTION => 'Group created by Group.create'; + +sub post_success { + my $call = shift; + my $gid = $call->result->{id}; + ok($gid, "Got a non-zero group ID: $gid"); +} + +my ($config, $xmlrpc, $jsonrpc, $jsonrpc_get) = get_rpc_clients(); + +my @tests = ( + { args => { name => random_string(20), description => DESCRIPTION }, + error => 'You must log in', + test => 'Logged-out user cannot call Group.create', + }, + { user => 'unprivileged', + args => { name => random_string(20), description => DESCRIPTION }, + error => 'you are not authorized', + test => 'Unprivileged user cannot call Group.create', + }, + { user => 'admin', + args => { description => DESCRIPTION }, + error => 'You must enter a name', + test => 'Missing name to Group.create', + }, + { user => 'admin', + args => { name => random_string(20) }, + error => 'You must enter a description', + test => 'Missing description to Group.create', + }, + { user => 'admin', + args => { name => '', description => DESCRIPTION }, + error => 'You must enter a name', + test => 'Name to Group.create cannot be empty', + }, + { user => 'admin', + args => { name => random_string(20), description => '' }, + error => 'You must enter a description', + test => 'Description to Group.create cannot be empty', + }, + { user => 'admin', + args => { name => 'canconfirm', description => DESCRIPTION }, + error => 'already exists', + test => 'Name to Group.create already exists', + }, + { user => 'admin', + args => { name => 'caNConFIrm', description => DESCRIPTION }, + error => 'already exists', + test => 'Name to Group.create already exists but with a different case', + }, + { user => 'admin', + args => { name => random_string(20), description => DESCRIPTION, + user_regexp => '\\'}, + error => 'The regular expression you entered is invalid', + test => 'The regular expression passed to Group.create is invalid', + }, +); + +$jsonrpc_get->bz_call_fail('Group.create', + { name => random_string(20), description => 'Created with JSON-RPC via GET' }, + 'must use HTTP POST', 'Group.create fails over GET'); + +foreach my $rpc ($xmlrpc, $jsonrpc) { + # Tests which work must be called from here, + # to avoid creating twice the same group. + my @all_tests = (@tests, + { user => 'admin', + args => { name => random_string(20), description => DESCRIPTION }, + test => 'Passing the name and description only works', + }, + { user => 'admin', + args => { name => random_string(20), description => DESCRIPTION, + user_regexp => '\@foo.com$', is_active => 1, + icon_url => 'http://www.bugzilla.org/favicon.ico' }, + test => 'Passing all arguments works', + }, + ); + $rpc->bz_run_tests(tests => \@all_tests, method => 'Group.create', + post_success => \&post_success); +} diff --git a/xt/webservice/jsonp.t b/xt/webservice/jsonp.t new file mode 100644 index 000000000..75a0c0cfb --- /dev/null +++ b/xt/webservice/jsonp.t @@ -0,0 +1,34 @@ +# 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. + +use 5.10.1; +use strict; +use warnings; + +use FindBin qw($RealBin); +use lib "$RealBin/../lib"; + +use Test::More tests => 85; +use QA::Util; +my $jsonrpc_get = QA::Util::get_jsonrpc_client('GET'); + +my @chars = (0..9, 'A'..'Z', 'a'..'z', '_[].'); + +our @tests = ( + { args => { callback => join('', @chars) }, + test => 'callback accepts all legal characters.' }, +); +foreach my $char (qw(! ~ ` @ $ % ^ & * - + = { } ; : ' " < > / ? |), + '(', ')', '\\', '#', ',') +{ + push(@tests, + { args => { callback => "a$char" }, + error => "as your 'callback' parameter", + test => "$char is not valid in callback" }); +} + +$jsonrpc_get->bz_run_tests(method => 'Bugzilla.version', tests => \@tests); diff --git a/xt/webservice/product_create.t b/xt/webservice/product_create.t new file mode 100644 index 000000000..0ca117c31 --- /dev/null +++ b/xt/webservice/product_create.t @@ -0,0 +1,167 @@ +# 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 Product.create() # +############################################ + +use 5.10.1; +use strict; +use warnings; + +use FindBin qw($RealBin); +use lib "$RealBin/../lib"; + +use Test::More tests => 121; +use QA::Util; + +use constant DESCRIPTION => 'Product created by Product.create'; +use constant PROD_VERSION => 'unspecified'; + +sub post_success { + my ($call, $test, $self) = @_; + my $args = $test->{args}; + my $prod_id = $call->result->{id}; + ok($prod_id, "Got a non-zero product ID: $prod_id"); + + $call = $self->bz_call_success("Product.get", {ids => [$prod_id]}); + my $product = $call->result->{products}->[0]; + my $prod_name = $product->{name}; + my $is_active = defined $args->{is_open} ? $args->{is_open} : 1; + ok($product->{is_active} == $is_active, + "Product $prod_name has the correct value for is_active/is_open: $is_active"); + my $has_unco = defined $args->{has_unconfirmed} ? $args->{has_unconfirmed} : 1; + ok($product->{has_unconfirmed} == $has_unco, + "Product $prod_name has the correct value for has_unconfirmed: $has_unco"); +} + +my ($config, $xmlrpc, $jsonrpc, $jsonrpc_get) = get_rpc_clients(); + +my @tests = ( + { args => { name => random_string(20), version => PROD_VERSION, + description => DESCRIPTION }, + error => 'You must log in', + test => 'Logged-out user cannot call Product.create', + }, + { user => 'unprivileged', + args => { name => random_string(20), version => PROD_VERSION, + description => DESCRIPTION }, + error => 'you are not authorized', + test => 'Unprivileged user cannot call Product.create', + }, + { user => 'admin', + args => { version => PROD_VERSION, description => DESCRIPTION }, + error => 'You must enter a name', + test => 'Missing name to Product.create', + }, + { user => 'admin', + args => { name => random_string(20), version => PROD_VERSION }, + error => 'You must enter a description', + test => 'Missing description to Product.create', + }, + { user => 'admin', + args => { name => random_string(20), description => DESCRIPTION }, + error => 'You must enter a valid version', + test => 'Missing version to Product.create', + }, + { user => 'admin', + args => { name => '', version => PROD_VERSION, description => DESCRIPTION }, + error => 'You must enter a name', + test => 'Name to Product.create cannot be empty', + }, + { user => 'admin', + args => { name => random_string(20), version => PROD_VERSION, description => '' }, + error => 'You must enter a description', + test => 'Description to Product.create cannot be empty', + }, + { user => 'admin', + args => { name => random_string(20), version => '', description => DESCRIPTION }, + error => 'You must enter a valid version', + test => 'Version to Product.create cannot be empty', + }, + { user => 'admin', + args => { name => random_string(20000), version => PROD_VERSION, + description => DESCRIPTION }, + error => 'The name of a product is limited', + test => 'Name to Product.create too long', + }, + { user => 'admin', + args => { name => 'Another Product', version => PROD_VERSION, + description => DESCRIPTION }, + error => 'already exists', + test => 'Name to Product.create already exists', + }, + { user => 'admin', + args => { name => 'aNoThEr Product', version => PROD_VERSION, + description => DESCRIPTION }, + error => 'differs from existing product', + test => 'Name to Product.create already exists but with a different case', + }, +); + +# FIXME - Should be: if (classifications enabled). +# But there is currently now way to query the value of a parameter via WS. +if (0) { + push(@tests, + { user => 'admin', + args => { name => random_string(20), version => PROD_VERSION, + description => DESCRIPTION, has_unconfirmed => 1, + classification => '', default_milestone => '2.0', + is_open => 1, create_series => 1 }, + error => 'You must select/enter a classification', + test => 'Passing an empty classification to Product.create fails', + }, + { user => 'admin', + args => { name => random_string(20), version => PROD_VERSION, + description => DESCRIPTION, has_unconfirmed => 1, + classification => random_string(10), default_milestone => '2.0', + is_open => 1, create_series => 1 }, + error => 'You must select/enter a classification', + test => 'Passing an invalid classification to Product.create fails', + }, + ) +} + +$jsonrpc_get->bz_call_fail('Product.create', + { name => random_string(20), version => PROD_VERSION, + description => 'Created with JSON-RPC via GET' }, + 'must use HTTP POST', 'Product.create fails over GET'); + +foreach my $rpc ($xmlrpc, $jsonrpc) { + # Tests which work must be called from here, + # to avoid creating twice the same product. + my @all_tests = (@tests, + { user => 'admin', + args => { name => random_string(20), version => PROD_VERSION, + description => DESCRIPTION }, + test => 'Passing the name, description and version only works', + }, + { user => 'admin', + args => { name => random_string(20), version => PROD_VERSION, + description => DESCRIPTION, has_unconfirmed => 1, + classification => 'Class2_QA', default_milestone => '2.0', + is_open => 1, create_series => 1 }, + test => 'Passing all arguments works', + }, + { user => 'admin', + args => { name => random_string(20), version => PROD_VERSION, + description => DESCRIPTION, has_unconfirmed => 0, + classification => 'Class2_QA', default_milestone => '2.0', + is_open => 0, create_series => 0 }, + test => 'Passing null values works', + }, + { user => 'admin', + args => { name => random_string(20), version => PROD_VERSION, + description => DESCRIPTION, has_unconfirmed => 1, + classification => 'Class2_QA', default_milestone => '', + is_open => 1, create_series => 1 }, + test => 'Passing an empty default milestone works (falls back to "---")', + }, + ); + $rpc->bz_run_tests(tests => \@all_tests, method => 'Product.create', + post_success => \&post_success); +} diff --git a/xt/webservice/product_get.t b/xt/webservice/product_get.t new file mode 100644 index 000000000..5cc6022d5 --- /dev/null +++ b/xt/webservice/product_get.t @@ -0,0 +1,113 @@ +# 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 calls to: # +# Product.get_selectable_products() # +# Product.get_enterable_products() # +# Product.get_accessible_products() # +# Product.get() # +######################################## + +use 5.10.1; +use strict; +use warnings; + +use FindBin qw($RealBin); +use lib "$RealBin/../lib"; + +use Test::More tests => 134; +use QA::Util; +my ($config, @clients) = get_rpc_clients(); + +my $products = $clients[0]->bz_get_products(); +my $public = $products->{'Another Product'}; +my $private = $products->{'QA-Selenium-TEST'}; +my $no_entry = $products->{'QA Entry Only'}; +my $no_search = $products->{'QA Search Only'}; + +my %id_map = reverse %$products; + +my $tests = { + 'QA_Selenium_TEST' => { + selectable => [$public, $private, $no_entry, $no_search], + enterable => [$public, $private, $no_entry, $no_search], + accessible => [$public, $private, $no_entry, $no_search], + }, + 'unprivileged' => { + selectable => [$public, $no_entry], + not_selectable => $no_search, + enterable => [$public, $no_search], + not_enterable => $no_entry, + accessible => [$public, $no_entry, $no_search], + not_accessible => $private, + }, + '' => { + selectable => [$public, $no_entry], + not_selectable => $no_search, + enterable => [$public, $no_search], + not_enterable => $no_entry, + accessible => [$public, $no_entry, $no_search], + not_accessible => $private, + }, +}; + +foreach my $rpc (@clients) { + foreach my $user (keys %$tests) { + my @selectable = @{ $tests->{$user}->{selectable} }; + my @enterable = @{ $tests->{$user}->{enterable} }; + my @accessible = @{ $tests->{$user}->{accessible} }; + my $not_selectable = $tests->{$user}->{not_selectable}; + my $not_enterable = $tests->{$user}->{not_enterable}; + my $not_accessible = $tests->{$user}->{not_accessible}; + + $rpc->bz_log_in($user) if $user; + $user ||= "Logged-out user"; + + my $select_call = + $rpc->bz_call_success('Product.get_selectable_products'); + my $select_ids = $select_call->result->{ids}; + foreach my $id (@selectable) { + ok(grep($_ == $id, @$select_ids), + "$user can select " . $id_map{$id}); + } + if ($not_selectable) { + ok(!grep($_ == $not_selectable, @$select_ids), + "$user cannot select " . $id_map{$not_selectable}); + } + + my $enter_call = + $rpc->bz_call_success('Product.get_enterable_products'); + my $enter_ids = $enter_call->result->{ids}; + foreach my $id (@enterable) { + ok(grep($_ == $id, @$enter_ids), "$user can enter " . $id_map{$id}); + } + if ($not_enterable) { + ok(!grep($_ == $not_enterable, @$enter_ids), + "$user cannot enter " . $id_map{$not_enterable}); + } + + my $access_call = + $rpc->bz_call_success('Product.get_accessible_products'); + my $get_call = $rpc->bz_call_success('Product.get', + { ids => \@accessible }); + my $products = $get_call->result->{products}; + my $expected_count = scalar @accessible; + cmp_ok(scalar @$products, '==', $expected_count, + "Product.get gets all $expected_count accessible products" + . " for $user."); + if ($not_accessible) { + my $no_access_call = $rpc->bz_call_success( + 'Product.get', { ids => [$not_accessible] }); + ok(!scalar @{ $no_access_call->result->{products} }, + "$user gets 0 products when asking for " + . $id_map{$not_accessible}); + } + + $rpc->bz_call_success('User.logout') if $user ne "Logged-out user"; + } +} diff --git a/xt/webservice/user_create.t b/xt/webservice/user_create.t new file mode 100644 index 000000000..38b55e69a --- /dev/null +++ b/xt/webservice/user_create.t @@ -0,0 +1,118 @@ +# 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.Create() # +######################################### + +use 5.10.1; +use strict; +use warnings; + +use FindBin qw($RealBin); +use lib "$RealBin/../lib"; + +use QA::Util; +use Test::More tests => 75; +my ($config, $xmlrpc, $jsonrpc, $jsonrpc_get) = get_rpc_clients(); + +use constant NEW_PASSWORD => 'password'; +use constant NEW_FULLNAME => 'WebService Created User'; + +use constant PASSWORD_TOO_SHORT => 'a'; + +# These are the characters that are actually invalid per RFC. +use constant INVALID_EMAIL => '()[]\;:,<>@webservice.test'; + +sub new_login { + return 'created_' . random_string(@_) . '@webservice.test'; +} + +sub post_success { + my ($call) = @_; + ok($call->result->{id}, "Got a non-zero user id"); +} + +$jsonrpc_get->bz_call_fail('User.create', + { email => new_login(), full_name => NEW_FULLNAME, + password => '*' }, + 'must use HTTP POST', 'User.create fails over GET'); + +# We have to wrap @tests in the foreach, because we want a different +# login for each user, separately for each RPC client. (You can't create +# two users with the same username, and XML-RPC would otherwise try to +# create the same users that JSON-RPC created.) +foreach my $rpc ($jsonrpc, $xmlrpc) { + my @tests = ( + # Permissions checks + { args => { email => new_login(), full_name => NEW_FULLNAME, + password => NEW_PASSWORD }, + error => "you are not authorized", + test => 'Logged-out user cannot call User.create', + }, + { user => 'unprivileged', + args => { email => new_login(), full_name => NEW_FULLNAME, + password => NEW_PASSWORD }, + error => "you are not authorized", + test => 'Unprivileged user cannot call User.create', + }, + + # Login name checks. + { user => 'admin', + args => { full_name => NEW_FULLNAME, password => NEW_PASSWORD }, + error => "argument was not set", + test => 'Leaving out email argument fails', + }, + { user => 'admin', + args => { email => '', full_name => NEW_FULLNAME, + password => NEW_PASSWORD }, + error => "argument was not set", + test => "Passing an empty email argument fails", + }, + { user => 'admin', + args => { email => INVALID_EMAIL, full_name => NEW_FULLNAME, + password => NEW_PASSWORD }, + error => "didn't pass our syntax checking", + test => 'Invalid email address fails', + }, + { user => 'admin', + args => { email => new_login(128), full_name => NEW_FULLNAME, + password => NEW_PASSWORD }, + error => "didn't pass our syntax checking", + test => 'Too long (> 127 chars) email address fails', + }, + { user => 'admin', + args => { email => $config->{unprivileged_user_login}, + full_name => NEW_FULLNAME, password => NEW_PASSWORD }, + error => "There is already an account", + test => 'Trying to use an existing login name fails', + }, + + { user => 'admin', + args => { email => new_login(), full_name => NEW_FULLNAME, + password => PASSWORD_TOO_SHORT }, + error => 'password must be at least', + test => 'Password Too Short fails', + }, + { user => 'admin', + args => { email => new_login(), full_name => NEW_FULLNAME, + password => NEW_PASSWORD }, + test => 'Creating a user with all arguments and correct privileges', + }, + { user => 'admin', + args => { email => new_login(), password => NEW_PASSWORD }, + test => 'Leaving out fullname works', + }, + { user => 'admin', + args => { email => new_login(), full_name => NEW_FULLNAME }, + test => 'Leaving out password works', + }, + ); + + $rpc->bz_run_tests(tests => \@tests, method => 'User.create', + post_success => \&post_success); +} diff --git a/xt/webservice/user_get.t b/xt/webservice/user_get.t new file mode 100644 index 000000000..02cf00fe7 --- /dev/null +++ b/xt/webservice/user_get.t @@ -0,0 +1,222 @@ +# 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.10.1; +use strict; +use warnings; + +use FindBin qw($RealBin); +use lib "$RealBin/../lib"; + +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->{email} && exists $item->{can_login}, + 'Logged-in user correctly gets email and 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'); +} diff --git a/xt/webservice/user_login_logout.t b/xt/webservice/user_login_logout.t new file mode 100644 index 000000000..fd5f8ef6b --- /dev/null +++ b/xt/webservice/user_login_logout.t @@ -0,0 +1,128 @@ +# 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.login() and User.logout() # +########################################################## + +use 5.10.1; +use strict; +use warnings; + +use FindBin qw($RealBin); +use lib "$RealBin/../lib"; + +use Data::Dumper; +use QA::Util; +use Test::More tests => 119; +my ($config, @clients) = get_rpc_clients(); + +use constant INVALID_EMAIL => '@invalid_user@'; + +my $user = $config->{unprivileged_user_login}; +my $pass = $config->{unprivileged_user_passwd}; +my $error = "The login or password you entered is not valid"; + +my @tests = ( + { user => 'unprivileged', + test => "Unprivileged user can log in successfully", + }, + + { args => { login => $user, password => '' }, + error => $error, + test => "Empty password can't log in", + }, + { args => { login => '', password => $pass }, + error => $error, + test => "Empty login can't log in", + }, + { args => { login => $user }, + error => "requires a password argument", + test => "Undef password can't log in", + }, + { args => { password => $pass }, + error => "requires a login argument", + test => "Undef login can't log in", + }, + + { args => { login => INVALID_EMAIL, password => $pass }, + error => $error, + test => "Invalid email can't log in", + }, + { args => { login => $user, password => '*' }, + error => $error, + test => "Invalid password can't log in", + }, + + { args => { login => $config->{disabled_user_login}, + password => $config->{disabled_user_passwd} }, + error => "!!This is the text!!", + test => "Can't log in with a disabled account", + }, + { args => { login => $config->{disabled_user_login}, password => '*' }, + error => $error, + test => "Logging in with invalid password doesn't show disabledtext", + }, +); + +sub _login_args { + my $args = shift; + my %fixed_args = %$args; + $fixed_args{Bugzilla_login} = delete $fixed_args{login}; + $fixed_args{Bugzilla_password} = delete $fixed_args{password}; + return \%fixed_args; +} + +foreach my $rpc (@clients) { + if ($rpc->bz_get_mode) { + $rpc->bz_call_fail('User.logout', undef, 'must use HTTP POST', + 'User.logout fails when called via GET'); + } + + foreach my $t (@tests) { + if ($t->{user}) { + my $username = $config->{$t->{user} . '_user_login'}; + my $password = $config->{$t->{user} . '_user_passwd'}; + + if ($rpc->bz_get_mode) { + $rpc->bz_call_fail('User.login', + { login => $username, password => $password }, + 'must use HTTP POST', $t->{test} . ' (fails on GET)'); + } + else { + $rpc->bz_log_in($t->{user}); + ok($rpc->{_bz_credentials}->{token}, 'Login token returned'); + $rpc->bz_call_success('User.logout'); + } + + if ($t->{error}) { + $rpc->bz_call_fail('Bugzilla.version', + { Bugzilla_login => $username, + Bugzilla_password => $password }); + } + else { + $rpc->bz_call_success('Bugzilla.version', + { Bugzilla_login => $username, + Bugzilla_password => $password }); + } + } + else { + # Under GET, there's no reason to have extra failing tests. + if (!$rpc->bz_get_mode) { + $rpc->bz_call_fail('User.login', $t->{args}, $t->{error}, + $t->{test}); + } + if (defined $t->{args}->{login} + and defined $t->{args}->{password}) + { + my $fixed_args = _login_args($t->{args}); + $rpc->bz_call_fail('Bugzilla.version', $fixed_args, + $t->{error}, "Bugzilla_login: " . $t->{test}); + } + } + } +} diff --git a/xt/webservice/user_offer_account_by_email.t b/xt/webservice/user_offer_account_by_email.t new file mode 100644 index 000000000..785932167 --- /dev/null +++ b/xt/webservice/user_offer_account_by_email.t @@ -0,0 +1,63 @@ +# 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.offer_account_by_email() # +######################################################### + +use 5.10.1; +use strict; +use warnings; + +use FindBin qw($RealBin); +use lib "$RealBin/../lib"; + +use QA::Util; +use Test::More tests => 29; +my ($config, $xmlrpc, $jsonrpc, $jsonrpc_get) = get_rpc_clients(); + +# These are the characters that are actually invalid per RFC. +use constant INVALID_EMAIL => '()[]\;:,<>@webservice.test'; + +sub new_login { + return 'requested_' . random_string() . '@webservice.test'; +} + +$jsonrpc_get->bz_call_fail('User.offer_account_by_email', + { email => new_login() }, + 'must use HTTP POST', 'offer_account_by_email fails over GET'); + +# Have to wrap @tests in the foreach so that new_login returns something +# different each time. +foreach my $rpc ($jsonrpc, $xmlrpc) { + my @tests = ( + # Login name checks. + { args => { }, + error => "argument was not set", + test => 'Leaving out email argument fails', + }, + { args => { email => '' }, + error => "argument was not set", + test => "Passing an empty email argument fails", + }, + { args => { email => INVALID_EMAIL }, + error => "didn't pass our syntax checking", + test => 'Invalid email address fails', + }, + { args => { email => $config->{unprivileged_user_login} }, + error => "There is already an account", + test => 'Trying to use an existing login name fails', + }, + + { args => { email => new_login() }, + test => 'Valid, non-existing email passes.', + }, + ); + + $rpc->bz_run_tests(tests => \@tests, + method => 'User.offer_account_by_email'); +} -- cgit v1.2.3-24-g4f1b