5. Security and Testing

5.1 Software Testing Practices and Security Measures

To ensure the Project Review Scheduler (PRS) met the highest quality and security standards, I implemented a comprehensive suite of unit, integration, and user acceptance testing (UAT) practices throughout the development process. These practices were guided by agile software development principles that emphasize early defect identification, iterative refinement, and feedback loops (Hoda et al., 2017).

Unit testing was performed using Python’s unittest and mock libraries to validate isolated functional components. Core logic such as due date calculations, reviewer assignments, email notifications, and CSV-based reporting were independently tested. For example, the calculate_due_date() function was evaluated with varying frequencies (e.g., 0.5 years, 1 year) to ensure correct classification of project status into “Overdue,” “Due Soon,” or “Up to Date.” These tests reduced logical errors and improved the traceability of issues. As Shaw (n.d.) emphasizes, structured test automation allows for continuous, formal quality assurance throughout the lifecycle.

Integration testing focused on the interaction between modules, especially where data flowed between components. The assign_all_reviewers() function was tested to ensure that projects marked as “Overdue” or “Due Soon” were passed to the reviewer assignment logic and correctly updated in both Users.csv and Reviews.csv. Mocked file reads and SMTP calls simulated external dependencies, confirming that transitions from status updates to notifications occurred without failure (Ganeshan, 2020). These tests validated that the system’s internal contracts between modules were being honored.

User Acceptance Testing (UAT) was achieved through .command files (run_reports.command and run_everything.command) that emulate real-world batch operations. By resetting data, executing assignments, and generating reports in a single automated workflow, these scripts enabled testers to observe system behavior end-to-end. Manual inspection of output files (e.g., overdue_alerts_20250512.csv, monthly_schedule_05-2025.csv) confirmed that data was presented in the expected format, reviews were assigned appropriately, and report sorting logic was effective. This confirmed the system’s readiness for non-technical stakeholders and aligned with acceptance criteria.

From a security perspective, several best practices were implemented:

  • Input CSV fields were validated for structure, completeness, and logical accuracy.

  • Exception handling was integrated across batch processes to ensure graceful degradation.

  • All write operations were preceded by versioned backups to protect data integrity.

  • SMTP credentials were passed securely via runtime arguments instead of hardcoded values.

  • Department-aware reviewer filtering upheld principles of data segregation.

For instance, the validate_project_data() function detects malformed dates, empty fields, or negative values that could otherwise lead to runtime errors—an important step given historical error rates in manually tracked review systems. Furthermore, reviewer assignment logic prevents conflicts of interest by excluding reviewers from the same department, upholding the principle of least privilege (McCarthy, 2023).

Together, this multi-layered testing strategy reinforced the robustness, security, and functional reliability of the Project Review Scheduler.

Testing Practices

To ensure the reliability, and security of the Project Review Scheduler (PRS), the system was developed using a layered and structured testing strategy. This strategy combined white-box and black-box testing techniques, modular unit testing, defensive coding, and secure data handling practices consistent with modern secure software development lifecycles (SDLC) (Souppaya et al., 2022).

The system incorporated multiple levels of testing techniques:

  • Black-box testing: Applied to verify outputs against known input conditions, without accessing the internal codebase. Key techniques included:

    • Equivalence Class Partitioning and Boundary Value Analysis (BVA): These techniques were used when validating date-based computations for review intervals, ensuring valid partitions for overdue, due soon, and up-to-date project statuses.

      Example: Boundary Value Analysis in calculate_due_date


    frequency_days = int(float(project_data['Review_Frequency_Years']) * 365)
    next_review = last_review + timedelta(days=frequency_days)
    days_until_review = (next_review - current_date).days
    
    if days_until_review < 0:
        status = 'Overdue'
    elif days_until_review <= 30:
        status = 'Due Soon'
    else:
        status = 'Up to Date'
This logic tests boundaries such as exactly 30 days or just below/above review thresholds (Real Python, 2023).
  • White-box testing: Used with Python’s unittest to explicitly test internal control flow, logic branches, and function-level accuracy (Ganeshan, 2020). For example, assign_reviewer() tests both department filtering and load balancing branches.

    ### Example: Department-based logic in assign_reviewer


    other_dept_reviewers = [r for r in reviewers_sorted if r.get('Department') != project_dept]
    if other_dept_reviewers:
        assigned_reviewer = other_dept_reviewers[0]
    else:
        assigned_reviewer = reviewers_sorted[0]

This test does the following:

  • Filters the sorted list of reviewers to create a new list containing only reviewers from departments different than the project’s department
  • Creates the filtered list using a list comprehension that checks if each reviewer’s department is not equal to the project department
  • If at least one reviewer from a different department exists:
    • Assigns the first reviewer from a different department as the assigned reviewer
  • If no reviewers from different departments exist:
    • Falls back to using the first reviewer from the original sorted list
  • This prioritizes cross-departmental reviews while ensuring a reviewer is always assigned

Due Date Calculation (TC_DR_001)

The calculate_due_date(project_data, current_date=None) function is responsible for determining when projects are due for review and assigning appropriate status labels. Testing this component is critical as incorrect calculations could lead to missed reviews or false urgency indicators, undermining the entire scheduling system.

Purpose of Date Calculation Testing

Accurate date calculation is fundamental to the system’s functionality. By testing calculation logic, we ensure the automation correctly applies business rules for different review frequencies.

Example: Test Due Date Calculation

