Skip to content

[Coverage] Phase 2: Add Tests for Utility Functions (25-68% → 90%) #106

Description

@jeremyeder

Cold-Start Issue: Phase 2 - Add Tests for Utility Functions (25-68% → 90%)

Objective

Add comprehensive unit tests for utility modules to achieve 90% coverage on:

  • src/agentready/utils/privacy.py (25% → 90%)
  • src/agentready/utils/subprocess_utils.py (68% → 90%)

Estimated Time: 30 minutes
Priority: P0 (pure functions, easy to test)


Context

These are utility modules with pure functions - no complex state or dependencies. Testing is straightforward:

  • privacy.py: PII detection, path sanitization, sensitive data handling
  • subprocess_utils.py: Command execution with timeouts, output limits, error handling

Implementation Guide

Files to Create/Modify

  1. Create tests/unit/utils/test_privacy.py (new file)
  2. Expand tests/unit/utils/test_subprocess_utils.py (if exists, otherwise create)

privacy.py Testing

Module Overview

Read src/agentready/utils/privacy.py to understand:

  • PII detection functions (emails, API keys, tokens)
  • Path sanitization
  • Sensitive directory detection
  • Privacy-preserving transformations

Test Structure

"""Unit tests for privacy utilities."""

import pytest
from agentready.utils.privacy import (
    detect_email,
    detect_api_key,
    sanitize_path,
    is_sensitive_directory,
    redact_pii,
)


class TestPIIDetection:
    """Test PII detection functions."""

    @pytest.mark.parametrize("text,expected", [
        ("user@example.com", True),
        ("contact.me@domain.co.uk", True),
        ("not-an-email", False),
        ("missing@symbol", False),
        ("", False),
    ])
    def test_detect_email(self, text, expected):
        """Test email detection in various formats."""
        assert detect_email(text) == expected

    @pytest.mark.parametrize("text,expected", [
        ("sk-ant-api03-xxxxx", True),
        ("ghp_1234567890abcdef", True),
        ("AKIA1234567890ABCDEF", True),  # AWS key
        ("just-a-string", False),
        ("", False),
    ])
    def test_detect_api_key(self, text, expected):
        """Test API key detection."""
        assert detect_api_key(text) == expected


class TestPathSanitization:
    """Test path sanitization functions."""

    @pytest.mark.parametrize("path,expected", [
        ("/home/user/project", "/home/user/project"),
        ("/etc/passwd", "[REDACTED]"),
        ("/sys/kernel", "[REDACTED]"),
        ("relative/path", "relative/path"),
    ])
    def test_sanitize_path(self, path, expected):
        """Test path sanitization for sensitive directories."""
        assert sanitize_path(path) == expected

    @pytest.mark.parametrize("directory,expected", [
        ("/etc", True),
        ("/sys", True),
        ("/proc", True),
        ("/home/user", False),
        ("/tmp", False),
    ])
    def test_is_sensitive_directory(self, directory, expected):
        """Test sensitive directory detection."""
        assert is_sensitive_directory(directory) == expected


class TestPIIRedaction:
    """Test PII redaction in text."""

    def test_redact_email():
        """Test email redaction in text."""
        text = "Contact user@example.com for help"
        redacted = redact_pii(text)
        assert "user@example.com" not in redacted
        assert "[EMAIL]" in redacted or "[REDACTED]" in redacted

    def test_redact_api_key():
        """Test API key redaction."""
        text = "Use key sk-ant-api03-xxxxx to authenticate"
        redacted = redact_pii(text)
        assert "sk-ant-api03" not in redacted
        assert "[API_KEY]" in redacted or "[REDACTED]" in redacted

    def test_redact_multiple_pii():
        """Test redacting multiple PII types."""
        text = "Email user@example.com with key sk-test-123"
        redacted = redact_pii(text)
        assert "user@example.com" not in redacted
        assert "sk-test-123" not in redacted

Specific Tests Needed for privacy.py

Check the actual module for exact function names, then test:

  • Email detection (valid formats)
  • Email detection (invalid formats)
  • API key detection (Anthropic, OpenAI, GitHub, AWS)
  • Path sanitization for /etc, /sys, /proc, /.ssh
  • Path sanitization for safe paths
  • Sensitive directory detection
  • PII redaction in text
  • Edge cases: empty strings, None values
  • Unicode handling in paths/text

subprocess_utils.py Testing

Module Overview

Read src/agentready/utils/subprocess_utils.py to understand:

  • Command execution with timeout
  • Output size limiting
  • Error handling
  • Encoding fallbacks

Test Structure

"""Unit tests for subprocess utilities."""

import pytest
from agentready.utils.subprocess_utils import (
    run_command,
    run_command_with_timeout,
    check_output_limit,
)


class TestCommandExecution:
    """Test command execution functions."""

    def test_run_command_success():
        """Test successful command execution."""
        result = run_command(["echo", "hello"])
        assert result.returncode == 0
        assert "hello" in result.stdout

    def test_run_command_failure():
        """Test failed command execution."""
        result = run_command(["false"])
        assert result.returncode != 0

    def test_run_command_with_timeout_success():
        """Test command completes within timeout."""
        result = run_command_with_timeout(["echo", "test"], timeout=5.0)
        assert result.returncode == 0

    def test_run_command_with_timeout_expires():
        """Test command times out."""
        with pytest.raises(TimeoutError):
            run_command_with_timeout(["sleep", "10"], timeout=0.1)


class TestOutputLimiting:
    """Test output size limiting."""

    def test_output_within_limit():
        """Test small output passes limit check."""
        output = "small output"
        assert check_output_limit(output, max_size=1000) == True

    def test_output_exceeds_limit():
        """Test large output fails limit check."""
        output = "x" * 10000
        assert check_output_limit(output, max_size=1000) == False


class TestEncodingHandling:
    """Test encoding fallbacks."""

    def test_utf8_encoding():
        """Test UTF-8 encoded output."""
        # Test decoding UTF-8 bytes
        pass

    def test_latin1_fallback():
        """Test fallback to latin-1 encoding."""
        # Test decoding with fallback
        pass

Specific Tests Needed for subprocess_utils.py

Check the actual module, then test:

  • run_command() success case
  • run_command() failure case
  • run_command_with_timeout() success
  • run_command_with_timeout() timeout exception
  • Output size limit checking
  • Encoding detection (UTF-8)
  • Encoding fallback (latin-1)
  • Error message capture
  • Empty output handling
  • Command not found error

Running Tests

# Run utility tests
pytest tests/unit/utils/ -v

# Check coverage
pytest tests/unit/utils/ --cov=src/agentready/utils --cov-report=term-missing

# Target: Both files at 90%+

Acceptance Criteria

  • privacy.py coverage ≥ 90%
  • subprocess_utils.py coverage ≥ 90%
  • All tests pass
  • Used pytest.mark.parametrize for multiple cases
  • Tests run in < 5 seconds
  • No external dependencies (use mocks if needed)

Tips

  1. Read the source first: Check actual function signatures in both files
  2. Mock subprocess calls: Use unittest.mock for subprocess.run if needed
  3. Test error paths: Don't just test happy paths
  4. Use parametrize: Test multiple email/key formats in one test
  5. Keep tests simple: These are pure functions, tests should be too

Files to Reference

  • src/agentready/utils/privacy.py - 88 lines, PII detection and sanitization
  • src/agentready/utils/subprocess_utils.py - 71 lines, command execution utilities

Estimated Lines of Test Code: ~100-150 lines
Estimated Time: 30 minutes
Impact: +8-10 percentage points overall coverage

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions