# 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.14.0;
use strict;
use warnings;

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

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");
    }
}