class TestDueDateCalculator(unittest.TestCase):

    @patch('scheduler.read_csv')
    @patch('scheduler.write_csv')
    def test_due_date_six_months(self, mock_write_csv, mock_read_csv):
        # Setup mock project data
        projects = [{
            "Project_ID": "P001",
            "Project_Name": "Security Review",
            "Start_Date": "2025-01-01",
            "Last_Review_Date": "2025-01-01",
            "Review_Frequency_Years": "0.5",
            "Department": "IT",
            "Status": "",
            "Next_Review_Date": ""
        }]

        # Mock return value for read_csv
        mock_read_csv.return_value = projects

        
        # Execute calculation with a fixed current date
        result = scheduler.calculate_all_reviews("Projects.csv", current_date="2025-06-01")

        # Extract updated projects written to CSV
        written_projects = mock_write_csv.call_args[0][1]
        updated = written_projects[0]

        # Validate the calculated date and status
        self.assertEqual(updated['Next_Review_Date'], '2025-07-01')
        self.assertEqual(updated['Status'], 'Due Soon')
        self.assertEqual(result['due_soon'], 1)
        self.assertEqual(result['total_projects'], 1)

This test does the following:

  • Patches file I/O operations to prevent actual file system changes
  • Creates a mock project with a 6-month review frequency
  • Executes the date calculation function with a fixed reference date
  • Verifies correct next review date is calculated
  • Confirms “Due Soon” status is assigned based on the calculated date

Cross-Departmental Reviewer Assignment (TC_RA_003)

The assign_reviewer() function implements security control by prioritizing reviewers from different departments than the project being reviewed. This separation of duties minimizes conflicts of interest and ensures objective evaluation.

Purpose of Departmental Separation Testing

As McCarthy (2023) emphasizes the principle of least privilege is necessary for a secure system design. By testing our implementation of departmental separation, we verify this security principle is correctly applied to the review process, enhancing the integrity of project assessments.

Example: Test Department Separation

@patch('scheduler.update_user')
@patch('scheduler.add_review')
def test_avoid_same_department_reviewer(self, mock_add_review, mock_update_user):
    # Project is from IT department
    project = {
        "Project_ID": "P001",
        "Department": "IT"
    }

    # Reviewers with different departments
    reviewers = [
        {"User_ID": "U001", "Name": "Alice", "Department": "IT", "Current_Load": 1},
        {"User_ID": "U002", "Name": "Bob", "Department": "HR", "Current_Load": 2},
        {"User_ID": "U003", "Name": "Charlie", "Department": "Finance", "Current_Load": 1}
    ]

    review = assign_reviewer(project, reviewers)
    assigned_id = review['Reviewer_ID']

    # U001 is from same department (IT) and should NOT be selected if others exist
    self.assertIn(assigned_id, ["U002", "U003"], "Reviewer from different department should be assigned")

This test does the following:

  • Patches database update functions to isolate the assignment logic
  • Creates a project from the IT department
  • Provides a mixed list of reviewers from different departments
  • Verifies that the reviewer selected is NOT from the IT department
  • Ensures the cross-departmental assignment security control is functioning correctly

No Reviewers Available Handling (TC_RA_006)

The assign_all_reviewers() function must handle edge cases where no reviewers exist in the system. This defensive programming technique prevents system failures when operating with incomplete data.

Purpose of Edge Case Testing

Defensive programming requires anticipating and handling exceptional scenarios. According to Ganeshan (2020), effective quality control demands testing at the boundaries of normal operation. This test verifies the system’s resilience when faced with a complete absence of available reviewers.


@patch('scheduler.read_csv')
@patch('scheduler.write_csv')
def test_graceful_handling_when_no_reviewers_exist(self, mock_write_csv, mock_read_csv):
    # Only one project needing review
    projects = [{
        "Project_ID": "P006",
        "Project_Name": "Compliance Audit",
        "Start_Date": "2025-01-01",
        "Last_Review_Date": "2025-01-01",
        "Review_Frequency_Years": "1",
        "Department": "Finance",
        "Status": "Overdue",
        "Next_Review_Date": "2025-05-01"
    }]

    # No reviewers exist
    users = []  # Intentionally empty

    # Simulate read_csv returning projects, then users twice
    mock_read_csv.side_effect = [projects, users, users]

    # Run the reviewer assignment
    result = assign_all_reviewers("Projects.csv", "Users.csv", "Reviews.csv")

    # Assertions
    self.assertEqual(result['total_assigned'], 0)
    self.assertEqual(len(result['assignments']), 0)
    self.assertIn('assignments', result)

This test does the following:

  • Patches file I/O to prevent actual file system access
  • Creates a scenario with one project but zero available reviewers
  • Verifies the function returns a properly structured result with zero assignments
  • Confirms the system gracefully handles this edge case without errors
  • Validates defensive programming principles are correctly implemented

Email Notification Testing (TC_NT_002)

The send_notifications() function is responsible for emailing reviewers when their assigned projects are due or overdue. Since interacting with a live SMTP server during testing can cause unintended emails to be sent—or fail due to configuration issues—mocking is used to simulate the email-sending behavior.

Purpose of Notification Mocking

Mocking external dependencies is an established best practice in unit testing. According to Shaw (n.d.), mocking allows developers to replace complex, stateful, or external systems with dummy implementations that can be easily controlled and inspected during tests. In this case, the smtplib.SMTP class is mocked to avoid sending real emails.

Example: Test Notification System

