Kaynağa Gözat

Fix #65: Simplify binaries help by using perldoc, subroutine out user interrogation

Also add bin/testrail-bulk-mark-results and TestRail::API::bulkAddResults
George S. Baugh 10 yıl önce
ebeveyn
işleme
426189d210

+ 5 - 0
Changes

@@ -1,5 +1,10 @@
 Revision history for Perl module TestRail::API
 
+0.029 2015-07-11 TEODESIAN
+    - Add bulkAddResults function to TestRail::API
+    - Add new script testrail-bulk-mark-results
+    - Re-factor part of the scripts into TestRail::Utils::interrogateUser
+
 0.028 2015-06-16 TEODESIAN
     - Hotfix: forgot to include a module in the prove plugin.  How did this pass compile.t? A mystery.
     - Fix an issue where testrail-report incorrectly identified (or failed to identify) the file tested.

+ 185 - 0
bin/testrail-bulk-mark-results

@@ -0,0 +1,185 @@
+#!/usr/bin/env perl
+# ABSTRACT: Bulk mark entire runs/plans (or groups of tests therein) as the provided status.
+# PODNAME: testrail-bulk-mark-results
+
+=head1 USAGE
+
+  testrail-bulk-mark-results [OPTIONS] status [reason]
+
+=head1 DESCRIPTION
+
+Sometimes it is useful to mark entire runs of tests when, for example, a prerequisite test in a sequence invalidates all further tests.
+For example, if a binary produced for test fails to run at all, more detailed testing will be impossible;
+it would save time to just mark everything as blocked.
+
+=head2 PARAMETERS:
+
+=head3 MANDATORY PARAMETERS
+
+    -j --project [project]: desired project name.
+    -r --run [run]: desired run name.
+
+=head3 SEMI-OPTIONAL PARAMETERS
+
+    -p --plan [plan]: desired plan name.  Required if the run passed is a child of a plan.
+    -e --encoding: Character encoding of arguments.  Defaults to UTF-8.
+                   See L<Encode::Supported> for supported encodings.
+
+=head3 OPTIONAL PARAMETERS
+
+    -c --config [config]: configuration name to filter plans in run.  Can be passed multiple times.
+    -a --assignedto [user]: only mark tests assigned to user. Can be passed multiple times.
+
+=head3 CONFIG OPTIONS
+
+    In your \$HOME, (or the current directory, if your system has no
+    concept of a home directory) put a file called .testrailrc with
+    key=value syntax separated by newlines.
+    Valid Keys are: apiurl,user,password
+
+=head3 CONFIG OVERRIDES
+
+    These override the config, if present.
+    If neither are used, you will be prompted.
+
+  --apiurl   [url] : full URL to get to TestRail index document
+  --password [key] : Your TestRail Password, or a valid API key (TestRail 4.2 and above).
+  --user    [name] : Your TestRail User Name.
+
+=head2 TESTING OPTIONS:
+
+    --mock: don't do any real HTTP requests.
+    --help: show this output
+
+=cut
+
+use strict;
+use warnings;
+use utf8;
+
+use TestRail::API;
+use TestRail::Utils;
+
+use Getopt::Long;
+Getopt::Long::Configure('pass_through');
+
+use File::HomeDir qw{my_home};
+use File::Find;
+use Cwd qw{abs_path};
+use File::Basename qw{basename};
+
+use Pod::Perldoc 3.10;
+
+sub help {
+    @ARGV = ($0);
+    Pod::Perldoc->run();
+    exit 0;
+}
+
+my %opts;
+
+GetOptions(
+    'apiurl=s'        => \$opts{'apiurl'},
+    'password=s'      => \$opts{'pass'},
+    'user=s'          => \$opts{'user'},
+    'j|project=s'     => \$opts{'project'},
+    'p|plan=s'        => \$opts{'plan'},
+    'r|run=s'         => \$opts{'run'},
+    'c|config=s@'     => \$opts{'configs'},
+    'a|assignedto=s@' => \$opts{'users'},
+    'mock'            => \$opts{'mock'},
+    'e|encoding=s'    => \$opts{'encoding'},
+    'h|help'          => \$opts{'help'}
+);
+
+if ($opts{help}) { help(); }
+
+my $status = $ARGV[0];
+my $reason = $ARGV[1];
+
+die("No status to set provided.") unless $status;
+#Parse config file if we are missing api url/key or user
+my $homedir = my_home() || '.';
+if (-e $homedir . '/.testrailrc' && (!$opts{apiurl} || !$opts{pass} || !$opts{user}) ) {
+    ($opts{apiurl},$opts{pass},$opts{user}) = TestRail::Utils::parseConfig($homedir,1);
+}
+
+TestRail::Utils::interrogateUser(\%opts,qw{apiurl user pass project run});
+
+if ($opts{mock}) {
+    use Test::LWP::UserAgent::TestRailMock;
+    $opts{browser} = $Test::LWP::UserAgent::TestRailMock::mockObject;
+    $opts{debug} = 1;
+}
+
+my $tr = TestRail::API->new($opts{apiurl},$opts{user},$opts{pass},$opts{'encoding'},$opts{'debug'});
+$tr->{'browser'} = $opts{'browser'} if $opts{'browser'};
+$tr->{'debug'} = 0;
+
+my $project = $tr->getProjectByName($opts{'project'});
+if (!$project) {
+    print "No such project '$opts{project}'.\n";
+    exit 6;
+}
+
+my ($run,$plan);
+
+if ($opts{'plan'}) {
+    $plan = $tr->getPlanByName($project->{'id'},$opts{'plan'});
+    if (!$plan) {
+        print "No such plan '$opts{plan}'!\n";
+        exit 1;
+    }
+    $run = $tr->getChildRunByName($plan,$opts{'run'}, $opts{'configs'});
+} else {
+    $run = $tr->getRunByName($project->{'id'},$opts{'run'});
+}
+
+if (!$run) {
+    print "No such run '$opts{run}' matching the provided configs (if any).\n";
+    exit 2;
+}
+
+my $user_ids;
+#Process assignedto ids
+if ($opts{'users'}) {
+    eval { @$user_ids = $tr->userNamesToIds(@{$opts{'users'}}); };
+    if ($@) {
+        print "$@\n";
+        exit 5;
+    }
+}
+
+my $cases = $tr->getTests($run->{'id'},undef,$user_ids);
+
+if (!$cases) {
+    print "No cases in TestRail to mark!\n";
+    exit 3;
+}
+
+my ($status_id) = $tr->statusNamesToIds($status);
+
+@$cases = map {
+    {
+        'test_id' => $_->{'id'},
+        'status_id' => $status_id,
+        'comment'   => $reason,
+        'version'   => $opts{'version'}
+    }    
+} @$cases;
+
+my $results = $tr->bulkAddResults($run->{'id'},$cases);
+
+print "Successfully set the status of ".scalar(@$results)." cases to $status.\n";
+
+exit 0;
+
+__END__
+
+L<TestRail::API>
+
+L<File::HomeDir> for the finding of .testrailrc
+
+=head1 SPECIAL THANKS
+
+Thanks to cPanel Inc, for graciously funding the creation of this distribution.

