← Back to team overview

mvhub-dev team mailing list archive

[Merge] lp:~leegoodrich/mvhub/refactor_reminder_email into lp:mvhub

 

Lee Goodrich has proposed merging lp:~leegoodrich/mvhub/refactor_reminder_email into lp:mvhub.

Requested reviews:
  mvhub-dev (mvhub-dev)
Related bugs:
  #432738 Re-Factor Email Reminders
  https://bugs.launchpad.net/bugs/432738


Here's the big one. Automated email reminders, completely refactored. 
Comes with test suite for 100% code coverage.
-- 
https://code.launchpad.net/~leegoodrich/mvhub/refactor_reminder_email/+merge/22548
Your team mvhub-dev is subscribed to branch lp:mvhub.
=== removed file 'app-mvhub/bin/mvhub-reminders'
--- app-mvhub/bin/mvhub-reminders	2009-03-04 18:11:56 +0000
+++ app-mvhub/bin/mvhub-reminders	1970-01-01 00:00:00 +0000
@@ -1,9 +0,0 @@
-# $Revision: 1104 $
-# $Source$
-
-# goes in /etc/cron.d/
-
-MAILTO=root
-
-# minute |hour|day of month|month|day of week | user    | command 
-51         8    1,15         *     *           root       /home/sites/mvhub2/scripts/guide/reminder_email.pl

=== added file 'app-mvhub/bin/notification_email.pl'
--- app-mvhub/bin/notification_email.pl	1970-01-01 00:00:00 +0000
+++ app-mvhub/bin/notification_email.pl	2010-03-31 16:25:32 +0000
@@ -0,0 +1,153 @@
+#!/usr/bin/perl 
+
+# LICENSE: AGPLv3
+# CONTACT: joe@xxxxxxxxxx
+# PURPOSE: send emails to people asking people
+#          to update records that are old
+
+# goal is to send minimum number of emails to
+# each contact. Several out of date records
+# mean one email.
+
+use warnings;
+use strict;
+
+use Getopt::Long;
+
+use MVHub::Common;
+use MVHub::Utils;
+use MVHub::Utils::ConfigSimple qw/create_config_from/;
+use MVHub::Utils::DB qw/ get_dbh /;
+use MVHub::Notifications qw/
+    create_notifications_using
+    create_standard_notification_emails_with
+    create_fourth_notification_emails_with
+    create_final_notification_emails_with
+    die_if_record_count_mismatch
+    get_email_constants_from
+    get_expired_records
+    increment_reminder_levels_for
+    print_emails_contained_in
+    send_emails_contained_in
+    set_reminder_level_in_db
+    shift_off_notifications_with_reminder_count
+    /;
+
+# Config File Variables
+my $CFG
+    = MVHub::Utils::ConfigSimple::create_config_from( $ENV{MV_CONFIG_FILE} );
+
+my $MAX_NOTIFICATIONS = $CFG->param('NOTIFICATION.max_notifications');
+my $EXPIRE_MONTHS     = $CFG->param('NOTIFICATION.expire_months');
+
+my $DBH = MVHub::Utils::DB::get_dbh( $ENV{MV_CONFIG_FILE} );
+
+# Email Template Paths
+my $TEMPLATE_PATH = $CFG->param('ABSOLUTE_PATH.template_text_dir');
+
+my $FIRST_NOTIFICATION_TEMPLATE_FILE
+    = "$TEMPLATE_PATH/reminder_email_01.tmpl";
+my $SECOND_NOTIFICATION_TEMPLATE_FILE
+    = "$TEMPLATE_PATH/reminder_email_02.tmpl";
+my $THIRD_NOTIFICATION_TEMPLATE_FILE
+    = "$TEMPLATE_PATH/reminder_email_03.tmpl";
+my $FOURTH_NOTIFICATION_TEMPLATE_FILE
+    = "$TEMPLATE_PATH/reminder_email_04.tmpl";
+my $FINAL_NOTIFICATION_TEMPLATE_FILE
+    = "$TEMPLATE_PATH/reminder_email_05.tmpl";
+
+{    # Main
+    my $execute = 0;
+    Getopt::Long::GetOptions( 'execute' => \$execute )
+        or die "GetOptions failed";
+
+    my @date = Date::Calc::Add_Delta_YM( Date::Calc::Today(), 0,
+        -($EXPIRE_MONTHS) );
+    my $expire_date = join '-', @date;
+
+    my $expired_agency_records_aref = get_expired_records(
+        dbh               => $DBH,
+        max_notifications => $MAX_NOTIFICATIONS,
+        expire_date       => $expire_date,
+        query_name        => 'AGENCY_X_EXPIRED_RECORDS'
+    );
+
+    my $expired_program_records_aref = get_expired_records(
+        dbh               => $DBH,
+        max_notifications => $MAX_NOTIFICATIONS,
+        expire_date       => $expire_date,
+        query_name        => 'PROGRAM_X_EXPIRED_RECORDS'
+    );
+
+    my %all_notifications
+        = create_notifications_using($expired_program_records_aref);
+    %all_notifications
+        = create_notifications_using( $expired_agency_records_aref,
+        \%all_notifications );
+
+    die_if_record_count_mismatch(
+        'input_01' => $expired_agency_records_aref,
+        'input_02' => $expired_program_records_aref,
+        'output'   => \%all_notifications
+    );
+
+    my %final_notifications = shift_off_notifications_with_reminder_count( 4,
+        \%all_notifications );
+    my %fourth_notifications = shift_off_notifications_with_reminder_count( 3,
+        \%all_notifications );
+    my %third_notifications = shift_off_notifications_with_reminder_count( 2,
+        \%all_notifications );
+    my %second_notifications = shift_off_notifications_with_reminder_count( 1,
+        \%all_notifications );
+    my %first_notifications = shift_off_notifications_with_reminder_count( 0,
+        \%all_notifications );
+
+    MVHub::Utils::assert( !scalar(%all_notifications),
+        "Leftover notifications" );
+
+    my $email_constants_href
+        = MVHub::Notifications::get_email_constants_from($CFG);
+
+    my @emails;
+    push @emails,
+        create_standard_notification_emails_with(
+        $FIRST_NOTIFICATION_TEMPLATE_FILE,
+        $email_constants_href, %first_notifications );
+    push @emails,
+        create_standard_notification_emails_with(
+        $SECOND_NOTIFICATION_TEMPLATE_FILE,
+        $email_constants_href, %second_notifications );
+    push @emails,
+        create_standard_notification_emails_with(
+        $THIRD_NOTIFICATION_TEMPLATE_FILE,
+        $email_constants_href, %third_notifications );
+    push @emails,
+        create_fourth_notification_emails_with(
+        $FOURTH_NOTIFICATION_TEMPLATE_FILE,
+        $email_constants_href, %fourth_notifications );
+    push @emails,
+        create_final_notification_emails_with(
+        $FINAL_NOTIFICATION_TEMPLATE_FILE,
+        $email_constants_href, %final_notifications );
+
+    if ($execute) {
+        send_emails_contained_in(@emails);
+        %all_notifications = (
+            %first_notifications, %second_notifications,
+            %third_notifications, %fourth_notifications,
+            %final_notifications
+        );
+        increment_reminder_levels_for( \%all_notifications,
+            $MAX_NOTIFICATIONS );
+
+        $DBH->begin_work();
+        set_reminder_level_in_db( $DBH, $expire_date, \%all_notifications,
+            'AGENCY_X_REMINDERS_SENT' );
+        set_reminder_level_in_db( $DBH, $expire_date, \%all_notifications,
+            'PROGRAM_X_REMINDERS_SENT' );
+        $DBH->commit();
+    }
+    else {
+        print_emails_contained_in(@emails);
+    }
+}

=== added file 'app-mvhub/conf/sql_insert.lib'
--- app-mvhub/conf/sql_insert.lib	1970-01-01 00:00:00 +0000
+++ app-mvhub/conf/sql_insert.lib	2010-03-31 16:25:32 +0000
@@ -0,0 +1,59 @@
+[AGENCY_INSERT_RECORD]
+INSERT INTO agency 
+(
+ agency_id,
+ agency_name,
+ contact_email,
+ email,
+ last_updated,
+ reminders_sent, 
+ password,address1,city,state,zip,main_phone,hours,chief,
+ title,resource_library,meeting_space,staff_internet_access,
+ contact_first_name,contact_last_name,contact_phone,contact_email_available
+) 
+VALUES 
+(
+ ?,
+ ?,
+ ?,
+ ?,
+ ?,
+ ?,
+ 'magic word','address 1','city', 'state', 'zip','main_phone','hours','chief',
+ 'title','resource library','meeting space','staff_internet_access',
+ 'contact_first_name','contact_last_name','contact_phone','contact_email_available'
+);
+
+[PROGRAM_INSERT_RECORD]
+INSERT INTO program 
+(
+ agency_id,
+ program_id,
+ program_name, 
+ contact_email,
+ email,
+ last_updated,
+ reminders_sent, 
+ address1,city,state,zip,main_phone,hours,short_program_description,
+ long_program_description,eligibility,fees_charged,program_schedule,
+ languages,appointment,handicapped_accessible,child_care_provided,
+ wait_list,volunteer_opportunities,news_letter,public_net_access,
+ area_type,online_registration,area_type,area_data,resource_library,meeting_space,
+ contact_first_name,contact_last_name,contact_phone,contact_email_available
+) 
+VALUES 
+(
+ ?,
+ ?,
+ ?,
+ ?,
+ ?,
+ ?,
+ ?,
+ 'address 1','city', 'state', 'zip','main_phone','hours','short_program_description',
+ 'long_program_description','eligibility','fees_charged','program_schedule',
+ 'languages','appointment','handicapped_accessible','child_care_provided',
+ 'wait_list','volunteer_opportunities','news_letter','public_net_access',
+ 'area_type','online_registration','area_type','area_data','resource library','meeting space',
+ 'contact_first_name','contact_last_name','contact_phone', 'contact_email_available'
+);

=== modified file 'app-mvhub/conf/sql_select.lib'
--- app-mvhub/conf/sql_select.lib	2010-03-17 14:09:50 +0000
+++ app-mvhub/conf/sql_select.lib	2010-03-31 16:25:32 +0000
@@ -1,11 +1,14 @@
-# PURPOSE: contain all sql
+# PURPOSE: contain all sql select statements
 # LICENSE: AGPLv3 
 # CONTACT: joe@xxxxxxxxxx
 
 
 # queries should be named with this convention
 
-    # TABLE_NAME_TABLE_NAME_x_DESCRIPTION_OF_RETURNED_VALUES
+    # TABLE_NAME_TABLE_NAME_X_DESCRIPTION
+
+# If you can't think of a good description for the query, just list out
+# the returned values
 
 # The goals of this convention are:
 
@@ -58,6 +61,25 @@
   staff_internet_access, contact_email_available, tty_tdd
  FROM 
        agency
+
+[AGENCY_X_EXPIRED_RECORDS]
+   SELECT 
+      agency_id, 
+      agency_name, 
+      ( agency_name || ' (Main Agency Record)' ) AS record_name, 
+      contact_first_name, 
+      contact_last_name, 
+      contact_email,
+      contact_email as agency_contact_email,
+      email, 
+      last_updated, 
+      reminders_sent 
+   FROM 
+      agency 
+   WHERE 
+      last_updated < ?
+   AND
+      reminders_sent < ?
       
 [AGENCY_X_WEBSITE]       
 SELECT
@@ -112,6 +134,26 @@
 WHERE 
  hc.category_id =pc.category_id
  
+[PROGRAM_X_EXPIRED_RECORDS]
+   SELECT 
+      a.agency_id, 
+      a.agency_name,
+      a.contact_email as agency_contact_email,
+      a.email, 
+      p.program_name AS record_name, 
+      p.contact_first_name, 
+      p.contact_last_name, 
+      p.contact_email,
+      p.last_updated, 
+      p.reminders_sent 
+   FROM 
+	  agency a,
+      program p
+   WHERE 
+      a.agency_id=p.agency_id
+      AND p.last_updated < ?
+      AND p.reminders_sent < ?
+ 
 [PROGRAM_X_LAST_UPDATED]
 SELECT 
   last_updated 

