Skip to content

fix: input validation — empty search text, batch element types, non-string refs (ISSUES.md #40)#57

Merged
pablospe merged 7 commits into
mainfrom
task/1e37d832-Input-validation-bugs--empty-n
Jul 16, 2026
Merged

fix: input validation — empty search text, batch element types, non-string refs (ISSUES.md #40)#57
pablospe merged 7 commits into
mainfrom
task/1e37d832-Input-validation-bugs--empty-n

Conversation

@pablospe

@pablospe pablospe commented Jul 16, 2026

Copy link
Copy Markdown
Owner

Fixes the three input-validation bugs tracked as ISSUES.md #40 (repo-local issue file, not a GitHub issue number), targeting 0.6.1 — plus the wider type-hardening the multi-reviewer loop surfaced on top of them.

Bugs fixed (original scope)

  1. Empty search text silently vanished edits. An empty needle with an explicit occurrence produced a zero-width match with no DOM positions, so the apply helpers created zero revisions and the edit was silently dropped; without occurrence it raised a meaningless AmbiguousTextError. Both shared locate helpers now reject it with ValueError before any change is made.

  2. Non-EditOperation batch elements crashed with raw AttributeError. batch_edit now raises BatchOperationError carrying the offending operation_index; validate_batch keeps its never-raises contract and reports the element as an invalid row.

  3. Non-string paragraph refs leaked raw TypeError. ParagraphRef.parse rejects non-strings before the regex; the four Document edit methods reject non-string paragraph up front (paragraph=None previously fell through to the document-wide search branch silently).

Review-loop hardening (found by the multi-reviewer loop, same bug class)

  1. Truthy non-string search text and payloads (find=123, replace_with=123, b"needle") escaped the falsiness checks and surfaced as raw TypeError/AttributeError, breaking the documented batch contracts; dry-run even reported such ops as valid. Now rejected with ValueError at every boundary (constructors, direct API, batch apply wrapped with op index, dry-run invalid rows), and _validate_single catches ValueError like the apply loop does.

  2. occurrence type-leak. occurrence="0" hit "0" < 0 → raw TypeError on every path incl. add_comment; a float TypeErrored deeper in the search; a bool was silently misread as 0/1. New shared _require_valid_occurrence guard (xml_editor.py) used at all six boundaries; the negative-int message keeps its exact pinned wording.

  3. add_comment anchor and payload. A non-string anchor raised raw TypeError; worse, a non-string comment crashed in html.escape after the range markers were placed, leaving orphaned commentRangeStart/End markers in document.xml. Both now rejected up front with CommentError, before any mutation.

Notes

  • add_comment never had bug 1 — it pre-rejects empty anchors with its own CommentError; a test pins that asymmetry as deliberate.
  • Docstring Raises: sections and the SKILL.md error-contract table updated throughout.
  • Known follow-up (untouched API): reply_to_comment has the same non-string payload gap via the shared _add_to_comments_xml path.

Testing

27 new tests, all written TDD-first and observed failing on the buggy behaviors before each fix. Every fix verified end-to-end with live repro scripts (constructors, direct API, batch apply, dry-run, add_comment), including batch atomicity and the no-orphaned-markers invariant.

  • uv run pytest — 1084 passed, 2 skipped (pre-existing), re-run after every commit
  • uv run ruff check . / ruff format --check . — clean
  • uv run ty check — exit 0 (only the 6 pre-existing warnings in untouched code)

Review

5-iteration multi-reviewer loop (3 Claude subagents + codex per round, opus red-team verification of every finding). Iteration 5 verdict: SHIP. One codex HIGH was refuted with a live repro (claimed a dry-run ValueError escape via a nonexistent function; the actual code path returns invalid rows).

…tring refs (ISSUES.md #40)

Three input-validation gaps fixed, all raising field-specific ValueErrors
up front instead of failing silently or leaking raw internal errors:

- Empty/None search text: _locate_in_paragraph and _locate_document_wide
  now reject it before any change is made. Previously an empty needle
  with an explicit occurrence created zero revisions (the edit silently
  vanished); without one it raised a meaningless AmbiguousTextError.
- Non-EditOperation batch elements: batch_edit raises BatchOperationError
  with the offending index (original=None, like other batch-level rules);
  validate_batch keeps its never-raises contract and returns an invalid
  row. Both previously raised raw AttributeError.
- Non-string paragraph refs: ParagraphRef.parse rejects them before the
  regex (was raw TypeError), covering all EditOperation constructors; the
  four Document edit methods reject non-string paragraph args, which
  previously fell through to the document-wide search branch silently.

Docstrings and SKILL.md error-contract table updated; 13 new tests
(TDD-first), including silent-no-op regression repros and batch
atomicity checks.
@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@pablospe, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 56 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 722035b6-65b4-4e8a-8d1b-e1ce9e3b7b5e

📥 Commits

Reviewing files that changed from the base of the PR and between a458fee and ff3c574.

📒 Files selected for processing (6)
  • docx_editor/document.py
  • docx_editor/track_changes.py
  • docx_editor/xml_editor.py
  • skills/docx/SKILL.md
  • tests/test_batch_edit.py
  • tests/test_error_quality.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch task/1e37d832-Input-validation-bugs--empty-n

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Jul 16, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 95.3%. Comparing base (a458fee) to head (ff3c574).

Additional details and impacted files
@@          Coverage Diff          @@
##            main     #57   +/-   ##
=====================================
  Coverage   95.3%   95.3%           
=====================================
  Files         11      11           
  Lines       3576    3596   +20     
  Branches     719     725    +6     
=====================================
+ Hits        3410    3430   +20     
  Misses        79      79           
  Partials      87      87           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

pablospe added 6 commits July 16, 2026 23:47
Review found that the falsiness checks let truthy non-strings through:
find=123 raised a raw TypeError from str.find that escaped both
documented batch contracts (batch_edit raises only BatchOperationError;
validate_batch never raises), and a non-string payload (replace_with=123)
passed dry-run as valid then crashed apply with a raw AttributeError.

- Locate helpers: guard is now `not isinstance(text, str) or not text`
- _validate_single: catch (ValueError, DocxEditError) around the locate
  call, mirroring batch_edit's apply loop, so dry-run never raises
- _resolve_action_target: payload checks upgraded to isinstance so both
  batch paths reject non-string replace_with / insert text
- replace_text/_insert_text: direct-API payload guards
- EditOperation constructors: falsiness/None checks upgraded to
  isinstance, so bad ops fail at construction

8 new tests (TDD-first) covering constructors, direct API, batch apply
(wrapped with op index, document unchanged), and dry-run invalid rows.
Docstrings and SKILL.md error contract updated to match.

Skipped as non-blocking per red-team triage: _require_ref_string naming
(module-level pure guard, distinct from _ensure_* instance methods) and
the third "non-empty" message phrasing (cosmetic).
Iteration-2 review found occurrence was the one input the type-hardening
pass skipped: occurrence="0" hit `"0" < 0` and raised a raw TypeError on
every path (constructors, direct API, batch apply, dry-run, add_comment);
a float passed the < 0 check and TypeErrored deeper in the search; a bool
was silently misread as occurrence 0/1.

New shared guard _require_valid_occurrence in xml_editor.py (home of the
text-map search both callers already import from) rejects non-int and
negative occurrences with ValueError, called from _validate_common,
_locate_in_paragraph, _resolve_action_target, _locate_document_wide, and
comments._locate_anchor. The negative-int message keeps its exact pinned
wording; the type check fires first.

Also from review triage (LOW polish):
- EditOperation constructor messages now include the offending value
  (got {value!r}), matching the convention everywhere else in the diff
- _not_an_edit_operation renamed to _not_an_edit_operation_message —
  it builds a message string, not a predicate

5 new tests (TDD-first) covering constructor/direct/batch/dry-run/
add_comment paths; docstrings and SKILL.md updated.
Iteration-3 review found the one sibling anchor-search path the earlier
type-hardening missed: add_comment's guard was falsiness-only, so a
truthy non-string anchor (123, b"needle") passed it and raised a raw
TypeError from str.find inside the text-map search — violating the
documented contract (CommentError for bad anchors).

The guard now requires a non-empty str and names the offending value,
keeping add_comment's established CommentError type. Test extended to
pin int and bytes anchors in both scoped and document-wide modes.
Final review pass found the sibling of the anchor guard: a non-string
comment_text passed anchor validation and _locate_anchor, then crashed
in html.escape with a raw AttributeError — after _place_markers had
already mutated document.xml, leaving orphaned commentRangeStart/End
markers (partial corruption, not a clean no-op).

The guard now fires before any mutation, keeping add_comment's
documented CommentError type. Test asserts both the error and that no
range markers were placed.

Known follow-up (untouched by this PR): reply_to_comment has the same
non-string payload gap via the shared _add_to_comments_xml path.
…alidation-bugs--empty-n

# Conflicts:
#	skills/docx/SKILL.md
@pablospe
pablospe merged commit bc24838 into main Jul 16, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant