---
phase: 05-workflow-hardening-and-optional-plugin-shell
plan: '02'
type: execute
wave: 1
depends_on: []
files_modified:
  - tests/test_smoke.py
  - tests/conftest.py
autonomous: true
requirements:
  - REL-02

must_haves:
  truths:
    - "Smoke test can run on a fixture vault without touching real vault"
    - "Smoke test covers setup validation, selection sync, index refresh, OCR doctor, and deep-reading queue"
  artifacts:
    - path: "tests/test_smoke.py"
      provides: "End-to-end smoke tests for the full pipeline"
      min_lines: 80
    - path: "tests/conftest.py"
      provides: "Fixture vault and test fixtures for smoke tests"
      min_lines: 40
  key_links:
    - from: "tests/test_smoke.py"
      to: "paperforge/cli.py"
      via: "paperforge CLI invocation"
    - from: "tests/test_smoke.py"
      to: "pipeline/worker/scripts/literature_pipeline.py"
      via: "worker function calls"
    - from: "tests/conftest.py"
      to: "paperforge/config.py"
      via: "fixture vault creation"
---

<objective>
Create a fixture-based smoke test that runs through the full pipeline setup validation, selection sync, index refresh, OCR doctor (dry-run), and deep-reading queue check without touching a real vault.
</objective>

<execution_context>
@C:/Users/Lin/.opencode/get-shit-done/workflows/execute-plan.md
@C:/Users/Lin/.opencode/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/STATE.md
@.planning/ROADMAP.md
@.planning/REQUIREMENTS.md
@.planning/research/DEFECTS.md

# Existing fixtures and test patterns

From tests/test_config.py:
```python
@pytest.fixture
def populated_vault(tmp_path: Path) -> Path:
    """A vault with realistic directory structure."""
    vault = tmp_path / "vault_populated"
    vault.mkdir()
    # Creates: 99_System/PaperForge/{exports,ocr}, 03_Resources/{Literature,LiteratureControl/library-records}, 05_Bases, .opencode/{skills,command}
```

From tests/test_cli_worker_dispatch.py:
```python
@pytest.fixture
def mock_vault(tmp_path):
    """Create a minimal mock vault with paperforge.json."""
```

From paperforge/cli.py (commands to smoke test):
```python
# paperforge doctor     -> run_doctor(vault)
# paperforge selection-sync -> run_selection_sync(vault)
# paperforge index-refresh -> run_index_refresh(vault)
# paperforge ocr doctor   -> _cmd_ocr_doctor(vault, args)
# paperforge deep-reading -> run_deep_reading(vault, verbose=False)
```
</context>

<tasks>

<task type="auto">
  <name>Task 1: Create Fixture Vault Factory in conftest.py</name>
<read_first>
  tests/test_config.py (existing fixtures)
  tests/conftest.py (existing conftest if any)
  paperforge/config.py (paperforge_paths, load_vault_config)
</read_first>
<files>tests/conftest.py</files>
<action>
Add shared pytest fixtures to tests/conftest.py for smoke testing:

1. **Create `fixture_vault` factory:**
   ```python
   @pytest.fixture
   def fixture_vault(tmp_path: Path) -> Path:
       """Create a complete fixture vault with realistic structure."""
       vault = tmp_path / "fixture_vault"
       vault.mkdir()
       # Create directory tree:
       vault / "99_System" / "PaperForge" / "exports"  (mkdir parents)
       vault / "99_System" / "PaperForge" / "ocr"
       vault / "03_Resources" / "Literature"
       vault / "03_Resources" / "LiteratureControl" / "library-records" / "骨科"
       vault / "05_Bases"
       vault / ".opencode" / "skills"
       vault / ".opencode" / "command"
       # Create paperforge.json with defaults
       pf_cfg = {
           "system_dir": "99_System",
           "resources_dir": "03_Resources",
           "literature_dir": "Literature",
           "control_dir": "LiteratureControl",
           "base_dir": "05_Bases",
       }
       (vault / "paperforge.json").write_text(json.dumps(pf_cfg))
       return vault
   ```

2. **Create `fixture_library_records`:**
   - Add 2-3 minimal library-record markdown files in the fixture vault
   - Each with frontmatter: zotero_key, domain, title, year, ocr_status, analyze, do_ocr

