Skip to content

Improve testability of pdb_deposition.py #83

Description

@tdudgeon

Context

pdb_deposition.py has one existing test file (test_validate_structure_cif_doc.py) covering only
the validate_structure_cif_doc() function. The main workhorse — process_input() — is a 383-line
function that tangles file discovery, SoakDB reads, CIF manipulation, token substitution, and file
writing with no seams for unit testing. The goal is to extract a small number of focused functions
and add tests that cover the behaviours most likely to regress.


Proposed refactoring

1. Extract substitute_tokens() — pure, highest value

The token-replacement logic is scattered across an if/elif/elif block inside the mmcifgen loop.
Extract it into a standalone pure function:

def substitute_tokens(
    template: str,
    xtal_name: str,
    cmpd_code: str,
    cmpd_codes: list[str],
    pose_id_str: str,
) -> str:

Handles $CompoundCode, $CrystalName, $PoseID, $ExternalCodeN (N=1–9) in one place. No side
effects, trivially unit-testable.

2. Extract merge_mmcifgen_into_structure() — CIF-level, integration-testable

The entire for item in mmcifgen_block: loop applies the template block from mmcif-gen to the
per-crystal CIF block. Extract it:

def merge_mmcifgen_into_structure(
    structure_block: cif.Block,
    mmcifgen_block,
    xtal_name: str,
    cmpd_code: str,
    cmpd_codes_dict: dict,
    pose_ids_dict: dict,
) -> None:

Can be tested end-to-end with real or synthetic CIF blocks (gemmi already works well in tests, as
shown by test_validate_structure_cif_doc.py).

3. Make run_mmcifgen() injectable in process_input()

Add a mmcifgen_runner=run_mmcifgen parameter with a default so tests can substitute a stub that
writes a known CIF instead of running the full external tool.


Tests to write

New file: tests/test_pdb_deposition.py

Unit tests (no I/O)

test_substitute_tokens

  • All four tokens present and substituted correctly
  • $ExternalCode3 picks the right column; others are erased
  • $PoseID with multiple pose codes, comma-separated
  • Crystal with no entry in cmpd_codes_dict or pose_ids_dict — empty substitution, no crash
  • Template with no tokens — returned unchanged

test_read_fragalysis_csv

test_read_cmpd_codes

  • Happy path: crystal → list of codes
  • filename=None → empty dict

Use tmp_path for CSV fixtures — no checked-in test data files needed.

CIF-level integration tests (uses gemmi, no external processes)

test_merge_mmcifgen_into_structure_title — build minimal mmcifgen CIF block in-memory with
_struct.title loop containing tokens; assert substituted correctly in destination block.

test_merge_mmcifgen_into_structure_keywords — same for _struct_keywords.text with $PoseID.

test_merge_mmcifgen_into_structure_passthrough — items with no tokens added unchanged.

Extend tests/test_validate_structure_cif_doc.py

  • Missing required loop category → issue reported
  • _refine R-factor out of range → issue reported
  • $CompoundCode not substituted in title → issue reported
  • Sequential _software.pdbx_ordinal passes validation

Files to change

File Change
src/pdbdepo/pdb_deposition.py Extract substitute_tokens() and merge_mmcifgen_into_structure(); add mmcifgen_runner param to process_input()
tests/test_pdb_deposition.py New — unit and CIF-level tests
tests/test_validate_structure_cif_doc.py Extended validation cases

No new test-data files needed — CSV fixtures use tmp_path, CIF fixtures built in memory.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions