# Story 7.1: Refactor Coverage-Padding Tests to Behavioral Tests

**Status:** Pending
**Priority:** P0
**Depends on:** None
**Effort:** 4-5 days

---

## Problem Statement

The Test Value Audit identified 16 test files that exist primarily to hit coverage lines rather than verify behavior. These files are characterized by:

1. **Explicit coverage-focused naming:**

   - `test_core_logger_priority1_coverage.py` through `_priority4_coverage.py`
   - `test_core_logger_comprehensive.py`
   - `test_core_logger_more_coverage.py`
   - `test_core_logger_extra.py`
   - `test_core_logger_cov_additions.py`
   - `test_core_logger_exceptional_coverage.py`
   - `test_core_logger_metrics_extra.py`
   - `test_core_errors_comprehensive_coverage.py`
   - `test_core_errors_more_coverage.py`
   - `test_core_errors_extra.py`
   - `test_init_comprehensive_coverage.py`
   - `test_field_mask_redactor_extra_coverage.py`
   - `test_serialization_extra.py`

2. **Line-number-targeted docstrings:**

   ```python
   """Test thread mode drain logic (Lines 176-177, 285-286)."""
   ```

3. **Always-true assertions:**

   ```python
   assert result.processed >= 0  # Always true
   assert result.dropped >= 0    # Always true
   ```

4. **Heavy internal mocking that tests mocks, not code:**
   ```python
   with patch("fapilog.plugins.sinks.stdout_json.StdoutJsonSink", return_value=mock_sink):
       logger = await get_async_logger(name="test")
       # Only verifies the mock was used, not real behavior
   ```

These tests provide false confidence: they pass regardless of whether the code behaves correctly.

**Note:** Weak assertions (`>= 0`) also appear in 14 files totaling ~100 occurrences, including some non-coverage-named files like `test_core_logger.py`, `test_async_logger_facade.py`, and `test_error_handling.py`. These should be fixed as part of Phase 2.

---

## Goals

1. **Keep the coverage** — All currently-covered lines should remain covered
2. **Add behavioral verification** — Every test should assert on _outcomes_, not just _execution_
3. **Remove line-number coupling** — Tests should describe behavior, not implementation details
4. **Rename files appropriately** — File names should describe what behavior they test
5. **Consolidate where sensible** — Reduce 16 files to 5-6 well-organized modules

---

## Acceptance Criteria

- [ ] All 16 files named `*_coverage.py`, `*_more_coverage.py`, `*_extra.py` renamed or consolidated
- [ ] Zero assertions of form `>= 0` or `>= 1` on result counters (in all test files, not just renamed ones)
- [ ] Zero docstrings referencing line numbers
- [ ] Every test has at least one behavioral assertion (outcome verification)
- [ ] All existing line coverage maintained
- [ ] All tests pass

---

## Technical Approach

### Phase 1: Discovery and Audit (Day 1)

First, run discovery commands to confirm the full scope:

```bash
# Find all coverage-named files
find tests/unit -name "*coverage*.py" -o -name "*extra*.py" -o -name "*comprehensive*.py"

# Count weak assertions across all test files
grep -r "assert.*>= 0" tests/ | wc -l
grep -r "assert.*>= 1" tests/ | wc -l

# Find line-number references in docstrings
grep -rE "lines? \d+-\d+" tests/unit --include="*.py"
```

Then review each coverage-padding file and categorize tests:

| Category                                | Action                                |
| --------------------------------------- | ------------------------------------- |
| Tests behavior but has weak assertions  | Strengthen assertions                 |
| Duplicates existing test                | Delete                                |
| Tests internal detail only              | Convert to integration test or delete |
| Tests valid behavior, just named poorly | Rename and move                       |

### Phase 2: Strengthen Assertions (Day 1-2)

Convert weak assertions to behavioral assertions:

**Before:**

```python
def test_drain_with_pending_items():
    logger = create_logger()
    for i in range(5):
        logger.info(f"msg-{i}")

    result = await logger.stop_and_drain()

    assert result.submitted >= 5
    assert result.processed >= 0  # WEAK: always true
    assert result.dropped >= 0    # WEAK: always true
```

**After:**

```python
def test_drain_flushes_all_pending_items():
    collected = []
    logger = create_logger(sink=lambda e: collected.append(e))

    for i in range(5):
        logger.info(f"msg-{i}")

    result = await logger.stop_and_drain()

    assert result.submitted == 5
    assert result.processed == 5
    assert result.dropped == 0
    assert len(collected) == 5
    assert [e["message"] for e in collected] == [f"msg-{i}" for i in range(5)]
```

### Phase 3: Remove Mock-Heavy Tests or Convert to Integration (Day 2-3)

Tests that mock the primary component under test should be:

- Deleted if they duplicate real tests
- Converted to integration tests if they test a real scenario
- Kept only if testing a genuine boundary (external service)

**Example of problematic test:**

```python
def test_get_async_logger_sink_start_failure():
    mock_sink = Mock()
    mock_sink.start.side_effect = RuntimeError("Start failed")

    with patch("fapilog.plugins.sinks.stdout_json.StdoutJsonSink", return_value=mock_sink):
        logger = await get_async_logger(name="test")
        # What does this prove? Only that we patched correctly.
```

**Convert to:**

```python
@pytest.mark.asyncio
async def test_logger_handles_sink_start_failure_gracefully():
    """Logger should log to fallback when primary sink fails to start."""

    class FailingSink:
        async def start(self):
            raise RuntimeError("Sink unavailable")
        async def write(self, event):
            pass

    fallback_events = []

    async def fallback_sink(event):
        fallback_events.append(event)

    logger = await get_async_logger(
        sinks=[FailingSink()],
        fallback_sink=fallback_sink,
    )

    await logger.info("test message")
    await logger.drain()

    # Verify fallback was used
    assert len(fallback_events) >= 1
    assert fallback_events[0]["message"] == "test message"
```

### Phase 4: Rename and Consolidate (Day 4-5)

Consolidate the 16 files into a logical structure:

| Old Files                                  | New File                           | Focus                        |
| ------------------------------------------ | ---------------------------------- | ---------------------------- |
| `test_core_logger_priority1_coverage.py`   | `test_logger_thread_lifecycle.py`  | Thread mode start/stop       |
| `test_core_logger_priority2_coverage.py`   | (merged above)                     |                              |
| `test_core_logger_priority3_coverage.py`   | `test_logger_error_containment.py` | Exception handling           |
| `test_core_logger_priority4_coverage.py`   | (merged above)                     |                              |
| `test_core_logger_exceptional_coverage.py` | (merged above)                     |                              |
| `test_core_logger_more_coverage.py`        | `test_logger_features.py`          | Enrichers, sampling, metrics |
| `test_core_logger_extra.py`                | (merged above)                     |                              |
| `test_core_logger_comprehensive.py`        | (merged above)                     |                              |
| `test_core_logger_cov_additions.py`        | (merged above)                     |                              |
| `test_core_logger_metrics_extra.py`        | (merged above)                     |                              |
| `test_core_errors_*_coverage.py`           | `test_error_serialization.py`      | Exception chain handling     |
| `test_serialization_extra.py`              | (merged above)                     |                              |
| `test_init_comprehensive_coverage.py`      | `test_logger_initialization.py`    | Startup paths                |
| `test_field_mask_redactor_extra_coverage.py` | `test_redactor_edge_cases.py`    | Redactor edge cases          |

---

## Files Changed

### Coverage-Named Files (Rename/Consolidate)