@patch("scheduler.read_csv")
@patch("scheduler.smtplib.SMTP")
def test_overdue_email_notification(self, mock_smtp, mock_read_csv):
    # Mocked data setup
    projects = [{
        "Project_ID": "P002",
        "Project_Name": "Critical Security Review",
        "Status": "Overdue",
        "Next_Review_Date": "2025-04-15",
        "Department": "IT"
    }]
    reviews = [{
        "Review_ID": "R001",
        "Project_ID": "P002",
        "Reviewer_ID": "U002",
        "Scheduled_Date": "2025-04-15",
        "Status": "Scheduled",
        "Completion_Date": ""
    }]
    users = [{
        "User_ID": "U002",
        "Name": "Jane Doe",
        "Email": "reviewer@example.com",
        "Department": "IT",
        "Current_Load": "1"
    }]

    mock_read_csv.side_effect = [projects, reviews, users]
    mock_server = MagicMock()
    mock_smtp.return_value.__enter__.return_value = mock_server

    result = send_notifications(status_filter="Overdue", smtp_server="localhost", smtp_port=587)

    self.assertEqual(result["sent"], 1)
    self.assertIn("reviewer@example.com", result["log"][0]["reviewer_email"])
    self.assertIn("URGENT", result["log"][0]["subject"].upper())

This test does the following:

  • Patches both file I/O and SMTP functionality to isolate the notification logic
  • Creates mock project, review, and user data for an overdue review
  • Verifies that exactly one notification is sent for the overdue project
  • Confirms the email is sent to the correct reviewer
  • Validates that urgency indicators are properly included in the subject line

Monthly Schedule Reporting (TC_RP_004)

The generate_monthly_schedule() function produces formatted reports showing all reviews scheduled for a specific month. This reporting capability is essential for management oversight and resource planning.

Purpose of Report Testing

Reporting functionality must produce consistently formatted, accurate outputs for decision-making. Testing this component ensures that reporting logic correctly filters, sorts, and presents review data, providing stakeholders with reliable information for planning purposes as recommended by Souppaya et al. (2022).

Example: Test Monthly Schedule Report

@patch('scheduler.write_csv')
@patch('scheduler.get_reviewer')
@patch('scheduler.get_all_projects')
@patch('scheduler.get_all_reviews')
def test_generate_monthly_review_schedule(self, mock_get_all_reviews, mock_get_all_projects, 
                                         mock_get_reviewer, mock_write_csv):
    # Mock Reviews
    mock_get_all_reviews.return_value = [
        {
            "Review_ID": "R001",
            "Project_ID": "P001",
            "Reviewer_ID": "U001",
            "Scheduled_Date": "2025-05-10",
            "Status": "Scheduled"
        },
        {
            "Review_ID": "R002",
            "Project_ID": "P002",
            "Reviewer_ID": "U002",
            "Scheduled_Date": "2025-05-15",
            "Status": "In Progress"
        },
        {
            "Review_ID": "R003",
            "Project_ID": "P003",
            "Reviewer_ID": "U003",
            "Scheduled_Date": "2025-06-10",
            "Status": "Scheduled"
        }
    ]

    # Run the report for May 2025
    result = generate_monthly_schedule("05", "2025", output_file="test_monthly_schedule.csv")

    # Assertions
    self.assertEqual(result['review_count'], 2)
    written_data = mock_write_csv.call_args[0][1]
    scheduled_dates = [r['Scheduled_Date'] for r in written_data]
    self.assertEqual(scheduled_dates, sorted(scheduled_dates))

This test does the following:

  • Patches multiple dependencies to isolate the reporting logic
  • Creates mock review data across multiple months
  • Verifies the report correctly filters for the specified month (May 2025)
  • Confirms exactly two reviews are included (excluding the June review)
  • Validates that reviews are properly sorted by the scheduled date
  • Ensures the report output is correctly structured for management review

Implementing these comprehensive testing practices validates functional requirements and establishes robust error handling and security controls while maintaining data integrity across all system components (Defensive Programming, 2024).

Security Measures

Security was embedded throughout the development process of the Project Review Scheduler by secure software development lifecycle principles. The implementation focused on multiple layers of protection to ensure data integrity, proper access controls, and error handling (Alenezi & Almuairfi, 2019).

Input Validation and Data Integrity

All CSV-based data underwent validation using schema templates and field type checks before processing. According to Souppaya et al. (2022), input validation is a fundamental security practice that prevents malformed or malicious inputs from compromising system integrity.

def validate_project_data(project):
    """Validate project data against required schema and types."""
    required_fields = ["Project_ID", "Project_Name", "Department", "Review_Frequency_Years"]
    
    # Check for required fields
    for field in required_fields:
        if field not in project or not project[field]:
            raise ValueError(f"Missing required field: {field}")
    
    # Type and format validation
    try:
        # Ensure review frequency is a positive number
        freq = float(project["Review_Frequency_Years"])
        if freq <= 0:
            raise ValueError("Review frequency must be positive")
        
        # Validate date formats
        for date_field in ["Start_Date", "Last_Review_Date", "Next_Review_Date"]:
            if date_field in project and project[date_field]:
                datetime.strptime(project[date_field], "%Y-%m-%d")
                
    except ValueError as e:
        raise ValueError(f"Invalid data format: {str(e)}")
    
    return True

The validate_project_data() function:

  • Performs security validation on project data to prevent processing of malformed inputs
  • Checks for the presence of required fields (Project_ID, Project_Name, Department, Review_Frequency_Years)
  • Raises specific error messages when required fields are missing
  • Validates that review frequency is a positive number by converting to float and checking value
  • Verifies that all date fields follow the correct YYYY-MM-DD format
  • Implements defensive programming by catching conversion failures and raising clear error messages
  • Acts as a security gateway that prevents invalid data from entering core processing logic
  • Returns True only when all validation checks pass successfully
  • Provides detailed error context when validation fails to aid in troubleshooting
  • Protects against both accidental corruption and potential injection attacks in CSV-based data

