Skip to content

Add a contract test enforcing the API-to-GUI reflection contract #254

Description

@GuyTeichman

Area: gui / tests / reflection contract

The API→GUI reflection layer (the documented "source of truth" contract) is fail-open at three stages, and each failure is invisible in a code diff:

  1. Discovery exposes by default (gui.py:1566):

    public_methods = [m for m in dir(self.obj()) if
                      (not m.startswith('_')) and callable(getattr(type(self.obj()), m)) and m not in self.EXCLUDED_FUNCS]

    No positive gate — not "has @readable_name", not "annotations are recognized". Any public, non-excluded callable is shipped as a GUI button, on every subclass (dir() is inherited). Safety is opt-out (_ prefix or per-subclass EXCLUDED_FUNCS).

  2. Tab placement is substring guesswork (gui.py:1578):

    elif 'normalize' in method: ... Normalize
    elif 'filter' in method or 'split' in method: ... Filter
    else: ... Visualize

    A rename across a keyword boundary silently moves a function's tab with no diff signal; a new method matching nothing lands in Visualize by default.

  3. Unrecognized annotation → a Python-literal box (gui_widgets.py:2299):

    else:
        widget = QtWidgets.QTextEdit()
        widget.setToolTip('Enter a Python expression here')

    The terminal branch for any annotation the mapper doesn't recognize — including a param with no annotation. Collected via ast.literal_eval (gui_widgets.py:2318), so no code-exec, but it demands a Python literal from a zero-programming-experience user, and literal_eval('')/a bare word raises → Python traceback.

Existing tests are mechanics-level (param_to_widget mappings at test_gui_widgets.py:1199+, one get_all_actions fixture test at test_gui.py:1846); there is no contract test over the whole public API.

Proposed fix. Add a parametrized contract test that enumerates every public Filter/FeatureSet/fastq/enrichment method surfaced by get_all_actions and asserts: (a) it carries @readable_name; (b) every parameter's annotation resolves to a concrete param_to_widget widget (none fall into the terminal QTextEdit else). Maintain an explicit allowlist for any legitimately free-form params. Optionally assert deterministic tab placement so a rename that changes a tab must update the expectation — making the GUI change visible in the diff.

Acceptance. A new public method missing @readable_name, or carrying an unrecognized/absent annotation, fails CI instead of shipping a Python-literal box to a biologist. Any intentional free-form box is documented in the allowlist.

Surfaced in a design/code-review pass over master @ 4.3.0.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions