File Coverage

blib/lib/TestLink/API.pm
Criterion Covered Total %
statement 22 24 91.6
branch n/a
condition n/a
subroutine 8 8 100.0
pod n/a
total 30 32 93.7


line stmt bran cond sub pod time code
1             # ABSTRACT: Provides an interface to TestLink's XMLRPC api via HTTP
2             # PODNAME: TestLink::API
3              
4             package TestLink::API;
5             $TestLink::API::VERSION = '0.011';
6              
7 1     1   19235 use 5.010;
  1         3  
  1         32  
8 1     1   4 use strict;
  1         2  
  1         22  
9 1     1   3 use warnings;
  1         2  
  1         19  
10 1     1   3 use Carp;
  1         1  
  1         51  
11 1     1   6 use Scalar::Util qw{reftype looks_like_number}; #boo, some functions return hashes and arrays depending on # of results (1 or many)
  1         1  
  1         86  
12 1     1   544 use Data::Validate::URI 'is_uri';
  1         45726  
  1         67  
13              
14 1     1   551 use Clone 'clone';
  1         525  
  1         46  
15              
16 1     1   996 use XMLRPC::Lite;
  0            
  0            
17              
18              
19             sub new {
20             my ($class,$apiurl,$apikey) = @_;
21             confess("Constructor must be called statically, not by an instance") if ref($class);
22             $apiurl ||= $ENV{'TESTLINK_SERVER_ADDR'};
23             $apikey ||= $ENV{'TESTLINK_API_KEY'};
24              
25             confess("No API key provided.") if !$apiurl;
26             confess("No API URL provided.") if !$apikey;
27             confess("API URL provided not valid.") unless is_uri($apiurl);
28              
29             my $self = {
30             apiurl => $apiurl,
31             apikey => $apikey,
32             testtree => [],
33             flattree => [],
34             invalidate_cache => 1 #since we don't have a cache right now... #TODO this should be granular down to project rather than global
35             };
36              
37             bless $self, $class;
38             return $self;
39             }
40              
41              
42             #EZ get/set of obj vars
43             sub AUTOLOAD {
44             my %public_elements = map {$_,1} qw{apiurl apikey}; #Public element access
45             our $AUTOLOAD;
46              
47             if ($AUTOLOAD =~ /::(\w+)$/ and exists $public_elements{$1} ) {
48             my $field = $1;
49             {
50             no strict 'refs';
51             *{$AUTOLOAD} = sub {
52             confess("Object parameters must be called by an instance") unless ref($_[0]);
53             return $_[0]->{$field} unless defined $_[1];
54             $_[0]->{$field} = $_[1];
55             return $_[0];
56             };
57             }
58             goto &{$AUTOLOAD};
59             }
60             confess("$AUTOLOAD not accessible property") unless $AUTOLOAD =~ /DESTROY$/;
61             }
62              
63              
64             sub createTestPlan {
65             my ($self,$name,$project,$notes,$active,$public) = @_;
66             confess("Object parameters must be called by an instance") unless ref($self);
67              
68             confess("Desired Test Plan Name is a required argument (0th).") if !$name;
69             confess("Parent Project Name is a required argument (1st).") if !$project;
70              
71             $notes ||= 'res ipsa loquiter';
72             $active ||= 1;
73             $public ||= 1;
74              
75             my $input = {
76             devKey => $self->apikey,
77             testplanname => $name,
78             testprojectname => $project,
79             notes => $notes,
80             active => $active,
81             public => $public
82             };
83              
84             my $rpc = XMLRPC::Lite->proxy($self->apiurl);
85             my $result = $rpc->call('tl.createTestPlan',$input);
86             warn $result->result->[0]->{'message'} if $result->result->[0]->{'code'};
87             return $result->result->[0]->{'id'} if $result->result->[0]->{'id'};
88             return 0;
89             }
90              
91              
92             sub createBuild {
93             my ($self,$plan_id,$name,$notes) = @_;
94             confess("Object parameters must be called by an instance") unless ref($self);
95             confess("Plan ID must be integer") unless looks_like_number($plan_id);
96             confess("Build name is a required argument (1st)") if !$name;
97             $notes ||= 'res ipsa loquiter';
98              
99             my $input = {
100             devKey => $self->apikey,
101             testplanid => $plan_id,
102             buildname => $name,
103             buildnotes => $notes
104             };
105              
106             my $rpc = XMLRPC::Lite->proxy($self->apiurl);
107             my $result = $rpc->call('tl.createBuild',$input);
108             warn $result->result->[0]->{'message'} if $result->result->[0]->{'code'};
109             return $result->result->[0]->{'id'} if $result->result->[0]->{'id'};
110             return 0;
111             }
112              
113              
114             sub createTestSuite {
115             my ($self,$project_id,$name,$details,$parent_id,$order) = @_;
116             confess("Object parameters must be called by an instance") unless ref($self);
117             confess("Parent Project ID (arg 0) must be an integer") unless looks_like_number($project_id);
118             confess("Name (arg 1) cannot be undefined") unless $name;
119              
120             $details ||= 'res ipsa loquiter';
121             $order ||= -1;
122              
123             my $input = {
124             devKey => $self->apikey,
125             testprojectid => $project_id,
126             testsuitename => $name,
127             details => $details,
128             parentid => $parent_id,
129             order => $order
130             };
131              
132             my $rpc = XMLRPC::Lite->proxy($self->apiurl);
133             my $result = $rpc->call('tl.createTestSuite',$input);
134             warn $result->result->[0]->{'message'} if $result->result->[0]->{'code'};
135             $self->{'invalidate_cache'} = 1 if $result->result->[0]->{'id'};
136             return $result->result->[0]->{'id'} if $result->result->[0]->{'id'};
137             return 0;
138             }
139              
140              
141             #XXX probably should not use
142             sub createTestProject {
143             my ($self,$name,$case_prefix,$notes,$options,$active,$public) = @_;
144             confess("Object parameters must be called by an instance") unless ref($self);
145             $notes //= 'res ipsa loquiter';
146             $options //= {};
147             $public //= 1;
148             $active //= 1;
149              
150             my $input = {
151             devKey => $self->apikey,
152             testprojectname => $name,
153             testcaseprefix => $case_prefix,
154             notes => $notes,
155             options => $options,
156             active => $active,
157             public => $public
158             };
159              
160             my $rpc = XMLRPC::Lite->proxy($self->apiurl);
161             my $result = $rpc->call('tl.createTestProject',$input);
162             #XXX i'm being very safe (haha), there's probably a better check
163             warn $result->result->[0]->{'message'} if $result->result->[0]->{'code'};
164             $self->{'invalidate_cache'} = 1 if $result->result->[0]->{'id'};
165             return $result->result->[0]->{'id'} if $result->result->[0]->{'id'};
166             return 0;
167              
168             }
169              
170              
171             sub createTestCase {
172             my ($self,$test_name,$test_suite_id,$test_project_id,$author_name,$summary,$steps,$preconditions,$execution,$order) = @_;
173             confess("Object parameters must be called by an instance") unless ref($self);
174              
175             my $input = {
176             devKey => $self->apikey,
177             testcasename => $test_name,
178             testsuiteid => $test_suite_id,
179             testprojectid => $test_project_id,
180             authorlogin => $author_name,
181             summary => $summary,
182             steps => $steps,
183             preconditions => $preconditions,
184             execution => $execution,
185             order => $order
186             };
187              
188             my $rpc = XMLRPC::Lite->proxy($self->apiurl);
189             my $result = $rpc->call('tl.createTestCase',$input);
190             #XXX i'm being very safe (haha), there's probably a better check
191             warn $result->result->[0]->{'message'} if $result->result->[0]->{'code'};
192             return $result->result->[0] if $result->result->[0]->{'id'};
193             return 0;
194              
195             }
196              
197              
198             sub reportTCResult {
199             my ($self,$case_id,$plan_id,$build_id,$status,$platform,$notes,$bugid) = @_;
200             confess("Object parameters must be called by an instance") unless ref($self);
201              
202             my $input = {
203             devKey => $self->apikey,
204             testcaseid => $case_id,
205             testplanid => $plan_id,
206             buildid => $build_id,
207             status => $status,
208             notes => $notes,
209             bugid => $bugid
210             };
211              
212             if (defined($platform) && !ref $platform) {
213             if (looks_like_number($platform)) {
214             $input->{'platformid'} = $platform;
215             } else {
216             $input->{'platformname'} = $platform;
217             }
218             }
219              
220             my $rpc = XMLRPC::Lite->proxy($self->apiurl);
221             my $result = $rpc->call('tl.reportTCResult',$input);
222             warn $result->result->[0]->{'message'} if $result->result->[0]->{'code'};
223             return $result->result->[0] unless $result->result->[0]->{'code'};
224             return 0;
225             }
226              
227              
228             #XXX this should be able to be done in batch
229             sub addTestCaseToTestPlan {
230             my ($self,$plan_id,$case_id,$project_id,$version,$platform,$order,$urgency) = @_;
231             confess("Object parameters must be called by an instance") unless ref($self);
232              
233             my $input = {
234             devKey => $self->apikey,
235             testplanid => $plan_id,
236             testcaseexternalid => $case_id,
237             testprojectid => $project_id,
238             version => $version,
239             platformid => $platform,
240             executionorder => $order,
241             urgency => $urgency
242             };
243              
244             my $rpc = XMLRPC::Lite->proxy($self->apiurl);
245             my $result = $rpc->call('tl.addTestCaseToTestPlan',$input);
246             warn $result->result->{'message'} if $result->result->{'code'};
247             return 1 unless $result->result->{'code'};
248             return 0;
249             }
250              
251              
252             sub uploadExecutionAttachment {
253             my ($self,$execution_id,$filename,$filetype,$content,$title,$description) = @_;
254             confess("Object parameters must be called by an instance") unless ref($self);
255              
256             my $input = {
257             devKey => $self->apikey,
258             executionid => $execution_id,
259             title => $title,
260             description => $description,
261             filename => $filename,
262             filetype => $filetype,
263             content => $content
264             };
265              
266             my $rpc = XMLRPC::Lite->proxy($self->apiurl);
267             my $result = $rpc->call('tl.uploadExecutionAttachment',$input);
268             warn $result->result->{'message'} if $result->result->{'code'};
269             return 1 unless $result->result->{'code'};
270             return 0;
271             }
272              
273              
274             sub uploadTestCaseAttachment {
275             my ($self,$case_id,$filename,$filetype,$content,$title,$description) = @_;
276             confess("Object parameters must be called by an instance") unless ref($self);
277              
278             my $input = {
279             devKey => $self->apikey,
280             testcaseid => $case_id,
281             title => $title,
282             description => $description,
283             filename => $filename,
284             filetype => $filetype,
285             content => $content
286             };
287              
288             my $rpc = XMLRPC::Lite->proxy($self->apiurl);
289             my $result = $rpc->call('tl.uploadTestCaseAttachment',$input);
290             warn $result->result->[0]->{'message'} if $result->result->[0]->{'code'};
291             return 1 unless $result->result->[0]->{'code'};
292             return 0;
293             }
294              
295              
296              
297             sub getProjects {
298             my $self = shift;
299             confess("Object parameters must be called by an instance") unless ref($self);
300              
301             my $input = {
302             devKey => $self->apikey
303             };
304              
305             my $rpc = XMLRPC::Lite->proxy($self->apiurl);
306             my $result = $rpc->call('tl.getProjects',$input);
307             warn $result->result->[0]->{'message'} if $result->result->[0]->{'code'};
308              
309             #Save state for future use, if needed
310             if (!scalar(@{$self->{'testtree'}})) {
311             $self->{'testtree'} = $result->result unless $result->result->[0]->{'code'};
312             }
313              
314             if (!exists($result->result->[0]->{'code'})) {
315             #Note that it's a project for future reference by recursive tree search
316             for my $pj (@{$result->result}) {
317             $pj->{'type'} = 'project';
318             }
319             return $result->result;
320             }
321              
322             return 0;
323             }
324              
325              
326             sub getProjectByName {
327             my ($self,$project) = @_;
328             confess("Object parameters must be called by an instance") unless ref($self);
329             confess "No project provided." unless $project;
330              
331             #See if we already have the project list...
332             my $projects = $self->{'testtree'};
333             $projects = $self->getProjects() unless scalar(@$projects);
334              
335             #Search project list for project
336             for my $candidate (@$projects) {
337             return $candidate if ($candidate->{'name'} eq $project);
338             }
339              
340             return 0;
341             }
342              
343              
344             sub getProjectByID {
345             my ($self,$project) = @_;
346             confess("Object parameters must be called by an instance") unless ref($self);
347             confess "No project provided." unless $project;
348              
349             #See if we already have the project list...
350             my $projects = $self->{'testtree'};
351             $projects = $self->getProjects() unless scalar(@$projects);
352              
353             #Search project list for project
354             for my $candidate (@$projects) {
355             return $candidate if ($candidate->{'id'} eq $project);
356             }
357              
358             return 0;
359             }
360              
361              
362              
363             sub getTLDTestSuitesForProject {
364             my ($self,$project,$get_tests) = @_;
365             confess("Object parameters must be called by an instance") unless ref($self);
366             confess "No project ID provided." unless $project;
367              
368             my $input = {
369             devKey => $self->apikey,
370             testprojectid => $project
371             };
372              
373             my $rpc = XMLRPC::Lite->proxy($self->apiurl);
374             my $result = $rpc->call('tl.getFirstLevelTestSuitesForTestProject',$input);
375              
376             #Error condition, return right away
377             warn $result->result->[0]->{'message'} if $result->result->[0]->{'code'};
378             return [] if $result->result->[0]->{'code'};
379              
380             #Handle bizarre output
381             if ($result->result && !(reftype($result->result) eq 'HASH' || reftype($result->result) eq 'ARRAY')) {
382             return [];
383             }
384             return [] if !$result->result;
385              
386             #Handle mixed return type for this function -- this POS will return arrayrefs, and 2 types of hashrefs.
387             my $res = [];
388             $res = $result->result if reftype($result->result) eq 'ARRAY';
389             @$res = values(%{$result->result}) if reftype($result->result) eq 'HASH' && !defined($result->result->{'id'});
390             $res = [$result->result] if reftype($result->result) eq 'HASH' && defined($result->result->{'id'});
391             return [] if (!scalar(keys(%{$res->[0]}))); #Catch bizarre edge case of blank hash being only thing there
392              
393             if ($get_tests) {
394             for (my $i=0; $i < scalar(@{$result->result}); $i++) {
395             $result->result->[$i]->{'tests'} = $self->getTestCasesForTestSuite($result->result->[$i]->{'id'},0,1);
396             }
397             }
398              
399             return $result->result;
400             }
401              
402              
403             sub getTestSuitesForTestSuite {
404             my ($self,$tsid,$get_tests) = @_;
405             confess("Object parameters must be called by an instance") unless ref($self);
406             confess "No TestSuite ID provided." unless $tsid;
407              
408             my $input = {
409             devKey => $self->apikey,
410             testsuiteid => $tsid
411             };
412              
413             my $rpc = XMLRPC::Lite->proxy($self->apiurl);
414             my $result = $rpc->call('tl.getTestSuitesForTestSuite',$input);
415              
416             #Handle bizarre output
417             if ($result->result && !(reftype($result->result) eq 'HASH' || reftype($result->result) eq 'ARRAY')) {
418             return [];
419             }
420             return [] if !$result->result;
421              
422             #Handle mixed return type for this function -- this POS will return arrayrefs, and 2 types of hashrefs.
423             my $res = [];
424             $res = $result->result if reftype($result->result) eq 'ARRAY';
425             @$res = values(%{$result->result}) if reftype($result->result) eq 'HASH' && !defined($result->result->{'id'});
426             $res = [$result->result] if reftype($result->result) eq 'HASH' && defined($result->result->{'id'});
427              
428             if ($get_tests) {
429             foreach my $row (@$res) {
430             $row->{'tests'} = $self->getTestCasesForTestSuite($row->{'id'},0,1);
431             }
432             }
433              
434             #Error condition, return false and don't bother searching arrays
435             warn $res->{'message'} if $res->[0]->{'code'};
436             return [] if $res->[0]->{'code'};
437             return $res;
438             }
439              
440              
441              
442             sub getTestSuitesByName {
443             my ($self,$project_id,$testsuite_name,$do_regex) = @_;
444             confess("Object parameters must be called by an instance") unless ref($self);
445             return 0 if (!$project_id || !$testsuite_name); #GIGO
446              
447             #use caching methods here to speed up subsequent calls
448             $self->_cacheProjectTree($project_id,1,1,0) if $self->{'invalidate_cache'};
449              
450             #my $tld = $self->getTLDTestSuitesForProject($project_id);
451             my $candidates = [];
452              
453             #Walk the whole tree. No other way to be sure.
454             foreach my $ts (@{$self->{'flattree'}}) {
455             if ($do_regex) {
456             push(@$candidates,$ts) if $ts->{'name'} =~ $testsuite_name;
457             } else {
458             push(@$candidates,$ts) if $ts->{'name'} eq $testsuite_name;
459             }
460             }
461             return $candidates;
462              
463             }
464              
465              
466             sub getTestSuiteByID {
467             my ($self,$testsuite_id) = @_;
468             confess("Object parameters must be called by an instance") unless ref($self);
469              
470             my $input = {
471             devKey => $self->apikey,
472             testsuiteid => $testsuite_id
473             };
474              
475             my $rpc = XMLRPC::Lite->proxy($self->apiurl);
476             my $result = $rpc->call('tl.getTestSuiteByID',$input);
477             warn $result->result->{'message'} if $result->result->{'code'};
478             return $result->result unless $result->result->{'code'};
479             return 0;
480             }
481              
482              
483             sub getTestCasesForTestSuite {
484             my ($self,$testsuite_id,$deep,$details) = @_;
485             confess("Object parameters must be called by an instance") unless ref($self);
486              
487             $details = 'full' if $details;
488              
489             my $input = {
490             devKey => $self->apikey,
491             testsuiteid => $testsuite_id,
492             deep => $deep,
493             details => $details
494             };
495              
496             my $rpc = XMLRPC::Lite->proxy($self->apiurl);
497             my $result = $rpc->call('tl.getTestCasesForTestSuite',$input);
498             return [] if !$result->result;
499              
500             return [] if !scalar(keys(%{$result->result->[0]})); # No tests
501              
502             warn $result->result->[0]->{'message'} if $result->result->[0]->{'code'};
503             return $result->result unless $result->result->[0]->{'code'};
504             return [];
505             }
506              
507              
508             sub getTestCaseByExternalId {
509             my ($self,$case_id,$version) = @_;
510             confess("Object parameters must be called by an instance") unless ref($self);
511              
512             my $input = {
513             devKey => $self->apikey,
514             testcaseexternalid => $case_id,
515             version => $version
516             };
517              
518             my $rpc = XMLRPC::Lite->proxy($self->apiurl);
519             my $result = $rpc->call('tl.getTestCase',$input);
520             warn $result->result->[0]->{'message'} if $result->result->[0]->{'code'};
521             return $result->result->[0] unless $result->result->[0]->{'code'};
522             return 0;
523             }
524              
525              
526             sub getTestCaseById {
527             my ($self,$case_id,$version) = @_;
528             confess("Object parameters must be called by an instance") unless ref($self);
529              
530             my $input = {
531             devKey => $self->apikey,
532             testcaseid => $case_id,
533             version => $version
534             };
535              
536             my $rpc = XMLRPC::Lite->proxy($self->apiurl);
537             my $result = $rpc->call('tl.getTestCase',$input);
538             warn $result->result->[0]->{'message'} if $result->result->[0]->{'code'};
539             return $result->result->[0] unless $result->result->[0]->{'code'};
540             return 0;
541             }
542              
543              
544             sub getTestCaseByName {
545             my ($self, $casename, $suitename, $projectname, $testcasepathname) = @_;
546             confess("Object parameters must be called by an instance") unless ref($self);
547              
548             my $input = {
549             devKey => $self->apikey,
550             testcasename => $casename,
551             testsuitename => $suitename,
552             testprojectname => $projectname,
553             testcasepathname => $testcasepathname
554             };
555              
556             my $rpc = XMLRPC::Lite->proxy($self->apiurl);
557             my $result = $rpc->call('tl.getTestCaseIDByName',$input);
558             warn $result->result->[0]->{'message'} if $result->result->[0]->{'code'};
559             return $result->result->[0] unless $result->result->[0]->{'code'};
560             return 0;
561             }
562              
563              
564             sub getTestCaseAttachments {
565             my ($self, $case_ext_id) = @_;
566             confess("Object parameters must be called by an instance") unless ref($self);
567              
568             my $input = {
569             devKey => $self->apikey,
570             testcaseexternalid => $case_ext_id,
571             };
572              
573             my $rpc = XMLRPC::Lite->proxy($self->apiurl);
574             my $result = $rpc->call('tl.getTestCaseAttachments',$input);
575             return 0 if (!$result->result);
576             warn $result->result->[0]->{'message'} if $result->result->[0]->{'code'};
577             return $result->result->[0] unless $result->result->[0]->{'code'};
578             return 0;
579             }
580              
581              
582             sub getTestPlansForProject {
583             my ($self,$project_id) = @_;
584             confess("Object parameters must be called by an instance") unless ref($self);
585              
586             my $input = {
587             devKey => $self->apikey,
588             testprojectid => $project_id
589             };
590              
591             my $rpc = XMLRPC::Lite->proxy($self->apiurl);
592             my $result = $rpc->call('tl.getProjectTestPlans',$input);
593             warn $result->result->[0]->{'message'} if $result->result->[0]->{'code'};
594             return $result->result unless $result->result->[0]->{'code'};
595             return 0;
596             }
597              
598              
599             # I find it highly bizarre that the only 'by name' method exists for test plans, and it's the only test plan getter.
600             sub getTestPlanByName {
601             my ($self,$plan_name,$project_name) = @_;
602             confess("Object parameters must be called by an instance") unless ref($self);
603              
604             my $input = {
605             devKey => $self->apikey,
606             testplanname => $plan_name,
607             testprojectname => $project_name
608             };
609              
610             my $rpc = XMLRPC::Lite->proxy($self->apiurl);
611             my $result = $rpc->call('tl.getTestPlanByName',$input);
612             warn $result->result->[0]->{'message'} if $result->result->[0]->{'code'};
613             return $result->result->[0] unless $result->result->[0]->{'code'};
614             return 0;
615             }
616              
617              
618             sub getBuildsForTestPlan {
619             my ($self,$plan_id) = @_;
620             confess("Object parameters must be called by an instance") unless ref($self);
621              
622             my $input = {
623             devKey => $self->apikey,
624             testplanid => $plan_id
625             };
626              
627             my $rpc = XMLRPC::Lite->proxy($self->apiurl);
628             my $result = $rpc->call('tl.getBuildsForTestPlan',$input);
629             warn $result->result->[0]->{'message'} if $result->result->[0]->{'code'};
630             return $result->result unless $result->result->[0]->{'code'};
631             return 0;
632             }
633              
634              
635             sub getTestCasesForTestPlan {
636             my ($self,$plan_id) = @_;
637             confess("Object parameters must be called by an instance") unless ref($self);
638              
639             my $input = {
640             devKey => $self->apikey,
641             testplanid => $plan_id
642             };
643              
644             my $rpc = XMLRPC::Lite->proxy($self->apiurl);
645             my $result = $rpc->call('tl.getTestCasesForTestPlan',$input);
646             warn $result->result->{'message'} if $result->result->{'code'};
647             return $result->result unless $result->result->{'code'};
648             return 0;
649             }
650              
651              
652             sub getLatestBuildForTestPlan {
653             my ($self,$plan_id) = @_;
654             confess("Object parameters must be called by an instance") unless ref($self);
655              
656             my $input = {
657             devKey => $self->apikey,
658             tplanid => $plan_id, #documented arg, but that's LIES, apparently it wants the next one
659             testplanid => $plan_id
660             };
661              
662             my $rpc = XMLRPC::Lite->proxy($self->apiurl);
663             my $result = $rpc->call('tl.getLatestBuildForTestPlan',$input);
664              
665             #Handle mixed return type
666             my $res = $result->result;
667             $res = [$res] if reftype($res) eq 'HASH';
668              
669             warn $res->[0]->{'message'} if $res->[0]->{'code'};
670             return $res->[0] unless $res->[0]->{'code'};
671             return 0;
672             }
673              
674              
675             #TODO cache stuff, don't require proj id?
676             sub getBuildByName {
677             my ($self,$build_name,$project_id) = @_;
678             confess("Object parameters must be called by an instance") unless ref($self);
679              
680             my $plans = $self->getTestPlansForProject($project_id);
681             for my $plan (@$plans) {
682             my $builds = $self->getBuildsForTestPlan($plan->{'id'});
683             for my $build (@$builds) {
684             return $build if $build->{'name'} eq $build_name;
685             }
686             }
687             return 0;
688             }
689              
690              
691             sub getTotalsForTestPlan {
692             my ($self,$plan_id) = @_;
693             confess("Object parameters must be called by an instance") unless ref($self);
694              
695             my $input = {
696             devKey => $self->apikey,
697             tplanid => $plan_id, #documented arg, but that's LIES, apparently it wants the next one
698             testplanid => $plan_id
699             };
700              
701             my $rpc = XMLRPC::Lite->proxy($self->apiurl);
702             my $result = $rpc->call('tl.getTotalsForTestPlan',$input);
703              
704             warn $result->result->{'message'} if $result->result->{'code'};
705             return $result->result unless $result->result->{'code'};
706             return 0;
707             }
708              
709              
710             sub dump {
711             my ($self,$project,$attachment,$flat) = @_;
712             confess("Object parameters must be called by an instance") unless ref($self);
713             confess("Getting attachments not yet implemented") if $attachment;
714              
715             my $res = $self->_cacheProjectTree($project,$flat);
716             return [] if !$res;
717              
718             return $res if !$project || $flat;
719             foreach my $pj (@{$res}) {
720             return $pj if $pj->{'name'} eq $project;
721             }
722             croak "COULD NOT DUMP, SOMETHING HORRIBLY WRONG";
723             }
724              
725             sub _cacheProjectTree {
726             my ($self,$project,$flat,$use_project_id,$get_tests) = @_;
727             confess("Object parameters must be called by an instance") unless ref($self);
728              
729             $flat //= 0;
730             $use_project_id //= 0;
731             $get_tests //= 1;
732              
733             #Cache Projects
734             if (!scalar(@{$self->{'testtree'}})) {
735             $self->getProjects();
736             }
737              
738             my @flattener = @{$self->{'testtree'}};
739              
740             for my $projhash (@flattener) {
741             if ($use_project_id) {
742             next if $project && $project ne $projhash->{'id'} && (defined($projhash->{'type'}) && $projhash->{'type'} eq 'project');
743             } else {
744             next if $project && $project ne $projhash->{'name'} && (defined($projhash->{'type'}) && $projhash->{'type'} eq 'project');
745             }
746              
747             #If TestSuites are not defined, this must be a TS which we have not traversed yet, so go and get it
748             if (exists($projhash->{'type'}) && $projhash->{'type'} eq 'project') {
749             $projhash->{'testsuites'} = $self->getTLDTestSuitesForProject($projhash->{'id'},$get_tests);
750             } else {
751             $projhash->{'testsuites'} = $self->getTestSuitesForTestSuite($projhash->{'id'},$get_tests);
752             }
753              
754             $projhash->{'testsuites'} = [] if !$projhash->{'testsuites'};
755             for my $tshash (@{$projhash->{'testsuites'}}) {
756             #Otherwise, push it's children to the end of our array so we can recurse as needed.
757             #I hope the designers of TL's schema were smart enough to not allow self-referential or circular suites..
758             push(@flattener,clone $tshash);
759             }
760              
761             }
762              
763             #Keep this for simple searches in the future.
764             $self->{'flattree'} = clone \@flattener;
765             return $self->{'flattree'} if $flat;
766             return $self->_expandTree($project,@flattener);
767             }
768              
769             sub _expandTree {
770             my $self = shift;
771             confess("Object parameters must be called by an instance") unless ref($self);
772             my $project = shift;
773             my @flattener = @_;
774             #The following algorithm relies implicitly on pass-by-reference.
775             #So we have a flat array of testsuites we want to map into parent-child relationships.
776             my ($i,$j);
777             foreach my $suite (@flattener) {
778             if (defined($suite->{'type'}) && $suite->{'type'} eq 'project') {
779             #Then skip it, since it's not a suite.
780             shift @flattener;
781             next;
782             }
783              
784             #This means we need to walk the hierarchy of every project, or just the one we passed
785             for ($j=0; $j < scalar(@{$self->{'testtree'}}); $j++) {
786             #If we have a project, skip the other ones
787             next unless $project && $self->{'testtree'}->[$j]->{'name'} eq $project;
788              
789             #Get the ball rolling if we have to
790             $self->{'testtree'}->[$j]->{'testsuites'} = $self->getTLDTestSuitesForProject($self->{'testtree'}->[$j]->{'id'},1) if !defined($self->{'testtree'}->[$j]->{'testsuites'});
791              
792             #So, let's tail recurse over the testsuites.
793             for ($i=0; $i < scalar(@{$self->{'testtree'}->[$j]->{'testsuites'}}); $i++) {
794             my $tailRecurseTSWalker = sub {
795             my ($ts,$desired_ts) = @_;
796              
797             #Mark it down if we found it
798             if ($ts->{'id'} eq $desired_ts->{'parent_id'}) {
799              
800             #Set the REF's 'testsuites' param, and quit searching
801             $ts->{'testsuites'} = [] if !defined($ts->{'testsuites'});
802             push(@{$ts->{'testsuites'}},$desired_ts);
803             $desired_ts->{'found'} = 1;
804             return;
805             }
806              
807             #If there's already (nonblank) hierarchy in the passed TS, then WE HAVE TO GO DEEPER
808             if (defined($ts->{'testsuites'}) && scalar(@{$desired_ts->{'testsuites'}})) {
809             for (my $i=0; $i < scalar(@{$ts->{'testsuites'}}); $i++) {
810             _tailRecurseTSWalker($ts->{'testsuites'}->[$i],$desired_ts);
811             }
812             }
813              
814             return;
815             };
816              
817             &$tailRecurseTSWalker($self->{'testtree'}->[$j]->{'testsuites'}->[$i],$suite);
818             #OPTIMIZE: break out if we found it already
819             last if $suite->{'found'};
820             }
821             last if $suite->{'found'};
822             }
823              
824             #If we didn't find this one yet, as the hierarchy build is progressive, add it to the end until it gets picked up.
825             if (!$suite->{'found'}) {
826             push(@flattener,shift @flattener); # If it wasn't found, push it on to the end of the array so the walk might find it next time.
827             } else {
828             shift @flattener;
829             }
830             }
831             return $self->{'testtree'};
832             }
833              
834             1;
835              
836             __END__