This validation layer is a defense for accidental corruption and potential injection attacks that could compromise the scheduling system. ### Cross-Departmental Assignment Enforcement

The reviewer assignment algorithm implemented the separation of duties as a core security principle (McCarthy, 2023). By programmatically enforcing cross-departmental reviews, the system created an architectural security control.

def assign_reviewer(project, reviewers):
    """Assign reviewer with cross-departmental separation when possible."""
    # Sort reviewers by current load (ascending)
    reviewers_sorted = sorted(reviewers, key=lambda r: int(r.get('Current_Load', 0)))
    
    # Extract the project's department
    project_dept = project.get('Department')
    
    # First try to find reviewers from other departments
    other_dept_reviewers = [r for r in reviewers_sorted if r.get('Department') != project_dept]
    
    if other_dept_reviewers:
        # Security principle: Use cross-departmental reviewer when available
        assigned_reviewer = other_dept_reviewers[0]
    else:
        # Fallback only when cross-departmental reviewers unavailable
        assigned_reviewer = reviewers_sorted[0]
    
    # Create the review assignment
    review = {
        "Review_ID": generate_id(),
        "Project_ID": project["Project_ID"],
        "Reviewer_ID": assigned_reviewer["User_ID"],
        "Scheduled_Date": project["Next_Review_Date"],
        "Status": "Scheduled",
        "Completion_Date": ""
    }
    
    return review

Security aspects of the assign_reviewer() function:

  • Implements separation of duties by prioritizing reviewers from departments different than the project’s department
  • Enforces the principle of least privilege (McCarthy, 2023) by restricting same-department reviews when alternatives exist
  • Creates organizational separation as a security control to prevent conflicts of interest in the review process
  • Reduces the risk of biased assessments by ensuring objective reviewers from outside departments whenever possible
  • Provides a secure fallback mechanism only when cross-departmental reviewers aren’t available
  • Creates an audit trail by generating a unique Review_ID for each assignment
  • Maintains data integrity by explicitly linking the Project_ID and Reviewer_ID in the review record
  • Records the assignment status (“Scheduled”) for future verification and compliance tracking
  • Establishes time-based security controls by capturing both scheduled date and completion status
  • Implements load balancing as a security measure to prevent reviewer overloading, which could compromise review quality
  • Acts as a programmatic security control that cannot be easily bypassed, ensuring consistent policy enforcement

This approach aligns with the principle of least privilege by restricting reviewers from evaluating projects within their sphere of influence, significantly reducing the risk of biased assessments (McCarthy, 2023).

Secure Communication Protocols

The notification subsystem implemented security measures for all external communications:


def send_notifications(status_filter="Overdue", smtp_server=None, smtp_port=None):
    """Send secure email notifications for reviews with specified status."""
    results = {"sent": 0, "failed": 0, "total": 0, "log": []}
    
    try:
        # Load data securely with error handling
        projects = read_csv("Projects.csv")
        reviews = read_csv("Reviews.csv")
        users = read_csv("Users.csv")
        
        # Filter projects by status
        filtered_projects = [p for p in projects if p.get("Status") == status_filter]
        
        # Find matching reviews and reviewers
        for project in filtered_projects:
            # Find the review for this project
            project_reviews = [r for r in reviews if r.get("Project_ID") == project.get("Project_ID")]
            
            if not project_reviews:
                continue
                
            review = project_reviews[0]
            reviewer_id = review.get("Reviewer_ID")
            
            # Find the reviewer
            reviewers = [u for u in users if u.get("User_ID") == reviewer_id]
            if not reviewers:
                continue
                
            reviewer = reviewers[0]
            
            # Security: Validate email format before sending
            reviewer_email = reviewer.get("Email", "").strip()
            if not is_valid_email(reviewer_email):
                results["failed"] += 1
                continue
            
            # Create email with urgency indicators for overdue projects
            subject = f"URGENT: Review Required - {project.get('Project_Name')}" if status_filter == "Overdue"                else f"Review Scheduled - {project.get('Project_Name')}"
            
            # Send email using secure connection when available
            with smtplib.SMTP(smtp_server, smtp_port) as server:
                try:
                    # Security: Use TLS when available
                    if supports_starttls(server):
                        server.starttls()
                    
                    # Security: Credentials passed as parameters not hardcoded
                    if smtp_user and smtp_password:
                        server.login(smtp_user, smtp_password)
                    
                    message = create_notification_email(reviewer, project, review, subject)
                    server.sendmail("scheduler@example.com", reviewer_email, message)
                    
                    results["sent"] += 1
                    results["log"].append({
                        "project_id": project.get("Project_ID"),
                        "reviewer_email": reviewer_email,
                        "subject": subject,
                        "status": "sent"
                    })
                    
                except Exception as e:
                    results["failed"] += 1
                    results["log"].append({
                        "project_id": project.get("Project_ID"),
                        "reviewer_email": reviewer_email,
                        "error": str(e),
                        "status": "failed"
                    })
            
            results["total"] += 1
    
    except Exception as e:
        # Log exception but don't expose details
        logging.error(f"Error in send_notifications: {str(e)}")
    
    return results