| File                                                      | Action                                        |
| --------------------------------------------------------- | --------------------------------------------- |
| `tests/unit/test_core_logger_priority1_coverage.py`       | Refactor → `test_logger_thread_lifecycle.py`  |
| `tests/unit/test_core_logger_priority2_coverage.py`       | Merge into above                              |
| `tests/unit/test_core_logger_priority3_coverage.py`       | Refactor → `test_logger_error_containment.py` |
| `tests/unit/test_core_logger_priority4_coverage.py`       | Merge into above                              |
| `tests/unit/test_core_logger_exceptional_coverage.py`     | Merge into above                              |
| `tests/unit/test_core_logger_more_coverage.py`            | Refactor → `test_logger_features.py`          |
| `tests/unit/test_core_logger_extra.py`                    | Merge into above                              |
| `tests/unit/test_core_logger_comprehensive.py`            | Merge into above                              |
| `tests/unit/test_core_logger_cov_additions.py`            | Merge into above                              |
| `tests/unit/test_core_logger_metrics_extra.py`            | Merge into above                              |
| `tests/unit/test_core_errors_comprehensive_coverage.py`   | Refactor → `test_error_serialization.py`      |
| `tests/unit/test_core_errors_more_coverage.py`            | Merge into above                              |
| `tests/unit/test_core_errors_extra.py`                    | Merge into above                              |
| `tests/unit/test_serialization_extra.py`                  | Merge into above                              |
| `tests/unit/test_init_comprehensive_coverage.py`          | Refactor → `test_logger_initialization.py`    |
| `tests/unit/test_field_mask_redactor_extra_coverage.py`   | Refactor → `test_redactor_edge_cases.py`      |

### Non-Coverage-Named Files (Fix Assertions Only)

These files have weak assertions but don't need renaming:

| File                                        | Action                          |
| ------------------------------------------- | ------------------------------- |
| `tests/unit/test_core_logger.py`            | Strengthen `>= 0` assertions    |
| `tests/unit/test_async_logger_facade.py`    | Strengthen `>= 0` assertions    |
| `tests/unit/test_error_handling.py`         | Strengthen `>= 0` assertions    |
| `tests/unit/test_public_api_runtime.py`     | Strengthen `>= 0` assertions    |
| `tests/unit/test_filters_adaptive_sampling.py` | Strengthen `>= 0` assertions |

**Net file count change:** -10 files (from 16 coverage-named to 6 behavioral)

---

## Refactoring Patterns

### Pattern 1: Counter Assertions

**Before:**

```python
assert result.dropped >= 0
```

**After:**

```python
# If testing happy path:
assert result.dropped == 0

# If testing backpressure:
expected_drops = submitted - queue_capacity
assert result.dropped == expected_drops
```

### Pattern 2: "Didn't Crash" Tests

**Before:**

```python
def test_handles_exception_gracefully():
    with patch("module.func", side_effect=RuntimeError()):
        result = call_function()
    # No assertion - just verifying no exception raised
```

**After:**

```python
def test_logs_error_and_continues_on_exception():
    errors_logged = []

    with patch("module.func", side_effect=RuntimeError("boom")):
        result = call_function(on_error=lambda e: errors_logged.append(e))

    assert len(errors_logged) == 1
    assert "boom" in str(errors_logged[0])
    assert result.status == "degraded"  # Verify actual outcome
```

### Pattern 3: Line-Number Docstrings

**Before:**

```python
def test_thread_mode_drain_logic():
    """Test thread mode drain logic (Lines 176-177, 285-286)."""
```

**After:**

```python
def test_drain_waits_for_worker_thread_to_complete():
    """Drain should block until the background worker finishes processing."""
```

---

## Test Plan

After refactoring, run:

```bash
# Verify coverage maintained
pytest tests/ --cov=src/fapilog --cov-report=term-missing --cov-fail-under=90

# Verify no regressions
pytest tests/ -x

# Check for weak assertions (manual review)
grep -r "assert.*>= 0" tests/
grep -r "assert.*>= 1" tests/
```

---

## Risk Assessment

**Risk Level: Low**

- Tests are being strengthened, not removed
- Coverage is maintained
- No production code changes
- Changes are fully reversible

---

## Rollback Plan

Revert commits; original test files are preserved in git history.

---

## Definition of Done

- [ ] Zero files named `*_coverage.py`, `*_extra.py`, or `*_comprehensive*.py`
- [ ] Zero `>= 0` assertions on counters (all 21 affected files)
- [ ] Zero line-number references in docstrings
- [ ] 90% coverage maintained
- [ ] All tests pass
- [ ] PR review confirms assertions verify behavior
