initial import master START
authorivan <ivan>
Thu, 26 Feb 2009 15:28:31 +0000 (15:28 +0000)
committerivan <ivan>
Thu, 26 Feb 2009 15:28:31 +0000 (15:28 +0000)
.cvsignore [new file with mode: 0644]
Changes [new file with mode: 0644]
MANIFEST [new file with mode: 0644]
Makefile.PL [new file with mode: 0644]
README [new file with mode: 0644]
lib/Net/Indosoft/Voicebridge.pm [new file with mode: 0644]
t/00-load.t [new file with mode: 0644]
t/01-Account_Client_Conference.t [new file with mode: 0644]
t/pod-coverage.t [new file with mode: 0644]
t/pod.t [new file with mode: 0644]
voicebridgeAPI.wsdl [new file with mode: 0644]

diff --git a/.cvsignore b/.cvsignore
new file mode 100644 (file)
index 0000000..a51d4a9
--- /dev/null
@@ -0,0 +1,10 @@
+blib*
+Makefile
+Makefile.old
+Build
+_build*
+pm_to_blib*
+*.tar.gz
+.lwpcookies
+Net-Indosoft-Voicebridge-*
+cover_db
diff --git a/Changes b/Changes
new file mode 100644 (file)
index 0000000..1d81eb0
--- /dev/null
+++ b/Changes
@@ -0,0 +1,5 @@
+Revision history for Net-Indosoft-Voicebridge
+
+0.01    unreleased
+        First version, released on an unsuspecting world.
+
diff --git a/MANIFEST b/MANIFEST
new file mode 100644 (file)
index 0000000..4e493ed
--- /dev/null
+++ b/MANIFEST
@@ -0,0 +1,10 @@
+Changes
+MANIFEST
+Makefile.PL
+README
+voicebridgeAPI.wsdl
+lib/Net/Indosoft/Voicebridge.pm
+t/00-load.t
+t/01-Account_Client_Conference.t
+t/pod-coverage.t
+t/pod.t
diff --git a/Makefile.PL b/Makefile.PL
new file mode 100644 (file)
index 0000000..1d536a0
--- /dev/null
@@ -0,0 +1,17 @@
+use strict;
+use warnings;
+use ExtUtils::MakeMaker;
+
+WriteMakefile(
+    NAME                => 'Net::Indosoft::Voicebridge',
+    AUTHOR              => 'Ivan Kohler <ivan-voicebridge@freeside.biz>',
+    VERSION_FROM        => 'lib/Net/Indosoft/Voicebridge.pm',
+    ABSTRACT_FROM       => 'lib/Net/Indosoft/Voicebridge.pm',
+    PL_FILES            => {},
+    PREREQ_PM => {
+        'Test::More' => 0,
+        'SOAP::Lite' => 0,
+    },
+    dist                => { COMPRESS => 'gzip -9f', SUFFIX => 'gz', },
+    clean               => { FILES => 'Net-Indosoft-Voicebridge-*' },
+);
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..f6a8bd8
--- /dev/null
+++ b/README
@@ -0,0 +1,43 @@
+Net-Indosoft-Voicebridge
+
+This is an interface to the Indosoft Voicebridge API.  It is only useful if you
+have an account with Indosoft to resell confernce bridge service.
+
+INSTALLATION
+
+To install this module, run the following commands:
+
+       perl Makefile.PL
+       make
+       make test
+       make install
+
+SUPPORT AND DOCUMENTATION
+
+After installing, you can find documentation for this module with the
+perldoc command.
+
+    perldoc Net::Indosoft::Voicebridge
+
+You can also look for information at:
+
+    RT, CPAN's request tracker
+        http://rt.cpan.org/NoAuth/Bugs.html?Dist=Net-Indosoft-Voicebridge
+
+    AnnoCPAN, Annotated CPAN documentation
+        http://annocpan.org/dist/Net-Indosoft-Voicebridge
+
+    CPAN Ratings
+        http://cpanratings.perl.org/d/Net-Indosoft-Voicebridge
+
+    Search CPAN
+        http://search.cpan.org/dist/Net-Indosoft-Voicebridge
+
+
+COPYRIGHT AND LICENCE
+
+Copyright (C) 2009 Freeside Internet Services, Inc.
+
+This program is free software; you can redistribute it and/or modify it
+under the same terms as Perl itself.
+
diff --git a/lib/Net/Indosoft/Voicebridge.pm b/lib/Net/Indosoft/Voicebridge.pm
new file mode 100644 (file)
index 0000000..e493af2
--- /dev/null
@@ -0,0 +1,411 @@
+package Net::Indosoft::Voicebridge;
+
+use warnings;
+use strict;
+use Data::Dumper;
+use SOAP::Lite;
+
+our $AUTOLOAD;
+our $DEBUG = 1;
+
+=head1 NAME
+
+Net::Indosoft::Voicebridge - Interface to Indosoft Voicebridge API
+
+=head1 VERSION
+
+Version 0.01
+
+=cut
+
+our $VERSION = '0.01';
+
+=head1 SYNOPSYS
+
+  use Net::Indosoft::Voicebridge;
+
+  my $handle = Net::GlobalPOPs::MediaServicesAPI->new(
+    'url'    => 'http://your_tag.kanobe.net:8080/vbsoap/voicebridgeAPI.php',
+  );
+
+=head1 METHODS
+
+=head2 new
+
+Creates a new Net::Indosoft::Voicebridge object.
+
+=cut
+
+sub new {
+  my $proto = shift;
+  my $class = ref($proto) || $proto;
+  my $self = ref($_[0]) ? shift : { @_ };
+
+#  my $soap = new SOAP::Lite (
+#    'service' => $URL.'?wsdl',
+#  );
+#  $self->{soap} = $soap;
+
+  bless($self, $class);
+}
+
+#ideally this could be retreived from the WSDL, but hey
+# or at least derived from the method name?
+our %requestobj = (
+  'addAccount'       => 'Account',
+  'modifyAccount'    => 'Account',
+  'addClient'        => 'Client',
+  'modifyClient'     => 'Client',
+  'addConference'    => 'Conference',
+  'modifyConference' => 'Conference',
+);
+
+#this is think they're just on crack.  returnning a string to parse inside
+#a ton of XML?  somehow that misses the point.
+our %returnparse = (
+  'addAccount'       => [ qw( account_id ) ],
+  'modifyAccount'    => [ qw( account_id ) ],
+  'addClient'        => [ qw( client_id  ) ],
+  'modifyClient'     => [ qw( client_id  ) ],
+  'addConference'    => [ qw( conference_id moderator_pin participant_pin ) ],
+  'modifyConference' => [ qw( conference_id ) ],
+);
+
+sub AUTOLOAD {
+  my $self = shift;
+
+  $AUTOLOAD =~ /(^|::)(\w+)$/ or die "unparsable AUTOLOAD: $AUTOLOAD";
+  my $function = $2;
+  return if $function eq 'DESTROY';
+
+  my $uri = "voicebridgeAPI/$function";
+
+
+  my $soap = SOAP::Lite
+#    -> readable(1)
+    -> proxy($self->{url})
+#    -> on_action( sub { join '/', @_ } ) #according to the wsdl, right?
+#    -> on_action( sub { "urn:voicebridgeAPI/$function" } )
+    -> autotype(0)
+#    -> default_ns("urn:voicebridgeAPI")
+  ;
+
+  my $request;
+  my $obj = $requestobj{$function};
+  if ( $obj ) {
+
+    my $opts = ref($_[0]) ? shift : { @_ };
+    if ( $DEBUG > 1 ) {
+      warn "$_: ". $opts->{$_}. "\n" foreach keys %$opts;
+    }
+
+    $request = SOAP::Data->name( $obj =>
+      \SOAP::Data->value(
+        map SOAP::Data->name( $_ )->value( $opts->{$_} ), keys %$opts
+      )
+    );
+
+  } else {
+
+    $request = shift;
+
+  }
+
+  my $som = $soap->call( $function, $request );
+
+  die $som->faultstring if $som->fault;
+  return { 'error' => $som->faultdetail } if $som->fault;
+
+  warn Dumper($som->body) if $DEBUG > 1;
+
+  my $return = $som->valueof("//${function}Response/return");
+
+  if ( $return =~ /^<errors/i ) {
+    my $rsom = SOAP::Deserializer->deserialize($return);
+    my @errors = $rsom->valueof('//errors/error');
+    return { 'error' => join(' / ', @errors ) } if @errors;
+  }
+
+  if ( $function =~ /^delete/ && $return !~ /Deleted Success?fully/i ) {
+    return { 'error'  => 'Deletion unsuccessful',
+             'return' => $return,
+           };
+  }
+
+  my %return = ( 'return'=>$return );
+  foreach my $rp ( @{ $returnparse{$function} || [] } ) {
+                              #are they always numeric?
+    $return =~ /\W$rp\s*=\s*(\d+)/ and $return{$rp} = $1;
+  }
+
+  return \%return;
+}
+
+=head2 addAccount
+
+Pass a list of key=>value pairs or a hash reference:
+
+i.account_name: Required Field: Alpha Numeric 
+ii.account_desc: Alpha Numeric
+iii.account_addr: Alpha Numeric
+iv.account_city: Alpha Numeric
+v.account_state: Alpha Numeric
+vi.account_country: Alpha Numeric
+vii.account_zip: Alpha Numeric
+viii.account_phone: Required Field: Alpha Numeric Ex: 123-123-1234
+ix.account_fax: Alpha Numeric
+x.account_email: Required Field, have to be in valid email format
+xi.account_password: Required Field: Alpha Numeric
+
+Returns a hash reference with one element, either 'error' for error conditions,
+or 'account_id' if the account is created sucessfully.
+
+=head2 modifyAccount
+
+Pass a list of key=>value pairs or a hash reference:
+
+xiv.account_id: Required Field (Provided when the account was created) 
+xv.account_name: Required Field
+xvi.account_desc 
+xvii.account_addr 
+xviii.account_city 
+xix.account_state 
+xx.account_country 
+xxi.account_zip 
+xxii.account_phone: Required Field 
+xxiii.account_fax
+xxiv.account_email: Required Field
+xxv.account_password: Required Field
+
+Returns a hash reference with one element, either 'error' for error conditions,
+or 'account_id' if the account is modified sucessfully.
+
+=head2 addClient
+
+Pass a list of key=>value pairs or a hash reference:
+
+account_id: Required Field Alpha Numeric 
+client_contact_name: Required Field Alpha Numeric
+client_contact_addr Alpha Numeric
+client_contact_city Alpha Numeric
+client_contact_state Alpha Numeric
+client_contact_country Alpha Numeric
+client_contact_zip Alpha Numeric
+client_contact_phone: Required Field Alpha Numeric
+client_contact_fax Alpha Numeric
+client_contact_email: Required Field Alpha Numeric
+client_contact_password: Required Field Alpha Numeric
+
+Returns a hash reference with one element, either 'error' for error conditions,
+or 'client_id' if the account is created sucessfully.
+
+=head2 modifyClient
+
+Pass a list of key=>value pairs or a hash reference:
+
+client_id: Required Field
+account_id: Required Field
+client_contact_name: Required Field
+client_contact_addr
+client_contact_city
+client_contact_state
+client_contact_country
+client_contact_zip
+client_contact_phone: Required Field
+client_contact_fax
+client_contact_email: Required Field
+client_contact_password: Required Field
+
+Returns a hash reference with one element, either 'error' for error conditions,
+or 'client_id' if the account is modified sucessfully.
+
+=head2 addConference
+
+Pass a list of key=>value pairs or a hash reference:
+
+client_id: Required Field Alpha Numeric (Required to associate conference to client, client ID provided when new client is added)
+conference_name: Required Field Alpha Numeric
+conference_desc Alpha Numeric
+start_time: Required Field Alpha Numeric
+moderated_flag: 
+1 ¿ Presentation mode 
+0 ¿ Conversation Mode
+entry_ann_flag: Integer
+0 ¿ None
+1 -  Tone
+2 - Name 
+record_flag: Integer
+0 ¿ Stop Recording
+1 ¿ Start Recording
+moh_flag: Integer
+0 ¿ Stop MOH
+1 ¿ Start MOH
+talk_detect_flag
+play_user_cnt_flag: Integer
+1- Announce number of conference members currently in conference
+0 ¿ no Announcement
+wait_for_admin_flag: Integer
+1 - Only start conference once admin enters
+0 ¿ all users without admin
+stop_on_admin_exit_flag: Integer
+1 - End conference when admin exits:
+0 - No
+second_pin_flag: Integer
+1 - Prompt conference members for a second pin/password when logging in?
+0 - No Extra conference PIN
+secondary_pin: Integer
+If second_pin_flag is 1 also pass the PIN
+allow_sub_conf: Integer
+1 ¿ Allow sub conference for this conference
+0 ¿ Donot Allow sub conference
+duration: Integer
+Duration in minutes e.g 30 for 30 minutes
+conference_type: reservationless/reserved
+
+On errors, returns a hash reference with one element, 'error', otherwise
+returns a hash reference with the following keys: conference_id, moderator_pin
+and participant_pin.
+
+=head2 modifyConference
+
+Pass a list of key=>value pairs or a hash reference:
+
+conference_id: Required Field
+client_id: Required Field
+conference_name: Required Field
+conference_desc
+start_time: Required Field
+moderated_flag: 
+1 ¿ Presentation mode 
+0 ¿ Conversation Mode
+entry_ann_flag: Integer
+0 ¿ None
+1 -  Tone
+2 - Name 
+record_flag: Integer
+0 ¿ Stop Recording
+1 ¿ Start Recording
+moh_flag: Integer
+0 ¿ Stop MOH
+1 ¿ Start MOH
+play_user_cnt_flag: Integer
+1- Announce number of conference members currently in conference
+0 ¿ no Announcement
+wait_for_admin_flag: Integer
+1 - Only start conference once admin enters
+0 ¿ all users without admin
+stop_on_admin_exit_flag: Integer
+1 - End conference when admin exits:
+0 - No
+second_pin_flag: Integer
+1 - Prompt conference members for a second pin/password when logging in?
+0 - No Extra conference PIN
+secondary_pin: Integer
+If second_pin_flag is 1 also pass the PIN
+allow_sub_conf: Integer
+1 ¿ Allow sub conference for this conference
+0 ¿ Donot Allow sub conference
+duration: Integer
+Duration in minutes e.g 30 for 30 minutes
+
+Returns a hash reference with one element, either 'error' for error conditions,
+or 'conference_id' if the conference is modified sucessfully.
+
+=head2 deleteAccount
+
+Pass a list of key=>value pairs or a hash reference:
+
+i.account_id: Required Field
+
+On errors, should returns a hash reference with one element, 'error'.
+
+=head2 deleteClient
+
+Pass a list of key=>value pairs or a hash reference:
+
+ii.client_id: Required Field
+
+On errors, should returns a hash reference with one element, 'error'.
+
+=head2 deleteConference
+
+Pass a list of key=>value pairs or a hash reference:
+
+iii.conference_id: Required Field
+
+On errors, should returns a hash reference with one element, 'error'.
+
+=head1 The services below are not yet documented/online
+
+=head2 addConferencePIN
+
+=head2 modifyConferencePIN
+
+=head2 deleteConferencePIN
+
+=head2 addDNIS
+
+=head2 modifyDNIS
+
+=head2 deleteDNIS
+
+=head1 AUTHOR
+
+Ivan Kohler, C<< <ivan-voicebridge at freeside.biz> >>
+
+=head1 BUGS
+
+Please report any bugs or feature requests to C<bug-net-indosoft-voicebridge at rt.cpan.org>, or through
+the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Net-Indosoft-Voicebridge>.  I will be notified, and then you'll
+automatically be notified of progress on your bug as I make changes.
+
+=head1 SUPPORT
+
+You can find documentation for this module with the perldoc command.
+
+    perldoc Net::Indosoft::Voicebridge
+
+You can also look for information at:
+
+=over 4
+
+=item * RT: CPAN's request tracker
+
+L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Net-Indosoft-Voicebridge>
+
+=item * AnnoCPAN: Annotated CPAN documentation
+
+L<http://annocpan.org/dist/Net-Indosoft-Voicebridge>
+
+=item * CPAN Ratings
+
+L<http://cpanratings.perl.org/d/Net-Indosoft-Voicebridge>
+
+=item * Search CPAN
+
+L<http://search.cpan.org/dist/Net-Indosoft-Voicebridge>
+
+=back
+
+=head1 ACKNOWLEDGEMENTS
+
+This module was developed by Freeside Internet Services, Inc.
+If you need a complete, open-source web-based application to manage your
+customers, conferences, billing and trouble ticketing, please visit
+http://freeside.biz/
+
+Development sponsored by NxxTcom Conferencing.  If you need a cost-effective
+voice, web or video conference, please visit http://www.nxxtcom.net/
+
+=head1 COPYRIGHT & LICENSE
+
+Copyright (c) 2009 Freeside Internet Services, Inc. <http://freeside.biz/>
+All rights reserved.
+
+This program is free software; you can redistribute it and/or modify it
+under the same terms as Perl itself.
+
+=cut
+
+1;
+
diff --git a/t/00-load.t b/t/00-load.t
new file mode 100644 (file)
index 0000000..860081a
--- /dev/null
@@ -0,0 +1,9 @@
+#!perl -T
+
+use Test::More tests => 1;
+
+BEGIN {
+       use_ok( 'Net::Indosoft::Voicebridge' );
+}
+
+diag( "Testing Net::Indosoft::Voicebridge $Net::Indosoft::Voicebridge::VERSION, Perl $], $^X" );
diff --git a/t/01-Account_Client_Conference.t b/t/01-Account_Client_Conference.t
new file mode 100644 (file)
index 0000000..21effd9
--- /dev/null
@@ -0,0 +1,262 @@
+#!perl
+
+use Test::More;
+
+open URL, "t/URL"
+  or do { plan 'skip_all' => 'Put your URL in the t/URL file to test'; exit };
+chomp( my $URL = scalar(<URL>) );
+
+plan tests => 6 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2;
+
+use Net::Indosoft::Voicebridge;
+
+my $vb = new Net::Indosoft::Voicebridge( 'url' => $URL );
+
+my $return;
+
+# addAccount (6)
+
+$return = $vb->addAccount(
+  'account_name'     => 'aname', #i think this is more like a username
+  'account_password' => 'test54',
+
+  'account_desc'     => 'A test account',
+
+  'account_addr'     => '1234 Test Lane',
+  'account_city'     => 'Testville',
+  'account_state'    => 'CA',
+  'account_zip'      => '54321',
+  'account_country'  => 'US',
+
+  'account_phone'    => '415-555-1212',
+#  'account_fax'      => '415-555-1212',
+  'account_email'    => 'ivan-voicebridge-test@freeside.biz',
+);
+
+ok( ! $return->{error}, 'addAccount successful' ) or warn $return->{error};
+
+like($return->{account_id}, qr/^(\d+)$/, 'addAccount returned account_id');
+
+my $account_id = $return->{account_id};
+
+$return = $vb->addAccount(
+);
+
+ok( $return->{error}, 'addAccount empty error test', );
+
+like($return->{error}, qr/Please, fill in/, 'addAccount empty error test got expected error');
+
+$return = $vb->addAccount(
+  #'account_name'     => 'A. Name',
+  'account_desc'     => 'A test account',
+  'account_addr'     => '1234 Test Lane',
+  'account_city'     => 'Testville',
+  'account_state'    => 'CA',
+  'account_zip'      => '54321',
+  #'account_phone'    => '415-555-1212',
+  'account_fax'      => '415-555-1212',
+  #'account_email'    => 'ivan-voicebridge-test@freeside.biz',
+  #'account_password' => 'testing',
+);
+
+ok( $return->{error}, 'addAccount bad data error test', );
+
+like($return->{error}, qr/Please, fill in/, 'addAccount bad data error test got expected error');
+
+# modifyAccount (2)
+
+$return = $vb->modifyAccount(
+  'account_id'       => $account_id,
+  'account_name'     => 'anewname', #i think this is more like a username
+  'account_password' => 'test32',
+
+  'account_desc'     => 'A modified test account',
+
+  'account_addr'     => '5432 Test Lane',
+  'account_city'     => 'Test City',
+  'account_state'    => 'NY',
+  'account_zip'      => '12345',
+  'account_country'  => 'US',
+
+  'account_phone'    => '212-555-1212',
+  'account_fax'      => '212-555-1212',
+  'account_email'    => 'ivan-voicebridge-test2@freeside.biz',
+);
+
+ok( ! $return->{error}, 'modifyAccount successful' ) or warn $return->{error};
+
+is($return->{account_id}, $account_id, 'modifyAccount returned account_id');
+
+# addClient (2)
+
+$return = $vb->addClient(
+  'account_id'              => $account_id,
+
+  'client_contact_name'     => 'Tofu Beast',
+  'client_contact_password' => 'word54',
+
+  'client_contact_addr'     => '5432 Test Lane Suite 11',
+  'client_contact_city'     => 'Test City',
+  'client_contact_state'    => 'NY',
+  'client_contact_country'  => 'US',
+  'client_contact_zip'      => '12345',
+
+  'client_contact_phone'    => '212-555-1234',
+  'client_contact_fax'      => '212-555-1234',
+  'client_contact_email'    => 'ivan-voicebridge-test3@freeside.biz',
+
+);
+
+ok( ! $return->{error}, 'addClient successful' ) or warn $return->{error};
+
+like($return->{client_id}, qr/^(\d+)$/, 'addClient returned client_id');
+
+my $client_id = $return->{client_id};
+
+# modifyClient (2)
+
+$return = $vb->modifyClient(
+  'account_id'              => $account_id,
+  'client_id'               => $client_id,
+
+  'client_contact_name'     => 'Secret Agent X',
+  'client_contact_password' => 'word32',
+
+  'client_contact_addr'     => '1234 Test Lane Suite 55',
+  'client_contact_city'     => 'Testville',
+  'client_contact_state'    => 'CA',
+  'client_contact_country'  => 'US',
+  'client_contact_zip'      => '54321',
+
+  'client_contact_phone'    => '415-555-1234',
+  'client_contact_fax'      => '415-555-1234',
+  'client_contact_email'    => 'ivan-voicebridge-test4@freeside.biz',
+  
+);
+
+ok( ! $return->{error}, 'modifyClient successful' ) or warn $return->{error};
+
+is($return->{client_id}, $client_id, 'modifyClient returned client_id');
+
+# addConference (2)
+
+$return = $vb->addConference(
+  'client_id'          => $client_id,
+  'conference_name'    => 'Test Conference',
+  'conference_desc'    => 'Net Indosoft Voicebridge test',
+  'start_time'         => '2010-20-04 16:20:00',
+  #'moderated_flag'     => 0,
+  #'entry_ann_flag'     => 0
+  #'record_flag'        => 0
+  #'moh_flag'           => 0
+  #'talk_detect_flag'   => 0
+  #'play_user_cnt_flag' => 0
+  #'wait_for_admin'     => 0
+  #'stop_on_admin_exit' => 0
+  #'second_pin'         => 0
+  #'secondary_pin'      => 0,
+  #'allow_sub-conf'     => 0,
+  #'duration'           => 0,
+  #'conference_type' => 'reservation', #'reservationless',
+
+);
+
+ok( ! $return->{error}, 'addConference successful' ) or warn $return->{error};
+
+like($return->{conference_id}, qr/^(\d+)$/, 'addConference returned conference_id');
+
+my $conference_id = $return->{conference_id};
+
+# modifyConference (2)
+
+$return = $vb->modifyConference(
+  'conference_id'      => $conference_id,
+  'conference_name'    => 'Modified Test Conference',
+  'conference_desc'    => 'Net Indosoft Voicebridge modify test',
+  'start_time'         => '2010-20-04 16:20:00',
+  'moderated_flag'     => 1,
+  #'entry_ann_flag'     => 0
+  #'record_flag'        => 0
+  #'moh_flag'           => 0
+  #'talk_detect_flag'   => 0
+  #'play_user_cnt_flag' => 0
+  'wait_for_admin'     => 1
+  #'stop_on_admin_exit' => 0
+  #'second_pin'         => 0
+  #'secondary_pin'      => 0,
+  #'allow_sub-conf'     => 0,
+  #'duration'           => 0,
+  #'conference_type' => 'reservation', #'reservationless',
+
+);
+
+ok( ! $return->{error}, 'modifyConference successful' ) or warn $return->{error};
+
+is($return->{conference_id}, $conference_id, 'modifyConference returned conference_id')
+  or warn $return->{'return'};
+
+# deleteConference (2)
+
+$return = $vb->deleteConference(
+  'conference_id' => $conference_id,
+);
+
+ok( ! $return->{error}, 'deleteConference successful' )
+  or warn $return->{error}. "\n". $return->{return}. "\n";
+
+TODO: {
+
+  local $TODO = 'deleteConference always returns "Deleted successfully", even when nothing was';
+
+  $return = $vb->deleteConference(
+    'conference_id' => '999999999',
+  );
+
+  ok( $return->{error}, 'deleteConference bad data error test' )
+    ;#or warn $return->{return};
+
+}
+
+# deleteClient (2)
+
+$return = $vb->deleteClient(
+  'client_id' => $client_id,
+);
+
+ok( ! $return->{error}, 'deleteClient successful' )
+  or warn $return->{error}. "\n". $return->{return}. "\n";
+
+TODO: {
+
+  local $TODO = 'deleteClient always returns "Deleted successfully", even when nothing was';
+
+  $return = $vb->deleteClient(
+    'client_id' => '999999999',
+  );
+
+  ok( $return->{error}, 'deleteClient bad data error test' )
+    ;#or warn $return->{return};
+
+}
+
+# deleteAccount (2)
+
+$return = $vb->deleteAccount(
+  'account_id' => $account_id,
+);
+
+ok( ! $return->{error}, 'deleteAccount successful' )
+  or warn $return->{error}. "\n". $return->{return}. "\n";
+
+TODO: {
+
+  local $TODO = 'deleteAccount always returns "Deleted successfully", even when nothing was';
+
+  $return = $vb->deleteAccount(
+    'account_id' => '999999999',
+  );
+
+  ok( $return->{error}, 'deleteAccount bad data error test' )
+    ;#or warn $return->{return};
+
+}
diff --git a/t/pod-coverage.t b/t/pod-coverage.t
new file mode 100644 (file)
index 0000000..fc40a57
--- /dev/null
@@ -0,0 +1,18 @@
+use strict;
+use warnings;
+use Test::More;
+
+# Ensure a recent version of Test::Pod::Coverage
+my $min_tpc = 1.08;
+eval "use Test::Pod::Coverage $min_tpc";
+plan skip_all => "Test::Pod::Coverage $min_tpc required for testing POD coverage"
+    if $@;
+
+# Test::Pod::Coverage doesn't require a minimum Pod::Coverage version,
+# but older versions don't recognize some common documentation styles
+my $min_pc = 0.18;
+eval "use Pod::Coverage $min_pc";
+plan skip_all => "Pod::Coverage $min_pc required for testing POD coverage"
+    if $@;
+
+all_pod_coverage_ok();
diff --git a/t/pod.t b/t/pod.t
new file mode 100644 (file)
index 0000000..ee8b18a
--- /dev/null
+++ b/t/pod.t
@@ -0,0 +1,12 @@
+#!perl -T
+
+use strict;
+use warnings;
+use Test::More;
+
+# Ensure a recent version of Test::Pod
+my $min_tp = 1.22;
+eval "use Test::Pod $min_tp";
+plan skip_all => "Test::Pod $min_tp required for testing POD" if $@;
+
+all_pod_files_ok();
diff --git a/voicebridgeAPI.wsdl b/voicebridgeAPI.wsdl
new file mode 100644 (file)
index 0000000..5e1b921
--- /dev/null
@@ -0,0 +1,273 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<definitions xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tns="urn:voicebridgeAPI" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns="http://schemas.xmlsoap.org/wsdl/" targetNamespace="urn:voicebridgeAPI">
+<types>
+<xsd:schema targetNamespace="urn:voicebridgeAPI"
+>
+ <xsd:import namespace="http://schemas.xmlsoap.org/soap/encoding/" />
+ <xsd:import namespace="http://schemas.xmlsoap.org/wsdl/" />
+ <xsd:complexType name="Account">
+  <xsd:all>
+   <xsd:element name="account_id" type="xsd:int"/>
+   <xsd:element name="account_name" type="xsd:string"/>
+   <xsd:element name="account_desc" type="xsd:string"/>
+   <xsd:element name="account_addr" type="xsd:string"/>
+   <xsd:element name="account_city" type="xsd:string"/>
+   <xsd:element name="account_state" type="xsd:string"/>
+   <xsd:element name="account_country" type="xsd:string"/>
+   <xsd:element name="account_zip" type="xsd:string"/>
+   <xsd:element name="account_phone" type="xsd:string"/>
+   <xsd:element name="account_fax" type="xsd:string"/>
+   <xsd:element name="account_email" type="xsd:string"/>
+   <xsd:element name="account_password" type="xsd:string"/>
+  </xsd:all>
+ </xsd:complexType>
+ <xsd:complexType name="Client">
+  <xsd:all>
+   <xsd:element name="client_id" type="xsd:int"/>
+   <xsd:element name="account_id" type="xsd:int"/>
+   <xsd:element name="client_contact_name" type="xsd:string"/>
+   <xsd:element name="client_contact_addr" type="xsd:string"/>
+   <xsd:element name="client_contact_city" type="xsd:string"/>
+   <xsd:element name="client_contact_state" type="xsd:string"/>
+   <xsd:element name="client_contact_country" type="xsd:string"/>
+   <xsd:element name="client_contact_zip" type="xsd:string"/>
+   <xsd:element name="client_contact_phone" type="xsd:string"/>
+   <xsd:element name="client_contact_fax" type="xsd:string"/>
+   <xsd:element name="client_contact_email" type="xsd:string"/>
+   <xsd:element name="client_contact_password" type="xsd:string"/>
+  </xsd:all>
+ </xsd:complexType>
+ <xsd:complexType name="Conference">
+  <xsd:all>
+   <xsd:element name="conference_id" type="xsd:int"/>
+   <xsd:element name="client_id" type="xsd:int"/>
+   <xsd:element name="conference_name" type="xsd:string"/>
+   <xsd:element name="conference_desc" type="xsd:string"/>
+   <xsd:element name="start_time" type="xsd:string"/>
+   <xsd:element name="moderated_flag" type="xsd:int"/>
+   <xsd:element name="entry_ann_flag" type="xsd:int"/>
+   <xsd:element name="record_flag" type="xsd:int"/>
+   <xsd:element name="moh_flag" type="xsd:int"/>
+   <xsd:element name="talk_detect_flag" type="xsd:int"/>
+   <xsd:element name="play_user_cnt_flag" type="xsd:int"/>
+   <xsd:element name="wait_for_admin_flag" type="xsd:int"/>
+   <xsd:element name="stop_on_admin_exit_flag" type="xsd:int"/>
+   <xsd:element name="second_pin_flag" type="xsd:int"/>
+   <xsd:element name="allow_sub_conf" type="xsd:int"/>
+   <xsd:element name="secondary_pin" type="xsd:int"/>
+   <xsd:element name="duration" type="xsd:int"/>
+   <xsd:element name="conference_type" type="xsd:string"/>
+  </xsd:all>
+ </xsd:complexType>
+ <xsd:complexType name="DNIS">
+  <xsd:all>
+   <xsd:element name="dnis_id" type="xsd:int"/>
+   <xsd:element name="dnis" type="xsd:int"/>
+   <xsd:element name="line_name" type="xsd:string"/>
+   <xsd:element name="account_id" type="xsd:int"/>
+   <xsd:element name="client_id" type="xsd:int"/>
+   <xsd:element name="conference_id" type="xsd:int"/>
+   <xsd:element name="message_id" type="xsd:int"/>
+   <xsd:element name="dnis_action" type="xsd:string"/>
+   <xsd:element name="digit_timeout" type="xsd:int"/>
+   <xsd:element name="retries" type="xsd:int"/>
+   <xsd:element name="status" type="xsd:int"/>
+  </xsd:all>
+ </xsd:complexType>
+</xsd:schema>
+</types>
+<message name="addAccountRequest">
+  <part name="account" type="tns:Account" /></message>
+<message name="addAccountResponse">
+  <part name="return" type="xsd:string" /></message>
+<message name="modifyAccountRequest">
+  <part name="account" type="tns:Account" /></message>
+<message name="modifyAccountResponse">
+  <part name="return" type="xsd:string" /></message>
+<message name="addClientRequest">
+  <part name="client" type="tns:Client" /></message>
+<message name="addClientResponse">
+  <part name="return" type="xsd:string" /></message>
+<message name="modifyClientRequest">
+  <part name="client" type="tns:Client" /></message>
+<message name="modifyClientResponse">
+  <part name="return" type="xsd:string" /></message>
+<message name="addConferenceRequest">
+  <part name="conference" type="tns:Conference" /></message>
+<message name="addConferenceResponse">
+  <part name="return" type="xsd:string" /></message>
+<message name="modifyConferenceRequest">
+  <part name="conference" type="tns:Conference" /></message>
+<message name="modifyConferenceResponse">
+  <part name="return" type="xsd:string" /></message>
+<message name="deleteAccountRequest">
+  <part name="account_id" type="xsd:int" /></message>
+<message name="deleteAccountResponse">
+  <part name="return" type="xsd:string" /></message>
+<message name="deleteClientRequest">
+  <part name="client_id" type="xsd:int" /></message>
+<message name="deleteClientResponse">
+  <part name="return" type="xsd:string" /></message>
+<message name="deleteConferenceRequest">
+  <part name="conference_id" type="xsd:int" /></message>
+<message name="deleteConferenceResponse">
+  <part name="return" type="xsd:string" /></message>
+<message name="addConferencePINRequest">
+  <part name="conference_id" type="xsd:int" /></message>
+<message name="addConferencePINResponse">
+  <part name="return" type="xsd:string" /></message>
+<message name="modifyConferencePINRequest">
+  <part name="conference_id" type="xsd:int" />
+  <part name="new_pin" type="xsd:int" />
+  <part name="old_pin" type="xsd:int" /></message>
+<message name="modifyConferencePINResponse">
+  <part name="return" type="xsd:string" /></message>
+<message name="deleteConferencePINRequest">
+  <part name="conference_id" type="xsd:int" /></message>
+<message name="deleteConferencePINResponse">
+  <part name="return" type="xsd:string" /></message>
+<message name="addDNISRequest">
+  <part name="dnis" type="tns:DNIS" /></message>
+<message name="addDNISResponse">
+  <part name="return" type="xsd:string" /></message>
+<message name="modifyDNISRequest">
+  <part name="dnis" type="tns:DNIS" /></message>
+<message name="modifyDNISResponse">
+  <part name="return" type="xsd:string" /></message>
+<portType name="voicebridgeAPIPortType">
+  <operation name="addAccount">
+    <input message="tns:addAccountRequest"/>
+    <output message="tns:addAccountResponse"/>
+  </operation>
+  <operation name="modifyAccount">
+    <input message="tns:modifyAccountRequest"/>
+    <output message="tns:modifyAccountResponse"/>
+  </operation>
+  <operation name="addClient">
+    <input message="tns:addClientRequest"/>
+    <output message="tns:addClientResponse"/>
+  </operation>
+  <operation name="modifyClient">
+    <input message="tns:modifyClientRequest"/>
+    <output message="tns:modifyClientResponse"/>
+  </operation>
+  <operation name="addConference">
+    <input message="tns:addConferenceRequest"/>
+    <output message="tns:addConferenceResponse"/>
+  </operation>
+  <operation name="modifyConference">
+    <input message="tns:modifyConferenceRequest"/>
+    <output message="tns:modifyConferenceResponse"/>
+  </operation>
+  <operation name="deleteAccount">
+    <input message="tns:deleteAccountRequest"/>
+    <output message="tns:deleteAccountResponse"/>
+  </operation>
+  <operation name="deleteClient">
+    <input message="tns:deleteClientRequest"/>
+    <output message="tns:deleteClientResponse"/>
+  </operation>
+  <operation name="deleteConference">
+    <input message="tns:deleteConferenceRequest"/>
+    <output message="tns:deleteConferenceResponse"/>
+  </operation>
+  <operation name="addConferencePIN">
+    <input message="tns:addConferencePINRequest"/>
+    <output message="tns:addConferencePINResponse"/>
+  </operation>
+  <operation name="modifyConferencePIN">
+    <input message="tns:modifyConferencePINRequest"/>
+    <output message="tns:modifyConferencePINResponse"/>
+  </operation>
+  <operation name="deleteConferencePIN">
+    <input message="tns:deleteConferencePINRequest"/>
+    <output message="tns:deleteConferencePINResponse"/>
+  </operation>
+  <operation name="addDNIS">
+    <input message="tns:addDNISRequest"/>
+    <output message="tns:addDNISResponse"/>
+  </operation>
+  <operation name="modifyDNIS">
+    <input message="tns:modifyDNISRequest"/>
+    <output message="tns:modifyDNISResponse"/>
+  </operation>
+</portType>
+<binding name="voicebridgeAPIBinding" type="tns:voicebridgeAPIPortType">
+  <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
+  <operation name="addAccount">
+    <soap:operation soapAction="urn:voicebridgeAPI/addAccount" style="rpc"/>
+    <input><soap:body use="encoded" namespace="urn:voicebridgeAPI" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/></input>
+    <output><soap:body use="encoded" namespace="urn:voicebridgeAPI" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/></output>
+  </operation>
+  <operation name="modifyAccount">
+    <soap:operation soapAction="urn:voicebridgeAPI/modifyAccount" style="rpc"/>
+    <input><soap:body use="encoded" namespace="urn:voicebridgeAPI" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/></input>
+    <output><soap:body use="encoded" namespace="urn:voicebridgeAPI" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/></output>
+  </operation>
+  <operation name="addClient">
+    <soap:operation soapAction="urn:voicebridgeAPI/addClient" style="rpc"/>
+    <input><soap:body use="encoded" namespace="urn:voicebridgeAPI" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/></input>
+    <output><soap:body use="encoded" namespace="urn:voicebridgeAPI" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/></output>
+  </operation>
+  <operation name="modifyClient">
+    <soap:operation soapAction="urn:voicebridgeAPI/modifyClient" style="rpc"/>
+    <input><soap:body use="encoded" namespace="urn:voicebridgeAPI" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/></input>
+    <output><soap:body use="encoded" namespace="urn:voicebridgeAPI" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/></output>
+  </operation>
+  <operation name="addConference">
+    <soap:operation soapAction="urn:voicebridgeAPI/addConference" style="rpc"/>
+    <input><soap:body use="encoded" namespace="urn:voicebridgeAPI" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/></input>
+    <output><soap:body use="encoded" namespace="urn:voicebridgeAPI" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/></output>
+  </operation>
+  <operation name="modifyConference">
+    <soap:operation soapAction="urn:voicebridgeAPI/modifyConference" style="rpc"/>
+    <input><soap:body use="encoded" namespace="urn:voicebridgeAPI" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/></input>
+    <output><soap:body use="encoded" namespace="urn:voicebridgeAPI" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/></output>
+  </operation>
+  <operation name="deleteAccount">
+    <soap:operation soapAction="urn:voicebridgeAPI/deleteAccount" style="rpc"/>
+    <input><soap:body use="encoded" namespace="urn:voicebridgeAPI" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/></input>
+    <output><soap:body use="encoded" namespace="urn:voicebridgeAPI" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/></output>
+  </operation>
+  <operation name="deleteClient">
+    <soap:operation soapAction="urn:voicebridgeAPI/deleteClient" style="rpc"/>
+    <input><soap:body use="encoded" namespace="urn:voicebridgeAPI" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/></input>
+    <output><soap:body use="encoded" namespace="urn:voicebridgeAPI" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/></output>
+  </operation>
+  <operation name="deleteConference">
+    <soap:operation soapAction="urn:voicebridgeAPI/deleteConference" style="rpc"/>
+    <input><soap:body use="encoded" namespace="urn:voicebridgeAPI" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/></input>
+    <output><soap:body use="encoded" namespace="urn:voicebridgeAPI" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/></output>
+  </operation>
+  <operation name="addConferencePIN">
+    <soap:operation soapAction="urn:voicebridgeAPI/addConferencePIN" style="rpc"/>
+    <input><soap:body use="encoded" namespace="urn:voicebridgeAPI" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/></input>
+    <output><soap:body use="encoded" namespace="urn:voicebridgeAPI" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/></output>
+  </operation>
+  <operation name="modifyConferencePIN">
+    <soap:operation soapAction="urn:voicebridgeAPI/modifyConferencePIN" style="rpc"/>
+    <input><soap:body use="encoded" namespace="urn:voicebridgeAPI" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/></input>
+    <output><soap:body use="encoded" namespace="urn:voicebridgeAPI" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/></output>
+  </operation>
+  <operation name="deleteConferencePIN">
+    <soap:operation soapAction="urn:voicebridgeAPI/deleteConferencePIN" style="rpc"/>
+    <input><soap:body use="encoded" namespace="urn:voicebridgeAPI" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/></input>
+    <output><soap:body use="encoded" namespace="urn:voicebridgeAPI" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/></output>
+  </operation>
+  <operation name="addDNIS">
+    <soap:operation soapAction="urn:voicebridgeAPI/addDNIS" style="rpc"/>
+    <input><soap:body use="encoded" namespace="urn:voicebridgeAPI" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/></input>
+    <output><soap:body use="encoded" namespace="urn:voicebridgeAPI" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/></output>
+  </operation>
+  <operation name="modifyDNIS">
+    <soap:operation soapAction="urn:voicebridgeAPI/modifyDNIS" style="rpc"/>
+    <input><soap:body use="encoded" namespace="urn:voicebridgeAPI" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/></input>
+    <output><soap:body use="encoded" namespace="urn:voicebridgeAPI" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/></output>
+  </operation>
+</binding>
+<service name="voicebridgeAPI">
+  <port name="voicebridgeAPIPort" binding="tns:voicebridgeAPIBinding">
+    <soap:address location="http://nxxtcom1.kanobe.net:8080/vbsoap/voicebridgeAPI.php"/>
+  </port>
+</service>
+</definitions>
\ No newline at end of file