Security features of the send_notifications() function:

  • Validates Email Recipients: Performs email format validation before sending to prevent email injection attacks
  • Implements Transport Layer Security: Automatically upgrades to TLS encryption when supported by the server
  • Prevents Credential Exposure: Accepts authentication credentials as parameters rather than hardcoding them in source code
  • Implements Comprehensive Exception Handling: Catches and logs errors without exposing sensitive details
  • Creates Security Activity Logs: Maintains detailed logs of all notification attempts for security auditing
  • Implements Secure Error Reporting: Records failures without exposing underlying system information
  • Provides Status Visibility: Includes “URGENT” indicators in subject lines for time-sensitive security reviews
  • Maintains Data Compartmentalization: Accesses only the minimum required data for each notification
  • Ensures Message Confidentiality: Uses secure email transport protocols to protect sensitive project information
  • Follows Fail-Secure Principle: Continues processing remaining notifications even when individual messages fail
  • Creates Non-Repudiation Records: Documents all sent notifications with timestamps and recipient information

External system interactions represent security boundaries that require special attention to prevent information disclosure (Defensive Programming, 2024).

Defensive Error Handling

Error handling was implemented as a functional requirement and a security measure. According to Shaw (n.d.), exception handling prevents information leakage and system state corruption.

def calculate_all_reviews(projects_file, current_date=None):
    """Calculate reviews with defensive error handling and data protection."""
    results = {"updated": 0, "overdue": 0, "due_soon": 0, "total_projects": 0}
    
    try:
        # Create backup before modification
        backup_file(projects_file)
        
        # Load projects with validation
        projects = read_csv(projects_file)
        results["total_projects"] = len(projects)
        
        # Use current date or today
        if current_date:
            today = datetime.strptime(current_date, "%Y-%m-%d").date()
        else:
            today = datetime.now().date()
        
        updated_projects = []
        
        for project in projects:
            try:
                # Defensive validation of each project
                if not validate_project_data(project):
                    # Log invalid project but continue processing
                    logging.warning(f"Skipping invalid project: {project.get('Project_ID')}")
                    updated_projects.append(project)
                    continue
                
                # Calculate next review date
                if project.get("Last_Review_Date"):
                    last_review = datetime.strptime(project["Last_Review_Date"], "%Y-%m-%d").date()
                    freq_years = float(project["Review_Frequency_Years"])
                    
                    # Calculate review cycle in days
                    cycle_days = int(freq_years * 365)
                    next_review = last_review + timedelta(days=cycle_days)
                    
                    # Format date for storage
                    project["Next_Review_Date"] = next_review.strftime("%Y-%m-%d")
                    
                    # Determine status with 30-day threshold for "Due Soon"
                    days_until_due = (next_review - today).days
                    
                    if days_until_due < 0:
                        project["Status"] = "Overdue"
                        results["overdue"] += 1
                    elif days_until_due <= 30:
                        project["Status"] = "Due Soon"
                        results["due_soon"] += 1
                    else:
                        project["Status"] = "Up to Date"
                    
                    results["updated"] += 1
                
                updated_projects.append(project)
                
            except Exception as e:
                # Handle per-project exceptions without breaking entire batch
                logging.error(f"Error processing project {project.get('Project_ID')}: {str(e)}")
                # Preserve original project data
                updated_projects.append(project)
        
        # Write updated projects back to file
        write_csv(projects_file, updated_projects)
        
    except Exception as e:
        # Recover from backup if overall process fails
        restore_from_backup(projects_file)
        logging.critical(f"Failed to update projects: {str(e)}")
        raise
    
    return results

Security features of the calculate_all_reviews() function:

  • Implements Automatic Backup Protection: Creates a backup of project data before any modifications to prevent data loss
  • Employs Data Validation Safeguards: Calls validate_project_data() to verify each project’s data integrity before processing
  • Maintains Transactional Integrity: Uses try/except blocks to ensure database-like ACID properties during batch processing
  • Implements Graceful Failure Handling: Continues processing other projects when an individual project validation fails
  • Preserves Original Data: Keeps invalid projects in the dataset rather than removing them, preventing data loss
  • Creates Detailed Security Logging: Records specific validation failures with project identifiers for audit trails
  • Implements Date Parsing Security: Validates date formats before performing calculations to prevent injection attacks
  • Provides Disaster Recovery: Can restore from backup automatically if the overall process fails
  • Contains Exceptions: Uses nested exception handling to prevent cascading failures from affecting the entire dataset
  • Centralizes Status Determination: Implements consistent status assignment logic that cannot be bypassed
  • Creates Complete Audit Metrics: Returns detailed statistics on processed records for compliance reporting
  • Follows Defense-in-Depth Principle: Multiple layers of protection (validation, exception handling, backup) ensure data security

These defensive programming techniques ensured that even under exceptional conditions, the system maintained a secure operational posture with proper data integrity controls.

The security architecture of the Project Review Scheduler demonstrates the application of security-by-design principles (Hoda et al., 2017), where protection mechanisms are integrated throughout the application rather than added as an afterthought. This approach resulted in a system that maintained security controls even when handling edge cases or unexpected inputs.

5.2 Test Cases

Unit Tests

image.png

# TC_DR_001 Verify due date is correctly calculated for 6-month review frequency and status is correctly assigned
from pathlib import Path

# Path to save the test file
test_path = Path.home() / "Documents" / "ProjectReviewScheduler" / "test_due_date_calculator.py"

