summaryrefslogtreecommitdiffstats
path: root/xt/webservice/bug_update.t
diff options
context:
space:
mode:
Diffstat (limited to 'xt/webservice/bug_update.t')
-rw-r--r--xt/webservice/bug_update.t705
1 files changed, 705 insertions, 0 deletions
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);
+}