3. **Create `fixture_bbt_json`:**
   - Create a Better BibTeX JSON export file in exports/
   - With 2-3 mock paper entries matching the library records

4. **Create `fixture_with_pdf`:**
   - Create a real temporary PDF file (use tmp_path for actual file)
   - Set has_pdf: true and pdf_path in library-record

5. **Ensure fixtures work with Windows paths:**
   - Use pathlib.Path throughout
   - No hardcoded forward slashes
</action>
<acceptance_criteria>
- tests/conftest.py defines fixture_vault that creates a complete vault structure
- fixture_vault creates paperforge.json with proper JSON
- Fixture works with both default and custom directory names
- conftest.py imports work correctly (no import errors)
- Path fixtures are all absolute Path objects
</acceptance_criteria>
<done>conftest.py has reusable fixtures for smoke testing with complete fixture vault factory</done>
</task>

<task type="auto">
  <name>Task 2: Create Smoke Test Suite</name>
<read_first>
  tests/conftest.py (fixtures created in Task 1)
  paperforge/cli.py (command entry points)
  pipeline/worker/scripts/literature_pipeline.py (worker functions)
</read_first>
<files>tests/test_smoke.py</files>
<action>
Create tests/test_smoke.py with end-to-end smoke tests:

1. **Smoke Test: Setup Validation (paperforge doctor):**
   - Test: run_doctor on fixture_vault returns 0 (all checks pass)
   - Mock file existence checks to simulate valid setup
   - Verify output contains expected check categories

2. **Smoke Test: Selection Sync:**
   - Test: run_selection_sync on fixture_vault returns 0
   - Mock the BBT JSON read to return fixture data
   - Verify library-records directory is scanned/matched

3. **Smoke Test: Index Refresh:**
   - Test: run_index_refresh on fixture_vault returns 0
   - Uses fixture_library_records
   - Verify formal literature notes are generated

4. **Smoke Test: OCR Doctor (L1-L3, no live):**
   - Test: _cmd_ocr_doctor returns 0 or 1 (diagnostic runs)
   - Mock env checks (PADDLEOCR_API_TOKEN present in mock env)
   - Mock URL validation checks
   - Verify it produces level and passed fields

5. **Smoke Test: Deep-Reading Queue:**
   - Test: run_deep_reading on fixture_vault returns 0
   - Verify it produces three-state output (就绪/等待OCR/阻塞)
   - Mock OCR status checks

6. **Smoke Test: CLI Main Entry:**
   - Test: cli.main(["--vault", str(fixture_vault), "doctor"]) returns 0
   - Test: cli.main(["--vault", str(fixture_vault), "status"]) returns 0
   - Verify integer exit codes (not sys.exit)

7. **No real vault touches:**
   - All tests use tmp_path fixtures
   - All network calls mocked
   - All file operations on tmp_path
</action>
<acceptance_criteria>
- tests/test_smoke.py exists with 6+ smoke test functions
- All tests run on tmp_path fixtures (no real vault)
- All tests use mocks for network/file-external operations
- pytest tests/test_smoke.py -v passes with exit code 0
- Tests cover all smoke test criteria from REL-02 success criteria
</acceptance_criteria>
<done>Smoke test suite covers full pipeline from setup validation to deep-reading queue check</done>
</task>

</tasks>

<verification>
Run the smoke tests:
```bash
pytest tests/test_smoke.py -v --tb=short
```

Run full test suite to ensure no regressions:
```bash
pytest tests/ -v --tb=short
```
</verification>

<success_criteria>
- [ ] tests/conftest.py has fixture_vault, fixture_library_records, fixture_bbt_json, fixture_with_pdf
- [ ] tests/test_smoke.py has 6+ smoke test functions
- [ ] All smoke tests use tmp_path fixtures, no real vault access
- [ ] pytest tests/test_smoke.py -v passes
- [ ] pytest tests/ -v passes (full suite, no regressions)
</success_criteria>

<output>
After completion, create `.planning/phases/05-workflow-hardening-and-optional-plugin-shell/05-02-SUMMARY.md`
</output>