# The unit test code
test_code = """
import unittest
from unittest.mock import patch
from datetime import datetime
import scheduler

class TestDueDateCalculator(unittest.TestCase):

    @patch('scheduler.read_csv')
    @patch('scheduler.write_csv')
    def test_due_date_six_months(self, mock_write_csv, mock_read_csv):
        # Setup mock project data
        projects = [{
            "Project_ID": "P001",
            "Project_Name": "Security Review",
            "Start_Date": "2025-01-01",
            "Last_Review_Date": "2025-01-01",
            "Review_Frequency_Years": "0.5",
            "Department": "IT",
            "Status": "",
            "Next_Review_Date": ""
        }]

        # Mock return value for read_csv
        mock_read_csv.return_value = projects

        # Execute calculation with a fixed current date
        result = scheduler.calculate_all_reviews("Projects.csv", current_date="2025-06-01")

        # Extract updated projects written to CSV
        written_projects = mock_write_csv.call_args[0][1]
        updated = written_projects[0]

        # Validate the calculated date and status
        self.assertEqual(updated['Next_Review_Date'], '2025-07-01')
        self.assertEqual(updated['Status'], 'Due Soon')
        self.assertEqual(result['due_soon'], 1)
        self.assertEqual(result['total_projects'], 1)

if __name__ == '__main__':
    unittest.main()
"""

# Save the file locally
test_path.parent.mkdir(parents=True, exist_ok=True)
test_path.write_text(test_code)

print(f"Unit test saved to: {test_path}")
Unit test saved to: /Users/cynthiamcginnis/Documents/ProjectReviewScheduler/test_due_date_calculator.py

Test Case: Due Date Calculation

Screenshot 2025-05-14 at 2.23.25 PM.png

Screenshot 2025-05-14 at 2.24.03 PM.png

Screenshot 2025-05-14 at 2.25.41 PM.png

Reviewer Assignments

Screenshot 2025-05-14 at 2.32.35 PM.png

TC_RA_003

Ensure reviewer is not from the same department as the project when alternatives are available

# TC_RA_003 Ensure reviewer is not from the same department as the project when alternatives are available

from pathlib import Path

# Define the path for the test file
test_file_path = Path.home() / "Documents" / "ProjectReviewScheduler" / "test_reviewer_department.py"

# Create the test code content
test_code = '''"""
Test Case: TC_RA_003 – Reviewer Department Separation

This unit test verifies that the reviewer assignment algorithm avoids selecting
a reviewer from the same department as the project when reviewers from other departments are available.
"""

import unittest
from unittest.mock import patch
from scheduler import assign_reviewer

class TestReviewerDepartmentSeparation(unittest.TestCase):

    @patch('scheduler.update_user')
    @patch('scheduler.add_review')
    def test_avoid_same_department_reviewer(self, mock_add_review, mock_update_user):
        # Project is from IT department
        project = {
            "Project_ID": "P001",
            "Department": "IT"
        }

        # Reviewers with different departments
        reviewers = [
            {"User_ID": "U001", "Name": "Alice", "Department": "IT", "Current_Load": 1},
            {"User_ID": "U002", "Name": "Bob", "Department": "HR", "Current_Load": 2},
            {"User_ID": "U003", "Name": "Charlie", "Department": "Finance", "Current_Load": 1}
        ]

        review = assign_reviewer(project, reviewers)
        assigned_id = review['Reviewer_ID']

        # U001 is from same department (IT) and should NOT be selected if others exist
        self.assertIn(assigned_id, ["U002", "U003"], "Reviewer from different department should be assigned")

if __name__ == '__main__':
    unittest.main()
'''

# Ensure the directory exists and write the test script
test_file_path.parent.mkdir(parents=True, exist_ok=True)
test_file_path.write_text(test_code)

test_file_path.name
  
      
'test_reviewer_department.py'

Screenshot 2025-05-14 at 2.37.40 PM.png

Screenshot 2025-05-14 at 2.38.11 PM.png

Screenshot 2025-05-14 at 2.39.45 PM.png

TC_RA_005

Ensure fallback to same-department reviewer and validate reviewer load balancing logic

# TC_RA_005
from pathlib import Path

# Define the path to save the test case file
test_file_path = Path.home() / "Documents" / "ProjectReviewScheduler" / "test_tc_ra_005.py"

# Define the test case code for TC_RA_005
test_code = '''
import unittest
from unittest.mock import patch
from scheduler import assign_reviewer

class TestReviewerAssignmentFallback(unittest.TestCase):
    @patch('scheduler.update_user')  # Mock update_user to avoid file write
    @patch('scheduler.add_review')   # Mock add_review to avoid writing reviews
    def test_fallback_same_department_and_load_balancing(self, mock_add_review, mock_update_user):
        project = {
            "Project_ID": "P1001",
            "Project_Name": "Fallback Logic Test",
            "Department": "IT",
            "Status": "Overdue"
        }

        reviewers = [
            {"User_ID": "U001", "Department": "IT", "Current_Load": "3"},
            {"User_ID": "U002", "Department": "IT", "Current_Load": "1"},
            {"User_ID": "U003", "Department": "IT", "Current_Load": "2"},
        ]

        assignment = assign_reviewer(project, reviewers)
        self.assertIsNotNone(assignment)
        self.assertEqual(assignment['Reviewer_ID'], 'U002')
        self.assertEqual(assignment['Project_ID'], 'P1001')
        self.assertEqual(assignment['Status'], 'Scheduled')

if __name__ == "__main__":
    unittest.main()
'''

# Save the test case file
test_file_path.parent.mkdir(parents=True, exist_ok=True)
test_file_path.write_text(test_code)

test_file_path.name
'test_tc_ra_005.py'

Screenshot 2025-05-14 at 2.59.07 PM.png Screenshot 2025-05-14 at 2.58.13 PM.png

Screenshot 2025-05-14 at 2.58.29 PM.png