=== added file 'app-mvhub/conf/sql_update.lib'
--- app-mvhub/conf/sql_update.lib	1970-01-01 00:00:00 +0000
+++ app-mvhub/conf/sql_update.lib	2010-03-31 16:25:32 +0000
@@ -0,0 +1,49 @@
+# PURPOSE: contain all sql update statements
+# LICENSE: AGPLv3 
+# CONTACT: joe@xxxxxxxxxx
+
+
+# queries should be named with this convention
+
+    # TABLE_NAME_TABLE_NAME_X_DESCRIPTION
+
+# If you can't think of a good description for the query, just list out
+# the returned values
+
+# The goals of this convention are:
+
+#      When queries are in this file sorted
+#      alphabetically, duplicate queries will
+#      jump out
+
+#      You should have a general idea of
+#      what the query does by looking at the name
+
+#      Keep query names relatively brief
+#      It is pointless to the query name be the same
+#      as the query sql
+
+#  We deliberately do not include WHERE clause
+#  info in the query name. Where clause
+#  info will be relatively obvious from the names of
+#  the variables passed in as placeholders to calls to DBI routines
+#  example:
+#    $dbi->selectallsomething('PROGRAM_X_LAST_UPDATED]',($progam _id);
+
+[AGENCY_X_REMINDERS_SENT]
+   UPDATE 
+      agency 
+   SET 
+      reminders_sent = ?
+   WHERE 
+      agency_id = ?
+      AND last_updated < ?
+
+[PROGRAM_X_REMINDERS_SENT]
+   UPDATE 
+      program 
+   SET 
+      reminders_sent = ?
+   WHERE 
+      agency_id = ?
+      AND last_updated < ?

=== added file 'app-mvhub/conf/templates/text/reminder_email_01.tmpl'
--- app-mvhub/conf/templates/text/reminder_email_01.tmpl	1970-01-01 00:00:00 +0000
+++ app-mvhub/conf/templates/text/reminder_email_01.tmpl	2010-03-31 16:25:32 +0000
@@ -0,0 +1,26 @@
+Hi <!--TMPL_VAR name="first_name"-->,
+
+I write to ask you to update your <!--TMPL_VAR name="agency_name"--> listing at <!--TMPL_VAR name="website_name"-->. You, or somebody in your organization, added your agency to our directory. 
+
+Our users say they trust up-to-date information. Therefore, we ask you to confirm your information every 6 months.
+
+Your listings below have not been updated in the past 6 months:
+
+<!--TMPL_VAR name="expired_records"-->
+
+So people trust your listing, even if you know that nothing needs to be changed, please click on:
+
+<!--TMPL_VAR name="quick_login_url"-->
+
+Click on the links with an hourglass icon to review the information, and then click the Submit button.
+
+If you are no longer a contact for <!--TMPL_VAR name="agency_name"--> or any of its programs, let us know and we will stop emailing you. You can reach us at:
+
+  <!--TMPL_VAR name="contact_us_email"--> 
+  <!--TMPL_VAR name="contact_us_phone"-->
+
+Please let us know if you have any other questions or concerns.
+	
+Thanks so much,
+<!--TMPL_VAR name="signer"-->
+<!--TMPL_VAR name="team_name"-->

=== added file 'app-mvhub/conf/templates/text/reminder_email_02.tmpl'
--- app-mvhub/conf/templates/text/reminder_email_02.tmpl	1970-01-01 00:00:00 +0000
+++ app-mvhub/conf/templates/text/reminder_email_02.tmpl	2010-03-31 16:25:32 +0000
@@ -0,0 +1,24 @@
+Hi <!--TMPL_VAR name="first_name"-->,
+
+A 1-2 weeks ago we sent you reminder asking if you could update your <!--TMPL_VAR name="agency_name"--> listing at <!--TMPL_VAR name="website_name"-->. You, or somebody in your organization, added your agency to our directory. 
+
+We know you are busy and we hate to bug you, but we want people to trust your listings below:
+
+<!--TMPL_VAR name="expired_records"-->
+
+Please click on:
+
+<!--TMPL_VAR name="quick_login_url"-->
+
+On the web page, the hourglass icons mark records to review.
+
+If you are no longer a contact for <!--TMPL_VAR name="agency_name"--> or any of its programs, let us know and we will stop emailing you. You can reach us at:
+
+  <!--TMPL_VAR name="contact_us_email"--> 
+  <!--TMPL_VAR name="contact_us_phone"-->
+
+Please let us know if you have questions or concerns.
+	
+Thank you very much. 
+<!--TMPL_VAR name="signer"-->
+<!--TMPL_VAR name="team_name"-->

=== added file 'app-mvhub/conf/templates/text/reminder_email_03.tmpl'
--- app-mvhub/conf/templates/text/reminder_email_03.tmpl	1970-01-01 00:00:00 +0000
+++ app-mvhub/conf/templates/text/reminder_email_03.tmpl	2010-03-31 16:25:32 +0000
@@ -0,0 +1,24 @@
+Hello <!--TMPL_VAR name="first_name"-->,
+
+On behalf of your listing at: <!--TMPL_VAR name="website_name"-->, I've emailed you a couple times now. 
+
+Are you the right contact person for these programs?  
+
+<!--TMPL_VAR name="expired_records"-->
+
+If nothing has changed, it will take less than 5 minutes to re-assure your visitors that your information is up to date. 
+
+When you have 5 minutes, please click the link below, click any links with an hourglass icon to review the information, and click the submit button to confirm.
+
+<!--TMPL_VAR name="quick_login_url"-->
+
+If you are no longer a contact for <!--TMPL_VAR name="agency_name"--> or any of its programs, let us know and we will stop emailing you. You can reach us at:
+
+  <!--TMPL_VAR name="contact_us_email"--> 
+  <!--TMPL_VAR name="contact_us_phone"-->
+
+Please let us know if you have any other questions or concerns.
+	
+Thanks,
+<!--TMPL_VAR name="signer"-->
+<!--TMPL_VAR name="team_name"-->

=== added file 'app-mvhub/conf/templates/text/reminder_email_04.tmpl'
--- app-mvhub/conf/templates/text/reminder_email_04.tmpl	1970-01-01 00:00:00 +0000
+++ app-mvhub/conf/templates/text/reminder_email_04.tmpl	2010-03-31 16:25:32 +0000
@@ -0,0 +1,28 @@
+Hello <!--TMPL_VAR name="first_name"-->,
+
+Sorry to keep bugging you. (this is the 4th time) 
+
+It would be really good if you could update your <!--TMPL_VAR name="agency_name"--> listing at <!--TMPL_VAR name="website_name"-->. 
+
+In case your email address is no longer valid or there is some other problem, the contact person for your agency record is CC'd.
+
+Our users trust up-to-date information. Therefore, we ask you to take 5 minutes to confirm your information. These listings are getting a bit old. 
+
+<!--TMPL_VAR name="expired_records"-->
+
+Please take 5 minutes to click on:
+
+<!--TMPL_VAR name="quick_login_url"-->
+
+Click on the links with an hourglass icon to review the information, and then click the Submit button.
+
+If you are no longer a contact for <!--TMPL_VAR name="agency_name"--> or any of its programs, let us know and we will stop emailing you. You can reach us at:
+
+  <!--TMPL_VAR name="contact_us_email"--> 
+  <!--TMPL_VAR name="contact_us_phone"-->
+
+Please let us know if you have any other questions or concerns.
+	
+Thank you,
+<!--TMPL_VAR name="signer"-->
+<!--TMPL_VAR name="team_name"-->

=== added file 'app-mvhub/conf/templates/text/reminder_email_05.tmpl'
--- app-mvhub/conf/templates/text/reminder_email_05.tmpl	1970-01-01 00:00:00 +0000
+++ app-mvhub/conf/templates/text/reminder_email_05.tmpl	2010-03-31 16:25:32 +0000
@@ -0,0 +1,26 @@
+Hi <!--TMPL_VAR name="first_name"-->,
+
+We've had some trouble reaching you about <!--TMPL_VAR name="agency_name"--> listing at <!--TMPL_VAR name="website_name"-->.
+
+Before we break down and pick up the telephone, we are trying this last email to all the email addresses we hope are good. 
+
+The following records still have not been updated.
+
+<!--TMPL_VAR name="expired_records"-->
+
+Please click the following link to update your out-of-date records (the ones with the hourglass icon). 
+
+<!--TMPL_VAR name="quick_login_url"-->
+
+Click on the links with an hourglass icon to review the information, and then click the Submit button.
+
+If you are receiving this email in error, let us know and we will stop emailing you. You can reach us at:
+
+  <!--TMPL_VAR name="contact_us_email"--> 
+  <!--TMPL_VAR name="contact_us_phone"-->
+
+Please let us know if you have any other questions or concerns.
+	
+Thanks so much,
+<!--TMPL_VAR name="signer"-->
+<!--TMPL_VAR name="team_name"-->

=== modified file 'app-mvhub/project-tools/templates/template.conf'
--- app-mvhub/project-tools/templates/template.conf	2010-03-23 17:11:37 +0000
+++ app-mvhub/project-tools/templates/template.conf	2010-03-31 16:25:32 +0000
@@ -102,6 +102,7 @@
 template_conf_dir=link-to-live-code/app-mvhub/project-tools/templates/
 template_html_dir=link-to-live-code/app-mvhub/conf/templates/html/
 template_text_dir=link-to-live-code/app-mvhub/conf/templates/text/
+project_tools_dir=link-to-live-code/app-mvhub/project-tools/
 reports_dir=reports/
 project_tools_dir=link-to-live-code/app-mvhub/project-tools/
 tmp_dir=tmp/

=== modified file 'app-mvhub/setup/etc/cron.d/mvhub-cron'
--- app-mvhub/setup/etc/cron.d/mvhub-cron	2010-02-08 22:13:12 +0000
+++ app-mvhub/setup/etc/cron.d/mvhub-cron	2010-03-31 16:25:32 +0000
@@ -19,4 +19,4 @@
     10    0    *             *     *           www-data   $BIN_DIR//generate_agency_program_pdf.pl $CONF_DIR/$MVH_CONF_FILE
     15    0    *             *     *           www-data   $BIN_DIR//generate_agency_program_pdf.pl $CONF_DIR/$NSP_CONF_FILE
 
-
+    51    8    1,15          *     *           www-data   $BIN_DIR/notification_email.pl --execute

=== modified file 'app-mvhub/t/sql_lib_sanity.t'
--- app-mvhub/t/sql_lib_sanity.t	2010-03-17 14:09:50 +0000
+++ app-mvhub/t/sql_lib_sanity.t	2010-03-31 16:25:32 +0000
@@ -37,6 +37,7 @@
 
 sub get_test_placeholders {
     my %data = (
+        'AGENCY_X_EXPIRED_RECORDS'                  => [ '01-01-2008', 0 ],
         'AGENCY_X_WEBSITE'                          => [103553],
         'AGENCY_PROGRAM_X_AGENCY_NAME_AGENCY_ALIAS' => [510296],
         'CATEGORY_PROGRAM_VIEW_HEADING_CATEGORY_X_ID_NAME_COUNT' =>
@@ -44,9 +45,9 @@
         'CATEGORY_X_CATEGORY_NAME' => [800363],
         'PROGRAM_AGENCY_PROGRAM_CATEGORY_X_AGENCY_NAME_ID_PROGRAM_NAME' =>
             [800363],
-        'PROGRAM_X_WEBSITE'      => [509548],
-        'PROGRAM_X_LAST_UPDATED' => [509548],
-
+        'PROGRAM_X_EXPIRED_RECORDS' => [ '01-01-2008', 0 ],
+        'PROGRAM_X_WEBSITE'         => [509548],
+        'PROGRAM_X_LAST_UPDATED'    => [509548],
     );
     return \%data;
 }

=== modified file 'lib-mvhub/lib/MVHub/AgencyAccount.pm'
--- lib-mvhub/lib/MVHub/AgencyAccount.pm	2010-02-01 23:43:18 +0000
+++ lib-mvhub/lib/MVHub/AgencyAccount.pm	2010-03-31 16:25:32 +0000
@@ -15,7 +15,7 @@
 
 use MVHub::AuthAccount;
 use MVHub::Common;
