Skip to content

fix: match sequence-valued params in query_param_matcher - #816

Open
dylanpulver wants to merge 2 commits into
getsentry:masterfrom
dylanpulver:fix/query-param-matcher-sequence-values
Open

fix: match sequence-valued params in query_param_matcher#816
dylanpulver wants to merge 2 commits into
getsentry:masterfrom
dylanpulver:fix/query-param-matcher-sequence-values

Conversation

@dylanpulver

@dylanpulver dylanpulver commented Aug 30, 2026

Copy link
Copy Markdown

What type of PR is this? (check all applicable)

  • Refactor
  • Feature
  • Bug Fix
  • Optimization
  • Documentation Update
  • Other

Description

query_param_matcher stringified int/float values only at the top level of the
mapping, so a sequence-valued param never matched:

params = {"ids": [1, 2]}
responses.get(url, match=[matchers.query_param_matcher(params)])
requests.get(url, params=params)   # ConnectionError

responses parses the request's query string into strings and groups a repeated key
into a list, collapsing a key seen once to a bare value, so the matcher compared
{"ids": [1, 2]} against {'ids': ['1', '2']}. Tuples and single-item sequences
failed the same way even with string items. Sequences are now normalized the way that
parser produces them.

Deliberately unchanged: an empty sequence, and None, still fail to match a request
that omits the key. requests drops both, but dropping the key here would also change
what strict_match=False filters on.

Checked with an oracle that reuses one dict for both sides across 78 param shapes and
never reads matchers.py: 56 failures on master, 2 after, both the empty-sequence case
above. Reverting only matchers.py fails all 5 new cases, so none of them is a pin.
Suite goes 233 → 238 passed; coverage and mypy failure sets are identical to master.

Related Issues

PR checklist

Before submitting this pull request, I have done the following:

  • Read the contributing guidelines
  • Ran tox and pre-commit checks locally — pre-commit run (all hooks, pinned
    versions) is green and both mypy invocations report the same errors as clean
    master; pytest ran on 3.13 only, not the full tox matrix.
  • Added my changes to the CHANGES file

Added/updated tests?

  • Yes
  • No, and this is why:
  • I need help with writing tests

Disclosure: prepared with AI assistance (Claude Code, Claude Opus 5, claude-opus-5). I have reviewed the change and the measurements above.

@dylanpulver
dylanpulver requested a review from markstory as a code owner August 30, 2026 07:23
query_param_matcher converted int/float values to strings only at the top
level of the params mapping, so a list- or tuple-valued param never matched.
responses parses the request's query string into strings and groups a
repeated key into a list (collapsing a key seen once to a bare value), so
{"ids": [1, 2]} was compared against {'ids': ['1', '2']} and failed, as did
any tuple and any single-item sequence.

Normalize sequence values the same way the request parser produces them.
An empty sequence still does not match a request that omits the key.
@dylanpulver
dylanpulver force-pushed the fix/query-param-matcher-sequence-values branch from 3c2cb47 to 0c371a5 Compare August 30, 2026 07:23
Comment thread responses/matchers.py Outdated
values = [
str(item) if isinstance(item, (int, float)) else item for item in value
]
return values[0] if len(values) == 1 else values

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't you also need to recurse into nested structures? Some one could make multi-level query parameter string data that won't have the same normalization applied?

[
{"ids": [1, 2]},
{"ids": (1, 2)},
{"ids": ["a", 2], "page": 1},

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
{"ids": ["a", 2], "page": 1},
{"ids": ["a", 2], "page": 1},
{"ids": [1, 2], "attrs": {"counter": 3, "favorites": [1, 2]}},

Shouldn't data like this also work?

requests builds the query string by iterating a sequence value once and then
handing the result to urlencode(..., doseq=True), which splices a sequence
nested inside that value in one further level. It also drops a None element.
So {"ids": [[1, 2], [3]]} is sent as ids=1&ids=2&ids=3 and {"ids": [1, None]}
as ids=1, neither of which the matcher's expectation reproduced.

Mirror both rules. Nesting deeper than one level is url-encoded by requests as
its str(), and a dict value is emitted by requests as its keys alone (the
values never reach the wire), so neither is normalized here.

Co-Authored-By: Claude <noreply@anthropic.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want reviews to match your repository better? Bugbot Learning can learn team-specific rules from PR activity. A team admin can enable Learning in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 264057f. Configure here.

Comment thread responses/matchers.py
# ``requests`` omits a ``None`` element from the query string.
continue
if isinstance(item, (list, tuple)):
values.extend(_stringify(nested) for nested in item)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nested None not stringified

Low Severity

When _normalize flattens a nested sequence, each item goes through _stringify, which only converts int and float. A None inside that nested sequence stays None, while urlencode with doseq emits the string 'None', so the matcher misses a request built from the same params dict.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 264057f. Configure here.

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.

2 participants