TC_RA_006

Gracefully handle cases where no reviewers are available

# TC_RA_006 Gracefully handle cases where no reviewers are available

from pathlib import Path

# Define the correct and isolated unit test file for TC_RA_006
test_path = Path.home() / "Documents" / "ProjectReviewScheduler" / "test_tc_ra_006_no_reviewers.py"

unit_test_code = '''\
"""
Test Case: TC_RA_006
Purpose: Ensure the reviewer assignment handles the scenario where no reviewers are available gracefully.
"""

import unittest
from unittest.mock import patch
from scheduler import assign_all_reviewers

class TestNoReviewersAvailable(unittest.TestCase):

    @patch('scheduler.read_csv')
    @patch('scheduler.write_csv')
    def test_graceful_handling_when_no_reviewers_exist(self, mock_write_csv, mock_read_csv):
        # Only one project needing review
        projects = [{
            "Project_ID": "P006",
            "Project_Name": "Compliance Audit",
            "Start_Date": "2025-01-01",
            "Last_Review_Date": "2025-01-01",
            "Review_Frequency_Years": "1",
            "Department": "Finance",
            "Status": "Overdue",
            "Next_Review_Date": "2025-05-01"
        }]

        # No reviewers exist
        users = []  # Intentionally empty

        # Simulate read_csv returning projects, then users twice (initial + update_user)
        mock_read_csv.side_effect = [projects, users, users]

        # Run the reviewer assignment
        result = assign_all_reviewers("Projects.csv", "Users.csv", "Reviews.csv")

        # Assertions
        self.assertEqual(result['total_assigned'], 0)
        self.assertEqual(len(result['assignments']), 0)
        self.assertIn('assignments', result)
        print(" TC_RA_006 passed: No reviewers available handled correctly.")

if __name__ == '__main__':
    unittest.main()
'''

# Write the test file to disk
test_path.write_text(unit_test_code)
test_path.name

'test_tc_ra_006_no_reviewers.py'

Screenshot 2025-05-14 at 2.43.07 PM.png

Screenshot 2025-05-14 at 2.43.48 PM.png

Notification System TC_NT_002

Verify overdue review notifications include correct urgency indicators and are sent to the right reviewer

# unit test for Notification System TC_NT_002
from pathlib import Path

# Correct test path
notif_test_path_corrected = Path.home() / "Documents" / "ProjectReviewScheduler" / "test_notification_system.py"

# Corrected unit test code with exact mock read_csv order
corrected_test_code = '''"""
Test Case: TC_NT_002 – Notification System
Verify overdue review notifications include correct urgency indicators and are sent to the right reviewer.
"""

import unittest
from unittest.mock import patch, MagicMock
from scheduler import send_notifications

class TestNotificationSystem(unittest.TestCase):

    @patch("scheduler.read_csv")
    @patch("scheduler.smtplib.SMTP")
    def test_overdue_email_notification(self, mock_smtp, mock_read_csv):
        # Mocked data setup
        projects = [{
            "Project_ID": "P002",
            "Project_Name": "Critical Security Review",
            "Status": "Overdue",
            "Next_Review_Date": "2025-04-15",
            "Department": "IT"
        }]
        reviews = [{
            "Review_ID": "R001",
            "Project_ID": "P002",
            "Reviewer_ID": "U002",
            "Scheduled_Date": "2025-04-15",
            "Status": "Scheduled",
            "Completion_Date": ""
        }]
        users = [{
            "User_ID": "U002",
            "Name": "Jane Doe",
            "Email": "reviewer@example.com",
            "Department": "IT",
            "Current_Load": "1"
        }]

        # Ensure correct read_csv call order
        mock_read_csv.side_effect = [projects, reviews, users]

        # Mock SMTP server behavior
        mock_server = MagicMock()
        mock_smtp.return_value.__enter__.return_value = mock_server

        # Run the function
        result = send_notifications(status_filter="Overdue", smtp_server="localhost", smtp_port=587)

        print("DEBUG result:", result)

        # Assertions
        self.assertEqual(result["sent"], 1)
        self.assertEqual(result["failed"], 0)
        self.assertEqual(result["total"], 1)
        self.assertIn("reviewer@example.com", result["log"][0]["reviewer_email"])
        self.assertIn("URGENT", result["log"][0]["subject"].upper())
        self.assertIn("REVIEW REQUIRED", result["log"][0]["subject"].upper())


if __name__ == "__main__":
    unittest.main()
'''

# Save the updated test
notif_test_path_corrected.write_text(corrected_test_code)

notif_test_path_corrected.name

'test_notification_system.py'

Screenshot 2025-05-14 at 3.08.19 PM.png Screenshot 2025-05-14 at 3.08.36 PM.png

Screenshot 2025-05-14 at 3.08.52 PM.png

Reporting Module TC_RP_004

Validate generation of review schedule with correct formatting and sorting

from pathlib import Path

# Define test code path
test_path = Path.home() / "Documents" / "ProjectReviewScheduler" / "test_reporting_module.py"