-use MVHub::Utils qw/ assert clean_cgi_params/;
+use MVHub::Utils qw/ assert clean_cgi_params generate_quick_login_url/;
 
 my $AGENCY_HOME_TEMPLATE_FILE  = 'agency_home.tmpl';
 my $AH_AGENCY_ID_TAG           = 'AGENCY_ID_TAG';
@@ -318,14 +318,10 @@
         = int $self->get_config_param('NOTIFICATION.expire_months');
     my $agency_needs_updating = has_expired( $last_update, 6 );
 
-    my $ql_id = encode_quick_login_id($agency_id);
-    my $quick_login_url
-        = "http://$ENV{HTTP_HOST}/cgi-bin/mvhub/agency.pl?rm=ql&id=$ql_id";;
-
-    # http://mvhub.com/cgi-bin/guide/agency.pl?rm=ql&id=10752872761
-
     $template->param(
-        'QUICK_LOGIN_URL'   => $quick_login_url,
+        'QUICK_LOGIN_URL' => MVHub::Utils::generate_quick_login_url(
+            $ENV{HTTP_HOST}, $agency_id
+        ),
         $AH_AGENCY_ID_TAG   => $agency_id,
         $AH_MESSAGE_TAG     => $message,
         $AH_AGENCY_NAME_TAG => $agency_href->{agency_name},

=== modified file 'lib-mvhub/lib/MVHub/Common.pm'
--- lib-mvhub/lib/MVHub/Common.pm	2010-03-12 15:34:38 +0000
+++ lib-mvhub/lib/MVHub/Common.pm	2010-03-31 16:25:32 +0000
@@ -32,6 +32,7 @@
     get_agency_select_list
     prepare_form
     print_page_and_exit
+    sendmail
     undelimit_field
 );
 

=== added file 'lib-mvhub/lib/MVHub/Notifications.pm'
--- lib-mvhub/lib/MVHub/Notifications.pm	1970-01-01 00:00:00 +0000
+++ lib-mvhub/lib/MVHub/Notifications.pm	2010-03-31 16:25:32 +0000
@@ -0,0 +1,469 @@
+package MVHub::Notifications;
+use strict;
+use warnings;
+
+use Carp;
+use Date::Calc;
+
+use MVHub::Common qw/sendmail/;
+use MVHub::Utils;
+use MVHub::Utils::DB qw/get_sql_select_statement get_sql_update_statement/;
+
+use HTML::Template;
+
+our ($VERSION) = '$Revision: 1334 $' =~ /([.\d]+)/;
+
+use base 'Exporter';
+our @EXPORT_OK = qw(
+    _push_and_return_programs
+    create_notifications_using
+    create_standard_notification_emails_with
+    create_fourth_notification_emails_with
+    create_final_notification_emails_with
+    die_if_record_count_mismatch
+    get_email_constants_from
+    get_expired_records
+    increment_reminder_levels_for
+    print_emails_contained_in
+    send_emails_contained_in
+    set_reminder_level_in_db
+    shift_off_notifications_with_reminder_count
+);
+
+sub _format_expired_record_list {
+    my $expired_records_aref = shift;
+    my $result = join "\n   ", @$expired_records_aref;
+    return "   $result";
+}
+
+sub _prepare_standard_body {
+    my $template_file        = shift;
+    my $email_constants_href = shift;
+    my $notice_href          = shift;
+
+    my $tmpl = HTML::Template->new( filename => $template_file );
+
+    my $quick_login_url
+        = MVHub::Utils::generate_quick_login_url(
+        $email_constants_href->{website_name},
+        $notice_href->{agency_id} );
+    my $expired_records
+        = _format_expired_record_list( $notice_href->{expired_record_list} );
+
+    $tmpl->param(
+        first_name       => $notice_href->{contact_first_name},
+        agency_name      => $notice_href->{agency_name},
+        contact_us_email => $email_constants_href->{admin_email},
+        contact_us_phone => $email_constants_href->{admin_phone},
+        expired_records  => $expired_records,
+        signer           => $email_constants_href->{admin_name},
+        team_name        => $email_constants_href->{team_name},
+        website_name     => $email_constants_href->{website_name},
+        quick_login_url  => $quick_login_url,
+    );
+
+    return $tmpl->output();
+}
+
+sub _prepare_standard_subject {
+    my $record_href = shift;
+
+    my %nth = (
+        0 => '',                   # This is supposed to be empty
+        1 => '2nd Reminder: ',
+        2 => '3rd Reminder: ',
+        3 => '4th Reminder: ',
+        4 => 'FINAL REMINDER: ',
+    );
+
+    my $subject = $nth{ $record_href->{reminders_sent} }
+        . "Time to update your $record_href->{agency_name} listing";
+
+    return $subject;
+}
+
+sub _push_and_return_programs {
+    scalar @_ == 2 or croak "Bad number of parameters";
+    my $programs_aref = shift || [];
+    croak "Parameter not an array reference" if ref $programs_aref ne 'ARRAY';
+
+    my $new_program = shift or croak 'Parameter $new_program not defined';
+    push @$programs_aref, $new_program;
+
+    return $programs_aref;
+}
+
+sub create_notifications_using {
+    ( scalar @_ == 1 || scalar @_ == 2 ) or croak "Bad number of parameters";
+
+    my $expired_records_aref = shift;
+    croak "First Parameter not an array reference"
+        if ref $expired_records_aref ne 'ARRAY';
+
+    my $notifications_href = shift || {};
+    croak "Second Parameter not a hash reference"
+        if ref $notifications_href ne 'HASH';
+
+    my %notifications_for = %$notifications_href;
+
+    foreach my $expired_record_href (@$expired_records_aref) {
+        my $unique_id = $expired_record_href->{'agency_id'}
+            . $expired_record_href->{'contact_email'};
+
+        $notifications_for{$unique_id}{'agency_id'}
+            = $expired_record_href->{'agency_id'};
+
+        $notifications_for{$unique_id}{'agency_name'}
+            = $expired_record_href->{'agency_name'};
+
+        $notifications_for{$unique_id}{'contact_email'}
+            = $expired_record_href->{'contact_email'};
+
+        $notifications_for{$unique_id}{'agency_contact_email'}
+            = $expired_record_href->{'agency_contact_email'};
+
+        $notifications_for{$unique_id}{'public_email'}
+            = $expired_record_href->{'email'};
+
+        $notifications_for{$unique_id}{'contact_first_name'}
+            = $expired_record_href->{'contact_first_name'};
+
+        $notifications_for{$unique_id}{'contact_last_name'}
+            = $expired_record_href->{'contact_last_name'};
+
+        $notifications_for{$unique_id}{expired_record_list}
+            = _push_and_return_programs(
+            $notifications_for{$unique_id}{expired_record_list},
+            $expired_record_href->{'record_name'} );
+
+        # To be sure we send only one email per contact person
+        # We set the reminder count for all records to the
+        # highest count found in any record.
+        if ( !( defined $notifications_for{$unique_id}{'reminders_sent'} )
+            || $expired_record_href->{'reminders_sent'}
+            > $notifications_for{$unique_id}{'reminders_sent'} )
+        {
+            $notifications_for{$unique_id}{reminders_sent}
+                = $expired_record_href->{'reminders_sent'};
+        }
+    }
+    return %notifications_for;
+}
+
+sub create_standard_notification_emails_with {
+    my $template_file        = shift;
+    my $email_constants_href = shift;
+    croak "Second parameter must be a hash reference"
+        if ref $email_constants_href ne 'HASH';
+    my %notifications = @_;
+    my @emails;
+
+    foreach my $unique_id ( keys %notifications ) {
+        my $to
+            = qq{$notifications{$unique_id}{contact_first_name} $notifications{$unique_id}{contact_last_name} <$notifications{$unique_id}{contact_email}>};
+
+        my $email = MIME::Entity->build(
+            Type => 'text/plain',
+            From => $email_constants_href->{from},
+            To   => $to,
+            Subject =>
+                _prepare_standard_subject( $notifications{$unique_id} ),
+            Data => _prepare_standard_body(
+                $template_file, $email_constants_href,
+                $notifications{$unique_id}
+            ),
+        );
+        push @emails, $email;
+    }
+    return @emails;
+}
+
+sub create_fourth_notification_emails_with {
+    my $template_file        = shift;
+    my $email_constants_href = shift;
+    croak "Second parameter must be a hash reference"
+        if ref $email_constants_href ne 'HASH';
+    my %notifications = @_;
+    my @emails;
+
+    foreach my $unique_id ( keys %notifications ) {
+        next
+            if $notifications{$unique_id}{'contact_email'} eq
+                $notifications{$unique_id}{'agency_contact_email'};
+
+        my $to
+            = qq{$notifications{$unique_id}{contact_first_name} $notifications{$unique_id}{contact_last_name} <$notifications{$unique_id}{contact_email}>};
+
+        my $email = MIME::Entity->build(
+            Type => 'text/plain',
+            From => $email_constants_href->{from},
+            To   => $to,
+            CC   => $notifications{$unique_id}{'agency_contact_email'},
+            Subject =>
+                _prepare_standard_subject( $notifications{$unique_id} ),
+            Data => _prepare_standard_body(
+                $template_file, $email_constants_href,
+                $notifications{$unique_id}
+            ),
+        );
+        push @emails, $email;
+    }
+    return @emails;
+}
+
+sub create_final_notification_emails_with {
+    my $template_file        = shift;
+    my $email_constants_href = shift;
+    croak "Second parameter must be a hash reference"
+        if ref $email_constants_href ne 'HASH';
+    my %notifications = @_;
+    my @emails;
+
+    foreach my $unique_id ( keys %notifications ) {
+        next
+            if !( defined $notifications{$unique_id}{'public_email'} )
+                || $notifications{$unique_id}{'contact_email'} eq
+                $notifications{$unique_id}{'public_email'};
+
+        my $contact_email
+            = qq{$notifications{$unique_id}{contact_first_name} $notifications{$unique_id}{contact_last_name} <$notifications{$unique_id}{contact_email}>};
+
+        my $email = MIME::Entity->build(
+            Type => 'text/plain',
+            From => $email_constants_href->{from},
+            To   => $notifications{$unique_id}{'public_email'},
+            CC   => $contact_email,
+            Subject =>
+                _prepare_standard_subject( $notifications{$unique_id} ),
+            Data => _prepare_standard_body(
+                $template_file, $email_constants_href,
+                $notifications{$unique_id}
+            ),
+        );
+        push @emails, $email;
+    }
+    return @emails;
+}
+
+sub die_if_record_count_mismatch {
+    my %param         = @_;
+    my $input_01_aref = $param{input_01};
+    my $input_02_aref = $param{input_02};
+    my $in_count      = scalar @$input_01_aref + scalar @$input_02_aref;
+
+    my $output_href = $param{output};
+    my $out_count   = 0;
+    foreach my $k ( keys %$output_href ) {
+        my $expired_record_list = $$output_href{$k}{expired_record_list};
+        $out_count += scalar @$expired_record_list;
+    }
+    MVHub::Utils::assert(
+        $out_count == $in_count,
+        "\n****Record count mismatch****\n input records = $in_count\n output records = $out_count"
+    );
+}
+
+sub get_email_constants_from {
+    my $cfg = shift;
+    croak "Parameter must be a Config::Simple object"
+        if ref $cfg ne 'Config::Simple';
+
+    my %email_constants;
+    my $error_msg = '';
+
+    $email_constants{admin_name} = $cfg->param('NOTIFICATION.admin_name')
+        or $error_msg .= "\tNOTIFICATION.admin_name\n";
+    $email_constants{admin_email} = $cfg->param('NOTIFICATION.admin_email')
+        or $error_msg .= "\tNOTIFICATION.admin_email\n";
+    $email_constants{admin_phone} = $cfg->param('NOTIFICATION.admin_phone')
+        or $error_msg .= "\tNOTIFICATION.admin_phone\n";
+    $email_constants{team_name} = $cfg->param('NOTIFICATION.team_name')
+        or $error_msg .= "\tNOTIFICATION.team_name\n";
+    $email_constants{website_name} = $cfg->param('SITE.website_name')
+        or $error_msg .= "\tSITE.website_name\n";
+
+    my $config_filename = $cfg->param('META.this_config_file');
+
+    if ($error_msg) {
+        $error_msg
+            = "config file: ${config_filename}\nmissing keys: \n$error_msg";
+        croak $error_msg;
+    }
+
+    $email_constants{from}
+        = "$email_constants{admin_name} <$email_constants{admin_email}>";
+
+    return \%email_constants;
+}
+
+sub get_expired_records {
+    scalar @_ == 8
+        or croak "Bad number of parameters";
+    my %param = @_;
+
+    my $sql
+        = MVHub::Utils::DB::get_sql_select_statement( $param{'query_name'} );
+
+    my @bind_variables
+        = ( $param{'expire_date'}, $param{'max_notifications'} );
+
+    return $param{'dbh'}
+        ->selectall_arrayref( $sql, { Slice => {} }, @bind_variables );
+}
+
+sub increment_reminder_levels_for {
+    scalar @_ == 2 or croak "Bad number of parameters";
+    my $reminders_href    = shift;
+    my $max_notifications = shift;
+    foreach my $unique_id ( keys %$reminders_href ) {
+        warn
+            "SHOULDN'T SEE THIS: Reminder count greater than MAX_REMINDER for $unique_id\n"
+            if ( $reminders_href->{$unique_id}{'reminders_sent'}
+            >= $max_notifications );
+        $reminders_href->{$unique_id}{'reminders_sent'} += 1;
+    }
+}
+
+sub print_emails_contained_in {
+    my @emails = @_;
+    foreach my $email (@emails) {
+        print $email->as_string();
+    }
+}
+
+sub send_emails_contained_in {
+    my @emails = @_;
+    foreach my $email (@emails) {
+        MVHub::Common::sendmail($email);
+    }
+}
+
+sub set_reminder_level_in_db {
+    scalar @_ == 4
+        or croak "Bad number of parameters";
+    my $dbh            = shift;
+    my $expire_date    = shift;
+    my $reminders_href = shift;
+    my $statement      = shift;
+    my @bind_variables;
+
+    my $sql = MVHub::Utils::DB::get_sql_update_statement($statement);
+
+    foreach my $unique_id ( keys %$reminders_href ) {
+        @bind_variables = (
+            $reminders_href->{$unique_id}{'reminders_sent'},
+            $reminders_href->{$unique_id}{'agency_id'}, $expire_date
+        );
+        $dbh->do( $sql, undef, @bind_variables );
+    }
+}
+
+sub shift_off_notifications_with_reminder_count {
+    scalar @_ == 2
+        or croak "Bad number of parameters";
+
+    my $reminder_count     = shift;
+    my $notifications_href = shift;
+    croak "Second Parameter not a hash reference"
+        if ref $notifications_href ne 'HASH';
+
+    my %shifted_reminders;
+
+    foreach my $unique_id ( keys %$notifications_href ) {
+        if ( $$notifications_href{$unique_id}{reminders_sent}
+            == $reminder_count )
+        {
+            $shifted_reminders{$unique_id}
+                = delete $$notifications_href{$unique_id};
+        }
+    }
+    return %shifted_reminders;
+}
+
+1;
+__END__
+
+=head1 NAME
+ 
+MVHub::Notifications - subs that build lists of emails and related info needed to send notifications of out-of-date records.
+ 
+ 
+=head1 VERSION
+ 
+This documentation refers to MVHub::Notifications version $Revision: 1334 $
+ 
+=head1 SYNOPSIS
+ 
+    use MVHub::Notifications;
+    # Brief but working code example(s) here showing the most common usage(s)
+ 
+    # This section will be as far as many users bother reading
+    # so make it as educational and exemplary as possible.
+  
+  
+=head1 DESCRIPTION
+ 
+subs used by notification_email.pl and tested in notification_email.t
+ 
+ 
+=head1 SUBROUTINES/METHODS 
+ 
+A separate section listing the public components of the module's interface. 
+These normally consist of either subroutines that may be exported, or methods
+that may be called on objects belonging to the classes that the module provides.
+Name the section accordingly.
+ 
+In an object-oriented module, this section should begin with a sentence of the 
+form "An object of this class represents...", to give the reader a high-level
+context to help them understand the methods that are subsequently described.
+ 
+ 
+=head1 DIAGNOSTICS
+ 
+A list of every error and warning message that the module can generate
+(even the ones that will "never happen"), with a full explanation of each 
+problem, one or more likely causes, and any suggested remedies.
+(See also  QUOTE \" " INCLUDETEXT "13_ErrorHandling" "XREF83683_Documenting_Errors_"\! Documenting Errors QUOTE \" " QUOTE " in Chapter "  in Chapter  INCLUDETEXT "13_ErrorHandling" "XREF40477__"\! 13.)
+ 
+ 
+=head1 CONFIGURATION AND ENVIRONMENT
+ 
+A full explanation of any configuration system(s) used by the module,
+including the names and locations of any configuration files, and the
+meaning of any environment variables or properties that can be set. These
+descriptions must also include details of any configuration language used.
+(also see  QUOTE \" " INCLUDETEXT "19_Miscellanea" "XREF40334_Configuration_Files_"\! Configuration Files QUOTE \" " QUOTE " in Chapter "  in Chapter  INCLUDETEXT "19_Miscellanea" "XREF55683__"\! 19.)
+ 
+ 
+=head1 DEPENDENCIES
+ 
+A list of all the other modules that this module relies upon, including any
+restrictions on versions, and an indication whether these required modules are
+part of the standard Perl distribution, part of the module's distribution,
+or must be installed separately.
+ 
+ 
+=head1 INCOMPATIBILITIES
+ 
+There are no known incompatibilites
+ 
+=head1 BUGS AND LIMITATIONS
+ 
+There are no known bugs in this module. 
+Please report problems to help@xxxxxxxxxx
+Patches are welcome.
+ 
+=head1 AUTHOR
+
+Community Software Lab help@xxxxxxxxxx 
+ 
+=head1 LICENCE AND COPYRIGHT
+
+Copyright (c) 2004-2008 Community Software Lab, Inc ( http://thecsl.org )
+
+The GNU Affero General Public License, version 3.0 or later
+
+
+=cut
+ 
+

=== modified file 'lib-mvhub/lib/MVHub/Utils.pm'
--- lib-mvhub/lib/MVHub/Utils.pm	2010-01-20 19:56:11 +0000
+++ lib-mvhub/lib/MVHub/Utils.pm	2010-03-31 16:25:32 +0000
@@ -15,6 +15,8 @@
     choose_default_or_custom_file
     clean_cgi_params
     die_if_missing_env_vars
+    encode_quick_login_id
+    generate_quick_login_url
     mm_dd_yyyy_to_yyyy_mm_dd
     parse_zip_code
     trim
@@ -90,6 +92,19 @@
     }
 }
 
