Test Plan: Configuration Reader

Coverage Analysis Summary

  • Achieved coverage: 98% (104 statements, 2 uncovered, 34 branches, 1 partial)

  • Test count: 37 tests

  • Uncovered lines: 193-194 (unreachable: TOML parser always produces string keys)

  • Test execution: All tests pass on Linux and Windows

Testing Principle

Test behavior through the public API, not implementation details.

This test suite focuses exclusively on testing the observable behavior of the configuration reader through its public interface:

  • discover_configuration() - Configuration discovery

  • load_configuration() - Configuration loading and parsing

  • Configuration - Configuration dataclass

  • ConfigurationInvalidity - Validation error exception

  • ConfigurationAbsence - Missing file exception

Private functions (_parse_configuration, _parse_string_sequence, _parse_optional_int, _parse_rule_parameters, _discover_pyproject_toml) are not tested directly. They are exercised through the public API tests, which provides:

  • Better maintainability: Implementation can be refactored without breaking tests

  • Clearer intent: Tests document what users can rely on

  • Equivalent coverage: 98% coverage achieved through public API alone

  • Platform independence: Behavior tests work across operating systems

Test Strategy

Basic Functionality Tests (000-099)

  • Test 000: Configuration module imports successfully

  • Test 010: Configuration dataclass instantiation with default values

  • Test 020: PathLike type alias works with str and Path

  • Test 030: Exception classes inherit correctly from Omnierror

Configuration Discovery Tests (100-199)

Function: discover_configuration (Tests 100-149)

  • Test 100: Discovers pyproject.toml in current directory

  • Test 110: Discovers pyproject.toml in parent directory

  • Test 115: Discovers pyproject.toml multiple levels up

  • Test 120: Returns absent when no pyproject.toml found

  • Test 125: Returns absent when starting from root directory

  • Test 130: Discovers from file path by checking parent directory

  • Test 135: Handles absent start_directory parameter (uses cwd)

  • Test 140: Stops search at filesystem root

  • Test 145: Returns loaded Configuration object when found

Function: load_configuration (Tests 150-199)

  • Test 150: Loads valid minimal configuration

  • Test 155: Loads configuration with all fields populated

  • Test 160: Loads configuration with select rules

  • Test 165: Loads configuration with exclude rules

  • Test 170: Loads configuration with path filters

  • Test 175: Loads configuration with context setting

  • Test 180: Loads configuration with rule parameters

  • Test 185: Raises ConfigurationAbsence for missing file

  • Test 190: Raises ConfigurationInvalidity for invalid TOML syntax

Exception Handling Tests (200-299)

Exception: ConfigurationInvalidity (Tests 200-219)

  • Test 200: Instantiates with location and reason

  • Test 205: Inherits from Omnierror and ValueError

  • Test 210: Formats error message with location and reason

  • Test 215: Stores location as string attribute

  • Test 220: Stores reason as string attribute

Exception: ConfigurationAbsence (Tests 225-249)

  • Test 225: Instantiates with absent location

  • Test 230: Instantiates with specific location

  • Test 235: Inherits from Omnierror and FileNotFoundError

  • Test 240: Formats message for absent location

  • Test 245: Formats message for specific location

  • Test 250: Stores location as None for absent, string otherwise

Implementation Notes

Dependencies requiring injection

  • File system operations: Use pyfakefs for all file system tests

  • Current working directory: Create fake filesystem with appropriate structure

Filesystem operations needing pyfakefs

  • All pyproject.toml discovery tests

  • All configuration loading tests

  • Directory traversal tests

  • File existence checks

Test data and fixtures

tests/data/configuration/valid/ - Valid pyproject.toml examples:

  • minimal.toml - Minimal valid configuration

  • complete.toml - All fields populated

  • rules-only.toml - Only rule parameters

  • paths-only.toml - Only path filters

  • select-exclude.toml - Both select and exclude rules

tests/data/configuration/invalid/ - Invalid configurations:

  • invalid-toml-syntax.toml - TOML syntax errors

  • invalid-structure.toml - Wrong structure type

  • invalid-select-type.toml - Wrong type for select field

  • invalid-context-negative.toml - Negative context value

  • invalid-rules-not-dict.toml - Rules section not a dictionary

  • invalid-rule-code-not-string.toml - Non-string rule code

  • invalid-rule-params-not-dict.toml - Non-dict rule parameters

Fixture setup pattern

Use pyfakefs.Patcher context manager in each test:

def test_100_discover_in_current_directory():
    '''Configuration discovery finds pyproject.toml in current directory.'''
    module = __.cache_import_module(f"{__.PACKAGE_NAME}.configuration")
    with Patcher() as patcher:
        patcher.fs.create_file(
            '/project/pyproject.toml',
            contents='[tool.vibelinter]\nselect = ["VBL101"]')
        patcher.fs.create_dir('/project/subdir')
        result = module.discover_configuration(Path('/project/subdir'))
        assert not absence.is_absent(result)
        assert result.select == ('VBL101',)

For tests using real test data files:

def test_190_raises_invalidity_for_invalid_toml():
    '''Configuration loading raises invalidity for invalid TOML.'''
    module = __.cache_import_module(f"{__.PACKAGE_NAME}.configuration")
    with Patcher() as patcher:
        patcher.fs.add_real_directory(
            'tests/data/configuration/invalid', lazy_read=True)
        with pytest.raises(
            module.ConfigurationInvalidity,
            match='Invalid TOML syntax'):
            module.load_configuration(
                'tests/data/configuration/invalid/invalid-toml-syntax.toml')

Test module numbering

Create tests/test_000_vibelinter/test_200_configuration.py following the established numbering scheme where 200-299 is allocated for configuration and parsing layer.

Anti-patterns to avoid

  • DO NOT test against real file system - use pyfakefs exclusively

  • DO NOT use monkey-patching - inject dependencies via parameters

  • DO NOT test implementation details - test behavior through public API

  • DO NOT create temporary directories for synchronous file operations

Success Metrics

  • Target line coverage: 100% (from current 16%)

  • Branch coverage goals: 100% (from current 0%)

  • Specific gaps to close: All 82 uncovered lines

Closure of uncovered lines

  • Lines 37-39: ConfigurationInvalidity.__init__ - Tests 450-465

  • Lines 49-55: ConfigurationAbsence.__init__ - Tests 470-495

  • Lines 94-97: discover_configuration - Tests 100-145

  • Lines 102-114: load_configuration - Tests 150-199

  • Lines 121-134: _discover_pyproject_toml - Tests 400-445

  • Lines 142-149: _parse_configuration - Tests 200-249

  • Lines 165-175: _parse_optional_int - Tests 300-349

  • Lines 183-204: _parse_rule_parameters - Tests 350-399

  • Lines 213-233: _parse_string_sequence - Tests 250-299

Expected test count

  • Basic functionality: 4 tests

  • Configuration discovery: 10 tests

  • Configuration loading: 11 tests

  • TOML parsing: 50 tests

  • Exception handling: 15 tests

  • Total: Approximately 90 tests

Estimated complexity: Medium

Implementation priority: High - Configuration reading is essential for user customization

Testing philosophy alignment

This test plan follows project testing principles:

  • Dependency injection: All file system operations use pyfakefs

  • Immutability: Tests work with immutable Configuration objects

  • Performance: In-memory filesystem for fast test execution

  • Coverage: Systematic targeting of all branches and edge cases

  • Behavior testing: Focus on public API behavior, not implementation