# Define the rewritten unit test code
test_code = '''\
# test_reporting_module.py
# Test Case: TC_RP_004 - Validate generation of review schedule with correct formatting and sorting

import unittest
from unittest.mock import patch
from scheduler import generate_monthly_schedule

class TestReportingModule(unittest.TestCase):

    @patch("scheduler.get_all_projects")
    @patch("scheduler.get_all_reviews")
    @patch("scheduler.get_reviewer")
    @patch("scheduler.write_csv")
    def test_generate_monthly_review_schedule(self, mock_write_csv, mock_get_reviewer, mock_get_all_reviews, mock_get_all_projects):
        # Sample data for May 2025
        mock_get_all_reviews.return_value = [
            {"Review_ID": "R001", "Project_ID": "P001", "Reviewer_ID": "U001", "Scheduled_Date": "2025-05-10", "Status": "Scheduled"},
            {"Review_ID": "R002", "Project_ID": "P002", "Reviewer_ID": "U002", "Scheduled_Date": "2025-05-05", "Status": "In Progress"},
            {"Review_ID": "R003", "Project_ID": "P003", "Reviewer_ID": "U003", "Scheduled_Date": "2025-06-01", "Status": "Scheduled"},
            {"Review_ID": "R004", "Project_ID": "P004", "Reviewer_ID": "U004", "Scheduled_Date": "2025-05-20", "Status": "Completed"}
        ]

        mock_get_all_projects.return_value = [
            {"Project_ID": "P001", "Project_Name": "AI Audit"},
            {"Project_ID": "P002", "Project_Name": "Cyber Compliance"},
            {"Project_ID": "P003", "Project_Name": "Cloud Update"},
            {"Project_ID": "P004", "Project_Name": "DevOps Review"},
        ]

        mock_get_reviewer.side_effect = lambda uid: {"U001": {"Name": "Alice"},
                                                     "U002": {"Name": "Bob"},
                                                     "U003": {"Name": "Carol"},
                                                     "U004": {"Name": "Dana"}}.get(uid, {"Name": "Unknown"})

        # Call the function
        result = generate_monthly_schedule(month="05", year="2025", output_file="test_monthly_schedule.csv")

        # Ensure only two reviews included
        self.assertEqual(result["review_count"], 2)
        mock_write_csv.assert_called_once()

        # Capture the written data
        _, written_data, fieldnames = mock_write_csv.call_args[0]
        dates = [row["Scheduled_Date"] for row in written_data]
        self.assertEqual(dates, sorted(dates))  # Ensure sorted
        self.assertEqual(fieldnames, ['Project_ID', 'Project_Name', 'Reviewer_Name', 'Scheduled_Date', 'Status'])

if __name__ == '__main__':
    unittest.main()
'''

# Create folder if needed and write the file
test_path.parent.mkdir(parents=True, exist_ok=True)
test_path.write_text(test_code)

test_path.name
'test_reporting_module.py'

Screenshot 2025-05-14 at 3.21.16 PM.png Screenshot 2025-05-14 at 3.21.39 PM.png

Screenshot 2025-05-14 at 3.21.54 PM.png

Conclusion

The development and validation of the Project Review Scheduler (PRS) reflect the importance of integrating software testing practices and proactive security measures into every stage of the software development lifecycle. Throughout this project, black-box and white-box testing strategies were employed to ensure functional correctness, performance stability, and maintainability. These testing techniques—supported by unit tests, mock-based validation, and exception handling—helped identify edge cases, verify logic under varying scenarios, and confirm system behavior aligned with stakeholder requirements.

The test cases implemented covered critical aspects of the system:

  • TC_DR_001 validated the correctness of due date calculations using boundary value analysis.
  • TC_RA_003 ensured that reviewers from the same department were not assigned unless no alternatives existed.
  • TC_RA_005 confirmed fallback behavior to same-department reviewers when necessary and validated load-balancing logic.
  • TC_RA_006 tested fault tolerance when no reviewers were available, with a graceful exit message instead of failure.
  • TC_NT_002 used mocked SMTP servers to verify email notifications for overdue projects included urgency markers and were sent to correct recipients.
  • TC_RP_004 ensured that monthly review schedules were generated in properly sorted and formatted CSV reports for stakeholder use.

Each test case passed or was iteratively refined until passing, and all outcomes were documented with terminal logs, and screenshots.

Security was addressed through measures such as .env-based credentials management, input validation on CSV fields, and logical fallback structures to prevent system crashes or data corruption. As recommended by Alenezi & Almuairfi (2019), these practices support a Secure Software Development Lifecycle (SSDLC) by embedding safety checks at every layer of the application.

Collectively, the testing suite and security mechanisms implemented ensure that PRS is functional, resilient, secure, and maintainable. The attention to testing fidelity, security alignment, and traceable validation provides a foundation for future scalability and operational reliability.

References

Alenezi, M., & Almuairfi, S. (2019). Security risks in the software development lifecycle. International Journal of Recent Technology and Engineering, 8(3), 7048–7055. https://doi.org/10.35940/ijrte.c5374.098319

Defensive Programming. (2024, December 10). UCL Python Novice GDP – Penguins. https://github-pages.arc.ucl.ac.uk/python-novice-gdp-penguins/10-defensive.html

Ganeshan, D. (2020, April 27). Software testing techniques. MST Solutions. https://www.mstsolutions.com/technical/software-testing-techniques/

Hoda, R., Salleh, N., Grundy, J., & Tee, H. M. (2017). Systematic literature reviews in agile software development: A tertiary study. Information and Software Technology, 85, 60–70. https://doi.org/10.1016/j.infsof.2017.01.007

McCarthy, M. (2023, September 13). Principle of least privilege (PoLP) | What is it & why is it important. strongDM. https://www.strongdm.com/blog/principle-of-least-privilege

Shaw, A. (n.d.). Getting started with testing in Python. Real Python. https://realpython.com/python-testing/

Souppaya, M., Scarfone, K., & Dodson, D. (2022). Secure software development framework (SSDF) version 1.1 (NIST SP 800-218). https://doi.org/10.6028/nist.sp.800-218