+# Creates a quick log-in id from an agency id
+sub encode_quick_login_id {
+    my $agency_id = shift or croak "Missing Param: agency_id";
+    return $agency_id**2 + 12345;
+}
+
+sub generate_quick_login_url {
+    my $website_name = shift or croak "Missing Param: website_name\n";
+    my $agency_id    = shift or croak "Missing Param: agency_id\n";
+    my $ql_id = encode_quick_login_id($agency_id);
+    return "http://${website_name}/cgi-bin/mvhub/agency.pl?rm=ql&id=$ql_id";;
+}
+
 sub parse_zip_code {
     my $zip_code = shift;
     if ( defined $zip_code && $zip_code =~ /(\d{5})/xm ) {

=== modified file 'lib-mvhub/lib/MVHub/Utils/DB.pm'
--- lib-mvhub/lib/MVHub/Utils/DB.pm	2010-03-17 14:33:14 +0000
+++ lib-mvhub/lib/MVHub/Utils/DB.pm	2010-03-31 16:25:32 +0000
@@ -7,7 +7,9 @@
 use Carp;
 use DBI;
 
+use MVHub::Utils::ConfigSimple;
 use MVHub::Utils qw/assert undelimit_field /;
+
 use SQL::Library;
 
 use base 'Exporter';
@@ -19,7 +21,9 @@
     get_hash_of_agencies
     get_next_in_sequence
     get_program_name
+    get_sql_insert_statement
     get_sql_select_statement
+    get_sql_update_statement
     insert_one_record
     remove_agency
     select_agency_programs
@@ -151,19 +155,40 @@
     return $program_name;
 }
 
-# Checks a record's update status, as this
-# affects how data entry users can interact with it.
+sub get_sql_insert_statement {
+    my $statement_name = shift
+        or croak "missing parameter: \$statement_name\n";
+    my $cfg = MVHub::Utils::ConfigSimple::create_config_from(
+        $ENV{MV_CONFIG_FILE} );
+    my $conf_path = $cfg->param('ABSOLUTE_PATH.conf_dir');
+    my $file      = "$conf_path/sql_insert.lib";
+    my $sql_lib   = new SQL::Library { lib => $file };
+
+    return $sql_lib->retr($statement_name);
+}
 
 sub get_sql_select_statement {
     my $statement_name = shift
         or croak "missing parameter: \$statement_name\n";
-    my $file
-        = "/var/www/mvhub/$ENV{USER}/link-to-live-code/app-mvhub/conf/sql_select.lib";
-    my $sql_lib = new SQL::Library { lib => $file };
-
-    my $result = $sql_lib->retr($statement_name)
-        or croak "Couldn't get sql for $statement_name";
-    return $result;
+    my $cfg = MVHub::Utils::ConfigSimple::create_config_from(
+        $ENV{MV_CONFIG_FILE} );
+    my $conf_path = $cfg->param('ABSOLUTE_PATH.conf_dir');
+    my $file      = "$conf_path/sql_select.lib";
+    my $sql_lib   = new SQL::Library { lib => $file };
+
+    return $sql_lib->retr($statement_name);
+}
+
+sub get_sql_update_statement {
+    my $statement_name = shift
+        or croak "missing parameter: \$statement_name\n";
+    my $cfg = MVHub::Utils::ConfigSimple::create_config_from(
+        $ENV{MV_CONFIG_FILE} );
+    my $conf_path = $cfg->param('ABSOLUTE_PATH.conf_dir');
+    my $file      = "$conf_path/sql_update.lib";
+    my $sql_lib   = new SQL::Library { lib => $file };
+
+    return $sql_lib->retr($statement_name);
 }
 
 #################################

=== modified file 'lib-mvhub/t/00-setup.t'
--- lib-mvhub/t/00-setup.t	2009-12-18 18:09:42 +0000
+++ lib-mvhub/t/00-setup.t	2010-03-31 16:25:32 +0000
@@ -15,7 +15,10 @@
 use Test::More;
 
 use MVHub::Utils::ConfigSimple qw/create_config_from/;
-use MVHub::Utils::DB qw/get_data_source/;
+use MVHub::Utils::DB qw/ get_dbh /;
+
+use TestData qw/@agency @program/;
+use TestHelper qw/ add_test_data create_sqlite_db /;
 
 eval ' use Test::NoWarnings; ';
 if ($@) {
@@ -42,61 +45,9 @@
 ########### setup
 
 {
-    my $dbh = get_test_dbh( $ENV{MV_TEST_CONFIG_FILE} );
-    my $cfg = MVHub::Utils::ConfigSimple::create_config_from(
-        $ENV{MV_TEST_CONFIG_FILE} );
+    my $dbh = MVHub::Utils::DB::get_dbh( $ENV{MV_TEST_CONFIG_FILE} );
 
-    create_db( $dbh, $cfg->param('ABSOLUTE_PATH.conf_dir') )
+    TestHelper::create_sqlite_db($dbh)
         or die "failed to setup test database\n";
-    add_data($dbh);
-}
-
-sub add_data {
-    my $dbh = shift;
-    my $sql = <<SQL;
-INSERT INTO agency 
-(
- agency_id, last_updated, 
- password,agency_name, address1,
- city,state,zip,
- main_phone,hours,chief,
- title,resource_library,meeting_space,
- staff_internet_access,contact_first_name,
- contact_last_name,contact_phone, contact_email,
- contact_email_available
-) 
-VALUES 
-   (
-    1,'2008-12-02', 'magic word','Testing Group','address 1',
-    'city', 'state', 'zip',
-    'main_phone','hours','chief',
-    'title','resource library','meeting space',
-    'staff_internet_access','contact_first_name',
-    'contact_last_name','contact_phone', 'dan\@thecsl.org',
-    'contact_email_available'
-   );
-SQL
-    $dbh->do($sql);
-}
-
-sub create_db {
-    my ( $dbh, $conf_dir ) = @_;
-
-    my $sql_lib
-        = SQL::Library->new( { lib => "$conf_dir/create_sqlite.lib" } );
-
-    foreach my $sql_label ( sort $sql_lib->elements() ) {
-        my $sql = $sql_lib->retr($sql_label);
-        $dbh->do($sql);
-    }
-    return 1;
-}
-
-sub get_test_dbh {
-    my $config_file = shift;
-    my $data_source = MVHub::Utils::DB::get_data_source($config_file);
-    my $dbh         = DBI->connect($data_source);
-
-    return $dbh;
-}
-
+    TestHelper::add_test_data($dbh);
+}

=== modified file 'lib-mvhub/t/01-load.t'
--- lib-mvhub/t/01-load.t	2009-09-15 01:12:17 +0000
+++ lib-mvhub/t/01-load.t	2010-03-31 16:25:32 +0000
@@ -1,5 +1,7 @@
 #!/usr/bin/perl 
 
+# PURPOSE: do a 'use MVHub::ModuleName' on all our produciton modules
+
 use strict;
 use warnings;
 use Test::More;
@@ -26,7 +28,9 @@
 }
 
 sub get_modules {
-    my @raw_modules = grep { m/lib/; } @_;
+
+    # anything in lib but not in /t/
+    my @raw_modules = grep { $_ =~ m/lib/ && $_ !~ m|/t/|; } @_;
     my @modules;
 
     foreach my $file (@raw_modules) {

=== added directory 'lib-mvhub/t/Notifications'
=== added file 'lib-mvhub/t/Notifications.t.old'
--- lib-mvhub/t/Notifications.t.old	1970-01-01 00:00:00 +0000
+++ lib-mvhub/t/Notifications.t.old	2010-03-31 16:25:32 +0000
@@ -0,0 +1,123 @@
+#!/usr/bin/perl -w
+
+# $Source$
+# $Revision: 1209 $
+
+use strict;
+use warnings;
+
+# EDIT
+use Test::More tests => 23 + 1;    # +1 for NoWarnings
+use Test::NoWarnings;
+use Test::Exception;
+use Config::Simple;
+use MVHub::Notifications qw(
+    _push_and_return_programs
+    create_notifications_using
+    create_standard_notification_emails_with
+    create_fourth_notification_emails_with
+    create_final_notification_emails_with
+    die_if_record_count_mismatch
+    get_expired_agency_records_given
+    get_expired_program_records_given
+    print_emails_contained_in
+    send_emails_contained_in
+    set_reminder_level_for
+    shift_off_notifications_with_reminder_count
+);
+
+BEGIN {
+    use FindBin qw($Bin);
+    chdir $Bin;
+}
+
+my $test_message;
+my $result = 1;
+
+# Config File Variables
+my $CFG = new Config::Simple( $ENV{MV_CONFIG_FILE} );
+
+my $ADMIN_EMAIL   = $CFG->param('NOTIFICATION.admin_email');
+my $ADMIN_PHONE   = $CFG->param('NOTIFICATION.admin_phone');
+my $ADMIN_NAME    = $CFG->param('NOTIFICATION.admin_name');
+my $TEAM_NAME     = $CFG->param('NOTIFICATION.team_name');
+my $EXPIRE_MONTHS = $CFG->param('NOTIFICATION.expire_months');
+
+my $ADMIN_NAME_AND_EMAIL = $ADMIN_NAME . '<' . $ADMIN_EMAIL . '>';
+
+###
+
+
+$test_message;
+
+# _push_and_return_programs
+
+$test_message = '_push_and_return_programs: dies on > 2 parameters';
+dies_ok { _push_and_return_programs(qw/1 2 3 4/) } $test_message;
+
+$test_message = '_push_and_return_programs: dies on < 2 parameters';
+dies_ok { _push_and_return_programs(1) } $test_message;
+
+$test_message = '_push_and_return_programs: dies on undefined 2nd parameter';
+dies_ok {
+    _push_and_return_programs( $agency_dummy_data[0]{define_me}, undef );
+}
+$test_message;
+
+$test_message
+    = '_push_and_return_programs: handles undefined array reference';
+lives_ok {
+    $agency_dummy_data[0]{define_me}
+        = _push_and_return_programs( $agency_dummy_data[0]{define_me},
+        "pushme" );
+}
+
+$test_message
+    = '_push_and_return_programs: creates array ref from undefined parameter';
+is ref $agency_dummy_data[0]{define_me}, 'ARRAY', $test_message;
+
+$test_message = '_push_and_return_programs: successfully pushes';
+is $agency_dummy_data[0]{define_me}[0], 'pushme', $test_message;
+
+$test_message = '_push_and_return_programs: handles defined array reference';
+lives_ok {
+    $agency_dummy_data[0]{define_me}
+        = _push_and_return_programs( $agency_dummy_data[0]{define_me},
+        "pushme_again" );
+}
+$test_message;
+
+$test_message = '_push_and_return_programs: successfully pushes again';
+is $agency_dummy_data[0]{define_me}[1], 'pushme_again', $test_message;
+
+###
+
+__DATA__
+
+	Tests to implement:
+
+Tests for _push_and_return_programs
+
+Tests for get_expired_agency_records_given:
+- Check that records are actually out of date as defined by $EXPIRE_MONTHS and $MAX_REMINDERS
+
+Tests for get_expired_program_records_given:
+- Check that records are actually out of date as defined by $EXPIRE_MONTHS and $MAX_REMINDERS
+
+Tests for create_notifications_using:
+
+Tests for shift_off_notifications_with_reminder_count:
+
+Tests for create_standard_notification_emails_with:
+- Check that two parameters are being passed in
+- Creates valid emails.
+
+	Use Cases to Perform:
+- Expired and non-expired agency and program records
+- Mix of reminder levels
+- Contacts with only an agency, only programs, and both agency and programs out-of-date.
+- Contacts with blank emails
+- Contact email is not 'unknown' or 'noemail'
+- Contact email contains an @ sign
+- Type is either 'program' or 'agency'
+

=== added file 'lib-mvhub/t/Notifications/_push_and_return_programs.t'
--- lib-mvhub/t/Notifications/_push_and_return_programs.t	1970-01-01 00:00:00 +0000
+++ lib-mvhub/t/Notifications/_push_and_return_programs.t	2010-03-31 16:25:32 +0000
@@ -0,0 +1,83 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use HTML::Template;
+
+use Test::More tests => 9 + 1;    # +1 for NoWarnings
+use Test::NoWarnings;
+use Test::Exception;
+use Test::Warn;
+
+use TestData qw/ @agency /;
+use MVHub::Notifications qw/ _push_and_return_programs /;
+
+BEGIN {
+    use FindBin qw($Bin);
+    chdir $Bin;
+}
+
+my $test_message;
+my @agency_dummy_data = @TestData::agency;
+
+###
+$test_message = 'dies on > 2 parameters';
+###
+dies_ok { _push_and_return_programs(qw/1 2 3 4/) } $test_message;
+
+###
+$test_message = 'dies on < 2 parameters';
+###
+dies_ok { _push_and_return_programs(1) } $test_message;
+
+###
+$test_message = 'dies when 1st parameter not an array reference';
+###
+dies_ok {
+    _push_and_return_programs( 1, "pushme" );
+}
+$test_message;
+
+###
+$test_message = 'dies on undefined 2nd parameter';
+###
+dies_ok {
+    _push_and_return_programs( $agency_dummy_data[0]{define_me}, undef );
+}
+$test_message;
+
+###
+$test_message = 'handles undefined array reference';
+###
+lives_ok {
+    $agency_dummy_data[0]{define_me}
+        = _push_and_return_programs( $agency_dummy_data[0]{define_me},
+        "pushme" );
+}
+$test_message;
+
+###
+$test_message = 'creates array ref from undefined parameter';
+###
+is ref $agency_dummy_data[0]{define_me}, 'ARRAY', $test_message;
+
+###
+$test_message = 'successfully pushes';
+###
+is $agency_dummy_data[0]{define_me}[0], 'pushme', $test_message;
+
+###
+$test_message = 'handles defined array reference';
+###
+lives_ok {
+    $agency_dummy_data[0]{define_me}
+        = _push_and_return_programs( $agency_dummy_data[0]{define_me},
+        "pushme_again" );
+}
+$test_message;
+
+###
+$test_message = 'successfully pushes again';
+###
+is $agency_dummy_data[0]{define_me}[1], 'pushme_again', $test_message;

=== added file 'lib-mvhub/t/Notifications/create_final_notification_emails_with.t'
--- lib-mvhub/t/Notifications/create_final_notification_emails_with.t	1970-01-01 00:00:00 +0000
+++ lib-mvhub/t/Notifications/create_final_notification_emails_with.t	2010-03-31 16:25:32 +0000
@@ -0,0 +1,85 @@
+#!/usr/bin/perl 
+
+# $Source$
+# $Revision: 1209 $
+
+use strict;
+use warnings;
+
+BEGIN {
+    use FindBin qw($Bin);
+    chdir $Bin;
+}
+
+use Test::More tests => 5 + 1;    # +1 for NoWarnings
+use Test::NoWarnings;
+use Test::Exception;
+
+use TestData qw/ notifications_href /;
+
+use MVHub::Utils::ConfigSimple qw/ create_config_from /;
+use MVHub::Notifications qw/ create_final_notification_emails_with
+    get_email_constants_from /;
+
+my $test_message;
+my @emails;
+my %test_notifications = %$TestData::notifications_href;
+
+####
+$test_message = 'dies without hash reference parameter';
+####
+dies_ok { create_final_notification_emails_with( 1, 2, 3 ) } $test_message;
+
+####
+$test_message = 'dies on bad template file parameter';
+####
+my $cfg = MVHub::Utils::ConfigSimple::create_config_from(
+    $ENV{MV_TEST_CONFIG_FILE} );
+my $email_constants_href
+    = MVHub::Notifications::get_email_constants_from($cfg);
+
+my $template_file = "BAD_FILE.tmpl";
+
+dies_ok {
+    create_final_notification_emails_with( $template_file,
+        $email_constants_href, %test_notifications );
+}
+$test_message;
+
+####
+$test_message = 'Skips notification if contact and public email are the same';
+####
+my $template_path = $cfg->param('ABSOLUTE_PATH.template_text_dir');
+$template_file = "$template_path/reminder_email_01.tmpl";
+
+$test_notifications{'1001joeschmoe@xxxxxxxxxxxxxx'}{'public_email'}
+    = 'joe_schmoe@xxxxxxxxxxxxxx';
+
+push @emails,
+    create_final_notification_emails_with( $template_file,
+    $email_constants_href, %test_notifications );
+is( @emails, 4, $test_message );
+
+####
+$test_message = 'Skips notification if public_email does not exist';
+####
+@emails = ();
+$test_notifications{'1001joeschmoe@xxxxxxxxxxxxxx'}{'public_email'} = undef;
+
+push @emails,
+    create_final_notification_emails_with( $template_file,
+    $email_constants_href, %test_notifications );
+is( @emails, 4, $test_message );
+
+####
+$test_message = 'Otherwise, process notification';
+####
+@emails = ();
+$test_notifications{'1001joeschmoe@xxxxxxxxxxxxxx'}{'public_email'}
+    = 'notjoe@xxxxxxxxxxxxxx';
+
+push @emails,
+    create_final_notification_emails_with( $template_file,
+    $email_constants_href, %test_notifications );
+is( @emails, 5, $test_message );
+

=== added file 'lib-mvhub/t/Notifications/create_fourth_notification_emails_with.t'
--- lib-mvhub/t/Notifications/create_fourth_notification_emails_with.t	1970-01-01 00:00:00 +0000
+++ lib-mvhub/t/Notifications/create_fourth_notification_emails_with.t	2010-03-31 16:25:32 +0000
@@ -0,0 +1,74 @@
+#!/usr/bin/perl 
+
+# $Source$
+# $Revision: 1209 $
+
+use strict;
+use warnings;
+
+BEGIN {
+    use FindBin qw($Bin);
+    chdir $Bin;
+}
+
+use Test::More tests => 4 + 1;    # +1 for NoWarnings
+use Test::NoWarnings;
+use Test::Exception;
+
+use TestData qw/ notifications_href /;
+
+use MVHub::Utils::ConfigSimple qw/ create_config_from /;
+use MVHub::Notifications qw/ create_fourth_notification_emails_with
+    get_email_constants_from /;
+
+my $test_message;
+my @emails;
+my %test_notifications = %$TestData::notifications_href;
+
+####
+$test_message = 'dies without hash reference parameter';
+####
+dies_ok { create_fourth_notification_emails_with( 1, 2, 3 ) } $test_message;
+
+####
+$test_message = 'dies on bad template file parameter';
+####
+my $cfg = MVHub::Utils::ConfigSimple::create_config_from(
+    $ENV{MV_TEST_CONFIG_FILE} );
+my $email_constants_href
+    = MVHub::Notifications::get_email_constants_from($cfg);
+
+my $template_file = "BAD_FILE.tmpl";
+
+dies_ok {
+    create_fourth_notification_emails_with( $template_file,
+        $email_constants_href, %test_notifications );
+}
+$test_message;
+
+####
+$test_message = 'Skips notification if contact and agency email are the same';
+####
+my $template_path = $cfg->param('ABSOLUTE_PATH.template_text_dir');
+$template_file = "$template_path/reminder_email_01.tmpl";
+
+$test_notifications{'1001joeschmoe@xxxxxxxxxxxxxx'}{'agency_contact_email'}
+    = 'joe_schmoe@xxxxxxxxxxxxxx';
+
+push @emails,
+    create_fourth_notification_emails_with( $template_file,
+    $email_constants_href, %test_notifications );
+is( @emails, 4, $test_message );
+
+####
+$test_message
+    = 'Processes notification if contact and agency email are not the same';
+####
+@emails = ();
+$test_notifications{'1001joeschmoe@xxxxxxxxxxxxxx'}{'agency_contact_email'}
+    = 'notjoe@xxxxxxxxxxxxxx';
+
+push @emails,
+    create_fourth_notification_emails_with( $template_file,
+    $email_constants_href, %test_notifications );
+is( @emails, 5, $test_message );

=== added file 'lib-mvhub/t/Notifications/create_notifications_using.t'
--- lib-mvhub/t/Notifications/create_notifications_using.t	1970-01-01 00:00:00 +0000
+++ lib-mvhub/t/Notifications/create_notifications_using.t	2010-03-31 16:25:32 +0000
@@ -0,0 +1,53 @@
+#!/usr/bin/perl 
+
+# $Source$
+# $Revision: 1209 $
+
+use strict;
+use warnings;
+
+BEGIN {
+    use FindBin qw($Bin);
+    chdir $Bin;
+}
+
+use Test::More tests => 5 + 1;    # +1 for NoWarnings
+use Test::NoWarnings;
+use Test::Exception;
+
+use TestData qw/@agency @program/;
+
+use MVHub::Notifications qw/
+    create_notifications_using
+    die_if_record_count_mismatch
+    /;
+
+my $test_message;
+$test_message = 'lives with array reference parameter';
+lives_ok { create_notifications_using( \@TestData::agency ) } $test_message;
+
+$test_message = 'dies without array reference parameter';
+dies_ok { create_notifications_using(1) } $test_message;
+
+$test_message = 'dies without hash reference parameter';
+dies_ok { create_notifications_using( \@TestData::agency, 1 ) } $test_message;
+
+$test_message = 'dies on too many parameters';
+dies_ok { create_notifications_using(qw/1 2 3 4/) } $test_message;
+
+####
+
+$test_message = 'same number of records in input and output';
+
+my %test_notifications = create_notifications_using( \@TestData::program );
+%test_notifications
+    = create_notifications_using( \@TestData::agency, \%test_notifications );
+
+lives_ok {
+    die_if_record_count_mismatch(
+        'input_01' => \@TestData::agency,
+        'input_02' => \@TestData::program,
+        'output'   => \%test_notifications
+    );
+}
+$test_message;

=== added file 'lib-mvhub/t/Notifications/create_standard_notification_emails_with.t'
--- lib-mvhub/t/Notifications/create_standard_notification_emails_with.t	1970-01-01 00:00:00 +0000
+++ lib-mvhub/t/Notifications/create_standard_notification_emails_with.t	2010-03-31 16:25:32 +0000
@@ -0,0 +1,58 @@
+#!/usr/bin/perl 
+
+# $Source$
+# $Revision: 1209 $
+
+use strict;
+use warnings;
+
+BEGIN {
+    use FindBin qw($Bin);
+    chdir $Bin;
+}
+
+use Test::More tests => 3 + 1;    # +1 for NoWarnings
+use Test::NoWarnings;
+use Test::Exception;
+
+use TestData qw/ notifications_href /;
+
+use MVHub::Utils::ConfigSimple qw/ create_config_from /;
+use MVHub::Notifications qw/ create_standard_notification_emails_with
+    get_email_constants_from /;
+
+my $test_message;
+
+####
+$test_message = 'dies without hash reference parameter';
+####
+dies_ok { create_standard_notification_emails_with( 1, 2, 3 ) } $test_message;
+
+####
+$test_message = 'dies on bad template file parameter';
+####
+my $cfg = MVHub::Utils::ConfigSimple::create_config_from(
+    $ENV{MV_TEST_CONFIG_FILE} );
+my $email_constants_href
+    = MVHub::Notifications::get_email_constants_from($cfg);
+my %test_notifications = %$TestData::notifications_href;
+
+my $template_file = "BAD_FILE.tmpl";
+
+dies_ok {
+    create_standard_notification_emails_with( $template_file,
+        $email_constants_href, %test_notifications );
+}
+$test_message;
+
+####
+$test_message = 'lives with good parameters';
+####
+my $template_path = $cfg->param('ABSOLUTE_PATH.template_text_dir');
+$template_file = "$template_path/reminder_email_01.tmpl";
+
+lives_ok {
+    create_standard_notification_emails_with( $template_file,
+        $email_constants_href, %test_notifications );
+}
+$test_message;

=== added file 'lib-mvhub/t/Notifications/die_if_record_count_mismatch.t'
--- lib-mvhub/t/Notifications/die_if_record_count_mismatch.t	1970-01-01 00:00:00 +0000
+++ lib-mvhub/t/Notifications/die_if_record_count_mismatch.t	2010-03-31 16:25:32 +0000
@@ -0,0 +1,48 @@
+#!/usr/bin/perl 
+
+# $Source$
+# $Revision: 1209 $
+
+use strict;
+use warnings;
+
+BEGIN {
+    use FindBin qw($Bin);
+    chdir $Bin;
+}
+
+use Test::More tests => 2 + 1;    # +1 for NoWarnings
+use Test::NoWarnings;
+use Test::Exception;
+
+use TestData qw/program notifications_href/;
+
+use MVHub::Notifications qw/
+    die_if_record_count_mismatch
+    /;
+
+my $test_message;
+
+##
+$test_message = 'lives if input and output counts match';
+##
+lives_ok {
+    die_if_record_count_mismatch(
+        'input_01' => \@TestData::agency,
+        'input_02' => \@TestData::program,
+        'output'   => $TestData::notifications_href,
+    );
+}
+$test_message;
+
+##
+$test_message = 'dies if input and output counts mismatch';
+##
+dies_ok {
+    die_if_record_count_mismatch(
+        'input_01' => [],
+        'input_02' => \@TestData::program,
+        'output'   => $TestData::notifications_href,
+    );
+}
+$test_message;

=== added file 'lib-mvhub/t/Notifications/get_email_constants_from.t'
--- lib-mvhub/t/Notifications/get_email_constants_from.t	1970-01-01 00:00:00 +0000
+++ lib-mvhub/t/Notifications/get_email_constants_from.t	2010-03-31 16:25:32 +0000
@@ -0,0 +1,85 @@
+#!/usr/bin/perl -w
+
+# $Source$
+# $Revision: 1209 $
+
+use strict;
+use warnings;
+
+# EDIT
+use Test::More tests => 3 + 1;    # +1 for NoWarnings
+use Test::NoWarnings;
+use Test::Exception;
+
+use MVHub::Notifications qw/
+    get_email_constants_from
+    /;
+
+BEGIN {
+    use FindBin qw($Bin);
+    chdir $Bin;
+}
+
+my $test_message;
+
+###
+$test_message = 'die with missing parameter';
+###
+
+dies_ok { get_email_constants_from() } $test_message;
+
+###
+$test_message = 'die with config object missing our NOTIFICATION keys';
+###
+my $cfg = new Config::Simple( syntax => 'ini' );
+$cfg->param(
+    -block  => 'META',
+    -values => { 'this_config_file' => 'fake config file created in test' }
+);
+dies_ok { get_email_constants_from($cfg) } $test_message;
+
+$cfg->param(
+    -block  => 'NOTIFICATION',
+    -values => {
+        'admin_email' => 'admin_email',
+        'admin_name'  => 'admin_name',
+        'admin_phone' => 'admin_phone',
+        'team_name'   => 'team_name',
+    }
+);
+
+$cfg->param(
+    -block  => 'SITE',
+    -values => { 'website_name' => 'website_name', }
+);
+
+###
+$test_message = 'Got keys out we expected';
+###
+my $email_constants_href = get_email_constants_from($cfg);
+my @expected_keys
+    = qw/admin_email admin_name admin_phone team_name from website_name/;
+@expected_keys = sort @expected_keys;
+my @got_keys = sort keys %$email_constants_href;
+is_deeply( \@got_keys, \@expected_keys, $test_message );
+
+__DATA__
+ create Config::Simple object
+  dies_ok for missing params
+  $@ contains an error msg we expect
+  
+  %cfg returned has values we expect
+  
+
+  
+  $cfg->param(
+    -block  => 'RELATIVE_PATH',
+    -values => {
+        'test1' => 'path/one/',
+        'test2' => 'second/path/',
+        'test3' => 'last/one/to/do/'
+    }
+);
+
+#my @test_keys= qw/ admin_name admin_email admin_phone team_name from/;
+  

=== added file 'lib-mvhub/t/Notifications/get_expired_records.t'
--- lib-mvhub/t/Notifications/get_expired_records.t	1970-01-01 00:00:00 +0000
+++ lib-mvhub/t/Notifications/get_expired_records.t	2010-03-31 16:25:32 +0000
@@ -0,0 +1,50 @@
+#!/usr/bin/perl 
+
+# $Source$
+# $Revision: 1209 $
+
+use strict;
+use warnings;
+use Carp;
+
+# EDIT
+use Test::More tests => 3 + 1;    # +1 for NoWarnings
+use Test::NoWarnings;
+use Test::Exception;
+
+use MVHub::Notifications qw/ get_expired_records /;
+use MVHub::Utils::DB qw/ get_dbh /;
+use TestHelper qw/ add_test_data create_sqlite_db /;
+
+BEGIN {
+    use FindBin qw($Bin);
+    chdir $Bin;
+}
+my $cfg = MVHub::Utils::ConfigSimple::create_config_from(
+    $ENV{MV_TEST_CONFIG_FILE} );
+my $dbh = MVHub::Utils::DB::get_dbh( $ENV{MV_TEST_CONFIG_FILE} );
+
+TestHelper::create_sqlite_db($dbh) or croak "failed to setup test database\n";
+TestHelper::add_test_data($dbh);
+
+my $test_message;
+
+$test_message = 'dies on too few parameters';
+dies_ok { get_expired_records(1) } $test_message;
+
+$test_message = 'dies on too many parameters';
+dies_ok { get_expired_records( 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ) }
+$test_message;
+
+$test_message = 'lives on eight parameters';
+lives_ok {
+    get_expired_records(
+        dbh               => $dbh,
+        max_notifications => '4',
+        expire_date       => '01-01-2009',
+        query_name        => 'AGENCY_X_EXPIRED_RECORDS'
+    );
+}
+$test_message;
+
+unlink( $cfg->param('DATABASE.database_name') );

=== added file 'lib-mvhub/t/Notifications/increment_reminder_levels_for.t'
--- lib-mvhub/t/Notifications/increment_reminder_levels_for.t	1970-01-01 00:00:00 +0000
+++ lib-mvhub/t/Notifications/increment_reminder_levels_for.t	2010-03-31 16:25:32 +0000
@@ -0,0 +1,42 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 3 + 1;    # +1 for NoWarnings
+use Test::Warn;
+use Test::NoWarnings;
+use Test::Exception;
+
+use MVHub::Notifications qw/ increment_reminder_levels_for /;
+use TestData qw/ notifications_href /;
+
+BEGIN {
+    use FindBin qw($Bin);
+    chdir $Bin;
+}
+
+my $test_message;
+
+###
+$test_message = 'die with missing parameters';
+###
+dies_ok { increment_reminder_levels_for(1) } $test_message;
+
+###
+$test_message = 'Warn if max_notifications is less than reminder_count';
+###
+warnings_like {
+    increment_reminder_levels_for( $TestData::notifications_href, 4 );
+}
+[ qr/SHOULDN\'T SEE THIS:/, qr/SHOULDN\'T SEE THIS:/ ], $test_message;
+
+###
+$test_message = 'Reminder count incremented properly';
+###
+my @results;
+foreach my $unique_id ( sort keys %$TestData::notifications_href ) {
+    push @results,
+        $$TestData::notifications_href{$unique_id}{'reminders_sent'};
+}
+is_deeply( \@results, [ 1, 2, 3, 5, 5 ], $test_message );

=== added file 'lib-mvhub/t/Notifications/print_emails_contained_in.t'
--- lib-mvhub/t/Notifications/print_emails_contained_in.t	1970-01-01 00:00:00 +0000
+++ lib-mvhub/t/Notifications/print_emails_contained_in.t	2010-03-31 16:25:32 +0000
@@ -0,0 +1,64 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use HTML::Template;
+
+use Test::More tests => 3 + 1;    # +1 for NoWarnings
+use Test::NoWarnings;
+use Test::Exception;
+use Test::Warn;
+
+use MVHub::Notifications qw/ print_emails_contained_in /;
+
+BEGIN {
+    use FindBin qw($Bin);
+    chdir $Bin;
+}
+
+my $CFG
+    = MVHub::Utils::ConfigSimple::create_config_from( $ENV{MV_CONFIG_FILE} );
+
+my $test_message;
+
+###
+$test_message = 'lives with empty array parameter';
+###
+lives_ok { print_emails_contained_in( () ) } $test_message;
+
+###
+$test_message = 'lives with undef parameter';
+###
+lives_ok { print_emails_contained_in() } $test_message;
+
+###
+$test_message = 'Prints emails successfully';
+###
+my @emails;
+my $email = MIME::Entity->build(
+    Type    => 'text/plain',
+    From    => 'tester@xxxxxxxxxx',
+    To      => $CFG->param('NOTIFICATION.dev_email'),
+    Subject => 'send_emails_contained_in.t: Email 1',
+    Data    => 'First Email',
+);
+push @emails, $email;
+$email = MIME::Entity->build(
+    Type    => 'text/plain',
+    From    => 'tester@xxxxxxxxxx',
+    To      => $CFG->param('NOTIFICATION.dev_email'),
+    Subject => 'send_emails_contained_in.t: Email 2',
+    Data    => 'Second Email',
+);
+push @emails, $email;
+
+my $output;
+open my $fh, '>', \$output;
+{
+    local *STDOUT = $fh;
+    print_emails_contained_in(@emails);
+    like( $output, qr/Subject: send_emails_contained_in.t: Email 1/,
+        $test_message );
+}
+close $fh;

=== added file 'lib-mvhub/t/Notifications/send_emails_contained_in.t'
--- lib-mvhub/t/Notifications/send_emails_contained_in.t	1970-01-01 00:00:00 +0000
+++ lib-mvhub/t/Notifications/send_emails_contained_in.t	2010-03-31 16:25:32 +0000
@@ -0,0 +1,57 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use HTML::Template;
+
+use Test::More tests => 3 + 1;    # +1 for NoWarnings
+use Test::NoWarnings;
+use Test::Exception;
+use Test::Warn;
+
+use MVHub::Notifications qw/ send_emails_contained_in /;
+
+BEGIN {
+    use FindBin qw($Bin);
+    chdir $Bin;
+}
+
+my $CFG
+    = MVHub::Utils::ConfigSimple::create_config_from( $ENV{MV_CONFIG_FILE} );
+
+my $test_message;
+
+###
+$test_message = 'lives with empty array parameter';
+###
+lives_ok { send_emails_contained_in( () ) } $test_message;
+
+###
+$test_message = 'lives with undef parameter';
+###
+lives_ok { send_emails_contained_in() } $test_message;
+
+my @emails;
+my $email = MIME::Entity->build(
+    Type    => 'text/plain',
+    From    => 'tester@xxxxxxxxxx',
+    To      => $CFG->param('NOTIFICATION.dev_email'),
+    Subject => 'send_emails_contained_in.t: Email 1',
+    Data    => 'First Email',
+);
+push @emails, $email;
+$email = MIME::Entity->build(
+    Type    => 'text/plain',
+    From    => 'tester@xxxxxxxxxx',
+    To      => $CFG->param('NOTIFICATION.dev_email'),
+    Subject => 'send_emails_contained_in.t: Email 2',
+    Data    => 'Second Email',
+);
+push @emails, $email;
+
+###
+$test_message = 'Two emails sent: Check your inbox';
+###
+
+lives_ok { send_emails_contained_in(@emails) } $test_message;

=== added file 'lib-mvhub/t/Notifications/set_reminder_level_in_db.t'
--- lib-mvhub/t/Notifications/set_reminder_level_in_db.t	1970-01-01 00:00:00 +0000
+++ lib-mvhub/t/Notifications/set_reminder_level_in_db.t	2010-03-31 16:25:32 +0000
@@ -0,0 +1,77 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use Carp;
+
+use Date::Calc;
+use DBI;
+
+use Test::More tests => 4 + 1;    # +1 for NoWarnings
+use Test::NoWarnings;
+use Test::Exception;
+
+use MVHub::Notifications qw/ increment_reminder_levels_for
+    set_reminder_level_in_db /;
+use MVHub::Utils::DB qw / get_dbh /;
+use TestHelper qw/ add_test_data create_sqlite_db /;
+use TestData qw/ notifications_href /;
+
+BEGIN {
+    use FindBin qw($Bin);
+    chdir $Bin;
+}
+
+my $cfg = MVHub::Utils::ConfigSimple::create_config_from(
+    $ENV{MV_TEST_CONFIG_FILE} );
+my $max_notifications = $cfg->param('NOTIFICATION.max_notifications');
+my $expire_months     = $cfg->param('NOTIFICATION.expire_months');
+
+my $test_message;
+
+###
+$test_message = 'die with missing parameters';
+###
+dies_ok { set_reminder_level_in_db(1) } $test_message;
+
+my $dbh = MVHub::Utils::DB::get_dbh( $ENV{MV_TEST_CONFIG_FILE} );
+TestHelper::create_sqlite_db($dbh) or croak "failed to setup test database\n";
+
+my @date
+    = Date::Calc::Add_Delta_YM( Date::Calc::Today(), 0, -($expire_months) );
+my $expire_date = join '-', @date;
+
+###
+$test_message = 'No Agency / Program records need updating';
+###
+lives_ok {
+    set_reminder_level_in_db( $dbh, $expire_date, undef,
+        'AGENCY_X_REMINDERS_SENT' );
+}
+$test_message;
+
+TestHelper::add_test_data($dbh);
+increment_reminder_levels_for( $TestData::notifications_href, 5 );
+###
+$test_message = 'Agency Test Data equals expected result after update';
+###
+set_reminder_level_in_db( $dbh, $expire_date, $TestData::notifications_href,
+    'AGENCY_X_REMINDERS_SENT' );
+is_deeply(
+    $dbh->selectcol_arrayref("SELECT reminders_sent FROM agency"),
+    [ 1, 2, 3, 5, 5 ],
+    $test_message
+);
+
+###
+$test_message = 'Program Test Data equals expected result after update';
+###
+set_reminder_level_in_db( $dbh, $expire_date, $TestData::notifications_href,
+    'PROGRAM_X_REMINDERS_SENT' );
+is_deeply(
+    $dbh->selectcol_arrayref("SELECT reminders_sent FROM program"),
+    [ 1, 2, 3, 5 ],
+    $test_message
+);
+
+unlink( $cfg->param('DATABASE.database_name') );

=== added file 'lib-mvhub/t/Notifications/shift_off_notifications_with_reminder_count.t'
--- lib-mvhub/t/Notifications/shift_off_notifications_with_reminder_count.t	1970-01-01 00:00:00 +0000
+++ lib-mvhub/t/Notifications/shift_off_notifications_with_reminder_count.t	2010-03-31 16:25:32 +0000
@@ -0,0 +1,56 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use HTML::Template;
+
+use Test::More tests => 4 + 1;    # +1 for NoWarnings
+use Test::NoWarnings;
+use Test::Exception;
+use Test::Warn;
+
+use TestData qw/ notifications_href /;
+use MVHub::Notifications qw/ shift_off_notifications_with_reminder_count /;
+
+BEGIN {
+    use FindBin qw($Bin);
+    chdir $Bin;
+}
+
+my $test_message;
+
+###
+$test_message = 'dies when not passed 2 parameters';
+###
+dies_ok { shift_off_notifications_with_reminder_count(1) } $test_message;
+
+###
+$test_message = 'dies when 2nd parameter not a hash ref';
+###
+dies_ok { shift_off_notifications_with_reminder_count( 1, 2 ) } $test_message;
+
+###
+$test_message = 'values moved to new hash';
+###
+my %check_against;
+my %test_notifications = %$TestData::notifications_href;
+
+foreach my $unique_id ( keys %test_notifications ) {
+    if ( $test_notifications{$unique_id}{reminders_sent} == 0 ) {
+        $check_against{$unique_id} = $test_notifications{$unique_id};
+    }
+}
+my %results
+    = shift_off_notifications_with_reminder_count( 0, \%test_notifications );
+is_deeply( \%check_against, \%results, $test_message );
+
+###
+$test_message = 'All values removed from original hash';
+###
+shift_off_notifications_with_reminder_count( 1, \%test_notifications );
+shift_off_notifications_with_reminder_count( 2, \%test_notifications );
+shift_off_notifications_with_reminder_count( 3, \%test_notifications );
+shift_off_notifications_with_reminder_count( 4, \%test_notifications );
+shift_off_notifications_with_reminder_count( 5, \%test_notifications );
+is( scalar(%test_notifications), 0, $test_message );

=== modified file 'lib-mvhub/t/conf/all.conf'
--- lib-mvhub/t/conf/all.conf	2010-03-23 17:14:10 +0000
+++ lib-mvhub/t/conf/all.conf	2010-03-31 16:25:32 +0000
@@ -7,7 +7,7 @@
 [DATABASE]
 # not needed for sqlite
 database_host=
-database_name=/tmp/test.sqlite
+database_name=/home/lgoodrich/tmp/test.sqlite
 database_user=test
 # the database user's p---word
 database_magic_word=ignore
@@ -31,18 +31,18 @@
 # the name of the Administrator. This is the person to whom non-technical messages 
 # will be directed, and whose name will be used to personalize welcome and reminder
 # emails. It will be displayed on the contact page
-admin_name=
+admin_name=Chester Tester
 
 # the Adminstrative contact email address. This email address will be exposed
 # in email messages and saved in people's address books. 
 # You use an role alias like 'info@xxxxxxxxxxx' or 'help@xxxxxxxxx' rather than a 
 # personal address like "joe_smith@xxxxxxxxxxx" so if Joe leaves you, people will
 # still email the right place.
-admin_email=dan@xxxxxxxxxx
+admin_email=lgoodrich@xxxxxxxxxx
 
 # an Administrative contact phone number, also displayed in automated emails and on the 
 # contact page
-admin_phone=
+admin_phone=978-692-3458
 
 # a team/group name. This will be displayed as the signer in emails not requiring 
 # personalization
@@ -50,25 +50,25 @@
 
 # the name of a Developer who is the contact for technical issues; this is displayed 
 # on the contact page
-dev_name=
+dev_name=Chester Dev Tester
 
 # the email address of the Developer, to whom any bug/system-error/technical messages 
 # should be sent. This address will not be exposed
-dev_email=
+dev_email=lgoodrich@xxxxxxxxxx
 
 # the contact phone number of the Developer, to be displayed on the contact page
-dev_phone=
+dev_phone=978-692-3458
 
 # the number of months without update after which a program/agency record is considered
 # out-of-date
 expire_months=6
 
 # Number of notification emails to send before giving up
-max_notifications=4
+max_notifications=5
 
 [SITE]
 # the name of the website right now either NorthShorePort.org or MVHub.com
-website_name=NorthShorePort.org
+website_name=nsp.lgoodrich.testing123.net
 
 # the geographic location the site 
 # covers. currently North Shore
@@ -89,6 +89,7 @@
 template_conf_dir=link-to-live-code/app-mvhub/project-tools/templates/
 template_html_dir=link-to-live-code/app-mvhub/conf/templates/html/
 template_text_dir=link-to-live-code/app-mvhub/conf/templates/text/
+project_tools_dir=link-to-live-code/app-mvhub/project-tools/
 reports_dir=reports/
 project_tools_dir=link-to-live-code/app-mvhub/project-tools/
 tmp_dir=tmp/

=== added file 'lib-mvhub/t/lib/TestData.pm'
--- lib-mvhub/t/lib/TestData.pm	1970-01-01 00:00:00 +0000
+++ lib-mvhub/t/lib/TestData.pm	2010-03-31 16:25:32 +0000
@@ -0,0 +1,174 @@
+package TestData;
+
+use strict;
+use warnings;
+
+BEGIN {
+    use Exporter ();
+    our (@EXPORT_OK);
+
+    @EXPORT_OK = qw(@agency @program notifications_href);
+}
+our @EXPORT_OK;
+
+our @agency = (
+    {   agency_id          => '1001',
+        agency_name        => 'Test Agency 1',
+        record_name        => 'Test Agency 1 (Main Agency Record)',
+        contact_first_name => 'Dan',
+        contact_last_name  => 'MacNeil',
+        contact_email      => 'dan@xxxxxxxxxx',
+        email              => 'joe_boss@xxxxxxxxxxxxxx',
+        last_updated       => '2009-02-15',
+        reminders_sent     => '0',
+    },
+    {   agency_id          => '1002',
+        agency_name        => 'Test Agency 2',
+        record_name        => 'Test Agency 2 (Main Agency Record)',
+        contact_first_name => 'Jane',
+        contact_last_name  => 'Lane',
+        contact_email      => 'janelane@xxxxxxxxxxxxxx',
+        email              => 'jane_boss@xxxxxxxxxxxxxx',
+        last_updated       => '2008-12-02',
+        reminders_sent     => '1',
+    },
+    {   agency_id          => '1003',
+        agency_name        => 'Test Agency 3',
+        record_name        => 'Test Agency 3 (Main Agency Record)',
+        contact_first_name => 'Jack',
+        contact_last_name  => 'Black',
+        contact_email      => 'jackblack@xxxxxxxxxxxxxx',
+        email              => 'jack_boss@xxxxxxxxxxxxxx',
+        last_updated       => '2008-06-05',
+        reminders_sent     => '2',
+    },
+    {   agency_id          => '1004',
+        agency_name        => 'Test Agency 4',
+        record_name        => 'Test Agency 4 (Main Agency Record)',
+        contact_first_name => 'Sue',
+        contact_last_name  => 'Blue',
+        contact_email      => 'sueblue@xxxxxxxxxxxxxx',
+        email              => 'lgoodrich@xxxxxxxxxx',
+        last_updated       => '2007-08-20',
+        reminders_sent     => '3',
+    },
+    {   agency_id          => '1005',
+        agency_name        => 'Test Agency 5',
+        record_name        => 'Test Agency 5 (Main Agency Record)',
+        contact_first_name => 'Bob',
+        contact_last_name  => 'Cobb',
+        contact_email      => 'bobcobb@xxxxxxxxxxxxxx',
+        email              => 'bob_boss@xxxxxxxxxxxxxx',
+        last_updated       => '2007-08-20',
+        reminders_sent     => '4',
+    },
+);
+
+our @program = (
+    {   agency_id          => '1001',
+        agency_name        => 'Test Agency',
+        record_name        => 'Test Program',
+        contact_first_name => 'Joe',
+        contact_last_name  => 'Schmoe',
+        contact_email      => 'joeschmoe@xxxxxxxxxxxxxx',
+        email              => 'joe_boss@xxxxxxxxxxxxxx',
+        last_updated       => '2009-02-15',
+        reminders_sent     => '0',
+    },
+    {   agency_id          => '1002',
+        agency_name        => 'Test Agency 2',
+        record_name        => 'Test Program 2',
+        contact_first_name => 'Jane',
+        contact_last_name  => 'Lane',
+        contact_email      => 'janelane@xxxxxxxxxxxxxx',
+        email              => 'jane_boss@xxxxxxxxxxxxxx',
+        last_updated       => '2008-12-02',
+        reminders_sent     => '0',
+    },
+    {   agency_id          => '1003',
+        agency_name        => 'Test Agency 3',
+        record_name        => 'Test Program 3',
+        contact_first_name => 'Jack',
+        contact_last_name  => 'Black',
+        contact_email      => 'jackblack@xxxxxxxxxxxxxx',
+        email              => 'lgoodrich@xxxxxxxxxx',
+        last_updated       => '2008-12-02',
+        reminders_sent     => '2',
+    },
+    {   agency_id          => '1004',
+        agency_name        => 'Test Agency 4',
+        record_name        => 'Test Program 4',
+        contact_first_name => 'Sue',
+        contact_last_name  => 'Blue',
+        contact_email      => 'sueblue@xxxxxxxxxxxxxx',
+        email              => 'lgoodrich@xxxxxxxxxx',
+        last_updated       => '2008-12-02',
+        reminders_sent     => '4',
+    },
+);
+
+our $notifications_href = {
+
+    '1001joeschmoe@xxxxxxxxxxxxxx' => {
+        'contact_first_name' => 'Joe',
+        'contact_last_name'  => 'Schmoe',
+        'reminders_sent'     => 0,
+        'expired_record_list' =>
+            [ 'Test Program', 'Test Agency (Main Agency Record)' ],
+        'agency_contact_email' => 'joe_agency@xxxxxxxxxxxxxx',
+        'contact_email'        => 'joe_schmoe@xxxxxxxxxxxxxx',
+        'public_email'         => 'joe_public@xxxxxxxxxxxxxx',
+        'agency_id'            => '1001',
+        'agency_name'          => 'Test Agency',
+    },
+    '1002janelane@xxxxxxxxxxxxxx' => {
+        'contact_first_name' => 'Jane',
+        'contact_last_name'  => 'Lane',
+        'reminders_sent'     => 1,
+        'expired_record_list' =>
+            [ 'Test Program 2', 'Test Agency 2 (Main Agency Record)' ],
+        'agency_contact_email' => 'jane_agency@xxxxxxxxxxxxxx',
+        'contact_email'        => 'jane_lane@xxxxxxxxxxxxxx',
+        'public_email'         => 'jane_public@xxxxxxxxxxxxxx',
+        'agency_id'            => '1002',
+        'agency_name'          => 'Test Agency 2',
+    },
+    '1003jackblack@xxxxxxxxxxxxxx' => {
+        'contact_first_name' => 'Jack',
+        'contact_last_name'  => 'Black',
+        'reminders_sent'     => 2,
+        'expired_record_list' =>
+            [ 'Test Program 3', 'Test Agency 3 (Main Agency Record)' ],
+        'agency_contact_email' => 'jack_agency@xxxxxxxxxxxxxx',
+        'contact_email'        => 'jack_black@xxxxxxxxxxxxxx',
+        'public_email'         => 'jack_public@xxxxxxxxxxxxxx',
+        'agency_id'            => '1003',
+        'agency_name'          => 'Test Agency 3',
+    },
+    '1004sueblue@xxxxxxxxxxxxxx' => {
+        'contact_first_name' => 'Sue',
+        'contact_last_name'  => 'Blue',
+        'reminders_sent'     => 4,
+        'expired_record_list' =>
+            [ 'Test Program 4', 'Test Agency 4 (Main Agency Record)' ],
+        'agency_contact_email' => 'sue_agency@xxxxxxxxxxxxxx',
+        'contact_email'        => 'sue_blue@xxxxxxxxxxxxxx',
+        'public_email'         => 'sue_public@xxxxxxxxxxxxxx',
+        'agency_id'            => '1004',
+        'agency_name'          => 'Test Agency 4',
+    },
+    '1005bobcobb@xxxxxxxxxxxxxx' => {
+        'contact_first_name'   => 'Bob',
+        'contact_last_name'    => 'Cobb',
+        'reminders_sent'       => 4,
+        'expired_record_list'  => ['Test Agency 5 (Main Agency Record)'],
+        'agency_contact_email' => 'bob_agency@xxxxxxxxxxxxxx',
+        'contact_email'        => 'bob_cobb@xxxxxxxxxxxxxx',
+        'public_email'         => 'bob_public@xxxxxxxxxxxxxx',
+        'agency_id'            => '1005',
+        'agency_name'          => 'Test Agency 5',
+    },
+};
+
+# all good modules return true 1;
+1

=== modified file 'lib-mvhub/t/lib/TestHelper.pm'
--- lib-mvhub/t/lib/TestHelper.pm	2010-01-04 01:43:54 +0000
+++ lib-mvhub/t/lib/TestHelper.pm	2010-03-31 16:25:32 +0000
@@ -5,14 +5,21 @@
 require Exporter;
 
 use Carp;
+use DBI;
 use MIME::Base64;
 use Test::More;
 
+use MVHub::Utils::DB qw/ get_sql_insert_statement /;
+use TestData qw/ @agency @program /;
+
 use strict;
 use warnings;
 
 our @ISA    = qw(Exporter);
-our @EXPORT = qw( create_temp_auth
+our @EXPORT = qw(
+    add_test_data
+    create_sqlite_db
+    create_temp_auth
     get_files
     get_files_from
     get_host_to_check
@@ -26,6 +33,47 @@
 
 our ($VERSION) = '$Revision: 1647 $' =~ /([.\d]+)/;
 
+sub add_test_data {
+    my $dbh = shift or croak "missing parameter: \$dbh\n";
+    my $sql
+        = MVHub::Utils::DB::get_sql_insert_statement('AGENCY_INSERT_RECORD');
+    my @bind_variables;
+    foreach my $test_data (@TestData::agency) {
+        @bind_variables = (
+            $test_data->{'agency_id'},     $test_data->{'agency_name'},
+            $test_data->{'contact_email'}, $test_data->{'email'},
+            $test_data->{'last_updated'},  $test_data->{'reminders_sent'}
+        );
+        $dbh->do( $sql, undef, @bind_variables );
+    }
+    $sql
+        = MVHub::Utils::DB::get_sql_insert_statement('PROGRAM_INSERT_RECORD');
+    foreach my $test_data (@TestData::program) {
+        @bind_variables = (
+            $test_data->{'agency_id'},   $test_data->{'program_id'},
+            $test_data->{'record_name'}, $test_data->{'contact_email'},
+            $test_data->{'email'},       $test_data->{'last_updated'},
+            $test_data->{'reminders_sent'}
+        );
+        $dbh->do( $sql, undef, @bind_variables );
+    }
+}
+
+sub create_sqlite_db {
+    my $dbh = shift or croak "missing parameter: \$dbh\n";
+    my $cfg = MVHub::Utils::ConfigSimple::create_config_from(
+        $ENV{MV_TEST_CONFIG_FILE} );
+    my $conf_path = $cfg->param('ABSOLUTE_PATH.conf_dir');
+    my $sql_lib
+        = SQL::Library->new( { lib => "$conf_path/create_sqlite.lib" } );
+
+    foreach my $sql_label ( sort $sql_lib->elements() ) {
+        my $sql = $sql_lib->retr($sql_label);
+        $dbh->do($sql);
+    }
+    return 1;
+}
+
 # TODO review with ERIC
 # eventually use wrap /usr/bin/htpasswd to
 # add a temporary user


Follow ups