+ 50 - 201
bin/testrail-report

@@ -118,161 +118,43 @@ use File::HomeDir qw{my_home};
 
 print "testrail-report\n----------------------\n";
 
-sub help {
-    print "testrail-report - report raw TAP results to a TestRail install
-
-USAGE:
-  testrail-report [OPTIONS] tapfile
-  prove -v sometest.t > results.tap && testrail-report [OPTIONS] \\
-  results.tap
-
-  prove -v sometest.t | testrail-report [OPTIONS]
-
-  prove -PTestRail='http://some.testlink.install/','someUser',\\
-  'somePassword' sometest.t
-
-PARAMETERS:
-  [MANDATORY PARAMETERS]
-  --project [someproject] : associate results (if any) with the
-                            provided project name.
-
-  --run [somerun] : associates results (if any) with the provided run
-                    name.
-
-  IF none of these options are provided, you will be asked to type
-  these in as needed, supposing you are not redirecting input
-  (such as piping into this command).
-
-  [SEMI-OPTIONAL PARAMETERS]
-
-    --plan [someplan] : look for the provided run name within
-      the provided plan.
-
-    --configs [someconfigs] : filter run by the provided configuration.
-      This option can be passed multiple times for detailed filtering.
-
-    -e --encoding: Character encoding of arguments.  Defaults to UTF-8.
-                   See L<Encode::Supported> for supported encodings.
-
-  Test plans can have runs with the same name, but different
-  configurations, which is understandably confusing.  You can do the
-  same outside of plans, and without configurations; but doing so is
-  ill-advised, and the only option from there is to use IDs.  So, try
-  not to do that if you want to use this tool, and want sanity in your
-  Test Management System.
-
-  The way around this is to specify what plan and configuration
-  you want to set results for.  This should provide sufficient
-  uniqueness to get to any run using words.
-
-  [OPTIONAL PARAMETERS]
-
-    --spawn [testsuite_id] : Attempt to create a run based on the
-      provided testsuite ID.  Uses the name provided with --run.
-
-      If plans/configurations are supplied, it will attempt to create
-      it as a child of the provided plan, and with the supplied
-      configurations.
-
-      If the specified run already exists, the
-      program will simply use the existing run,
-      and disregard the supplied testsuite_id.
-
-      If the specified plan does not exist, it will be created
-      as well.
-
-    --section [section_name] : When spawning, restrict the cases used
-      in the provided testsuite ID to these sections.
-
-      Option may be passed multiple times to specify multiple sections.
-
-
-  [CONFIG OVERRIDES]
-  In your \$HOME, (or the current directory, if your system has no
-  concept of a home directory) put a file called .testrailrc with
-  key=value syntax separated by newlines.
-  Valid Keys are: apiurl,user,password
-
-  [CONFIG OPTIONS] - These override the config, if present.
-                     If neither are used, you will be prompted.
+use Pod::Perldoc 3.10;
 
-  --apiurl   [url] : full URL to get to TestRail index document
-  --password [key] : Your TestRail Password, or a valid API key (TestRail 4.2 and above).
-  --user    [name] : Your TestRail User Name.
-
-  [BEHAVIOR]
-  --case-ok      : Whether to consider each OK to correspond to
-                   a test in TestRail
-
-  --step-results [name] : 'System Name' of a 'step_results' type field
-                    to set for your tests.
-
-  These options are mutually exclusive.  If neither is set, the
-  overall result of the test will be used as the pass/fail for the test.
-
-  [RESULT OPTIONS]
-
-    --version : String describing the version of the system under test.
-    --autoclose : If there are no more tests in 'untested' or 'retest'
-                  status for the specified run/plan, close it.
-
-PROVE PLUGIN:
-
-  passing -PTestRail=apiurl,user,pass,project,run to prove will
-  automatically upload your test results while the test is running if
-  real-time results are desired.
-
-  See App::Prove::Plugin::TestRail for more information.
-
-REQUIREMENTS:
-  Your TestRail install must have 3 custom statuses with the internal
-  names 'skip', 'todo_pass', and 'todo_fail', to represent those
-  states which TAP can have.
-
-  Also, be sure your tests don't output non-TAP (unknown) lines
-  ending in dots (.)
-  This will cause the preceding characters to be interpreted as
-  a test name, which may lead to unexpected results.
-
-
-TESTING OPTIONS:
-
-    --mock don't do any real HTTP requests.
-
-";
+sub help {
+    @ARGV = ($0);
+    Pod::Perldoc->run();
     exit 0;
 }
 
 #Main loop------------
-
-my ($help,$apiurl,$user,$password,$project,$run,$case_per_ok,$step_results,$mock,$configs,$plan,$version,$spawn,$sections,$autoclose,$encoding);
+my %opts;
 
 #parse switches
 GetOptions(
-    'run=s'          => \$run,
-    'apiurl=s'       => \$apiurl,
-    'password=s'     => \$password,
-    'user=s'         => \$user,
-    'project=s'      => \$project,
-    'case-ok'        => \$case_per_ok,
-    'step-results=s' => \$step_results,
-    'mock'           => \$mock,
-    'config=s@'      => \$configs,
-    'plan=s'         => \$plan,
-    'version=s'      => \$version,
-    'spawn=i'        => \$spawn,
-    'section=s@'     => \$sections,
-    'autoclose'      => \$autoclose,
-    'e|encoding=s'   => \$encoding,
-    'help'           => \$help
+    'run=s'          => \$opts{run},
+    'apiurl=s'       => \$opts{apiurl},
+    'password=s'     => \$opts{password},
+    'user=s'         => \$opts{user},
+    'project=s'      => \$opts{project},
+    'case-ok'        => \$opts{case_per_ok},
+    'step-results=s' => \$opts{step_results},
+    'mock'           => \$opts{mock},
+    'config=s@'      => \$opts{configs},
+    'plan=s'         => \$opts{plan},
+    'version=s'      => \$opts{version},
+    'spawn=i'        => \$opts{spawn},
+    'section=s@'     => \$opts{sections},
+    'autoclose'      => \$opts{autoclose},
+    'e|encoding=s'   => \$opts{encoding},
+    'help'           => \$opts{help}
 );
 
-if ($help) { help(); }
+if ($opts{help}) { help(); }
 
 #Parse config file if we are missing api url/key or user
 my $homedir = my_home() || '.';
-if (-e $homedir . '/.testrailrc' && (!$apiurl || !$password || !$user) ) {
-    ($apiurl,$password,$user) = TestRail::Utils::parseConfig($homedir,1);
+if (-e $homedir . '/.testrailrc' && (!$opts{apiurl} || !$opts{password} || !$opts{user}) ) {
+    ($opts{apiurl},$opts{password},$opts{user}) = TestRail::Utils::parseConfig($homedir,1);
 }
 
 #If argument is passed use it instead of stdin
@@ -299,7 +181,7 @@ if ($file) {
         print "ERROR: no file passed, and no data piped in! See --help for usage.\n";
         exit(1);
     }
-    if ( !$run || !$apiurl || !$password || !$user || !$project ) {
+    if ( !$opts{run} || !$opts{apiurl} || !$opts{password} || !$opts{user} || !$opts{project} ) {
         print "ERROR: Interactive mode not allowed when piping input.  See --help for options.\n";
         exit(1);
     }
@@ -315,71 +197,38 @@ if ($file) {
     push(@files,$fcontents) if $fcontents;
 }
 
-#Interrogate user if they didn't provide info
-if (!$apiurl) {
-    print "Type the API endpoint url for your testLink install below:\n";
-    $apiurl = TestRail::Utils::userInput();
-}
-
-if (!$user) {
-    print "Type your testLink user name below:\n";
-    $user = TestRail::Utils::userInput();
-}
-
-if (!$password) {
-    print "Type the password for your testLink user below:\n";
-    $password = TestRail::Utils::userInput();
-}
-
-if (!$apiurl || !$password || !$user) {
-    print "ERROR: api url, username and password cannot be blank.\n";
-    exit 1;
-}
-
-#Interrogate user if they didn't provide info
-if (!$project) {
-    print "Type the name of the project you are testing under:\n";
-    $project = TestRail::Utils::userInput();
-}
-
-# Interrogate user if options were not passed
-if (!$run) {
-    print "Type the name of the existing run you would like to run against:\n";
-    $run = TestRail::Utils::userInput();
-}
+TestRail::Utils::interrogateUser(\%opts,qw{apiurl user password project run});
 
-my $debug = 0;
-my $browser;
-if ($mock) {
+$opts{debug} = 0;
+if ($opts{mock}) {
     use Test::LWP::UserAgent::TestRailMock;
-    $browser = $Test::LWP::UserAgent::TestRailMock::mockObject;
-    $debug = 1;
+    $opts{browser} = $Test::LWP::UserAgent::TestRailMock::mockObject;
+    $opts{debug} = 1;
 }
 
-my $result_options = undef;
-$result_options = {'version' => $version} if $version;
+$opts{result_options} = {'version' => $opts{version}} if $opts{version};
 
 my $tap;
 foreach my $phil (@files) {
     $tap = Test::Rail::Parser->new({
-        'tap'          => $phil,
-        'apiurl'       => $apiurl,
-        'user'         => $user,
-        'pass'         => $password,
-        'run'          => $run,
-        'project'      => $project,
-        'case_per_ok'  => $case_per_ok,
-        'step_results' => $step_results,
-        'debug'        => $debug,
-        'browser'      => $browser,
-        'plan'         => $plan,
-        'configs'      => $configs,
-        'result_options' => $result_options,
-        'spawn'        => $spawn,
-        'sections'     => $sections,
-        'autoclose'    => $autoclose,
-        'encoding'     => $encoding,
-        'merge'        => 1
+        'tap'            => $phil,
+        'apiurl'         => $opts{apiurl},
+        'user'           => $opts{user},
+        'pass'           => $opts{password},
+        'run'            => $opts{run},
+        'project'        => $opts{project},
+        'case_per_ok'    => $opts{case_per_ok},
+        'step_results'   => $opts{step_results},
+        'debug'          => $opts{debug},
+        'browser'        => $opts{browser},
+        'plan'           => $opts{plan},
+        'configs'        => $opts{configs},
+        'result_options' => $opts{result_options},
+        'spawn'          => $opts{spawn},
+        'sections'       => $opts{sections},
+        'autoclose'      => $opts{autoclose},
+        'encoding'       => $opts{encoding},
+        'merge'          => 1
     });
     $tap->run();
 
@@ -404,4 +253,4 @@ L<File::HomeDir> for the finding of .testrailrc
 
 =head1 SPECIAL THANKS
 
-Thanks to cPanel Inc, for graciously funding the creation of this module.
+Thanks to cPanel Inc, for graciously funding the creation of this distribution.

+ 6 - 65
bin/testrail-runs

@@ -60,45 +60,11 @@ use File::Find;
 use Cwd qw{abs_path};
 use File::Basename qw{basename};
 
-sub help {
-    print("
-testrail-tests - list tests in a run matching the provided filters.
-
-USAGE:
-
-  testrail-tests [OPTIONS] | xargs prove -PTestrail=...
-
-PARAMETERS:
-  [MANDATORY PARAMETERS]
-    -j --project [project]: desired project name.
-
-  [OPTIONAL PARAMETERS]
-
-    -c --config [config]: configuration name to filter runs.  Can be passed multiple times.
-    -s --status [status]: only list runs with one or more tests having [status] in testrail.  Can be passed multiple times.
-    -e --encoding: Character encoding of arguments.  Defaults to UTF-8.
-                   See L<Encode::Supported> for supported encodings.
-
-  [CONFIG OPTIONS]
-    In your \$HOME, (or the current directory, if your system has no
-    concept of a home directory) put a file called .testrailrc with
-    key=value syntax separated by newlines.
-    Valid Keys are: apiurl,user,password
-
-  [CONFIG OVERRIDES]
-    These override the config, if present.
-    If neither are used, you will be prompted.
-
-  --apiurl   [url] : full URL to get to TestRail index document
-  --password [key] : Your TestRail Password or API key (TestRail 4.4 and above).
-  --user    [name] : Your TestRail User Name.
-
-TESTING OPTIONS:
+use Pod::Perldoc 3.10;
 
-    --mock: don't do any real HTTP requests.
-    --help: show this output
-
-");
+sub help {
+    @ARGV = ($0);
+    Pod::Perldoc->run();
     exit 0;
 }
 
@@ -124,32 +90,7 @@ if (-e $homedir . '/.testrailrc' && (!$opts{apiurl} || !$opts{pass} || !$opts{us
     ($opts{apiurl},$opts{pass},$opts{user}) = TestRail::Utils::parseConfig($homedir,1);
 }
 
-#Interrogate user if they didn't provide info
-if (!$opts{apiurl}) {
-    print "Type the API endpoint url for your testLink install below:\n";
-    $opts{apiurl} = TestRail::Utils::userInput();
-}
-
-if (!$opts{user}) {
-    print "Type your testLink user name below:\n";
-    $opts{user} = TestRail::Utils::userInput();
-}
-
-if (!$opts{pass}) {
-    print "Type the password for your testLink user below:\n";
-    $opts{pass} = TestRail::Utils::userInput();
-}
-
-if (!$opts{apiurl} || !$opts{pass} || !$opts{user}) {
-    print "ERROR: api url, username and password cannot be blank.\n";
-    exit 1;
-}
-
-#Interrogate user if they didn't provide info
-if (!$opts{project}) {
-    print "Type the name of the project you are testing under:\n";
-    $opts{project} = TestRail::Utils::userInput();
-}
+TestRail::Utils::interrogateUser(\%opts,qw{apiurl user pass project});
 
 if ($opts{mock}) {
     use Test::LWP::UserAgent::TestRailMock;
@@ -231,4 +172,4 @@ L<File::HomeDir> for the finding of .testrailrc
 
 =head1 SPECIAL THANKS
 
-Thanks to cPanel Inc, for graciously funding the creation of this module.
+Thanks to cPanel Inc, for graciously funding the creation of this distribution.

+ 6 - 83
bin/testrail-tests

@@ -68,57 +68,11 @@ use File::Find;
 use Cwd qw{abs_path};
 use File::Basename qw{basename};
 
-sub help {
-    print("
-testrail-tests - list tests in a run matching the provided filters.
-
-USAGE:
-
-  testrail-tests [OPTIONS] | xargs prove -PTestrail=...
-
-PARAMETERS:
-  [MANDATORY PARAMETERS]
-    -j --project [project]: desired project name.
-    -r --run [run]: desired run name.
-
-  [SEMI-OPTIONAL PARAMETERS]
-
-    -p --plan [plan]: desired plan name.  Required if the run passed is a child of a plan.
-    -m --match [dir]: attempt to find filenames matching the test names in the provided dir.
-    --no-match [dir]: attempt to find filenames that do not match the test names in the provided dir.
-
-    The match and no-match options are mutually exclusive, and will terminate execution if both are passed.
-
-    -n --no-recurse: if match passed, do not recurse subdirectories.
-
-    -e --encoding: Character encoding of arguments.  Defaults to UTF-8.
-                   See L<Encode::Supported> for supported encodings.
-
-  [OPTIONAL PARAMETERS]
-
-    -c --config [config]: configuration name to filter plans in run.  Can be passed multiple times.
-    -s --status [status]: only list tests marked as [status] in testrail.  Can be passed multiple times.
-    -a --assignedto [user]: only list tests assigned to user. Can be passed multiple times.
-
-  [CONFIG OPTIONS]
-    In your \$HOME, (or the current directory, if your system has no
-    concept of a home directory) put a file called .testrailrc with
-    key=value syntax separated by newlines.
-    Valid Keys are: apiurl,user,password
-
-  [CONFIG OVERRIDES]
-    These override the config, if present.
-    If neither are used, you will be prompted.
+use Pod::Perldoc 3.10;
 
-  --apiurl   [url] : full URL to get to TestRail index document
-  --password [key] : Your TestRail Password or a valid API key (TestRail 4.2 and above).
-  --user    [name] : Your TestRail User Name.
-
-TESTING OPTIONS:
-
-    --mock: don't do any real HTTP requests.
-    --help: show this output
-");
+sub help {
+    @ARGV = ($0);
+    Pod::Perldoc->run();
     exit 0;
 }
 
@@ -155,38 +109,7 @@ if (-e $homedir . '/.testrailrc' && (!$opts{apiurl} || !$opts{pass} || !$opts{us
     ($opts{apiurl},$opts{pass},$opts{user}) = TestRail::Utils::parseConfig($homedir,1);
 }
 
-#Interrogate user if they didn't provide info
-if (!$opts{apiurl}) {
-    print "Type the API endpoint url for your testLink install below:\n";
-    $opts{apiurl} = TestRail::Utils::userInput();
-}
-
-if (!$opts{user}) {
-    print "Type your testLink user name below:\n";
-    $opts{user} = TestRail::Utils::userInput();
-}
-
-if (!$opts{pass}) {
-    print "Type the password for your testLink user below:\n";
-    $opts{pass} = TestRail::Utils::userInput();
-}
-
-if (!$opts{apiurl} || !$opts{pass} || !$opts{user}) {
-    print "ERROR: api url, username and password cannot be blank.\n";
-    exit 1;
-}
-
-#Interrogate user if they didn't provide info
-if (!$opts{project}) {
-    print "Type the name of the project you are testing under:\n";
-    $opts{project} = TestRail::Utils::userInput();
-}
-
-# Interrogate user if options were not passed
-if (!$opts{run}) {
-    print "Type the name of the existing run you would like to run against:\n";
-    $opts{run} = TestRail::Utils::userInput();
-}
+TestRail::Utils::interrogateUser(\%opts,qw{apiurl user pass project run});
 
 if ($opts{mock}) {
     use Test::LWP::UserAgent::TestRailMock;
@@ -277,4 +200,4 @@ L<File::HomeDir> for the finding of .testrailrc
 
 =head1 SPECIAL THANKS
 
-Thanks to cPanel Inc, for graciously funding the creation of this module.
+Thanks to cPanel Inc, for graciously funding the creation of this distribution.

+ 27 - 0
lib/Test/LWP/UserAgent/TestRailMock.pm

@@ -2251,4 +2251,31 @@ $VAR5 = '{"id":1066,"name":"BogoPlan","description":"zippy","milestone_id":null,
 $mockObject->map_response(qr/\Q$VAR1\E/,HTTP::Response->new($VAR2, $VAR3, $VAR4, $VAR5));
 }
 
+{
+
+$VAR1 = 'index.php?/api/v2/add_results/22';
+$VAR2 = '200';
+$VAR3 = 'OK';
+$VAR4 = bless( {
+                 'server' => 'Apache/2.4.7 (Ubuntu)',
+                 'x-powered-by' => 'PHP/5.5.9-1ubuntu4.5',
+                 'content-type' => 'application/json; charset=utf-8',
+                 'client-response-num' => 1,
+                 'client-date' => 'Sat, 11 Jul 2015 20:09:43 GMT',
+                 'date' => 'Sat, 11 Jul 2015 20:09:42 GMT',
+                 '::std_case' => {
+                                   'x-powered-by' => 'X-Powered-By',
+                                   'client-response-num' => 'Client-Response-Num',
+                                   'client-date' => 'Client-Date',
+                                   'client-peer' => 'Client-Peer'
+                                 },
+                 'client-peer' => '192.168.122.217:80',
+                 'content-length' => '179',
+                 'connection' => 'close'
+               }, 'HTTP::Headers' );
+$VAR5 = '[{"id":515,"test_id":286,"status_id":1,"created_by":1,"created_on":1436645382,"assignedto_id":null,"comment":"REAPER FORCES INBOUND","version":null,"elapsed":null,"defects":null}]';
+
+$mockObject->map_response(qr/\Q$VAR1\E/,HTTP::Response->new($VAR2, $VAR3, $VAR4, $VAR5));
+}
+
 1;

+ 30 - 4
lib/TestRail/API.pm

@@ -169,6 +169,7 @@ sub _doRequest {
 
     my $response = $self->{'browser'}->request($req);
 
+    #Uncomment to generate mocks
     #use Data::Dumper;
     #print Dumper($path,'200','OK',$response->headers,$response->content);
 
@@ -1856,15 +1857,15 @@ sub getMilestoneByID {
 
 =head2 B<getTests (run_id,status_ids,assignedto_ids)>
 
-Get tests for some run.  Optionally filter by provided status_ids.
+Get tests for some run.  Optionally filter by provided status_ids and assigned_to ids.
 
 =over 4
 
 =item INTEGER C<RUN ID> - ID of parent run.
 
-=item ARRAYREF C<STATUS IDS> (optional) - IDs of relevant test statuses to filter by.  get with getPossibleTestStatuses.
+=item ARRAYREF C<STATUS IDS> (optional) - IDs of relevant test statuses to filter by.  Get with getPossibleTestStatuses.
 
-=item ARRAYREF C<ASSIGNEDTO IDS> (optional) - IDs of users assigned to test to filter by.  get with getUsers.
+=item ARRAYREF C<ASSIGNEDTO IDS> (optional) - IDs of users assigned to test to filter by.  Get with getUsers.
 
 =back
 
@@ -1875,11 +1876,12 @@ Returns ARRAYREF of test definition HASHREFs.
 =cut
 
 sub getTests {
-    my ($self,$run_id,$status_ids,$assignedto_ids) = @_;
+    my ($self,$run_id,$status_ids,$assignedto_ids,$section_ids) = @_;
     confess("Object methods must be called by an instance") unless ref($self);
     confess("Run ID must be integer") unless $self->_checkInteger($run_id);
     confess("Status IDs must be ARRAYREF") unless !defined($status_ids) || ( reftype($status_ids) || 'undef' ) eq 'ARRAY';
     confess("Assigned to IDs must be ARRAYREF") unless !defined($assignedto_ids) || ( reftype($assignedto_ids) || 'undef' ) eq 'ARRAY';
+
     my $query_string = '';
     $query_string = '&status_id='.join(',',@$status_ids) if defined($status_ids) && scalar(@$status_ids);
     my $results = $self->_doRequest("index.php?/api/v2/get_tests/$run_id$query_string");
@@ -2110,6 +2112,30 @@ sub createTestResults {
     return $self->_doRequest("index.php?/api/v2/add_result/$test_id",'POST',$stuff);
 }
 
+=head2 bulkAddResults(run_id,results)
+
+Add multiple results to a run, where each result is a HASHREF with keys as outlined in the get_results API call documentation.
+
+=over 4
+
+=item INTEGER C<RUN_ID> - ID of desired run to add results to
+
+=item ARRAYREF C<RESULTS> - Array of result objects to upload.
+
+=back
+
+Returns ARRAYREF of result definition HASHREFs.
+
+=cut
+
+sub bulkAddResults {
+    my ($self, $run_id, $results) = @_;
+    confess("Object methods must be called by an instance") unless ref($self);
+    confess("Run ID must be integer") unless $self->_checkInteger($run_id);
+    confess("results must be arrayref") unless ( reftype($results) || 'undef' ) eq 'ARRAY';
+    return $self->_doRequest("index.php?/api/v2/add_results/$run_id", 'POST', { 'results' => $results });
+}
+
 =head2 B<getTestResults(test_id,limit,offset)>
 
 Get the recorded results for desired test, limiting output to 'limit' entries.

+ 20 - 0
lib/TestRail/Utils.pm

@@ -27,6 +27,26 @@ sub userInput {
  return $rt;
 }
 
+=head2 interrogateUser($options,@keys)
+
+Wait for specified keys via userInput, and put them into $options HASHREF, if they are not already defined.
+Returns modified $options HASHREF.
+Dies if the user provides no value.
+
+=cut
+
+sub interrogateUser {
+    my ($options,@keys) = @_;
+    foreach my $key (@keys) {
+        if (!$options->{$key}) {
+            print "Type the $key for your testLink install below:\n";
+            $options->{$key} = TestRail::Utils::userInput();
+            die "$key cannot be blank!" unless $options->{$key};
+        }
+    }
+    return $options;
+}
+
 =head2 parseConfig(homedir)
 
 Parse .testrailrc in the provided home directory.

+ 5 - 1
t/TestRail-API.t

@@ -4,7 +4,7 @@ use warnings;
 use TestRail::API;
 use Test::LWP::UserAgent::TestRailMock;
 
-use Test::More tests => 73;
+use Test::More tests => 74;
 use Test::Fatal;
 use Test::Deep;
 use Scalar::Util ();
@@ -165,6 +165,10 @@ ok(defined($result->{'id'}),"Can add test results");
 my $results = $tr->getTestResults($tests->[0]->{'id'});
 is($results->[0]->{'id'},$result->{'id'},"Can get results for test");
 
+#Bulk add results
+my $results = $tr->bulkAddResults($new_run->{'id'}, [{ 'test_id' => $tests->[0]->{'id'},'status_id' => $statusTypes->[0]->{'id'}, "comment" => "REAPER FORCES INBOUND" }]);
+ok(defined($results->[0]->{'id'}),"Can bulk add test results");
+
 #Test status and assignedto filtering
 my $filteredTests = $tr->getTests($new_run->{'id'},[$status_ids[0]]);
 is(scalar(@$filteredTests),1,"Test Filtering works: status id positive");

+ 4 - 1
t/arg_types.t

@@ -2,7 +2,7 @@ use strict;
 use warnings;
 
 use TestRail::API;
-use Test::More 'tests' => 141;
+use Test::More 'tests' => 144;
 use Test::Fatal;
 use Class::Inspector;
 use Test::LWP::UserAgent;
@@ -76,6 +76,7 @@ isnt( exception {$tr->userNamesToIds()}, undef,'userNamesToIds returns error whe
 isnt( exception {$tr->statusNamesToIds()}, undef,'statusNamesToIds returns error when no arguments are passed');
 isnt( exception {$tr->getRunSummary()}, undef,'getRunSummary returns error when no arguments are passed');
 isnt( exception {$tr->getPlanSummary()}, undef,'getPlanSummary returns error when no arguments are passed');
+isnt( exception {$tr->bulkAddResults()}, undef,'bulkAddResults returns error when no arguments are passed');
 
 #1-arg functions
 is(exception {$tr->deleteCase(1)},            undef,'deleteCase returns no error when int arg passed');
@@ -131,6 +132,7 @@ isnt(exception {$tr->getTestSuiteByName(1)}, undef,'getTestSuiteByName with 1 ar
 isnt(exception {$tr->getChildRunByName({}) },undef,'getChildRunByName returns error when 1 argument passed');
 isnt( exception {$tr->createRunInPlan(1) },undef,'createRunInPlan returns error when 1 argument passed');
 isnt( exception {$tr->translateConfigNamesToIds(1)}, undef,'translateConfigNamesToIds returns error when 1 argument passed');
+isnt( exception {$tr->bulkAddResults(1)}, undef,'bulkAddResults returns error when 1 argument is passed');
 
 #2 arg functions
 is(exception {$tr->createMilestone(1,'whee')}, undef,'createMilestone with 2 args returns no error');
@@ -146,6 +148,7 @@ is(exception {$tr->getTestSuiteByName(1,'zap')}, undef,'getTestSuiteByName with
 is(exception {$tr->createCase(1,'whee')}, undef,'createCase with 2 args returns no error');
 is(exception {$tr->getChildRunByName({},'whee')},undef,'getChildRunByName returns no error when 2 arguments passed');
 is(exception {$tr->translateConfigNamesToIds(1,[1,2,3])}, undef,'translateConfigNamesToIds returns no error when 2 arguments passed');
+is(exception {$tr->bulkAddResults(1,[])}, undef,'bulkAddResults returns no error when 2 arguments passed');
 
 isnt(exception {$tr->createRun(1,1)}, undef,'createRun with 2 args returns error');
 isnt(exception {$tr->createSection(1,1)}, undef,'createSection with 2 args returns error');

+ 18 - 0
t/testrail-bulk-mark-results.t

@@ -0,0 +1,18 @@
+use strict;
+use warnings;
+
+use Test::More "tests" => 4;
+
+my @args = ($^X,qw{bin/testrail-bulk-mark-results --help});
+my $out = `@args`;
+is($? >> 8, 0, "Exit code OK asking for help");
+like($out,qr/encoding of arguments/i,"Help output OK");
+
+#check plan mode
+@args = ($^X,qw{bin/testrail-bulk-mark-results --apiurl http://testrail.local --user "test@fake.fake" --password "fake" -j "CRUSH ALL HUMANS" -r "SEND T-1000 INFILTRATION UNITS BACK IN TIME" --mock blocked "Build was bad."});
+$out = `@args`;
+is($? >> 8, 0, "Exit code OK running against normal run");
+chomp $out;
+like($out,qr/set the status of 1 cases to blocked/,"Sets test correctly in single run mode");
+
+#TODO more thorough testing

+ 1 - 1
t/testrail-report.t

@@ -53,7 +53,7 @@ like($out,qr/closing plan/i,"Run closure reported to user");
 @args = ($^X,qw{bin/testrail-report --help});
 $out = `@args`;
 is($? >> 8, 0, "Exit code OK reported with help");
-$matches = () = $out =~ m/usage/ig;
+$matches = () = $out =~ m/encoding of arguments/ig;
 is($matches,1,"Help output OK");
 
 

+ 1 - 1
t/testrail-runs.t

@@ -34,4 +34,4 @@ is($out,'',"Gets no run correctly when filtering by unassigned config");
 @args = ($^X,qw{bin/testrail-runs --help});
 $out = `@args`;
 is($? >> 8, 0, "Exit code OK looking for help");
-like($out,qr/usage/i,"Help output OK");
+like($out,qr/encoding of arguments/i,"Help output OK");

+ 1 - 1
t/testrail-tests.t

@@ -90,7 +90,7 @@ like($out,qr/\nskipall\.test$/,"Gets test correctly in no plan mode, no recurse"
 @args = ($^X,qw{bin/testrail-tests --help});
 $out = `@args`;
 is($? >> 8, 0, "Exit code OK asking for help");
-like($out,qr/usage/i,"Help output OK");
+like($out,qr/encoding of arguments/i,"Help output OK");
 
 #Verify no-match and match are mutually exclusive
 @args = ($^X,qw{bin/testrail-tests --no-match t/ --match t/qa });